diff --git a/.core_files.yaml b/.core_files.yaml index 654730e5613..2928d450ce2 100644 --- a/.core_files.yaml +++ b/.core_files.yaml @@ -48,6 +48,7 @@ base_platforms: &base_platforms components: &components - homeassistant/components/alert/** - homeassistant/components/alexa/** + - homeassistant/components/application_credentials/** - homeassistant/components/auth/** - homeassistant/components/automation/** - homeassistant/components/backup/** diff --git a/.coveragerc b/.coveragerc index dc7143aa867..9e2c1e8c678 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,5 @@ [run] source = homeassistant - omit = homeassistant/__main__.py homeassistant/helpers/signal.py @@ -42,7 +41,6 @@ omit = homeassistant/components/airtouch4/const.py homeassistant/components/airvisual/__init__.py homeassistant/components/airvisual/sensor.py - homeassistant/components/aladdin_connect/* homeassistant/components/alarmdecoder/__init__.py homeassistant/components/alarmdecoder/alarm_control_panel.py homeassistant/components/alarmdecoder/binary_sensor.py @@ -58,7 +56,6 @@ omit = homeassistant/components/amcrest/* homeassistant/components/ampio/* homeassistant/components/android_ip_webcam/* - homeassistant/components/androidtv/__init__.py homeassistant/components/androidtv/diagnostics.py homeassistant/components/anel_pwrctrl/switch.py homeassistant/components/anthemav/media_player.py @@ -96,6 +93,14 @@ omit = homeassistant/components/azure_devops/const.py homeassistant/components/azure_devops/sensor.py homeassistant/components/azure_service_bus/* + homeassistant/components/baf/__init__.py + homeassistant/components/baf/climate.py + homeassistant/components/baf/entity.py + homeassistant/components/baf/fan.py + homeassistant/components/baf/light.py + homeassistant/components/baf/number.py + homeassistant/components/baf/sensor.py + homeassistant/components/baf/switch.py homeassistant/components/baidu/tts.py homeassistant/components/balboa/__init__.py homeassistant/components/beewi_smartclim/sensor.py @@ -205,12 +210,9 @@ omit = homeassistant/components/denonavr/media_player.py homeassistant/components/denonavr/receiver.py homeassistant/components/deutsche_bahn/sensor.py - homeassistant/components/devolo_home_control/climate.py - homeassistant/components/devolo_home_control/const.py homeassistant/components/devolo_home_control/cover.py homeassistant/components/devolo_home_control/light.py homeassistant/components/devolo_home_control/sensor.py - homeassistant/components/devolo_home_control/subscriber.py homeassistant/components/devolo_home_control/switch.py homeassistant/components/digital_ocean/* homeassistant/components/discogs/sensor.py @@ -313,6 +315,7 @@ omit = homeassistant/components/esphome/fan.py homeassistant/components/esphome/light.py homeassistant/components/esphome/lock.py + homeassistant/components/esphome/media_player.py homeassistant/components/esphome/number.py homeassistant/components/esphome/select.py homeassistant/components/esphome/sensor.py @@ -412,6 +415,11 @@ omit = homeassistant/components/garages_amsterdam/sensor.py homeassistant/components/gc100/* homeassistant/components/geniushub/* + homeassistant/components/geocaching/__init__.py + homeassistant/components/geocaching/const.py + homeassistant/components/geocaching/coordinator.py + homeassistant/components/geocaching/oauth.py + homeassistant/components/geocaching/sensor.py homeassistant/components/github/__init__.py homeassistant/components/github/coordinator.py homeassistant/components/github/sensor.py @@ -483,7 +491,6 @@ omit = homeassistant/components/homematic/* homeassistant/components/home_plus_control/api.py homeassistant/components/home_plus_control/switch.py - homeassistant/components/homewizard/diagnostics.py homeassistant/components/homeworks/* homeassistant/components/honeywell/__init__.py homeassistant/components/honeywell/climate.py @@ -506,6 +513,7 @@ omit = homeassistant/components/hvv_departures/__init__.py homeassistant/components/hydrawise/* homeassistant/components/ialarm/alarm_control_panel.py + homeassistant/components/ialarm_xr/alarm_control_panel.py homeassistant/components/iammeter/sensor.py homeassistant/components/iaqualink/binary_sensor.py homeassistant/components/iaqualink/climate.py @@ -542,6 +550,7 @@ omit = homeassistant/components/intellifire/coordinator.py homeassistant/components/intellifire/binary_sensor.py homeassistant/components/intellifire/sensor.py + homeassistant/components/intellifire/switch.py homeassistant/components/intellifire/entity.py homeassistant/components/incomfort/* homeassistant/components/intesishome/* @@ -575,6 +584,7 @@ omit = homeassistant/components/juicenet/const.py homeassistant/components/juicenet/device.py homeassistant/components/juicenet/entity.py + homeassistant/components/juicenet/number.py homeassistant/components/juicenet/sensor.py homeassistant/components/juicenet/switch.py homeassistant/components/kaiterra/* @@ -716,8 +726,10 @@ omit = homeassistant/components/modem_callerid/button.py homeassistant/components/modem_callerid/sensor.py homeassistant/components/moehlenhoff_alpha2/__init__.py + homeassistant/components/moehlenhoff_alpha2/binary_sensor.py homeassistant/components/moehlenhoff_alpha2/climate.py homeassistant/components/moehlenhoff_alpha2/const.py + homeassistant/components/moehlenhoff_alpha2/sensor.py homeassistant/components/motion_blinds/__init__.py homeassistant/components/motion_blinds/const.py homeassistant/components/motion_blinds/cover.py @@ -953,6 +965,7 @@ omit = homeassistant/components/rainmachine/model.py homeassistant/components/rainmachine/sensor.py homeassistant/components/rainmachine/switch.py + homeassistant/components/rainmachine/util.py homeassistant/components/raspyrfm/* homeassistant/components/recollect_waste/__init__.py homeassistant/components/recollect_waste/sensor.py @@ -988,7 +1001,6 @@ omit = homeassistant/components/route53/* homeassistant/components/rova/sensor.py homeassistant/components/rpi_camera/* - homeassistant/components/rpi_gpio/* homeassistant/components/rtorrent/sensor.py homeassistant/components/russound_rio/media_player.py homeassistant/components/russound_rnet/media_player.py @@ -1018,16 +1030,6 @@ omit = homeassistant/components/senseme/fan.py homeassistant/components/senseme/light.py homeassistant/components/senseme/switch.py - homeassistant/components/sensibo/__init__.py - homeassistant/components/sensibo/binary_sensor.py - homeassistant/components/sensibo/climate.py - homeassistant/components/sensibo/coordinator.py - homeassistant/components/sensibo/diagnostics.py - homeassistant/components/sensibo/entity.py - 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 @@ -1058,6 +1060,7 @@ omit = homeassistant/components/sky_hub/* homeassistant/components/skybeacon/sensor.py homeassistant/components/skybell/* + homeassistant/components/slack/__init__.py homeassistant/components/slack/notify.py homeassistant/components/sia/__init__.py homeassistant/components/sia/alarm_control_panel.py @@ -1470,6 +1473,15 @@ omit = homeassistant/components/yandex_transport/* homeassistant/components/yeelightsunflower/light.py homeassistant/components/yi/camera.py + homeassistant/components/yolink/__init__.py + homeassistant/components/yolink/api.py + homeassistant/components/yolink/binary_sensor.py + homeassistant/components/yolink/const.py + homeassistant/components/yolink/coordinator.py + homeassistant/components/yolink/entity.py + homeassistant/components/yolink/sensor.py + homeassistant/components/yolink/siren.py + homeassistant/components/yolink/switch.py homeassistant/components/youless/__init__.py homeassistant/components/youless/const.py homeassistant/components/youless/sensor.py diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 5a64b0d5b73..2783972953b 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -16,7 +16,7 @@ - Home Assistant Core release with the issue: diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9667775e8e9..6b13f0980b1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -31,7 +31,7 @@ body: label: What version of Home Assistant Core has the issue? placeholder: core- description: > - Can be found in: [Configuration panel -> Info](https://my.home-assistant.io/redirect/info/). + Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/). [![Open your Home Assistant instance and show your Home Assistant version information.](https://my.home-assistant.io/badges/info.svg)](https://my.home-assistant.io/redirect/info/) - type: input @@ -46,7 +46,7 @@ body: attributes: label: What type of installation are you running? description: > - Can be found in: [Configuration panel -> Info](https://my.home-assistant.io/redirect/info/). + Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/). [![Open your Home Assistant instance and show your Home Assistant version information.](https://my.home-assistant.io/badges/info.svg)](https://my.home-assistant.io/redirect/info/) options: diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 2a83ed47de0..48c0782dafa 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -122,13 +122,13 @@ jobs: echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE - name: Login to DockerHub - uses: docker/login-action@v1.14.1 + uses: docker/login-action@v2.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.14.1 + uses: docker/login-action@v2.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -187,13 +187,13 @@ jobs: fi - name: Login to DockerHub - uses: docker/login-action@v1.14.1 + uses: docker/login-action@v2.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v1.14.1 + uses: docker/login-action@v2.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -259,14 +259,14 @@ jobs: - name: Login to DockerHub if: matrix.registry == 'homeassistant' - uses: docker/login-action@v1.14.1 + uses: docker/login-action@v2.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry if: matrix.registry == 'ghcr.io/home-assistant' - uses: docker/login-action@v1.14.1 + uses: docker/login-action@v2.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c95c09bd175..fb659cf21d2 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.5 + HA_SHORT_VERSION: 2022.6 DEFAULT_PYTHON: 3.9 PRE_COMMIT_CACHE: ~/.cache/pre-commit PIP_CACHE: /tmp/pip-cache @@ -203,7 +203,7 @@ jobs: python -m venv venv . venv/bin/activate python --version - pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.1" setuptools wheel + pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.2" setuptools wheel pip install --cache-dir=$PIP_CACHE -r requirements.txt -r requirements_test.txt --use-deprecated=legacy-resolver - name: Generate partial pre-commit restore key id: generate-pre-commit-key @@ -608,7 +608,7 @@ jobs: python -m venv venv . venv/bin/activate python --version - pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.1" setuptools wheel + pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.2" setuptools wheel pip install --cache-dir=$PIP_CACHE -r requirements_all.txt --use-deprecated=legacy-resolver pip install --cache-dir=$PIP_CACHE -r requirements_test.txt --use-deprecated=legacy-resolver pip install -e . @@ -783,7 +783,7 @@ jobs: echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" - name: Run pytest (fully) if: needs.changes.outputs.test_full_suite == 'true' - timeout-minutes: 45 + timeout-minutes: 60 run: | . venv/bin/activate python --version @@ -827,7 +827,7 @@ jobs: -p no:sugar \ tests/components/${{ matrix.group }} - name: Upload coverage artifact - uses: actions/upload-artifact@v3.0.0 + uses: actions/upload-artifact@v3.1.0 with: name: coverage-${{ matrix.python-version }}-${{ matrix.group }} path: coverage.xml diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 950338cdd13..6019e533530 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -50,13 +50,13 @@ jobs: ) > .env_file - name: Upload env_file - uses: actions/upload-artifact@v3.0.0 + uses: actions/upload-artifact@v3.1.0 with: name: env_file path: ./.env_file - name: Upload requirements_diff - uses: actions/upload-artifact@v3.0.0 + uses: actions/upload-artifact@v3.1.0 with: name: requirements_diff path: ./requirements_diff.txt @@ -96,7 +96,7 @@ jobs: wheels-user: wheels env-file: true apk: "build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;cargo" - pip: "Cython;numpy" + pip: "Cython;numpy==1.21.6" skip-binary: aiohttp constraints: "homeassistant/package_constraints.txt" requirements-diff: "requirements_diff.txt" @@ -134,7 +134,6 @@ jobs: sed -i "s|# pybluez|pybluez|g" ${requirement_file} sed -i "s|# bluepy|bluepy|g" ${requirement_file} sed -i "s|# beacontools|beacontools|g" ${requirement_file} - sed -i "s|# RPi.GPIO|RPi.GPIO|g" ${requirement_file} sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file} sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file} sed -i "s|# evdev|evdev|g" ${requirement_file} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a012441a6b..539308c08f1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.32.0 + rev: v2.32.1 hooks: - id: pyupgrade args: [--py39-plus] diff --git a/.strict-typing b/.strict-typing index cda4f9e12fc..e07d8b9cfc8 100644 --- a/.strict-typing +++ b/.strict-typing @@ -16,13 +16,18 @@ homeassistant.auth.providers.* homeassistant.helpers.area_registry homeassistant.helpers.condition homeassistant.helpers.discovery +homeassistant.helpers.entity homeassistant.helpers.entity_values +homeassistant.helpers.event homeassistant.helpers.reload homeassistant.helpers.script_variables +homeassistant.helpers.sun homeassistant.helpers.translation homeassistant.util.async_ homeassistant.util.color homeassistant.util.decorator +homeassistant.util.location +homeassistant.util.logging homeassistant.util.process homeassistant.util.unit_system @@ -38,6 +43,7 @@ homeassistant.components.aftership.* homeassistant.components.air_quality.* homeassistant.components.airly.* homeassistant.components.airvisual.* +homeassistant.components.airzone.* homeassistant.components.aladdin_connect.* homeassistant.components.alarm_control_panel.* homeassistant.components.amazon_polly.* @@ -49,6 +55,7 @@ homeassistant.components.aseko_pool_live.* homeassistant.components.asuswrt.* homeassistant.components.automation.* homeassistant.components.backup.* +homeassistant.components.baf.* homeassistant.components.binary_sensor.* homeassistant.components.bluetooth_tracker.* homeassistant.components.bmw_connected_drive.* @@ -91,8 +98,10 @@ homeassistant.components.fronius.* homeassistant.components.frontend.* homeassistant.components.fritz.* homeassistant.components.geo_location.* +homeassistant.components.geocaching.* homeassistant.components.gios.* homeassistant.components.goalzero.* +homeassistant.components.google.* homeassistant.components.greeneye_monitor.* homeassistant.components.group.* homeassistant.components.guardian.* @@ -118,6 +127,7 @@ homeassistant.components.homewizard.* homeassistant.components.http.* homeassistant.components.huawei_lte.* homeassistant.components.hyperion.* +homeassistant.components.ialarm_xr.* homeassistant.components.image_processing.* homeassistant.components.input_button.* homeassistant.components.input_select.* @@ -130,10 +140,12 @@ homeassistant.components.kaleidescape.* homeassistant.components.knx.* homeassistant.components.kraken.* homeassistant.components.lametric.* +homeassistant.components.laundrify.* homeassistant.components.lcn.* homeassistant.components.light.* homeassistant.components.local_ip.* homeassistant.components.lock.* +homeassistant.components.logbook.* homeassistant.components.lookin.* homeassistant.components.luftdaten.* homeassistant.components.mailbox.* @@ -156,6 +168,7 @@ homeassistant.components.no_ip.* homeassistant.components.notify.* homeassistant.components.notion.* homeassistant.components.number.* +homeassistant.components.nut.* homeassistant.components.oncue.* homeassistant.components.onewire.* homeassistant.components.open_meteo.* @@ -168,21 +181,11 @@ homeassistant.components.powerwall.* homeassistant.components.proximity.* homeassistant.components.pvoutput.* homeassistant.components.pure_energie.* +homeassistant.components.qnap_qsw.* homeassistant.components.rainmachine.* homeassistant.components.rdw.* homeassistant.components.recollect_waste.* -homeassistant.components.recorder -homeassistant.components.recorder.const -homeassistant.components.recorder.backup -homeassistant.components.recorder.executor -homeassistant.components.recorder.history -homeassistant.components.recorder.models -homeassistant.components.recorder.pool -homeassistant.components.recorder.purge -homeassistant.components.recorder.repack -homeassistant.components.recorder.statistics -homeassistant.components.recorder.util -homeassistant.components.recorder.websocket_api +homeassistant.components.recorder.* homeassistant.components.remote.* homeassistant.components.renault.* homeassistant.components.ridwell.* diff --git a/CODEOWNERS b/CODEOWNERS index 8731f2860ce..1b0d0ae4d3c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -32,6 +32,8 @@ build.json @home-assistant/supervisor /tests/components/adguard/ @frenck /homeassistant/components/advantage_air/ @Bre77 /tests/components/advantage_air/ @Bre77 +/homeassistant/components/aemet/ @Noltari +/tests/components/aemet/ @Noltari /homeassistant/components/agent_dvr/ @ispysoftware /tests/components/agent_dvr/ @ispysoftware /homeassistant/components/air_quality/ @home-assistant/core @@ -48,6 +50,8 @@ build.json @home-assistant/supervisor /tests/components/airvisual/ @bachya /homeassistant/components/airzone/ @Noltari /tests/components/airzone/ @Noltari +/homeassistant/components/aladdin_connect/ @mkmer +/tests/components/aladdin_connect/ @mkmer /homeassistant/components/alarm_control_panel/ @home-assistant/core /tests/components/alarm_control_panel/ @home-assistant/core /homeassistant/components/alert/ @home-assistant/core @@ -75,6 +79,8 @@ build.json @home-assistant/supervisor /tests/components/api/ @home-assistant/core /homeassistant/components/apple_tv/ @postlund /tests/components/apple_tv/ @postlund +/homeassistant/components/application_credentials/ @home-assistant/core +/tests/components/application_credentials/ @home-assistant/core /homeassistant/components/apprise/ @caronc /tests/components/apprise/ @caronc /homeassistant/components/aprs/ @PhilRW @@ -114,6 +120,8 @@ build.json @home-assistant/supervisor /homeassistant/components/azure_service_bus/ @hfurubotten /homeassistant/components/backup/ @home-assistant/core /tests/components/backup/ @home-assistant/core +/homeassistant/components/baf/ @bdraco @jfroy +/tests/components/baf/ @bdraco @jfroy /homeassistant/components/balboa/ @garbled1 /tests/components/balboa/ @garbled1 /homeassistant/components/beewi_smartclim/ @alemuro @@ -129,8 +137,8 @@ build.json @home-assistant/supervisor /homeassistant/components/bluesound/ @thrawnarn /homeassistant/components/bmw_connected_drive/ @gerard33 @rikroe /tests/components/bmw_connected_drive/ @gerard33 @rikroe -/homeassistant/components/bond/ @bdraco @prystupa @joshs85 -/tests/components/bond/ @bdraco @prystupa @joshs85 +/homeassistant/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto +/tests/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto /homeassistant/components/bosch_shc/ @tschamm /tests/components/bosch_shc/ @tschamm /homeassistant/components/braviatv/ @bieniu @Drafteed @@ -370,6 +378,8 @@ build.json @home-assistant/supervisor /tests/components/geo_location/ @home-assistant/core /homeassistant/components/geo_rss_events/ @exxamalte /tests/components/geo_rss_events/ @exxamalte +/homeassistant/components/geocaching/ @Sholofly @reinder83 +/tests/components/geocaching/ @Sholofly @reinder83 /homeassistant/components/geonetnz_quakes/ @exxamalte /tests/components/geonetnz_quakes/ @exxamalte /homeassistant/components/geonetnz_volcano/ @exxamalte @@ -406,6 +416,10 @@ build.json @home-assistant/supervisor /tests/components/guardian/ @bachya /homeassistant/components/habitica/ @ASMfreaK @leikoilja /tests/components/habitica/ @ASMfreaK @leikoilja +/homeassistant/components/hardkernel/ @home-assistant/core +/tests/components/hardkernel/ @home-assistant/core +/homeassistant/components/hardware/ @home-assistant/core +/tests/components/hardware/ @home-assistant/core /homeassistant/components/harmony/ @ehendrix23 @bramkragten @bdraco @mkeesey @Aohzan /tests/components/harmony/ @ehendrix23 @bramkragten @bdraco @mkeesey @Aohzan /homeassistant/components/hassio/ @home-assistant/supervisor @@ -451,8 +465,8 @@ build.json @home-assistant/supervisor /tests/components/huisbaasje/ @dennisschroer /homeassistant/components/humidifier/ @home-assistant/core @Shulyaka /tests/components/humidifier/ @home-assistant/core @Shulyaka -/homeassistant/components/hunterdouglas_powerview/ @bdraco -/tests/components/hunterdouglas_powerview/ @bdraco +/homeassistant/components/hunterdouglas_powerview/ @bdraco @trullock +/tests/components/hunterdouglas_powerview/ @bdraco @trullock /homeassistant/components/hvv_departures/ @vigonotion /tests/components/hvv_departures/ @vigonotion /homeassistant/components/hydrawise/ @ptcryan @@ -460,6 +474,8 @@ build.json @home-assistant/supervisor /tests/components/hyperion/ @dermotduffy /homeassistant/components/ialarm/ @RyuzakiKK /tests/components/ialarm/ @RyuzakiKK +/homeassistant/components/ialarm_xr/ @bigmoby +/tests/components/ialarm_xr/ @bigmoby /homeassistant/components/iammeter/ @lewei50 /homeassistant/components/iaqualink/ @flz /tests/components/iaqualink/ @flz @@ -546,6 +562,8 @@ build.json @home-assistant/supervisor /homeassistant/components/lametric/ @robbiet480 @frenck /homeassistant/components/launch_library/ @ludeeus @DurgNomis-drol /tests/components/launch_library/ @ludeeus @DurgNomis-drol +/homeassistant/components/laundrify/ @xLarry +/tests/components/laundrify/ @xLarry /homeassistant/components/lcn/ @alengwenus /tests/components/lcn/ @alengwenus /homeassistant/components/lg_netcast/ @Drafteed @@ -809,12 +827,14 @@ build.json @home-assistant/supervisor /homeassistant/components/radiotherm/ @vinnyfuria /homeassistant/components/rainbird/ @konikvranik /homeassistant/components/raincloud/ @vanstinator -/homeassistant/components/rainforest_eagle/ @gtdiehl @jcalbert -/tests/components/rainforest_eagle/ @gtdiehl @jcalbert +/homeassistant/components/rainforest_eagle/ @gtdiehl @jcalbert @hastarin +/tests/components/rainforest_eagle/ @gtdiehl @jcalbert @hastarin /homeassistant/components/rainmachine/ @bachya /tests/components/rainmachine/ @bachya /homeassistant/components/random/ @fabaff /tests/components/random/ @fabaff +/homeassistant/components/raspberry_pi/ @home-assistant/core +/tests/components/raspberry_pi/ @home-assistant/core /homeassistant/components/rdw/ @frenck /tests/components/rdw/ @frenck /homeassistant/components/recollect_waste/ @bachya @@ -897,7 +917,6 @@ build.json @home-assistant/supervisor /tests/components/shell_command/ @home-assistant/core /homeassistant/components/shelly/ @balloob @bieniu @thecode @chemelli74 /tests/components/shelly/ @balloob @bieniu @thecode @chemelli74 -/homeassistant/components/shiftr/ @fabaff /homeassistant/components/shodan/ @fabaff /homeassistant/components/sia/ @eavanvalkenburg /tests/components/sia/ @eavanvalkenburg @@ -912,8 +931,8 @@ build.json @home-assistant/supervisor /tests/components/siren/ @home-assistant/core @raman325 /homeassistant/components/sisyphus/ @jkeljo /homeassistant/components/sky_hub/ @rogerselwyn -/homeassistant/components/slack/ @bachya -/tests/components/slack/ @bachya +/homeassistant/components/slack/ @bachya @tkdrob +/tests/components/slack/ @bachya @tkdrob /homeassistant/components/sleepiq/ @mfugate1 @kbickar /tests/components/sleepiq/ @mfugate1 @kbickar /homeassistant/components/slide/ @ualex73 @@ -1105,8 +1124,6 @@ build.json @home-assistant/supervisor /homeassistant/components/velux/ @Julius2342 /homeassistant/components/venstar/ @garbled1 /tests/components/venstar/ @garbled1 -/homeassistant/components/vera/ @pavoni -/tests/components/vera/ @pavoni /homeassistant/components/verisure/ @frenck /tests/components/verisure/ @frenck /homeassistant/components/versasense/ @flamm3blemuff1n @@ -1123,7 +1140,6 @@ build.json @home-assistant/supervisor /tests/components/vizio/ @raman325 /homeassistant/components/vlc_telnet/ @rodripf @MartinHjelmare /tests/components/vlc_telnet/ @rodripf @MartinHjelmare -/homeassistant/components/volkszaehler/ @fabaff /homeassistant/components/volumio/ @OnFreund /tests/components/volumio/ @OnFreund /homeassistant/components/volvooncall/ @molobrakos @decompil3d @@ -1172,6 +1188,8 @@ build.json @home-assistant/supervisor /tests/components/workday/ @fabaff /homeassistant/components/worldclock/ @fabaff /tests/components/worldclock/ @fabaff +/homeassistant/components/ws66i/ @ssaenger +/tests/components/ws66i/ @ssaenger /homeassistant/components/xbox/ @hunterjm /tests/components/xbox/ @hunterjm /homeassistant/components/xbox_live/ @MartinHjelmare @@ -1191,6 +1209,8 @@ build.json @home-assistant/supervisor /tests/components/yeelight/ @zewelor @shenxn @starkillerOG @alexyao2015 /homeassistant/components/yeelightsunflower/ @lindsaymarkward /homeassistant/components/yi/ @bachya +/homeassistant/components/yolink/ @matrixd2 +/tests/components/yolink/ @matrixd2 /homeassistant/components/youless/ @gjong /tests/components/youless/ @gjong /homeassistant/components/zengge/ @emontnemery diff --git a/Dockerfile.dev b/Dockerfile.dev index dc04efe56fb..322c63f53dd 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -18,6 +18,7 @@ RUN \ libavfilter-dev \ libpcap-dev \ libturbojpeg0 \ + libxml2 \ git \ cmake \ && apt-get clean \ diff --git a/README.rst b/README.rst index cf8323d2e81..05e13695a58 100644 --- a/README.rst +++ b/README.rst @@ -22,7 +22,7 @@ of a component, check the `Home Assistant help section None: """Load the users.""" - [ent_reg, dev_reg, data] = await asyncio.gather( - self.hass.helpers.entity_registry.async_get_registry(), - self.hass.helpers.device_registry.async_get_registry(), - self._store.async_load(), - ) + dev_reg = dr.async_get(self.hass) + ent_reg = er.async_get(self.hass) + data = await self._store.async_load() # Make sure that we're not overriding data if 2 loads happened at the # same time @@ -316,7 +316,7 @@ class AuthStore: self._perm_lookup = perm_lookup = PermissionLookup(ent_reg, dev_reg) - if data is None: + if data is None or not isinstance(data, dict): self._set_defaults() return diff --git a/homeassistant/auth/mfa_modules/notify.py b/homeassistant/auth/mfa_modules/notify.py index 37b35a5087d..3872257a205 100644 --- a/homeassistant/auth/mfa_modules/notify.py +++ b/homeassistant/auth/mfa_modules/notify.py @@ -17,6 +17,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import ServiceNotFound from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.storage import Store from . import ( MULTI_FACTOR_AUTH_MODULE_SCHEMA, @@ -99,8 +100,8 @@ class NotifyAuthModule(MultiFactorAuthModule): """Initialize the user data store.""" super().__init__(hass, config) self._user_settings: _UsersDict | None = None - self._user_store = hass.helpers.storage.Store( - STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True + self._user_store = Store( + hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True ) self._include = config.get(CONF_INCLUDE, []) self._exclude = config.get(CONF_EXCLUDE, []) @@ -118,7 +119,9 @@ class NotifyAuthModule(MultiFactorAuthModule): if self._user_settings is not None: return - if (data := await self._user_store.async_load()) is None: + if (data := await self._user_store.async_load()) is None or not isinstance( + data, dict + ): data = {STORAGE_USERS: {}} self._user_settings = { diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index bb5fc47469f..e503198f08b 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -10,6 +10,7 @@ import voluptuous as vol from homeassistant.auth.models import User from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.storage import Store from . import ( MULTI_FACTOR_AUTH_MODULE_SCHEMA, @@ -76,8 +77,8 @@ class TotpAuthModule(MultiFactorAuthModule): """Initialize the user data store.""" super().__init__(hass, config) self._users: dict[str, str] | None = None - self._user_store = hass.helpers.storage.Store( - STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True + self._user_store = Store( + hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True ) self._init_lock = asyncio.Lock() @@ -92,7 +93,9 @@ class TotpAuthModule(MultiFactorAuthModule): if self._users is not None: return - if (data := await self._user_store.async_load()) is None: + if (data := await self._user_store.async_load()) is None or not isinstance( + data, dict + ): data = {STORAGE_USERS: {}} self._users = data.get(STORAGE_USERS, {}) diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index 3971cb43080..cb95907c9b2 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -14,6 +14,7 @@ from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.storage import Store from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from ..models import Credentials, UserMeta @@ -60,8 +61,8 @@ class Data: def __init__(self, hass: HomeAssistant) -> None: """Initialize the user data store.""" self.hass = hass - self._store = hass.helpers.storage.Store( - STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True + self._store = Store( + hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True ) self._data: dict[str, Any] | None = None # Legacy mode will allow usernames to start/end with whitespace @@ -79,7 +80,9 @@ class Data: async def async_load(self) -> None: """Load stored data.""" - if (data := await self._store.async_load()) is None: + if (data := await self._store.async_load()) is None or not isinstance( + data, dict + ): data = {"users": []} seen: set[str] = set() @@ -203,7 +206,8 @@ class Data: async def async_save(self) -> None: """Save data.""" - await self._store.async_save(self._data) + if self._data is not None: + await self._store.async_save(self._data) @AUTH_PROVIDERS.register("homeassistant") diff --git a/homeassistant/components/abode/translations/nl.json b/homeassistant/components/abode/translations/nl.json index 7b6a8b5aace..0b6b67c363c 100644 --- a/homeassistant/components/abode/translations/nl.json +++ b/homeassistant/components/abode/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "reauth_successful": "Herauthenticatie was succesvol", - "single_instance_allowed": "Slechts een enkele configuratie van Abode is toegestaan." + "reauth_successful": "Herauthenticatie geslaagd", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -26,7 +26,7 @@ "user": { "data": { "password": "Wachtwoord", - "username": "E-mailadres" + "username": "E-mail" }, "title": "Vul uw Abode-inloggegevens in" } diff --git a/homeassistant/components/accuweather/translations/ar.json b/homeassistant/components/accuweather/translations/ar.json index 0694e096019..5aadd35df92 100644 --- a/homeassistant/components/accuweather/translations/ar.json +++ b/homeassistant/components/accuweather/translations/ar.json @@ -2,12 +2,6 @@ "config": { "error": { "requests_exceeded": "\u062a\u0645 \u062a\u062c\u0627\u0648\u0632 \u0627\u0644\u0639\u062f\u062f \u0627\u0644\u0645\u0633\u0645\u0648\u062d \u0628\u0647 \u0645\u0646 \u0627\u0644\u0637\u0644\u0628\u0627\u062a \u0625\u0644\u0649 Accuweather API. \u0639\u0644\u064a\u0643 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631 \u0623\u0648 \u062a\u063a\u064a\u064a\u0631 \u0645\u0641\u062a\u0627\u062d API." - }, - "step": { - "user": { - "description": "\u0625\u0630\u0627 \u0643\u0646\u062a \u0628\u062d\u0627\u062c\u0629 \u0625\u0644\u0649 \u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0627\u0644\u062a\u0643\u0648\u064a\u0646 \u060c \u0641\u0642\u0645 \u0628\u0625\u0644\u0642\u0627\u0621 \u0646\u0638\u0631\u0629 \u0647\u0646\u0627: https://www.home-assistant.io/integrations/accuweather/ \n\n \u0644\u0627 \u064a\u062a\u0645 \u062a\u0645\u0643\u064a\u0646 \u0628\u0639\u0636 \u0623\u062c\u0647\u0632\u0629 \u0627\u0644\u0627\u0633\u062a\u0634\u0639\u0627\u0631 \u0628\u0634\u0643\u0644 \u0627\u0641\u062a\u0631\u0627\u0636\u064a. \u064a\u0645\u0643\u0646\u0643 \u062a\u0645\u0643\u064a\u0646\u0647\u0645 \u0641\u064a \u0633\u062c\u0644 \u0627\u0644\u0643\u064a\u0627\u0646 \u0628\u0639\u062f \u062a\u0643\u0648\u064a\u0646 \u0627\u0644\u062a\u0643\u0627\u0645\u0644.\n \u0644\u0627 \u064a\u062a\u0645 \u062a\u0645\u0643\u064a\u0646 \u062a\u0648\u0642\u0639\u0627\u062a \u0627\u0644\u0637\u0642\u0633 \u0627\u0641\u062a\u0631\u0627\u0636\u064a\u064b\u0627. \u064a\u0645\u0643\u0646\u0643 \u062a\u0645\u0643\u064a\u0646\u0647 \u0641\u064a \u062e\u064a\u0627\u0631\u0627\u062a \u0627\u0644\u062a\u0643\u0627\u0645\u0644.", - "title": "AccuWeather" - } } }, "options": { @@ -16,8 +10,7 @@ "data": { "forecast": "\u0627\u0644\u0646\u0634\u0631\u0629 \u0627\u0644\u062c\u0648\u064a\u0629" }, - "description": "\u0646\u0638\u0631\u064b\u0627 \u0644\u0642\u064a\u0648\u062f \u0627\u0644\u0625\u0635\u062f\u0627\u0631 \u0627\u0644\u0645\u062c\u0627\u0646\u064a \u0645\u0646 \u0645\u0641\u062a\u0627\u062d AccuWeather API \u060c \u0639\u0646\u062f \u062a\u0645\u0643\u064a\u0646 \u0627\u0644\u062a\u0646\u0628\u0624 \u0628\u0627\u0644\u0637\u0642\u0633 \u060c \u0633\u064a\u062a\u0645 \u0625\u062c\u0631\u0627\u0621 \u062a\u062d\u062f\u064a\u062b\u0627\u062a \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a \u0643\u0644 80 \u062f\u0642\u064a\u0642\u0629 \u0628\u062f\u0644\u0627\u064b \u0645\u0646 \u0643\u0644 40 \u062f\u0642\u064a\u0642\u0629.", - "title": "\u062e\u064a\u0627\u0631\u0627\u062a AccuWeather" + "description": "\u0646\u0638\u0631\u064b\u0627 \u0644\u0642\u064a\u0648\u062f \u0627\u0644\u0625\u0635\u062f\u0627\u0631 \u0627\u0644\u0645\u062c\u0627\u0646\u064a \u0645\u0646 \u0645\u0641\u062a\u0627\u062d AccuWeather API \u060c \u0639\u0646\u062f \u062a\u0645\u0643\u064a\u0646 \u0627\u0644\u062a\u0646\u0628\u0624 \u0628\u0627\u0644\u0637\u0642\u0633 \u060c \u0633\u064a\u062a\u0645 \u0625\u062c\u0631\u0627\u0621 \u062a\u062d\u062f\u064a\u062b\u0627\u062a \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a \u0643\u0644 80 \u062f\u0642\u064a\u0642\u0629 \u0628\u062f\u0644\u0627\u064b \u0645\u0646 \u0643\u0644 40 \u062f\u0642\u064a\u0642\u0629." } } }, diff --git a/homeassistant/components/accuweather/translations/bg.json b/homeassistant/components/accuweather/translations/bg.json index b037c01144f..26fdf8e85d5 100644 --- a/homeassistant/components/accuweather/translations/bg.json +++ b/homeassistant/components/accuweather/translations/bg.json @@ -14,8 +14,7 @@ "latitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0448\u0438\u0440\u0438\u043d\u0430", "longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430", "name": "\u0418\u043c\u0435" - }, - "title": "AccuWeather" + } } } } diff --git a/homeassistant/components/accuweather/translations/ca.json b/homeassistant/components/accuweather/translations/ca.json index feda485d8fd..e80a006141a 100644 --- a/homeassistant/components/accuweather/translations/ca.json +++ b/homeassistant/components/accuweather/translations/ca.json @@ -18,9 +18,7 @@ "latitude": "Latitud", "longitude": "Longitud", "name": "Nom" - }, - "description": "Si necessites ajuda amb la configuraci\u00f3, consulta els seg\u00fcent enlla\u00e7: https://www.home-assistant.io/integrations/accuweather/ \n\n Alguns sensors no estan activats de manera predeterminada. Els pots activar des del registre d'entitats, despr\u00e9s de la configurraci\u00f3 de la integraci\u00f3.\n La previsi\u00f3 meteorol\u00f2gica no est\u00e0 activada de manera predeterminada. Pots activar-la en les opcions de la integraci\u00f3.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Previsi\u00f3 meteorol\u00f2gica" }, - "description": "Per culpa de les limitacions de la versi\u00f3 gratu\u00efta l'API d'AccuWeather, quan habilitis la previsi\u00f3 meteorol\u00f2gica, les actualitzacions de dades es faran cada 80 minuts en comptes de cada 40.", - "title": "Opcions d'AccuWeather" + "description": "Per culpa de les limitacions de la versi\u00f3 gratu\u00efta l'API d'AccuWeather, quan habilitis la previsi\u00f3 meteorol\u00f2gica, les actualitzacions de dades es faran cada 80 minuts en comptes de cada 40." } } }, diff --git a/homeassistant/components/accuweather/translations/cs.json b/homeassistant/components/accuweather/translations/cs.json index 1cf34a42695..e3ae982cddc 100644 --- a/homeassistant/components/accuweather/translations/cs.json +++ b/homeassistant/components/accuweather/translations/cs.json @@ -15,9 +15,7 @@ "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka", "name": "Jm\u00e9no" - }, - "description": "Pokud pot\u0159ebujete pomoc s nastaven\u00ed, pod\u00edvejte se na: https://www.home-assistant.io/integrations/accuweather/\n\nN\u011bkter\u00e9 senzory nejsou ve v\u00fdchoz\u00edm nastaven\u00ed povoleny. M\u016f\u017eete je povolit po nastaven\u00ed integrace v registru entit.\nP\u0159edpov\u011b\u010f po\u010das\u00ed nen\u00ed ve v\u00fdchoz\u00edm nastaven\u00ed povolena. M\u016f\u017eete ji povolit v mo\u017enostech integrace.", - "title": "AccuWeather" + } } } }, @@ -27,8 +25,7 @@ "data": { "forecast": "P\u0159edpov\u011b\u010f po\u010das\u00ed" }, - "description": "Kdy\u017e povol\u00edte p\u0159edpov\u011b\u010f po\u010das\u00ed, budou aktualizace dat prov\u00e1d\u011bny ka\u017ed\u00fdch 80 minut nam\u00edsto 40 minut z d\u016fvodu omezen\u00ed bezplatn\u00e9 verze AccuWeather.", - "title": "Mo\u017enosti AccuWeather" + "description": "Kdy\u017e povol\u00edte p\u0159edpov\u011b\u010f po\u010das\u00ed, budou aktualizace dat prov\u00e1d\u011bny ka\u017ed\u00fdch 80 minut nam\u00edsto 40 minut z d\u016fvodu omezen\u00ed bezplatn\u00e9 verze AccuWeather." } } }, diff --git a/homeassistant/components/accuweather/translations/de.json b/homeassistant/components/accuweather/translations/de.json index ac0c430de04..f7b02feb091 100644 --- a/homeassistant/components/accuweather/translations/de.json +++ b/homeassistant/components/accuweather/translations/de.json @@ -18,9 +18,7 @@ "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad", "name": "Name" - }, - "description": "Wenn du Hilfe bei der Konfiguration ben\u00f6tigst, schaue hier nach: https://www.home-assistant.io/integrations/accuweather/\n\nEinige Sensoren sind standardm\u00e4\u00dfig nicht aktiviert. Du kannst sie in der Entit\u00e4tsregister nach der Integrationskonfiguration aktivieren.\nDie Wettervorhersage ist nicht standardm\u00e4\u00dfig aktiviert. Du kannst sie in den Integrationsoptionen aktivieren.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Wettervorhersage" }, - "description": "Aufgrund der Einschr\u00e4nkungen der kostenlosen Version des AccuWeather-API-Schl\u00fcssels werden bei aktivierter Wettervorhersage Datenaktualisierungen alle 80 Minuten statt alle 40 Minuten durchgef\u00fchrt.", - "title": "AccuWeather Optionen" + "description": "Aufgrund der Einschr\u00e4nkungen der kostenlosen Version des AccuWeather-API-Schl\u00fcssels werden bei aktivierter Wettervorhersage Datenaktualisierungen alle 80 Minuten statt alle 40 Minuten durchgef\u00fchrt." } } }, diff --git a/homeassistant/components/accuweather/translations/el.json b/homeassistant/components/accuweather/translations/el.json index 43a65158455..b8d7d22df70 100644 --- a/homeassistant/components/accuweather/translations/el.json +++ b/homeassistant/components/accuweather/translations/el.json @@ -18,9 +18,7 @@ "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" - }, - "description": "\u0391\u03bd \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7, \u03c1\u03af\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03bc\u03b1\u03c4\u03b9\u03ac \u03b5\u03b4\u03ce: https://www.home-assistant.io/integrations/accuweather/\n\n\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\u03bf\u03c5\u03c2 \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\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\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\u03b7\u03bd \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.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "\u03a0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd" }, - "description": "\u039b\u03cc\u03b3\u03c9 \u03c4\u03c9\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd \u03c4\u03b7\u03c2 \u03b4\u03c9\u03c1\u03b5\u03ac\u03bd \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd API \u03c4\u03bf\u03c5 AccuWeather, \u03cc\u03c4\u03b1\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd, \u03bf\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03b8\u03b1 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03ba\u03ac\u03b8\u03b5 80 \u03bb\u03b5\u03c0\u03c4\u03ac \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 40 \u03bb\u03b5\u03c0\u03c4\u03ac.", - "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 AccuWeather" + "description": "\u039b\u03cc\u03b3\u03c9 \u03c4\u03c9\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd \u03c4\u03b7\u03c2 \u03b4\u03c9\u03c1\u03b5\u03ac\u03bd \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd API \u03c4\u03bf\u03c5 AccuWeather, \u03cc\u03c4\u03b1\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd, \u03bf\u03b9 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03b8\u03b1 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03ba\u03ac\u03b8\u03b5 80 \u03bb\u03b5\u03c0\u03c4\u03ac \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 40 \u03bb\u03b5\u03c0\u03c4\u03ac." } } }, diff --git a/homeassistant/components/accuweather/translations/en.json b/homeassistant/components/accuweather/translations/en.json index a984080fd86..d391ce83e1e 100644 --- a/homeassistant/components/accuweather/translations/en.json +++ b/homeassistant/components/accuweather/translations/en.json @@ -18,9 +18,7 @@ "latitude": "Latitude", "longitude": "Longitude", "name": "Name" - }, - "description": "If you need help with the configuration have a look here: https://www.home-assistant.io/integrations/accuweather/\n\nSome 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.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Weather forecast" }, - "description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 80 minutes instead of every 40 minutes.", - "title": "AccuWeather Options" + "description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 80 minutes instead of every 40 minutes." } } }, diff --git a/homeassistant/components/accuweather/translations/es-419.json b/homeassistant/components/accuweather/translations/es-419.json index 72d295da073..2b2b8dc98ce 100644 --- a/homeassistant/components/accuweather/translations/es-419.json +++ b/homeassistant/components/accuweather/translations/es-419.json @@ -7,12 +7,6 @@ "cannot_connect": "No se pudo conectar", "invalid_api_key": "Clave de API no v\u00e1lida", "requests_exceeded": "Se super\u00f3 el n\u00famero permitido de solicitudes a la API de Accuweather. Tiene que esperar o cambiar la clave de API." - }, - "step": { - "user": { - "description": "Si necesita ayuda con la configuraci\u00f3n, eche un vistazo aqu\u00ed: https://www.home-assistant.io/integrations/accuweather/ \n\nAlgunos sensores no est\u00e1n habilitados de forma predeterminada. Puede habilitarlos en el registro de entidades despu\u00e9s de la configuraci\u00f3n de integraci\u00f3n. La previsi\u00f3n meteorol\u00f3gica no est\u00e1 habilitada de forma predeterminada. Puede habilitarlo en las opciones de integraci\u00f3n.", - "title": "AccuWeather" - } } }, "options": { @@ -21,8 +15,7 @@ "data": { "forecast": "Pron\u00f3stico del tiempo" }, - "description": "Debido a las limitaciones de la versi\u00f3n gratuita de la clave API de AccuWeather, cuando habilita el pron\u00f3stico del tiempo, las actualizaciones de datos se realizar\u00e1n cada 64 minutos en lugar de cada 32 minutos.", - "title": "Opciones de AccuWeather" + "description": "Debido a las limitaciones de la versi\u00f3n gratuita de la clave API de AccuWeather, cuando habilita el pron\u00f3stico del tiempo, las actualizaciones de datos se realizar\u00e1n cada 64 minutos en lugar de cada 32 minutos." } } }, diff --git a/homeassistant/components/accuweather/translations/es.json b/homeassistant/components/accuweather/translations/es.json index 9d0396fddb3..0c3d5560d67 100644 --- a/homeassistant/components/accuweather/translations/es.json +++ b/homeassistant/components/accuweather/translations/es.json @@ -15,9 +15,7 @@ "latitude": "Latitud", "longitude": "Longitud", "name": "Nombre" - }, - "description": "Si necesitas ayuda con la configuraci\u00f3n, echa un vistazo aqu\u00ed: https://www.home-assistant.io/integrations/accuweather/ \n\nAlgunos sensores no est\u00e1n habilitados por defecto. Los puedes habilitar en el registro de entidades despu\u00e9s de configurar la integraci\u00f3n.\nEl pron\u00f3stico del tiempo no est\u00e1 habilitado por defecto. Puedes habilitarlo en las opciones de la integraci\u00f3n.", - "title": "AccuWeather" + } } } }, @@ -27,8 +25,7 @@ "data": { "forecast": "Pron\u00f3stico del tiempo" }, - "description": "Debido a las limitaciones de la versi\u00f3n gratuita de la clave API de AccuWeather, cuando habilitas el pron\u00f3stico del tiempo, las actualizaciones de datos se realizar\u00e1n cada 64 minutos en lugar de cada 32 minutos.", - "title": "Opciones de AccuWeather" + "description": "Debido a las limitaciones de la versi\u00f3n gratuita de la clave API de AccuWeather, cuando habilitas el pron\u00f3stico del tiempo, las actualizaciones de datos se realizar\u00e1n cada 64 minutos en lugar de cada 32 minutos." } } }, diff --git a/homeassistant/components/accuweather/translations/et.json b/homeassistant/components/accuweather/translations/et.json index 429e2dec61e..2f85bd8663e 100644 --- a/homeassistant/components/accuweather/translations/et.json +++ b/homeassistant/components/accuweather/translations/et.json @@ -18,9 +18,7 @@ "latitude": "Laiuskraad", "longitude": "Pikkuskraad", "name": "Sidumise nimi" - }, - "description": "Kui vajate seadistamisel abi vaadake siit: https://www.home-assistant.io/integrations/accuweather/ \n\n M\u00f5ni andur pole vaikimisi lubatud. P\u00e4rast sidumise seadistamist saate need \u00fcksused lubada. \n Ilmapennustus pole vaikimisi lubatud. Saate selle lubada sidumise s\u00e4tetes.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Ilmateade" }, - "description": "AccuWeather API tasuta versioonis toimub ilmaennustuse lubamisel andmete v\u00e4rskendamine iga 80 minuti j\u00e4rel (muidu 40 minutit).", - "title": "AccuWeatheri valikud" + "description": "AccuWeather API tasuta versioonis toimub ilmaennustuse lubamisel andmete v\u00e4rskendamine iga 80 minuti j\u00e4rel (muidu 40 minutit)." } } }, diff --git a/homeassistant/components/accuweather/translations/fr.json b/homeassistant/components/accuweather/translations/fr.json index cf407ef3962..75ef209a1cf 100644 --- a/homeassistant/components/accuweather/translations/fr.json +++ b/homeassistant/components/accuweather/translations/fr.json @@ -18,9 +18,7 @@ "latitude": "Latitude", "longitude": "Longitude", "name": "Nom" - }, - "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" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Pr\u00e9visions m\u00e9t\u00e9orologiques" }, - "description": "En raison des limitations de la version gratuite de la cl\u00e9 API AccuWeather, lorsque vous activez les pr\u00e9visions m\u00e9t\u00e9orologiques, les mises \u00e0 jour des donn\u00e9es seront effectu\u00e9es toutes les 64 minutes au lieu de toutes les 32 minutes.", - "title": "Options AccuWeather" + "description": "En raison des limitations de la version gratuite de la cl\u00e9 API AccuWeather, lorsque vous activez les pr\u00e9visions m\u00e9t\u00e9orologiques, les mises \u00e0 jour des donn\u00e9es seront effectu\u00e9es toutes les 64 minutes au lieu de toutes les 32 minutes." } } }, diff --git a/homeassistant/components/accuweather/translations/he.json b/homeassistant/components/accuweather/translations/he.json index 77c1e54f3e5..0f054ff11fe 100644 --- a/homeassistant/components/accuweather/translations/he.json +++ b/homeassistant/components/accuweather/translations/he.json @@ -15,9 +15,7 @@ "latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1", "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", "name": "\u05e9\u05dd" - }, - "description": "\u05d0\u05dd \u05d4\u05d9\u05e0\u05da \u05d6\u05e7\u05d5\u05e7 \u05dc\u05e2\u05d6\u05e8\u05d4 \u05e2\u05dd \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4, \u05d9\u05e9 \u05dc\u05e2\u05d9\u05d9\u05df \u05db\u05d0\u05df: https://www.home-assistant.io/integrations/accuweather/\n\n\u05d7\u05d9\u05d9\u05e9\u05e0\u05d9\u05dd \u05de\u05e1\u05d5\u05d9\u05de\u05d9\u05dd \u05d0\u05d9\u05e0\u05dd \u05d6\u05de\u05d9\u05e0\u05d9\u05dd \u05db\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc. \u05d1\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea\u05da \u05dc\u05d4\u05e4\u05d5\u05da \u05d0\u05d5\u05ea\u05dd \u05dc\u05d6\u05de\u05d9\u05e0\u05d9\u05dd \u05d1\u05e8\u05d9\u05e9\u05d5\u05dd \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05dc\u05d0\u05d7\u05e8 \u05e7\u05d1\u05d9\u05e2\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1.\n\u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d4\u05d0\u05d5\u05d5\u05d9\u05e8 \u05d0\u05d9\u05e0\u05d4 \u05d6\u05de\u05d9\u05e0\u05d4 \u05db\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc. \u05d1\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea\u05da \u05dc\u05d4\u05e4\u05d5\u05da \u05d0\u05d5\u05ea\u05d5 \u05dc\u05d6\u05de\u05d9\u05df \u05d1\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1.", - "title": "AccuWeather" + } } } }, @@ -27,8 +25,7 @@ "data": { "forecast": "\u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d4\u05d0\u05d5\u05d5\u05d9\u05e8" }, - "description": "\u05d1\u05e9\u05dc \u05de\u05d2\u05d1\u05dc\u05d5\u05ea \u05d4\u05d2\u05d9\u05e8\u05e1\u05d4 \u05d4\u05d7\u05d9\u05e0\u05de\u05d9\u05ea \u05e9\u05dc \u05de\u05e4\u05ea\u05d7 \u05d4-API \u05e9\u05dc AccuWeather, \u05db\u05d0\u05e9\u05e8 \u05ea\u05e4\u05e2\u05d9\u05dc \u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d0\u05d5\u05d5\u05d9\u05e8, \u05e2\u05d3\u05db\u05d5\u05e0\u05d9 \u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05d9\u05d1\u05d5\u05e6\u05e2\u05d5 \u05db\u05dc 80 \u05d3\u05e7\u05d5\u05ea \u05d1\u05de\u05e7\u05d5\u05dd \u05db\u05dc 40 \u05d3\u05e7\u05d5\u05ea.", - "title": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea AccuWeather" + "description": "\u05d1\u05e9\u05dc \u05de\u05d2\u05d1\u05dc\u05d5\u05ea \u05d4\u05d2\u05d9\u05e8\u05e1\u05d4 \u05d4\u05d7\u05d9\u05e0\u05de\u05d9\u05ea \u05e9\u05dc \u05de\u05e4\u05ea\u05d7 \u05d4-API \u05e9\u05dc AccuWeather, \u05db\u05d0\u05e9\u05e8 \u05ea\u05e4\u05e2\u05d9\u05dc \u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d0\u05d5\u05d5\u05d9\u05e8, \u05e2\u05d3\u05db\u05d5\u05e0\u05d9 \u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05d9\u05d1\u05d5\u05e6\u05e2\u05d5 \u05db\u05dc 80 \u05d3\u05e7\u05d5\u05ea \u05d1\u05de\u05e7\u05d5\u05dd \u05db\u05dc 40 \u05d3\u05e7\u05d5\u05ea." } } }, diff --git a/homeassistant/components/accuweather/translations/hu.json b/homeassistant/components/accuweather/translations/hu.json index 1b30dd09daf..0e207c5fde8 100644 --- a/homeassistant/components/accuweather/translations/hu.json +++ b/homeassistant/components/accuweather/translations/hu.json @@ -18,9 +18,7 @@ "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", "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" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Id\u0151j\u00e1r\u00e1s el\u0151rejelz\u00e9s" }, - "description": "Az AccuWeather API kulcs ingyenes verzi\u00f3j\u00e1nak korl\u00e1tai miatt, amikor enged\u00e9lyezi az id\u0151j\u00e1r\u00e1s -el\u0151rejelz\u00e9st, az adatfriss\u00edt\u00e9seket 40 percenk\u00e9nt 80 percenk\u00e9nt hajtj\u00e1k v\u00e9gre.", - "title": "AccuWeather be\u00e1ll\u00edt\u00e1sok" + "description": "Az AccuWeather API kulcs ingyenes verzi\u00f3j\u00e1nak korl\u00e1tai miatt, amikor enged\u00e9lyezi az id\u0151j\u00e1r\u00e1s -el\u0151rejelz\u00e9st, az adatfriss\u00edt\u00e9seket 40 percenk\u00e9nt 80 percenk\u00e9nt hajtj\u00e1k v\u00e9gre." } } }, diff --git a/homeassistant/components/accuweather/translations/id.json b/homeassistant/components/accuweather/translations/id.json index 7bf9f27e8b2..1cd49752342 100644 --- a/homeassistant/components/accuweather/translations/id.json +++ b/homeassistant/components/accuweather/translations/id.json @@ -18,9 +18,7 @@ "latitude": "Lintang", "longitude": "Bujur", "name": "Nama" - }, - "description": "Jika Anda memerlukan bantuan tentang konfigurasi, baca di sini: https://www.home-assistant.io/integrations/accuweather/\n\nBeberapa 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.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Prakiraan cuaca" }, - "description": "Karena keterbatasan versi gratis kunci API AccuWeather, ketika Anda mengaktifkan prakiraan cuaca, pembaruan data akan dilakukan setiap 80 menit, bukan setiap 40 menit.", - "title": "Opsi AccuWeather" + "description": "Karena keterbatasan versi gratis kunci API AccuWeather, ketika Anda mengaktifkan prakiraan cuaca, pembaruan data akan dilakukan setiap 80 menit, bukan setiap 40 menit." } } }, diff --git a/homeassistant/components/accuweather/translations/it.json b/homeassistant/components/accuweather/translations/it.json index 9daaf378fb4..d07a900adc9 100644 --- a/homeassistant/components/accuweather/translations/it.json +++ b/homeassistant/components/accuweather/translations/it.json @@ -18,9 +18,7 @@ "latitude": "Latitudine", "longitude": "Logitudine", "name": "Nome" - }, - "description": "Se hai bisogno di aiuto con la configurazione dai un'occhiata qui: https://www.home-assistant.io/integrations/accuweather/ \n\nAlcuni sensori non sono abilitati per impostazione predefinita. \u00c8 possibile abilitarli nel registro entit\u00e0 dopo la configurazione di integrazione. \nLe previsioni meteo non sono abilitate per impostazione predefinita. Puoi abilitarle nelle opzioni di integrazione.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Previsioni meteo" }, - "description": "A causa delle limitazioni della versione gratuita della chiave API AccuWeather, quando si abilitano le previsioni del tempo, gli aggiornamenti dei dati verranno eseguiti ogni 80 minuti invece che ogni 40.", - "title": "Opzioni AccuWeather" + "description": "A causa delle limitazioni della versione gratuita della chiave API AccuWeather, quando si abilitano le previsioni del tempo, gli aggiornamenti dei dati verranno eseguiti ogni 80 minuti invece che ogni 40." } } }, diff --git a/homeassistant/components/accuweather/translations/ja.json b/homeassistant/components/accuweather/translations/ja.json index d05e75e74b5..21ec4e6068d 100644 --- a/homeassistant/components/accuweather/translations/ja.json +++ b/homeassistant/components/accuweather/translations/ja.json @@ -18,9 +18,7 @@ "latitude": "\u7def\u5ea6", "longitude": "\u7d4c\u5ea6", "name": "\u540d\u524d" - }, - "description": "\u8a2d\u5b9a\u306b\u3064\u3044\u3066\u30d8\u30eb\u30d7\u304c\u5fc5\u8981\u306a\u5834\u5408\u306f\u3001https://www.home-assistant.io/integrations/accuweather/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044\n\n\u4e00\u90e8\u306e\u30bb\u30f3\u30b5\u30fc\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\u3067\u306e\u8a2d\u5b9a\u5f8c\u306b\u3001\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30ec\u30b8\u30b9\u30c8\u30ea\u3067\u6709\u52b9\u306b\u3059\u308b\u3053\u3068\u304c\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\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "\u5929\u6c17\u4e88\u5831" }, - "description": "\u5236\u9650\u306b\u3088\u308a\u7121\u6599\u30d0\u30fc\u30b8\u30e7\u30f3\u306eAccuWeather API\u30ad\u30fc\u3067\u306f\u3001\u5929\u6c17\u4e88\u5831\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u30c7\u30fc\u30bf\u306e\u66f4\u65b0\u306f40\u5206\u3067\u306f\u306a\u304f80\u5206\u3054\u3068\u3067\u3059\u3002", - "title": "AccuWeather\u306e\u30aa\u30d7\u30b7\u30e7\u30f3" + "description": "\u5236\u9650\u306b\u3088\u308a\u7121\u6599\u30d0\u30fc\u30b8\u30e7\u30f3\u306eAccuWeather API\u30ad\u30fc\u3067\u306f\u3001\u5929\u6c17\u4e88\u5831\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u30c7\u30fc\u30bf\u306e\u66f4\u65b0\u306f40\u5206\u3067\u306f\u306a\u304f80\u5206\u3054\u3068\u3067\u3059\u3002" } } }, diff --git a/homeassistant/components/accuweather/translations/ko.json b/homeassistant/components/accuweather/translations/ko.json index d992d0bfdd4..ad33fae591c 100644 --- a/homeassistant/components/accuweather/translations/ko.json +++ b/homeassistant/components/accuweather/translations/ko.json @@ -15,9 +15,7 @@ "latitude": "\uc704\ub3c4", "longitude": "\uacbd\ub3c4", "name": "\uc774\ub984" - }, - "description": "\uad6c\uc131\uc5d0 \ub300\ud55c \ub3c4\uc6c0\uc774 \ud544\uc694\ud55c \uacbd\uc6b0 \ub2e4\uc74c\uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.home-assistant.io/integrations/accuweather/\n\n\uc77c\ubd80 \uc13c\uc11c\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uad6c\uc131 \ud6c4 \uad6c\uc131\uc694\uc18c \ub808\uc9c0\uc2a4\ud2b8\ub9ac\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n\uc77c\uae30\uc608\ubcf4\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc635\uc158\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "AccuWeather" + } } } }, @@ -27,8 +25,7 @@ "data": { "forecast": "\ub0a0\uc528 \uc608\ubcf4" }, - "description": "\ubb34\ub8cc \ubc84\uc804\uc758 AccuWeather API \ud0a4\ub85c \uc77c\uae30\uc608\ubcf4\ub97c \ud65c\uc131\ud654\ud55c \uacbd\uc6b0 \uc81c\ud55c\uc0ac\ud56d\uc73c\ub85c \uc778\ud574 \uc5c5\ub370\uc774\ud2b8\ub294 40 \ubd84\uc774 \uc544\ub2cc 80 \ubd84\ub9c8\ub2e4 \uc218\ud589\ub429\ub2c8\ub2e4.", - "title": "AccuWeather \uc635\uc158" + "description": "\ubb34\ub8cc \ubc84\uc804\uc758 AccuWeather API \ud0a4\ub85c \uc77c\uae30\uc608\ubcf4\ub97c \ud65c\uc131\ud654\ud55c \uacbd\uc6b0 \uc81c\ud55c\uc0ac\ud56d\uc73c\ub85c \uc778\ud574 \uc5c5\ub370\uc774\ud2b8\ub294 40 \ubd84\uc774 \uc544\ub2cc 80 \ubd84\ub9c8\ub2e4 \uc218\ud589\ub429\ub2c8\ub2e4." } } }, diff --git a/homeassistant/components/accuweather/translations/lb.json b/homeassistant/components/accuweather/translations/lb.json index 7f3855a7b9c..618b9ef984b 100644 --- a/homeassistant/components/accuweather/translations/lb.json +++ b/homeassistant/components/accuweather/translations/lb.json @@ -15,9 +15,7 @@ "latitude": "Breedegrad", "longitude": "L\u00e4ngegrad", "name": "Numm" - }, - "description": "Falls du H\u00ebllef mat der Konfiguratioun brauch kuck h\u00e9i:\nhttps://www.home-assistant.io/integrations/accuweather/\n\nVerschidde Sensoren si standardm\u00e9isseg net aktiv. Du kanns d\u00e9i an der Entit\u00e9ie Registry no der Konfiguratioun vun der Integratioun aschalten.\n\nWieder Pr\u00e9visounen si standardm\u00e9isseg net aktiv. Du kanns d\u00e9i an den Optioune vun der Integratioun aschalten.", - "title": "AccuWeather" + } } } }, @@ -27,8 +25,7 @@ "data": { "forecast": "Wieder Pr\u00e9visioun" }, - "description": "Duerch d'Limite vun der Gratis Versioun vun der AccuWeather API, wann d'Wieder Pr\u00e9visoune aktiv\u00e9iert sinn, ginn d'Aktualis\u00e9ierungen all 64 Minutten gemaach, am plaatz vun all 32 Minutten.", - "title": "AccuWeather Optiounen" + "description": "Duerch d'Limite vun der Gratis Versioun vun der AccuWeather API, wann d'Wieder Pr\u00e9visoune aktiv\u00e9iert sinn, ginn d'Aktualis\u00e9ierungen all 64 Minutten gemaach, am plaatz vun all 32 Minutten." } } }, diff --git a/homeassistant/components/accuweather/translations/nl.json b/homeassistant/components/accuweather/translations/nl.json index 2a6dd1a812b..df5c71dcf45 100644 --- a/homeassistant/components/accuweather/translations/nl.json +++ b/homeassistant/components/accuweather/translations/nl.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n 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", + "invalid_api_key": "Ongeldige API-sleutel", "requests_exceeded": "Het toegestane aantal verzoeken aan de Accuweather API is overschreden. U moet wachten of de API-sleutel wijzigen." }, "step": { @@ -18,9 +18,7 @@ "latitude": "Breedtegraad", "longitude": "Lengtegraad", "name": "Naam" - }, - "description": "Als je hulp nodig hebt bij de configuratie, kijk dan hier: https://www.home-assistant.io/integrations/accuweather/ \n\n Sommige sensoren zijn niet standaard ingeschakeld. U kunt ze inschakelen in het entiteitenregister na de integratieconfiguratie.\n Weersvoorspelling is niet standaard ingeschakeld. U kunt het inschakelen in de integratieopties.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Weervoorspelling" }, - "description": "Vanwege de beperkingen van de gratis versie van de AccuWeather API-sleutel, worden gegevensupdates elke 64 minuten in plaats van elke 32 minuten uitgevoerd wanneer u weersvoorspelling inschakelt.", - "title": "AccuWeather-opties" + "description": "Vanwege de beperkingen van de gratis versie van de AccuWeather API-sleutel, worden gegevensupdates elke 64 minuten in plaats van elke 32 minuten uitgevoerd wanneer u weersvoorspelling inschakelt." } } }, diff --git a/homeassistant/components/accuweather/translations/no.json b/homeassistant/components/accuweather/translations/no.json index af689b2fd1c..dc203abe714 100644 --- a/homeassistant/components/accuweather/translations/no.json +++ b/homeassistant/components/accuweather/translations/no.json @@ -18,9 +18,7 @@ "latitude": "Breddegrad", "longitude": "Lengdegrad", "name": "Navn" - }, - "description": "Hvis du trenger hjelp med konfigurasjonen, kan du se her: https://www.home-assistant.io/integrations/accuweather/ \n\nNoen sensorer er ikke aktivert som standard. Du kan aktivere dem i entitetsregisteret etter integrasjonskonfigurasjonen. \nV\u00e6rmelding er ikke aktivert som standard. Du kan aktivere det i integrasjonsalternativene.", - "title": "" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "V\u00e6rmelding" }, - "description": "P\u00e5 grunn av begrensningene i den gratis versjonen av AccuWeather API-n\u00f8kkelen, vil dataoppdateringer utf\u00f8res hvert 80. minutt i stedet for hvert 40. minutt n\u00e5r du aktiverer v\u00e6rmelding.", - "title": "AccuWeather-alternativer" + "description": "P\u00e5 grunn av begrensningene i den gratis versjonen av AccuWeather API-n\u00f8kkelen, vil dataoppdateringer utf\u00f8res hvert 80. minutt i stedet for hvert 40. minutt n\u00e5r du aktiverer v\u00e6rmelding." } } }, diff --git a/homeassistant/components/accuweather/translations/pl.json b/homeassistant/components/accuweather/translations/pl.json index a8ef8a3bfa0..764218f8e11 100644 --- a/homeassistant/components/accuweather/translations/pl.json +++ b/homeassistant/components/accuweather/translations/pl.json @@ -18,9 +18,7 @@ "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "name": "Nazwa" - }, - "description": "Je\u015bli potrzebujesz pomocy z konfiguracj\u0105, przejd\u017a na stron\u0119: https://www.home-assistant.io/integrations/accuweather/ \n\nCz\u0119\u015b\u0107 sensor\u00f3w nie jest w\u0142\u0105czona domy\u015blnie. Mo\u017cesz je w\u0142\u0105czy\u0107 w rejestrze encji po konfiguracji integracji.\nPrognoza pogody nie jest domy\u015blnie w\u0142\u0105czona. Mo\u017cesz j\u0105 w\u0142\u0105czy\u0107 w opcjach integracji.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Prognoza pogody" }, - "description": "Ze wzgl\u0119du na ograniczenia darmowej wersji klucza API AccuWeather po w\u0142\u0105czeniu prognozy pogody aktualizacje danych b\u0119d\u0105 wykonywane co 80 minut zamiast co 40 minut.", - "title": "Opcje AccuWeather" + "description": "Ze wzgl\u0119du na ograniczenia darmowej wersji klucza API AccuWeather po w\u0142\u0105czeniu prognozy pogody aktualizacje danych b\u0119d\u0105 wykonywane co 80 minut zamiast co 40 minut." } } }, diff --git a/homeassistant/components/accuweather/translations/pt-BR.json b/homeassistant/components/accuweather/translations/pt-BR.json index 4bb1bbbc97e..3fc0d95ad2a 100644 --- a/homeassistant/components/accuweather/translations/pt-BR.json +++ b/homeassistant/components/accuweather/translations/pt-BR.json @@ -18,9 +18,7 @@ "latitude": "Latitude", "longitude": "Longitude", "name": "Nome" - }, - "description": "Se precisar de ajuda com a configura\u00e7\u00e3o, d\u00ea uma olhada aqui: https://www.home-assistant.io/integrations/accuweather/ \n\nAlguns 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.\nA 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.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Previs\u00e3o do Tempo" }, - "description": "Devido \u00e0s limita\u00e7\u00f5es da vers\u00e3o gratuita da chave da API AccuWeather, quando voc\u00ea habilita a previs\u00e3o do tempo, as atualiza\u00e7\u00f5es de dados ser\u00e3o realizadas a cada 64 minutos em vez de a cada 32 minutos.", - "title": "Op\u00e7\u00f5es do AccuWeather" + "description": "Devido \u00e0s limita\u00e7\u00f5es da vers\u00e3o gratuita da chave da API AccuWeather, quando voc\u00ea habilita a previs\u00e3o do tempo, as atualiza\u00e7\u00f5es de dados ser\u00e3o realizadas a cada 64 minutos em vez de a cada 32 minutos." } } }, diff --git a/homeassistant/components/accuweather/translations/ru.json b/homeassistant/components/accuweather/translations/ru.json index dfd24b6f147..2f08263b4f5 100644 --- a/homeassistant/components/accuweather/translations/ru.json +++ b/homeassistant/components/accuweather/translations/ru.json @@ -18,9 +18,7 @@ "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" - }, - "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438, \u0435\u0441\u043b\u0438 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u0430 \u043f\u043e\u043c\u043e\u0449\u044c \u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439:\nhttps://www.home-assistant.io/integrations/accuweather/ \n\n\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.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "\u041f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u044b" }, - "description": "\u0412 \u0441\u0432\u044f\u0437\u0438 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u043f\u043e\u0433\u043e\u0434\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0435 80 \u043c\u0438\u043d\u0443\u0442, \u0430 \u043d\u0435 \u043a\u0430\u0436\u0434\u044b\u0435 40 \u043c\u0438\u043d\u0443\u0442.", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 AccuWeather" + "description": "\u0412 \u0441\u0432\u044f\u0437\u0438 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u043f\u043e\u0433\u043e\u0434\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0435 80 \u043c\u0438\u043d\u0443\u0442, \u0430 \u043d\u0435 \u043a\u0430\u0436\u0434\u044b\u0435 40 \u043c\u0438\u043d\u0443\u0442." } } }, diff --git a/homeassistant/components/accuweather/translations/tr.json b/homeassistant/components/accuweather/translations/tr.json index b5efeb7080e..e0907fe2fa8 100644 --- a/homeassistant/components/accuweather/translations/tr.json +++ b/homeassistant/components/accuweather/translations/tr.json @@ -18,9 +18,7 @@ "latitude": "Enlem", "longitude": "Boylam", "name": "Ad" - }, - "description": "Yap\u0131land\u0131rmayla ilgili yard\u0131ma ihtiyac\u0131n\u0131z varsa buraya bak\u0131n: https://www.home-assistant.io/integrations/accuweather/ \n\n 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.", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "Hava Durumu tahmini" }, - "description": "AccuWeather API anahtar\u0131n\u0131n \u00fccretsiz s\u00fcr\u00fcm\u00fcn\u00fcn s\u0131n\u0131rlamalar\u0131 nedeniyle, hava tahminini etkinle\u015ftirdi\u011finizde, veri g\u00fcncellemeleri her 40 dakikada bir yerine 80 dakikada bir ger\u00e7ekle\u015ftirilir.", - "title": "AccuWeather Se\u00e7enekleri" + "description": "AccuWeather API anahtar\u0131n\u0131n \u00fccretsiz s\u00fcr\u00fcm\u00fcn\u00fcn s\u0131n\u0131rlamalar\u0131 nedeniyle, hava tahminini etkinle\u015ftirdi\u011finizde, veri g\u00fcncellemeleri her 40 dakikada bir yerine 80 dakikada bir ger\u00e7ekle\u015ftirilir." } } }, diff --git a/homeassistant/components/accuweather/translations/uk.json b/homeassistant/components/accuweather/translations/uk.json index cb02c7e0ad5..99818d354f9 100644 --- a/homeassistant/components/accuweather/translations/uk.json +++ b/homeassistant/components/accuweather/translations/uk.json @@ -15,9 +15,7 @@ "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", "name": "\u041d\u0430\u0437\u0432\u0430" - }, - "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438, \u044f\u043a\u0449\u043e \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u0430 \u0437 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c:\n https://www.home-assistant.io/integrations/accuweather/ \n\n\u0417\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0434\u0435\u044f\u043a\u0456 \u0441\u0435\u043d\u0441\u043e\u0440\u0438 \u043f\u0440\u0438\u0445\u043e\u0432\u0430\u043d\u0456 \u0456 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438. \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0430\u043a\u0442\u0438\u0432\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0438\u0445 \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432 \u0432 \u0440\u0435\u0454\u0441\u0442\u0440\u0456 \u043e\u0431'\u0454\u043a\u0442\u0456\u0432 \u0456 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438 \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u0445 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457.", - "title": "AccuWeather" + } } } }, @@ -27,8 +25,7 @@ "data": { "forecast": "\u041f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438" }, - "description": "\u0423 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u043e\u0431\u043c\u0435\u0436\u0435\u043d\u043d\u044f\u043c\u0438 \u0431\u0435\u0437\u043a\u043e\u0448\u0442\u043e\u0432\u043d\u043e\u0457 \u0432\u0435\u0440\u0441\u0456\u0457 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0443 \u043f\u043e\u0433\u043e\u0434\u0438 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0434\u0430\u043d\u0438\u0445 \u0431\u0443\u0434\u0435 \u0432\u0456\u0434\u0431\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u043a\u043e\u0436\u043d\u0456 64 \u0445\u0432\u0438\u043b\u0438\u043d\u0438, \u0430 \u043d\u0435 \u043a\u043e\u0436\u043d\u0456 32 \u0445\u0432\u0438\u043b\u0438\u043d\u0438.", - "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AccuWeather" + "description": "\u0423 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u043e\u0431\u043c\u0435\u0436\u0435\u043d\u043d\u044f\u043c\u0438 \u0431\u0435\u0437\u043a\u043e\u0448\u0442\u043e\u0432\u043d\u043e\u0457 \u0432\u0435\u0440\u0441\u0456\u0457 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0443 \u043f\u043e\u0433\u043e\u0434\u0438 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0434\u0430\u043d\u0438\u0445 \u0431\u0443\u0434\u0435 \u0432\u0456\u0434\u0431\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u043a\u043e\u0436\u043d\u0456 64 \u0445\u0432\u0438\u043b\u0438\u043d\u0438, \u0430 \u043d\u0435 \u043a\u043e\u0436\u043d\u0456 32 \u0445\u0432\u0438\u043b\u0438\u043d\u0438." } } }, diff --git a/homeassistant/components/accuweather/translations/zh-Hant.json b/homeassistant/components/accuweather/translations/zh-Hant.json index 6d9e7e1c36f..c7476b33460 100644 --- a/homeassistant/components/accuweather/translations/zh-Hant.json +++ b/homeassistant/components/accuweather/translations/zh-Hant.json @@ -18,9 +18,7 @@ "latitude": "\u7def\u5ea6", "longitude": "\u7d93\u5ea6", "name": "\u540d\u7a31" - }, - "description": "\u5047\u5982\u4f60\u9700\u8981\u5354\u52a9\u9032\u884c\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1\uff1ahttps://www.home-assistant.io/integrations/accuweather/\n\n\u67d0\u4e9b\u611f\u6e2c\u5668\u9810\u8a2d\u70ba\u672a\u555f\u7528\uff0c\u53ef\u4ee5\u65bc\u6574\u5408\u8a2d\u5b9a\u4e2d\u555f\u7528\u9019\u4e9b\u5be6\u9ad4\u3002\u5929\u6c23\u9810\u5831\u9810\u8a2d\u672a\u958b\u555f\u3002\u53ef\u4ee5\u65bc\u6574\u5408\u9078\u9805\u4e2d\u958b\u555f\u3002", - "title": "AccuWeather" + } } } }, @@ -30,8 +28,7 @@ "data": { "forecast": "\u5929\u6c23\u9810\u5831" }, - "description": "\u7531\u65bc AccuWeather API \u91d1\u9470\u514d\u8cbb\u7248\u672c\u9650\u5236\uff0c\u7576\u958b\u555f\u5929\u6c23\u9810\u5831\u6642\u3001\u6578\u64da\u6703\u6bcf 80 \u5206\u9418\u66f4\u65b0\u4e00\u6b21\uff0c\u800c\u975e 40 \u5206\u9418\u3002", - "title": "AccuWeather \u9078\u9805" + "description": "\u7531\u65bc AccuWeather API \u91d1\u9470\u514d\u8cbb\u7248\u672c\u9650\u5236\uff0c\u7576\u958b\u555f\u5929\u6c23\u9810\u5831\u6642\u3001\u6578\u64da\u6703\u6bcf 80 \u5206\u9418\u66f4\u65b0\u4e00\u6b21\uff0c\u800c\u975e 40 \u5206\u9418\u3002" } } }, diff --git a/homeassistant/components/acmeda/base.py b/homeassistant/components/acmeda/base.py index 3338bf9667d..e9ffb94c6c6 100644 --- a/homeassistant/components/acmeda/base.py +++ b/homeassistant/components/acmeda/base.py @@ -2,10 +2,8 @@ import aiopulse from homeassistant.core import callback -from homeassistant.helpers import entity -from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg +from homeassistant.helpers import device_registry as dr, entity, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity_registry import async_get_registry as get_ent_reg from .const import ACMEDA_ENTITY_REMOVE, DOMAIN, LOGGER @@ -21,11 +19,11 @@ class AcmedaBase(entity.Entity): """Unregister from entity and device registry and call entity remove function.""" LOGGER.error("Removing %s %s", self.__class__.__name__, self.unique_id) - ent_registry = await get_ent_reg(self.hass) + ent_registry = er.async_get(self.hass) if self.entity_id in ent_registry.entities: ent_registry.async_remove(self.entity_id) - dev_registry = await get_dev_reg(self.hass) + dev_registry = dr.async_get(self.hass) device = dev_registry.async_get_device(identifiers={(DOMAIN, self.unique_id)}) if device is not None: dev_registry.async_update_device( diff --git a/homeassistant/components/acmeda/helpers.py b/homeassistant/components/acmeda/helpers.py index a1a262be77e..ff8f28ffbc3 100644 --- a/homeassistant/components/acmeda/helpers.py +++ b/homeassistant/components/acmeda/helpers.py @@ -3,7 +3,7 @@ from __future__ import annotations from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN, LOGGER @@ -36,7 +36,7 @@ def async_add_acmeda_entities( async def update_devices(hass: HomeAssistant, config_entry: ConfigEntry, api): """Tell hass that device info has been updated.""" - dev_registry = await get_dev_reg(hass) + dev_registry = dr.async_get(hass) for api_item in api.values(): # Update Device name diff --git a/homeassistant/components/adax/manifest.json b/homeassistant/components/adax/manifest.json index b5c19269f09..408c099b8ac 100644 --- a/homeassistant/components/adax/manifest.json +++ b/homeassistant/components/adax/manifest.json @@ -3,7 +3,7 @@ "name": "Adax", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/adax", - "requirements": ["adax==0.2.0", "Adax-local==0.1.3"], + "requirements": ["adax==0.2.0", "Adax-local==0.1.4"], "codeowners": ["@danielhiversen"], "iot_class": "local_polling", "loggers": ["adax", "adax_local"] diff --git a/homeassistant/components/adax/translations/bg.json b/homeassistant/components/adax/translations/bg.json index d76109bfe01..e0204d6b43b 100644 --- a/homeassistant/components/adax/translations/bg.json +++ b/homeassistant/components/adax/translations/bg.json @@ -5,8 +5,7 @@ "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" }, "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" + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "step": { "cloud": { @@ -19,12 +18,6 @@ "wifi_pswd": "Wi-Fi \u043f\u0430\u0440\u043e\u043b\u0430", "wifi_ssid": "Wi-Fi SSID" } - }, - "user": { - "data": { - "host": "\u0425\u043e\u0441\u0442", - "password": "\u041f\u0430\u0440\u043e\u043b\u0430" - } } } } diff --git a/homeassistant/components/adax/translations/ca.json b/homeassistant/components/adax/translations/ca.json index 4e9f5eeb317..af3f2c3dc3f 100644 --- a/homeassistant/components/adax/translations/ca.json +++ b/homeassistant/components/adax/translations/ca.json @@ -7,8 +7,7 @@ "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" }, "error": { - "cannot_connect": "Ha fallat la connexi\u00f3", - "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "ID del compte", - "connection_type": "Selecciona el tipus de connexi\u00f3", - "host": "Amfitri\u00f3", - "password": "Contrasenya" + "connection_type": "Selecciona el tipus de connexi\u00f3" }, "description": "Selecciona el tipus de connexi\u00f3. La local necessita escalfadors amb Bluetooth" } diff --git a/homeassistant/components/adax/translations/cs.json b/homeassistant/components/adax/translations/cs.json index 07eeaa34f63..cbe934d3495 100644 --- a/homeassistant/components/adax/translations/cs.json +++ b/homeassistant/components/adax/translations/cs.json @@ -7,8 +7,7 @@ "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" }, "error": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "ID \u00fa\u010dtu", - "connection_type": "Vyberte typ p\u0159ipojen\u00ed", - "host": "Hostitel", - "password": "Heslo" + "connection_type": "Vyberte typ p\u0159ipojen\u00ed" }, "description": "Vyberte typ p\u0159ipojen\u00ed. Lok\u00e1ln\u00ed vy\u017eaduje oh\u0159\u00edva\u010de s bluetooth" } diff --git a/homeassistant/components/adax/translations/de.json b/homeassistant/components/adax/translations/de.json index 711a8b44645..a3e4b1b78d4 100644 --- a/homeassistant/components/adax/translations/de.json +++ b/homeassistant/components/adax/translations/de.json @@ -7,8 +7,7 @@ "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen", - "invalid_auth": "Ung\u00fcltige Authentifizierung" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "Konto-ID", - "connection_type": "Verbindungstyp ausw\u00e4hlen", - "host": "Host", - "password": "Passwort" + "connection_type": "Verbindungstyp ausw\u00e4hlen" }, "description": "Verbindungstyp ausw\u00e4hlen. Lokal erfordert Heizungen mit Bluetooth" } diff --git a/homeassistant/components/adax/translations/el.json b/homeassistant/components/adax/translations/el.json index ead0eb7e4f1..e1be44a197a 100644 --- a/homeassistant/components/adax/translations/el.json +++ b/homeassistant/components/adax/translations/el.json @@ -7,8 +7,7 @@ "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" }, "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" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", - "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "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" + "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03cd\u03c0\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03b8\u03b5\u03c1\u03bc\u03ac\u03c3\u03c4\u03c1\u03b5\u03c2 \u03bc\u03b5 bluetooth" } diff --git a/homeassistant/components/adax/translations/en.json b/homeassistant/components/adax/translations/en.json index ae31fdbc041..89c73702106 100644 --- a/homeassistant/components/adax/translations/en.json +++ b/homeassistant/components/adax/translations/en.json @@ -7,8 +7,7 @@ "invalid_auth": "Invalid authentication" }, "error": { - "cannot_connect": "Failed to connect", - "invalid_auth": "Invalid authentication" + "cannot_connect": "Failed to connect" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "Account ID", - "connection_type": "Select connection type", - "host": "Host", - "password": "Password" + "connection_type": "Select connection type" }, "description": "Select connection type. Local requires heaters with bluetooth" } diff --git a/homeassistant/components/adax/translations/es-419.json b/homeassistant/components/adax/translations/es-419.json index 85df150d663..597d031c598 100644 --- a/homeassistant/components/adax/translations/es-419.json +++ b/homeassistant/components/adax/translations/es-419.json @@ -20,7 +20,6 @@ }, "user": { "data": { - "account_id": "ID de cuenta", "connection_type": "Seleccione el tipo de conexi\u00f3n" }, "description": "Seleccione el tipo de conexi\u00f3n. Local requiere calentadores con bluetooth" diff --git a/homeassistant/components/adax/translations/es.json b/homeassistant/components/adax/translations/es.json index 413f8be0fa0..ea350d97f4e 100644 --- a/homeassistant/components/adax/translations/es.json +++ b/homeassistant/components/adax/translations/es.json @@ -7,8 +7,7 @@ "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" }, "error": { - "cannot_connect": "No se pudo conectar", - "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + "cannot_connect": "No se pudo conectar" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "ID de la cuenta", - "connection_type": "Seleccione el tipo de conexi\u00f3n", - "host": "Host", - "password": "Contrase\u00f1a" + "connection_type": "Seleccione el tipo de conexi\u00f3n" }, "description": "Seleccione el tipo de conexi\u00f3n. Local requiere calentadores con bluetooth" } diff --git a/homeassistant/components/adax/translations/et.json b/homeassistant/components/adax/translations/et.json index 2d154614295..8f164bc167c 100644 --- a/homeassistant/components/adax/translations/et.json +++ b/homeassistant/components/adax/translations/et.json @@ -7,8 +7,7 @@ "invalid_auth": "Tuvastamine nurjus" }, "error": { - "cannot_connect": "\u00dchendamine nurjus", - "invalid_auth": "Tuvastamise viga" + "cannot_connect": "\u00dchendamine nurjus" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "Konto ID", - "connection_type": "Vali \u00fchenduse t\u00fc\u00fcp", - "host": "Host", - "password": "Salas\u00f5na" + "connection_type": "Vali \u00fchenduse t\u00fc\u00fcp" }, "description": "Vali \u00fchenduse t\u00fc\u00fcp. Kohalik n\u00f5uab bluetoothiga k\u00fctteseadmeid" } diff --git a/homeassistant/components/adax/translations/fr.json b/homeassistant/components/adax/translations/fr.json index edcdcdd2738..1883db9c7f0 100644 --- a/homeassistant/components/adax/translations/fr.json +++ b/homeassistant/components/adax/translations/fr.json @@ -7,8 +7,7 @@ "invalid_auth": "Authentification non valide" }, "error": { - "cannot_connect": "\u00c9chec de connexion", - "invalid_auth": "Authentification non valide" + "cannot_connect": "\u00c9chec de connexion" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "identifiant de compte", - "connection_type": "S\u00e9lectionner le type de connexion", - "host": "H\u00f4te", - "password": "Mot de passe" + "connection_type": "S\u00e9lectionner le type de connexion" }, "description": "S\u00e9lectionnez le type de connexion. Local n\u00e9cessite des radiateurs avec Bluetooth" } diff --git a/homeassistant/components/adax/translations/he.json b/homeassistant/components/adax/translations/he.json index 0ba47926a07..e07cb6338ef 100644 --- a/homeassistant/components/adax/translations/he.json +++ b/homeassistant/components/adax/translations/he.json @@ -5,21 +5,13 @@ "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" }, "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" + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" }, "step": { "cloud": { "data": { "password": "\u05e1\u05d9\u05e1\u05de\u05d4" } - }, - "user": { - "data": { - "account_id": "\u05de\u05d6\u05d4\u05d4 \u05d7\u05e9\u05d1\u05d5\u05df", - "host": "\u05de\u05d0\u05e8\u05d7", - "password": "\u05e1\u05d9\u05e1\u05de\u05d4" - } } } } diff --git a/homeassistant/components/adax/translations/hu.json b/homeassistant/components/adax/translations/hu.json index a2a6dcdacaf..d0209483231 100644 --- a/homeassistant/components/adax/translations/hu.json +++ b/homeassistant/components/adax/translations/hu.json @@ -7,8 +7,7 @@ "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni", - "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + "cannot_connect": "Nem siker\u00fclt csatlakozni" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "Fi\u00f3k ID", - "connection_type": "V\u00e1lassza ki a kapcsolat t\u00edpus\u00e1t", - "host": "C\u00edm", - "password": "Jelsz\u00f3" + "connection_type": "V\u00e1lassza ki a kapcsolat t\u00edpus\u00e1t" }, "description": "V\u00e1lassza ki a kapcsolat t\u00edpus\u00e1t. A Helyi kapcsolathoz bluetooth-os f\u0171t\u0151berendez\u00e9sekre van sz\u00fcks\u00e9g" } diff --git a/homeassistant/components/adax/translations/id.json b/homeassistant/components/adax/translations/id.json index 864a8ed623d..5dd3f88d47d 100644 --- a/homeassistant/components/adax/translations/id.json +++ b/homeassistant/components/adax/translations/id.json @@ -7,8 +7,7 @@ "invalid_auth": "Autentikasi tidak valid" }, "error": { - "cannot_connect": "Gagal terhubung", - "invalid_auth": "Autentikasi tidak valid" + "cannot_connect": "Gagal terhubung" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "ID Akun", - "connection_type": "Pilih jenis koneksi", - "host": "Host", - "password": "Kata Sandi" + "connection_type": "Pilih jenis koneksi" }, "description": "Pilih jenis koneksi. Lokal membutuhkan pemanas dengan bluetooth" } diff --git a/homeassistant/components/adax/translations/it.json b/homeassistant/components/adax/translations/it.json index 17095fab12b..0b516951483 100644 --- a/homeassistant/components/adax/translations/it.json +++ b/homeassistant/components/adax/translations/it.json @@ -7,8 +7,7 @@ "invalid_auth": "Autenticazione non valida" }, "error": { - "cannot_connect": "Impossibile connettersi", - "invalid_auth": "Autenticazione non valida" + "cannot_connect": "Impossibile connettersi" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "ID account", - "connection_type": "Seleziona il tipo di connessione", - "host": "Host", - "password": "Password" + "connection_type": "Seleziona il tipo di connessione" }, "description": "Seleziona il tipo di connessione. Locale richiede riscaldatori con bluetooth" } diff --git a/homeassistant/components/adax/translations/ja.json b/homeassistant/components/adax/translations/ja.json index e432e23e081..604aa892f5b 100644 --- a/homeassistant/components/adax/translations/ja.json +++ b/homeassistant/components/adax/translations/ja.json @@ -7,8 +7,7 @@ "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c" }, "error": { - "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", - "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c" + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "\u30a2\u30ab\u30a6\u30f3\u30c8ID", - "connection_type": "\u63a5\u7d9a\u30bf\u30a4\u30d7\u306e\u9078\u629e", - "host": "\u30db\u30b9\u30c8", - "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" + "connection_type": "\u63a5\u7d9a\u30bf\u30a4\u30d7\u306e\u9078\u629e" }, "description": "\u63a5\u7d9a\u30bf\u30a4\u30d7\u3092\u9078\u629e\u3057\u307e\u3059\u3002\u30ed\u30fc\u30ab\u30eb\u306b\u306fBluetooth\u4ed8\u304d\u306e\u30d2\u30fc\u30bf\u30fc\u304c\u5fc5\u8981\u3067\u3059" } diff --git a/homeassistant/components/adax/translations/nl.json b/homeassistant/components/adax/translations/nl.json index ced3b8bdab0..cef5c118af5 100644 --- a/homeassistant/components/adax/translations/nl.json +++ b/homeassistant/components/adax/translations/nl.json @@ -7,8 +7,7 @@ "invalid_auth": "Ongeldige authenticatie" }, "error": { - "cannot_connect": "Kan geen verbinding maken", - "invalid_auth": "Ongeldige authenticatie" + "cannot_connect": "Kan geen verbinding maken" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "Account ID", - "connection_type": "Selecteer verbindingstype", - "host": "Host", - "password": "Wachtwoord" + "connection_type": "Selecteer verbindingstype" }, "description": "Selecteer verbindingstype. Lokaal vereist verwarming met Bluetooth." } diff --git a/homeassistant/components/adax/translations/no.json b/homeassistant/components/adax/translations/no.json index 23e82250673..a8787392af3 100644 --- a/homeassistant/components/adax/translations/no.json +++ b/homeassistant/components/adax/translations/no.json @@ -7,8 +7,7 @@ "invalid_auth": "Ugyldig godkjenning" }, "error": { - "cannot_connect": "Tilkobling mislyktes", - "invalid_auth": "Ugyldig godkjenning" + "cannot_connect": "Tilkobling mislyktes" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "Konto-ID", - "connection_type": "Velg tilkoblingstype", - "host": "Vert", - "password": "Passord" + "connection_type": "Velg tilkoblingstype" }, "description": "Velg tilkoblingstype. Lokalt krever varmeovner med bluetooth" } diff --git a/homeassistant/components/adax/translations/pl.json b/homeassistant/components/adax/translations/pl.json index 2a8e1016805..c92862cbcd4 100644 --- a/homeassistant/components/adax/translations/pl.json +++ b/homeassistant/components/adax/translations/pl.json @@ -7,8 +7,7 @@ "invalid_auth": "Niepoprawne uwierzytelnienie" }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "invalid_auth": "Niepoprawne uwierzytelnienie" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "Identyfikator konta", - "connection_type": "Wybierz typ po\u0142\u0105czenia", - "host": "Nazwa hosta lub adres IP", - "password": "Has\u0142o" + "connection_type": "Wybierz typ po\u0142\u0105czenia" }, "description": "Wybierz typ po\u0142\u0105czenia. \"Lokalny\" wymaga grzejnik\u00f3w z bluetooth." } diff --git a/homeassistant/components/adax/translations/pt-BR.json b/homeassistant/components/adax/translations/pt-BR.json index 09498932c20..f7cb68b30a9 100644 --- a/homeassistant/components/adax/translations/pt-BR.json +++ b/homeassistant/components/adax/translations/pt-BR.json @@ -7,8 +7,7 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "error": { - "cannot_connect": "Falha ao conectar", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "cannot_connect": "Falha ao conectar" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "ID da conta", - "connection_type": "Selecione o tipo de conex\u00e3o", - "host": "Nome do host", - "password": "Senha" + "connection_type": "Selecione o tipo de conex\u00e3o" }, "description": "Selecione o tipo de conex\u00e3o. Local requer aquecedores com bluetooth" } diff --git a/homeassistant/components/adax/translations/ru.json b/homeassistant/components/adax/translations/ru.json index 27596e86485..bb2d7c856e3 100644 --- a/homeassistant/components/adax/translations/ru.json +++ b/homeassistant/components/adax/translations/ru.json @@ -7,8 +7,7 @@ "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\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_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "ID \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438", - "connection_type": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", - "host": "\u0425\u043e\u0441\u0442", - "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + "connection_type": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043e\u0431\u043e\u0433\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u0438 \u0441 Bluetooth." } diff --git a/homeassistant/components/adax/translations/sk.json b/homeassistant/components/adax/translations/sk.json index 2c3ed1dd930..af9d83e3703 100644 --- a/homeassistant/components/adax/translations/sk.json +++ b/homeassistant/components/adax/translations/sk.json @@ -2,9 +2,6 @@ "config": { "abort": { "invalid_auth": "Neplatn\u00e9 overenie" - }, - "error": { - "invalid_auth": "Neplatn\u00e9 overenie" } } } \ No newline at end of file diff --git a/homeassistant/components/adax/translations/tr.json b/homeassistant/components/adax/translations/tr.json index bd7ef0fb6e9..e1fec21628f 100644 --- a/homeassistant/components/adax/translations/tr.json +++ b/homeassistant/components/adax/translations/tr.json @@ -7,8 +7,7 @@ "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" }, "error": { - "cannot_connect": "Ba\u011flanma hatas\u0131", - "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + "cannot_connect": "Ba\u011flanma hatas\u0131" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "Hesap Kimli\u011fi", - "connection_type": "Ba\u011flant\u0131 t\u00fcr\u00fcn\u00fc se\u00e7in", - "host": "Sunucu", - "password": "Parola" + "connection_type": "Ba\u011flant\u0131 t\u00fcr\u00fcn\u00fc se\u00e7in" }, "description": "Ba\u011flant\u0131 t\u00fcr\u00fcn\u00fc se\u00e7in. Yerel Bluetooth'lu \u0131s\u0131t\u0131c\u0131lar gerektirir" } diff --git a/homeassistant/components/adax/translations/zh-Hans.json b/homeassistant/components/adax/translations/zh-Hans.json index 86a404bcbf6..161112a3500 100644 --- a/homeassistant/components/adax/translations/zh-Hans.json +++ b/homeassistant/components/adax/translations/zh-Hans.json @@ -21,8 +21,7 @@ }, "user": { "data": { - "connection_type": "\u9009\u62e9\u8fde\u63a5\u7c7b\u578b", - "password": "\u5bc6\u7801" + "connection_type": "\u9009\u62e9\u8fde\u63a5\u7c7b\u578b" } } } diff --git a/homeassistant/components/adax/translations/zh-Hant.json b/homeassistant/components/adax/translations/zh-Hant.json index 92018be07da..cd99affe40e 100644 --- a/homeassistant/components/adax/translations/zh-Hant.json +++ b/homeassistant/components/adax/translations/zh-Hant.json @@ -7,8 +7,7 @@ "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { "cloud": { @@ -26,10 +25,7 @@ }, "user": { "data": { - "account_id": "\u5e33\u865f ID", - "connection_type": "\u9078\u64c7\u9023\u7dda\u985e\u5225", - "host": "\u4e3b\u6a5f\u7aef", - "password": "\u5bc6\u78bc" + "connection_type": "\u9078\u64c7\u9023\u7dda\u985e\u5225" }, "description": "\u9078\u64c7\u9023\u7dda\u985e\u5225\u3002\u672c\u5730\u7aef\u5c07\u9700\u8981\u5177\u5099\u85cd\u82bd\u52a0\u71b1\u5668" } diff --git a/homeassistant/components/adguard/translations/es.json b/homeassistant/components/adguard/translations/es.json index 5750808ab76..7fc2f972fa1 100644 --- a/homeassistant/components/adguard/translations/es.json +++ b/homeassistant/components/adguard/translations/es.json @@ -10,16 +10,16 @@ "step": { "hassio_confirm": { "description": "\u00bfDesea configurar Home Assistant para conectarse al AdGuard Home proporcionado por el complemento Supervisor: {addon} ?", - "title": "AdGuard Home a trav\u00e9s del complemento Supervisor" + "title": "AdGuard Home v\u00eda complemento de Home Assistant" }, "user": { "data": { "host": "Host", "password": "Contrase\u00f1a", "port": "Puerto", - "ssl": "AdGuard Home utiliza un certificado SSL", + "ssl": "Utiliza un certificado SSL", "username": "Usuario", - "verify_ssl": "AdGuard Home utiliza un certificado apropiado" + "verify_ssl": "Verificar certificado SSL" }, "description": "Configure su instancia de AdGuard Home para permitir la supervisi\u00f3n y el control." } diff --git a/homeassistant/components/adguard/translations/nl.json b/homeassistant/components/adguard/translations/nl.json index 9f991cbd407..345bc914ec5 100644 --- a/homeassistant/components/adguard/translations/nl.json +++ b/homeassistant/components/adguard/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", + "already_configured": "Dienst is al geconfigureerd", "existing_instance_updated": "Bestaande configuratie bijgewerkt." }, "error": { @@ -17,9 +17,9 @@ "host": "Host", "password": "Wachtwoord", "port": "Poort", - "ssl": "AdGuard Home maakt gebruik van een SSL certificaat", + "ssl": "Maakt gebruik van een SSL-certificaat", "username": "Gebruikersnaam", - "verify_ssl": "AdGuard Home maakt gebruik van een goed certificaat" + "verify_ssl": "SSL-certificaat verifi\u00ebren" }, "description": "Stel uw AdGuard Home-instantie in om toezicht en controle mogelijk te maken." } diff --git a/homeassistant/components/aemet/manifest.json b/homeassistant/components/aemet/manifest.json index 087d5c38820..e530489476e 100644 --- a/homeassistant/components/aemet/manifest.json +++ b/homeassistant/components/aemet/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/aemet", "requirements": ["AEMET-OpenData==0.2.1"], - "codeowners": [], + "codeowners": ["@Noltari"], "iot_class": "cloud_polling", "loggers": ["aemet_opendata"] } diff --git a/homeassistant/components/aemet/translations/ca.json b/homeassistant/components/aemet/translations/ca.json index 7dab53fc049..91641dd4329 100644 --- a/homeassistant/components/aemet/translations/ca.json +++ b/homeassistant/components/aemet/translations/ca.json @@ -14,8 +14,7 @@ "longitude": "Longitud", "name": "Nom de la integraci\u00f3" }, - "description": "Per generar la clau API, v\u00e9s a https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "Per generar la clau API, v\u00e9s a https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/de.json b/homeassistant/components/aemet/translations/de.json index 25f374f2e43..cc8b8ead259 100644 --- a/homeassistant/components/aemet/translations/de.json +++ b/homeassistant/components/aemet/translations/de.json @@ -14,8 +14,7 @@ "longitude": "L\u00e4ngengrad", "name": "Name der Integration" }, - "description": "Um den API-Schl\u00fcssel zu generieren, besuche https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "Um den API-Schl\u00fcssel zu generieren, besuche https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/el.json b/homeassistant/components/aemet/translations/el.json index 757fd41be70..a1aa6fa7ba2 100644 --- a/homeassistant/components/aemet/translations/el.json +++ b/homeassistant/components/aemet/translations/el.json @@ -14,8 +14,7 @@ "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, - "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 AEMET OpenData. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "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 AEMET OpenData. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/en.json b/homeassistant/components/aemet/translations/en.json index 7c10d1dc676..f732768c219 100644 --- a/homeassistant/components/aemet/translations/en.json +++ b/homeassistant/components/aemet/translations/en.json @@ -14,8 +14,7 @@ "longitude": "Longitude", "name": "Name of the integration" }, - "description": "To generate API key go to https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "To generate API key go to https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/es-419.json b/homeassistant/components/aemet/translations/es-419.json index 3a02d682f34..cc047365ee4 100644 --- a/homeassistant/components/aemet/translations/es-419.json +++ b/homeassistant/components/aemet/translations/es-419.json @@ -5,8 +5,7 @@ "data": { "name": "Nombre de la integraci\u00f3n" }, - "description": "Configure la integraci\u00f3n de AEMET OpenData. Para generar la clave API vaya a https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "Configure la integraci\u00f3n de AEMET OpenData. Para generar la clave API vaya a https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/es.json b/homeassistant/components/aemet/translations/es.json index 558d87886d9..b46f7720789 100644 --- a/homeassistant/components/aemet/translations/es.json +++ b/homeassistant/components/aemet/translations/es.json @@ -14,8 +14,7 @@ "longitude": "Longitud", "name": "Nombre de la integraci\u00f3n" }, - "description": "Configurar la integraci\u00f3n de AEMET OpenData. Para generar la clave API, ve a https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "Configurar la integraci\u00f3n de AEMET OpenData. Para generar la clave API, ve a https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/et.json b/homeassistant/components/aemet/translations/et.json index 04292b3d912..e1781241227 100644 --- a/homeassistant/components/aemet/translations/et.json +++ b/homeassistant/components/aemet/translations/et.json @@ -14,8 +14,7 @@ "longitude": "Pikkuskraad", "name": "Sidumise nimi" }, - "description": "API-v\u00f5tme loomiseks mine aadressile https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "API-v\u00f5tme loomiseks mine aadressile https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/fr.json b/homeassistant/components/aemet/translations/fr.json index bddfd2507bf..6c9d1be2cf2 100644 --- a/homeassistant/components/aemet/translations/fr.json +++ b/homeassistant/components/aemet/translations/fr.json @@ -14,8 +14,7 @@ "longitude": "Longitude", "name": "Nom de l'int\u00e9gration" }, - "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/hu.json b/homeassistant/components/aemet/translations/hu.json index 2f2984b1c90..2fa9636ccec 100644 --- a/homeassistant/components/aemet/translations/hu.json +++ b/homeassistant/components/aemet/translations/hu.json @@ -14,8 +14,7 @@ "longitude": "Hossz\u00fas\u00e1g", "name": "Az integr\u00e1ci\u00f3 neve" }, - "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://opendata.aemet.es/centrodedescargas/altaUsuario webhelyet", - "title": "AEMET OpenData" + "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://opendata.aemet.es/centrodedescargas/altaUsuario webhelyet" } } }, diff --git a/homeassistant/components/aemet/translations/id.json b/homeassistant/components/aemet/translations/id.json index 62239172aae..22e7213b4a3 100644 --- a/homeassistant/components/aemet/translations/id.json +++ b/homeassistant/components/aemet/translations/id.json @@ -14,8 +14,7 @@ "longitude": "Bujur", "name": "Nama integrasi" }, - "description": "Untuk membuat kunci API, buka https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "Untuk membuat kunci API, buka https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/it.json b/homeassistant/components/aemet/translations/it.json index 4b3f6f2251b..ac62822d3c9 100644 --- a/homeassistant/components/aemet/translations/it.json +++ b/homeassistant/components/aemet/translations/it.json @@ -14,8 +14,7 @@ "longitude": "Logitudine", "name": "Nome dell'integrazione" }, - "description": "Per generare la chiave API, vai su https://opendata.aemet.es/centrodescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "Per generare la chiave API, vai su https://opendata.aemet.es/centrodescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/ja.json b/homeassistant/components/aemet/translations/ja.json index a3a0904c223..fd79c699cee 100644 --- a/homeassistant/components/aemet/translations/ja.json +++ b/homeassistant/components/aemet/translations/ja.json @@ -14,8 +14,7 @@ "longitude": "\u7d4c\u5ea6", "name": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u540d\u524d" }, - "description": "AEMET OpenData\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://opendata.aemet.es/centrodedescargas/altaUsuario \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "AEMET OpenData" + "description": "AEMET OpenData\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://opendata.aemet.es/centrodedescargas/altaUsuario \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044" } } }, diff --git a/homeassistant/components/aemet/translations/ko.json b/homeassistant/components/aemet/translations/ko.json index 95c11b018fe..06d713755b6 100644 --- a/homeassistant/components/aemet/translations/ko.json +++ b/homeassistant/components/aemet/translations/ko.json @@ -14,8 +14,16 @@ "longitude": "\uacbd\ub3c4", "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc774\ub984" }, - "description": "AEMET OpenData \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://opendata.aemet.es/centrodedescargas/altaUsuario \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", - "title": "AEMET OpenData" + "description": "AEMET OpenData \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://opendata.aemet.es/centrodedescargas/altaUsuario \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "station_updates": "AEMET \uae30\uc0c1 \uad00\uce21\uc18c\uc5d0\uc11c \ub370\uc774\ud130 \uc218\uc9d1" + } } } } diff --git a/homeassistant/components/aemet/translations/lb.json b/homeassistant/components/aemet/translations/lb.json index 8e83c0e86d3..a2152f0b4d0 100644 --- a/homeassistant/components/aemet/translations/lb.json +++ b/homeassistant/components/aemet/translations/lb.json @@ -7,8 +7,7 @@ "latitude": "L\u00e4ngegrad", "longitude": "Breedegrad", "name": "Numm vun der Integratioun" - }, - "title": "AEMET OpenData" + } } } } diff --git a/homeassistant/components/aemet/translations/nl.json b/homeassistant/components/aemet/translations/nl.json index abf590e5a36..95062239ac2 100644 --- a/homeassistant/components/aemet/translations/nl.json +++ b/homeassistant/components/aemet/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { "invalid_api_key": "Ongeldige API-sleutel" @@ -14,8 +14,7 @@ "longitude": "Lengtegraad", "name": "Naam van de integratie" }, - "description": "Om een API sleutel te genereren ga naar https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "Om een API sleutel te genereren ga naar https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/no.json b/homeassistant/components/aemet/translations/no.json index a6076844122..86eed98a153 100644 --- a/homeassistant/components/aemet/translations/no.json +++ b/homeassistant/components/aemet/translations/no.json @@ -14,8 +14,7 @@ "longitude": "Lengdegrad", "name": "Navnet p\u00e5 integrasjonen" }, - "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/pl.json b/homeassistant/components/aemet/translations/pl.json index f1021585140..5c83e00b974 100644 --- a/homeassistant/components/aemet/translations/pl.json +++ b/homeassistant/components/aemet/translations/pl.json @@ -14,8 +14,7 @@ "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "name": "Nazwa integracji" }, - "description": "Aby wygenerowa\u0107 klucz API, przejd\u017a do https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "Aby wygenerowa\u0107 klucz API, przejd\u017a do https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/pt-BR.json b/homeassistant/components/aemet/translations/pt-BR.json index 7c5972dccd8..7cd1c704f99 100644 --- a/homeassistant/components/aemet/translations/pt-BR.json +++ b/homeassistant/components/aemet/translations/pt-BR.json @@ -14,8 +14,7 @@ "longitude": "Longitude", "name": "Nome da integra\u00e7\u00e3o" }, - "description": "Para gerar a chave API acesse https://opendata.aemet.es/centrodedescargas/altaUsuario", - "title": "AEMET OpenData" + "description": "Para gerar a chave API acesse https://opendata.aemet.es/centrodedescargas/altaUsuario" } } }, diff --git a/homeassistant/components/aemet/translations/ru.json b/homeassistant/components/aemet/translations/ru.json index 9d8496cbb2d..142b05d3a52 100644 --- a/homeassistant/components/aemet/translations/ru.json +++ b/homeassistant/components/aemet/translations/ru.json @@ -14,8 +14,7 @@ "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "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" + "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." } } }, diff --git a/homeassistant/components/aemet/translations/tr.json b/homeassistant/components/aemet/translations/tr.json index e1e3cae01ff..b835bf6ddc3 100644 --- a/homeassistant/components/aemet/translations/tr.json +++ b/homeassistant/components/aemet/translations/tr.json @@ -14,8 +14,7 @@ "longitude": "Boylam", "name": "Entegrasyonun ad\u0131" }, - "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://opendata.aemet.es/centrodedescargas/altaUsuario adresine gidin.", - "title": "AEMET OpenData" + "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://opendata.aemet.es/centrodedescargas/altaUsuario adresine gidin." } } }, diff --git a/homeassistant/components/aemet/translations/zh-Hant.json b/homeassistant/components/aemet/translations/zh-Hant.json index 4d77ca17505..63e6d69d6cd 100644 --- a/homeassistant/components/aemet/translations/zh-Hant.json +++ b/homeassistant/components/aemet/translations/zh-Hant.json @@ -14,8 +14,7 @@ "longitude": "\u7d93\u5ea6", "name": "\u6574\u5408\u540d\u7a31" }, - "description": "\u8acb\u81f3 https://opendata.aemet.es/centrodedescargas/altaUsuario \u4ee5\u7522\u751f API \u91d1\u9470", - "title": "AEMET OpenData" + "description": "\u8acb\u81f3 https://opendata.aemet.es/centrodedescargas/altaUsuario \u4ee5\u7522\u751f API \u91d1\u9470" } } }, diff --git a/homeassistant/components/aemet/weather.py b/homeassistant/components/aemet/weather.py index 2bbb4d0da55..d05442b621e 100644 --- a/homeassistant/components/aemet/weather.py +++ b/homeassistant/components/aemet/weather.py @@ -1,7 +1,7 @@ """Support for the AEMET OpenData service.""" from homeassistant.components.weather import WeatherEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import TEMP_CELSIUS +from homeassistant.const import PRESSURE_HPA, SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -48,6 +48,8 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity): _attr_attribution = ATTRIBUTION _attr_temperature_unit = TEMP_CELSIUS + _attr_pressure_unit = PRESSURE_HPA + _attr_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR def __init__( self, @@ -92,10 +94,10 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity): @property def wind_bearing(self): - """Return the temperature.""" + """Return the wind bearing.""" return self.coordinator.data[ATTR_API_WIND_BEARING] @property def wind_speed(self): - """Return the temperature.""" + """Return the wind speed.""" return self.coordinator.data[ATTR_API_WIND_SPEED] diff --git a/homeassistant/components/agent_dvr/alarm_control_panel.py b/homeassistant/components/agent_dvr/alarm_control_panel.py index 3e264a1985d..8978be97c1d 100644 --- a/homeassistant/components/agent_dvr/alarm_control_panel.py +++ b/homeassistant/components/agent_dvr/alarm_control_panel.py @@ -3,13 +3,16 @@ from homeassistant.components.alarm_control_panel import ( AlarmControlPanelEntity, AlarmControlPanelEntityFeature, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, ) +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import CONNECTION, DOMAIN as AGENT_DOMAIN @@ -23,8 +26,10 @@ CONST_ALARM_CONTROL_PANEL_NAME = "Alarm Panel" async def async_setup_entry( - hass, config_entry, async_add_entities, discovery_info=None -): + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the Agent DVR Alarm Control Panels.""" async_add_entities( [AgentBaseStation(hass.data[AGENT_DOMAIN][config_entry.entry_id][CONNECTION])] diff --git a/homeassistant/components/agent_dvr/camera.py b/homeassistant/components/agent_dvr/camera.py index 49ca342ca09..9aab33efd5a 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -6,9 +6,14 @@ from agent import AgentError 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 ATTR_ATTRIBUTION -from homeassistant.helpers import entity_platform +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import ( + AddEntitiesCallback, + async_get_current_platform, +) from .const import ( ATTRIBUTION, @@ -37,8 +42,10 @@ CAMERA_SERVICES = { async def async_setup_entry( - hass, config_entry, async_add_entities, discovery_info=None -): + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the Agent cameras.""" filter_urllib3_logging() cameras = [] @@ -55,7 +62,7 @@ async def async_setup_entry( async_add_entities(cameras) - platform = entity_platform.async_get_current_platform() + platform = async_get_current_platform() for service, method in CAMERA_SERVICES.items(): platform.async_register_entity_service(service, {}, method) diff --git a/homeassistant/components/agent_dvr/translations/nl.json b/homeassistant/components/agent_dvr/translations/nl.json index 7c679f66c11..3999d18f7a7 100644 --- a/homeassistant/components/agent_dvr/translations/nl.json +++ b/homeassistant/components/agent_dvr/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken" }, "step": { diff --git a/homeassistant/components/airly/__init__.py b/homeassistant/components/airly/__init__.py index 83a9a50ec7f..31396ecf51b 100644 --- a/homeassistant/components/airly/__init__.py +++ b/homeassistant/components/airly/__init__.py @@ -15,9 +15,8 @@ from homeassistant.components.air_quality import DOMAIN as AIR_QUALITY_PLATFORM from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry +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 async_get_registry from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt as dt_util @@ -82,7 +81,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # identifiers in device_info should use tuple[str, str] type, but latitude and # longitude are float, so we convert old device entries to use correct types # We used to use a str 3-tuple here sometime, convert that to a 2-tuple too. - device_registry = await async_get_registry(hass) + device_registry = dr.async_get(hass) old_ids = (DOMAIN, latitude, longitude) for old_ids in ( (DOMAIN, latitude, longitude), @@ -114,7 +113,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_setup_platforms(entry, PLATFORMS) # Remove air_quality entities from registry if they exist - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) unique_id = f"{coordinator.latitude}-{coordinator.longitude}" if entity_id := ent_reg.async_get_entity_id( AIR_QUALITY_PLATFORM, DOMAIN, unique_id diff --git a/homeassistant/components/airly/translations/bg.json b/homeassistant/components/airly/translations/bg.json index 955cc8c1ac4..03decc0ac69 100644 --- a/homeassistant/components/airly/translations/bg.json +++ b/homeassistant/components/airly/translations/bg.json @@ -15,8 +15,7 @@ "longitude": "\u0414\u044a\u043b\u0436\u0438\u043d\u0430", "name": "\u0418\u043c\u0435 \u043d\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u0442\u0430" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0430 \u0432\u044a\u0437\u0434\u0443\u0445\u0430 Airly \u0417\u0430 \u0434\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u0442\u0435 \u043a\u043b\u044e\u0447 \u0437\u0430 API, \u043e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 https://developer.airly.eu/register", - "title": "Airly" + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0430 \u0432\u044a\u0437\u0434\u0443\u0445\u0430 Airly \u0417\u0430 \u0434\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u0442\u0435 \u043a\u043b\u044e\u0447 \u0437\u0430 API, \u043e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 https://developer.airly.eu/register" } } } diff --git a/homeassistant/components/airly/translations/ca.json b/homeassistant/components/airly/translations/ca.json index ac956185c14..36d7aaf8190 100644 --- a/homeassistant/components/airly/translations/ca.json +++ b/homeassistant/components/airly/translations/ca.json @@ -15,8 +15,7 @@ "longitude": "Longitud", "name": "Nom" }, - "description": "Per generar la clau API, v\u00e9s a https://developer.airly.eu/register", - "title": "Airly" + "description": "Per generar la clau API, v\u00e9s a https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/cs.json b/homeassistant/components/airly/translations/cs.json index 8b35399bcb0..3b4f98f53f8 100644 --- a/homeassistant/components/airly/translations/cs.json +++ b/homeassistant/components/airly/translations/cs.json @@ -15,8 +15,7 @@ "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka", "name": "Jm\u00e9no" }, - "description": "Nastavte integraci kvality vzduchu Airly. Chcete-li vygenerovat kl\u00ed\u010d rozhran\u00ed API, p\u0159ejd\u011bte na https://developer.airly.eu/register", - "title": "Airly" + "description": "Nastavte integraci kvality vzduchu Airly. Chcete-li vygenerovat kl\u00ed\u010d rozhran\u00ed API, p\u0159ejd\u011bte na https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/da.json b/homeassistant/components/airly/translations/da.json index 53b05a38992..2b1cbe643b8 100644 --- a/homeassistant/components/airly/translations/da.json +++ b/homeassistant/components/airly/translations/da.json @@ -14,8 +14,7 @@ "longitude": "L\u00e6ngdegrad", "name": "Integrationens navn" }, - "description": "Konfigurer Airly luftkvalitetsintegration. For at generere API-n\u00f8gle, g\u00e5 til https://developer.airly.eu/register", - "title": "Airly" + "description": "Konfigurer Airly luftkvalitetsintegration. For at generere API-n\u00f8gle, g\u00e5 til https://developer.airly.eu/register" } } } diff --git a/homeassistant/components/airly/translations/de.json b/homeassistant/components/airly/translations/de.json index 216a8c56c6d..bc37bbc139c 100644 --- a/homeassistant/components/airly/translations/de.json +++ b/homeassistant/components/airly/translations/de.json @@ -15,8 +15,7 @@ "longitude": "L\u00e4ngengrad", "name": "Name" }, - "description": "Um einen API-Schl\u00fcssel zu generieren, besuche https://developer.airly.eu/register", - "title": "Airly" + "description": "Um einen API-Schl\u00fcssel zu generieren, besuche https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/el.json b/homeassistant/components/airly/translations/el.json index adb944a4259..e8d61743523 100644 --- a/homeassistant/components/airly/translations/el.json +++ b/homeassistant/components/airly/translations/el.json @@ -15,8 +15,7 @@ "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\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 \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03ad\u03c1\u03b1 Airly. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://developer.airly.eu/register", - "title": "Airly" + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b1\u03ad\u03c1\u03b1 Airly. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/en.json b/homeassistant/components/airly/translations/en.json index 249905eff2a..1dee608b1da 100644 --- a/homeassistant/components/airly/translations/en.json +++ b/homeassistant/components/airly/translations/en.json @@ -15,8 +15,7 @@ "longitude": "Longitude", "name": "Name" }, - "description": "To generate API key go to https://developer.airly.eu/register", - "title": "Airly" + "description": "To generate API key go to https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/es-419.json b/homeassistant/components/airly/translations/es-419.json index c7d1e388d67..31149641d11 100644 --- a/homeassistant/components/airly/translations/es-419.json +++ b/homeassistant/components/airly/translations/es-419.json @@ -14,8 +14,7 @@ "longitude": "Longitud", "name": "Nombre de la integraci\u00f3n" }, - "description": "Configure la integraci\u00f3n de la calidad del aire de Airly. Para generar la clave API, vaya a https://developer.airly.eu/register", - "title": "Airly" + "description": "Configure la integraci\u00f3n de la calidad del aire de Airly. Para generar la clave API, vaya a https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/es.json b/homeassistant/components/airly/translations/es.json index a96a2f62293..9d0b254713b 100644 --- a/homeassistant/components/airly/translations/es.json +++ b/homeassistant/components/airly/translations/es.json @@ -15,8 +15,7 @@ "longitude": "Longitud", "name": "Nombre" }, - "description": "Establecer la integraci\u00f3n de la calidad del aire de Airly. Para generar la clave de la API vaya a https://developer.airly.eu/register", - "title": "Airly" + "description": "Establecer la integraci\u00f3n de la calidad del aire de Airly. Para generar la clave de la API vaya a https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/et.json b/homeassistant/components/airly/translations/et.json index 6a55262ac6c..ea17ec01ea6 100644 --- a/homeassistant/components/airly/translations/et.json +++ b/homeassistant/components/airly/translations/et.json @@ -15,8 +15,7 @@ "longitude": "Pikkuskraad", "name": "Nimi" }, - "description": "API-v\u00f5tme loomiseks mine aadressile https://developer.airly.eu/register", - "title": "" + "description": "API-v\u00f5tme loomiseks mine aadressile https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/fr.json b/homeassistant/components/airly/translations/fr.json index 17a7248f7ad..e9134879c00 100644 --- a/homeassistant/components/airly/translations/fr.json +++ b/homeassistant/components/airly/translations/fr.json @@ -15,8 +15,7 @@ "longitude": "Longitude", "name": "Nom" }, - "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://developer.airly.eu/register", - "title": "Airly" + "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/he.json b/homeassistant/components/airly/translations/he.json index 8faa6ac2092..edbf648397f 100644 --- a/homeassistant/components/airly/translations/he.json +++ b/homeassistant/components/airly/translations/he.json @@ -13,8 +13,7 @@ "latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1", "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", "name": "\u05e9\u05dd" - }, - "title": "\u05d0\u05d5\u05d5\u05e8\u05d9\u05e8\u05d9" + } } } } diff --git a/homeassistant/components/airly/translations/hu.json b/homeassistant/components/airly/translations/hu.json index 04d667f4079..6955c2456cc 100644 --- a/homeassistant/components/airly/translations/hu.json +++ b/homeassistant/components/airly/translations/hu.json @@ -15,8 +15,7 @@ "longitude": "Hossz\u00fas\u00e1g", "name": "Elnevez\u00e9s" }, - "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://developer.airly.eu/register webhelyet", - "title": "Airly" + "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://developer.airly.eu/register webhelyet" } } }, diff --git a/homeassistant/components/airly/translations/id.json b/homeassistant/components/airly/translations/id.json index bc97db4135a..7e2d7e3930e 100644 --- a/homeassistant/components/airly/translations/id.json +++ b/homeassistant/components/airly/translations/id.json @@ -15,8 +15,7 @@ "longitude": "Bujur", "name": "Nama" }, - "description": "Untuk membuat kunci API, buka https://developer.airly.eu/register", - "title": "Airly" + "description": "Untuk membuat kunci API, buka https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/it.json b/homeassistant/components/airly/translations/it.json index b96d5c8a74c..d57023faeca 100644 --- a/homeassistant/components/airly/translations/it.json +++ b/homeassistant/components/airly/translations/it.json @@ -15,8 +15,7 @@ "longitude": "Logitudine", "name": "Nome" }, - "description": "Per generare la chiave API, vai su https://developer.airly.eu/register", - "title": "Airly" + "description": "Per generare la chiave API, vai su https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/ja.json b/homeassistant/components/airly/translations/ja.json index 6835412e6db..6ff5e2216a6 100644 --- a/homeassistant/components/airly/translations/ja.json +++ b/homeassistant/components/airly/translations/ja.json @@ -15,8 +15,7 @@ "longitude": "\u7d4c\u5ea6", "name": "\u540d\u524d" }, - "description": "Airly\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.airly.eu/register \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "Airly" + "description": "Airly\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.airly.eu/register \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044" } } }, diff --git a/homeassistant/components/airly/translations/ko.json b/homeassistant/components/airly/translations/ko.json index 1f9db4a592e..d66bb93dc39 100644 --- a/homeassistant/components/airly/translations/ko.json +++ b/homeassistant/components/airly/translations/ko.json @@ -15,8 +15,7 @@ "longitude": "\uacbd\ub3c4", "name": "\uc774\ub984" }, - "description": "Airly \uacf5\uae30 \ud488\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://developer.airly.eu/register \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", - "title": "Airly" + "description": "Airly \uacf5\uae30 \ud488\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://developer.airly.eu/register \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694" } } }, diff --git a/homeassistant/components/airly/translations/lb.json b/homeassistant/components/airly/translations/lb.json index dd24ee3066f..32832f52266 100644 --- a/homeassistant/components/airly/translations/lb.json +++ b/homeassistant/components/airly/translations/lb.json @@ -15,8 +15,7 @@ "longitude": "L\u00e4ngegrad", "name": "Numm" }, - "description": "Airly Loft Qualit\u00e9it Integratioun ariichten. Fir een API Schl\u00ebssel z'erstelle gitt op https://developer.airly.eu/register", - "title": "Airly" + "description": "Airly Loft Qualit\u00e9it Integratioun ariichten. Fir een API Schl\u00ebssel z'erstelle gitt op https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/nl.json b/homeassistant/components/airly/translations/nl.json index 70fbca2074f..9bfce7e7708 100644 --- a/homeassistant/components/airly/translations/nl.json +++ b/homeassistant/components/airly/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { "invalid_api_key": "Ongeldige API-sleutel", @@ -15,8 +15,7 @@ "longitude": "Lengtegraad", "name": "Naam" }, - "description": "Om een API sleutel te genereren ga naar https://developer.airly.eu/register", - "title": "Airly" + "description": "Om een API sleutel te genereren ga naar https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/no.json b/homeassistant/components/airly/translations/no.json index 015e95af8f2..f4f87d6d887 100644 --- a/homeassistant/components/airly/translations/no.json +++ b/homeassistant/components/airly/translations/no.json @@ -15,8 +15,7 @@ "longitude": "Lengdegrad", "name": "Navn" }, - "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://developer.airly.eu/register", - "title": "" + "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/pl.json b/homeassistant/components/airly/translations/pl.json index dd44f2bf635..72d35ec97a7 100644 --- a/homeassistant/components/airly/translations/pl.json +++ b/homeassistant/components/airly/translations/pl.json @@ -15,8 +15,7 @@ "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "name": "Nazwa" }, - "description": "By wygenerowa\u0107 klucz API, przejd\u017a na stron\u0119 https://developer.airly.eu/register", - "title": "Airly" + "description": "By wygenerowa\u0107 klucz API, przejd\u017a na stron\u0119 https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/pt-BR.json b/homeassistant/components/airly/translations/pt-BR.json index 0e9913b559c..fe19bc8b3a7 100644 --- a/homeassistant/components/airly/translations/pt-BR.json +++ b/homeassistant/components/airly/translations/pt-BR.json @@ -15,8 +15,7 @@ "longitude": "Longitude", "name": "Nome" }, - "description": "Para gerar a chave de API, acesse https://developer.airly.eu/register", - "title": "Airly" + "description": "Para gerar a chave de API, acesse https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/pt.json b/homeassistant/components/airly/translations/pt.json index 6ebb22b565a..aff2cb2399b 100644 --- a/homeassistant/components/airly/translations/pt.json +++ b/homeassistant/components/airly/translations/pt.json @@ -13,8 +13,7 @@ "latitude": "Latitude", "longitude": "Longitude", "name": "Nome" - }, - "title": "" + } } } } diff --git a/homeassistant/components/airly/translations/ru.json b/homeassistant/components/airly/translations/ru.json index 80c6a98813c..4b159f1e50d 100644 --- a/homeassistant/components/airly/translations/ru.json +++ b/homeassistant/components/airly/translations/ru.json @@ -15,8 +15,7 @@ "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "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" + "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." } } }, diff --git a/homeassistant/components/airly/translations/sl.json b/homeassistant/components/airly/translations/sl.json index e1c89501394..2debfc1c0ef 100644 --- a/homeassistant/components/airly/translations/sl.json +++ b/homeassistant/components/airly/translations/sl.json @@ -14,8 +14,7 @@ "longitude": "Zemljepisna dol\u017eina", "name": "Ime integracije" }, - "description": "Nastavite Airly integracijo za kakovost zraka. \u010ce \u017eelite ustvariti API klju\u010d pojdite na https://developer.airly.eu/register", - "title": "Airly" + "description": "Nastavite Airly integracijo za kakovost zraka. \u010ce \u017eelite ustvariti API klju\u010d pojdite na https://developer.airly.eu/register" } } }, diff --git a/homeassistant/components/airly/translations/sv.json b/homeassistant/components/airly/translations/sv.json index e47cb5cd328..d182115230b 100644 --- a/homeassistant/components/airly/translations/sv.json +++ b/homeassistant/components/airly/translations/sv.json @@ -14,8 +14,7 @@ "longitude": "Longitud", "name": "Integrationens namn" }, - "description": "Konfigurera integration av luftkvalitet. F\u00f6r att skapa API-nyckel, g\u00e5 till https://developer.airly.eu/register", - "title": "Airly" + "description": "Konfigurera integration av luftkvalitet. F\u00f6r att skapa API-nyckel, g\u00e5 till https://developer.airly.eu/register" } } } diff --git a/homeassistant/components/airly/translations/tr.json b/homeassistant/components/airly/translations/tr.json index 7f5643be2d0..989179b73d8 100644 --- a/homeassistant/components/airly/translations/tr.json +++ b/homeassistant/components/airly/translations/tr.json @@ -15,8 +15,7 @@ "longitude": "Boylam", "name": "Ad" }, - "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://developer.airly.eu/register adresine gidin.", - "title": "Airly" + "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://developer.airly.eu/register adresine gidin." } } }, diff --git a/homeassistant/components/airly/translations/uk.json b/homeassistant/components/airly/translations/uk.json index 51bcf5195df..08d92e2282d 100644 --- a/homeassistant/components/airly/translations/uk.json +++ b/homeassistant/components/airly/translations/uk.json @@ -15,8 +15,7 @@ "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", "name": "\u041d\u0430\u0437\u0432\u0430" }, - "description": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0441\u0435\u0440\u0432\u0456\u0441\u0443 \u0437 \u0430\u043d\u0430\u043b\u0456\u0437\u0443 \u044f\u043a\u043e\u0441\u0442\u0456 \u043f\u043e\u0432\u0456\u0442\u0440\u044f Airly. \u0429\u043e\u0431 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c https://developer.airly.eu/register.", - "title": "Airly" + "description": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0441\u0435\u0440\u0432\u0456\u0441\u0443 \u0437 \u0430\u043d\u0430\u043b\u0456\u0437\u0443 \u044f\u043a\u043e\u0441\u0442\u0456 \u043f\u043e\u0432\u0456\u0442\u0440\u044f Airly. \u0429\u043e\u0431 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c https://developer.airly.eu/register." } } }, diff --git a/homeassistant/components/airly/translations/zh-Hant.json b/homeassistant/components/airly/translations/zh-Hant.json index 65f0bf8cde5..9973269facd 100644 --- a/homeassistant/components/airly/translations/zh-Hant.json +++ b/homeassistant/components/airly/translations/zh-Hant.json @@ -15,8 +15,7 @@ "longitude": "\u7d93\u5ea6", "name": "\u540d\u7a31" }, - "description": "\u8acb\u81f3 https://developer.airly.eu/register \u4ee5\u7522\u751f API \u91d1\u9470", - "title": "Airly" + "description": "\u8acb\u81f3 https://developer.airly.eu/register \u4ee5\u7522\u751f API \u91d1\u9470" } } }, diff --git a/homeassistant/components/airnow/translations/ca.json b/homeassistant/components/airnow/translations/ca.json index 9a2b0aa9afc..a4f8de82284 100644 --- a/homeassistant/components/airnow/translations/ca.json +++ b/homeassistant/components/airnow/translations/ca.json @@ -17,10 +17,8 @@ "longitude": "Longitud", "radius": "Radi de l'estaci\u00f3 (milles; opcional)" }, - "description": "Per generar la clau API, v\u00e9s a https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "Per generar la clau API, v\u00e9s a https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/cs.json b/homeassistant/components/airnow/translations/cs.json index d978e44c70a..5268257905e 100644 --- a/homeassistant/components/airnow/translations/cs.json +++ b/homeassistant/components/airnow/translations/cs.json @@ -14,10 +14,8 @@ "api_key": "Kl\u00ed\u010d API", "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka" - }, - "title": "AirNow" + } } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/de.json b/homeassistant/components/airnow/translations/de.json index 3c207a39cce..c42da3491c1 100644 --- a/homeassistant/components/airnow/translations/de.json +++ b/homeassistant/components/airnow/translations/de.json @@ -17,10 +17,8 @@ "longitude": "L\u00e4ngengrad", "radius": "Stationsradius (Meilen; optional)" }, - "description": "Um den API-Schl\u00fcssel zu generieren, besuche https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "Um den API-Schl\u00fcssel zu generieren, besuche https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/el.json b/homeassistant/components/airnow/translations/el.json index caaaafc9e9a..c8e97c5a925 100644 --- a/homeassistant/components/airnow/translations/el.json +++ b/homeassistant/components/airnow/translations/el.json @@ -17,10 +17,8 @@ "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", "radius": "\u0391\u03ba\u03c4\u03af\u03bd\u03b1 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd (\u03bc\u03af\u03bb\u03b9\u03b1, \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" }, - "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 AirNow \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03b1\u03ad\u03c1\u03b1. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "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 AirNow \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03b1\u03ad\u03c1\u03b1. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/en.json b/homeassistant/components/airnow/translations/en.json index cb3081284b4..07915652847 100644 --- a/homeassistant/components/airnow/translations/en.json +++ b/homeassistant/components/airnow/translations/en.json @@ -17,10 +17,8 @@ "longitude": "Longitude", "radius": "Station Radius (miles; optional)" }, - "description": "To generate API key go to https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "To generate API key go to https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/es-419.json b/homeassistant/components/airnow/translations/es-419.json index 015d7242ef1..5f49ebdb143 100644 --- a/homeassistant/components/airnow/translations/es-419.json +++ b/homeassistant/components/airnow/translations/es-419.json @@ -8,10 +8,8 @@ "data": { "radius": "Radio de la estaci\u00f3n (millas; opcional)" }, - "description": "Configure la integraci\u00f3n de la calidad del aire de AirNow. Para generar la clave de API, vaya a https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "Configure la integraci\u00f3n de la calidad del aire de AirNow. Para generar la clave de API, vaya a https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/es.json b/homeassistant/components/airnow/translations/es.json index d6a228a6e27..b5b915910c5 100644 --- a/homeassistant/components/airnow/translations/es.json +++ b/homeassistant/components/airnow/translations/es.json @@ -17,10 +17,8 @@ "longitude": "Longitud", "radius": "Radio de la estaci\u00f3n (millas; opcional)" }, - "description": "Configurar la integraci\u00f3n de calidad del aire de AirNow. Para generar una clave API, ve a https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "Configurar la integraci\u00f3n de calidad del aire de AirNow. Para generar una clave API, ve a https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/et.json b/homeassistant/components/airnow/translations/et.json index 3bdf8661d36..1d6744e69ae 100644 --- a/homeassistant/components/airnow/translations/et.json +++ b/homeassistant/components/airnow/translations/et.json @@ -17,10 +17,8 @@ "longitude": "Pikkuskraad", "radius": "Jaama raadius (miilid; valikuline)" }, - "description": "API-v\u00f5tme loomiseks mine aadressile https://docs.airnowapi.org/account/request/", - "title": "" + "description": "API-v\u00f5tme loomiseks mine aadressile https://docs.airnowapi.org/account/request/" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/fr.json b/homeassistant/components/airnow/translations/fr.json index 2da7427e1be..ce45573f9bf 100644 --- a/homeassistant/components/airnow/translations/fr.json +++ b/homeassistant/components/airnow/translations/fr.json @@ -17,10 +17,8 @@ "longitude": "Longitude", "radius": "Rayon d'action de la station (en miles, facultatif)" }, - "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/hu.json b/homeassistant/components/airnow/translations/hu.json index 52bf8cd63a0..a0d86014768 100644 --- a/homeassistant/components/airnow/translations/hu.json +++ b/homeassistant/components/airnow/translations/hu.json @@ -17,10 +17,8 @@ "longitude": "Hossz\u00fas\u00e1g", "radius": "\u00c1llom\u00e1s sugara (m\u00e9rf\u00f6ld; opcion\u00e1lis)" }, - "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://docs.airnowapi.org/account/request/ webhelyet", - "title": "AirNow" + "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://docs.airnowapi.org/account/request/ webhelyet" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/id.json b/homeassistant/components/airnow/translations/id.json index 4d4ac987320..b0beea88cb1 100644 --- a/homeassistant/components/airnow/translations/id.json +++ b/homeassistant/components/airnow/translations/id.json @@ -17,10 +17,8 @@ "longitude": "Bujur", "radius": "Radius Stasiun (mil; opsional)" }, - "description": "Untuk membuat kunci API buka https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "Untuk membuat kunci API buka https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/it.json b/homeassistant/components/airnow/translations/it.json index 77715910c75..d35e4659c90 100644 --- a/homeassistant/components/airnow/translations/it.json +++ b/homeassistant/components/airnow/translations/it.json @@ -17,10 +17,8 @@ "longitude": "Logitudine", "radius": "Raggio stazione (miglia; opzionale)" }, - "description": "Per generare la chiave API vai su https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "Per generare la chiave API vai su https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/ja.json b/homeassistant/components/airnow/translations/ja.json index d535f7fc044..17a1b14e164 100644 --- a/homeassistant/components/airnow/translations/ja.json +++ b/homeassistant/components/airnow/translations/ja.json @@ -17,10 +17,8 @@ "longitude": "\u7d4c\u5ea6", "radius": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u534a\u5f84(\u30de\u30a4\u30eb; \u30aa\u30d7\u30b7\u30e7\u30f3)" }, - "description": "AirNow\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://docs.airnowapi.org/account/request/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "AirNow" + "description": "AirNow\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://docs.airnowapi.org/account/request/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/ko.json b/homeassistant/components/airnow/translations/ko.json index adfbf0be8ed..0364b7642a6 100644 --- a/homeassistant/components/airnow/translations/ko.json +++ b/homeassistant/components/airnow/translations/ko.json @@ -17,10 +17,8 @@ "longitude": "\uacbd\ub3c4", "radius": "\uce21\uc815 \uc2a4\ud14c\uc774\uc158 \ubc18\uacbd (\ub9c8\uc77c; \uc120\ud0dd \uc0ac\ud56d)" }, - "description": "AirNow \uacf5\uae30 \ud488\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://docs.airnowapi.org/account/request \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", - "title": "AirNow" + "description": "AirNow \uacf5\uae30 \ud488\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://docs.airnowapi.org/account/request \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/lb.json b/homeassistant/components/airnow/translations/lb.json index a62bd0bf478..90ce6b3a4b2 100644 --- a/homeassistant/components/airnow/translations/lb.json +++ b/homeassistant/components/airnow/translations/lb.json @@ -15,10 +15,8 @@ "api_key": "API Schl\u00ebssel", "latitude": "L\u00e4ngegrad", "longitude": "Breedegrag" - }, - "title": "AirNow" + } } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/nl.json b/homeassistant/components/airnow/translations/nl.json index 5e5e33bf93d..0ba6ce2a965 100644 --- a/homeassistant/components/airnow/translations/nl.json +++ b/homeassistant/components/airnow/translations/nl.json @@ -17,10 +17,8 @@ "longitude": "Lengtegraad", "radius": "Stationsradius (mijl; optioneel)" }, - "description": "Om een API sleutel te genereren ga naar https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "Om een API sleutel te genereren ga naar https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/no.json b/homeassistant/components/airnow/translations/no.json index b56b7096e2b..ee2bf44672c 100644 --- a/homeassistant/components/airnow/translations/no.json +++ b/homeassistant/components/airnow/translations/no.json @@ -17,10 +17,8 @@ "longitude": "Lengdegrad", "radius": "Stasjonsradius (miles; valgfritt)" }, - "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://docs.airnowapi.org/account/request/", - "title": "" + "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://docs.airnowapi.org/account/request/" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/pl.json b/homeassistant/components/airnow/translations/pl.json index f29a48dc47b..bbf31af597a 100644 --- a/homeassistant/components/airnow/translations/pl.json +++ b/homeassistant/components/airnow/translations/pl.json @@ -17,10 +17,8 @@ "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "radius": "Promie\u0144 od stacji (w milach; opcjonalnie)" }, - "description": "Aby wygenerowa\u0107 klucz API, przejd\u017a do https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "Aby wygenerowa\u0107 klucz API, przejd\u017a do https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/pt-BR.json b/homeassistant/components/airnow/translations/pt-BR.json index c1bda0098cd..a3450355e0c 100644 --- a/homeassistant/components/airnow/translations/pt-BR.json +++ b/homeassistant/components/airnow/translations/pt-BR.json @@ -17,10 +17,8 @@ "longitude": "Longitude", "radius": "Raio da Esta\u00e7\u00e3o (milhas; opcional)" }, - "description": "Para gerar a chave de API, acesse https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "Para gerar a chave de API, acesse https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/pt.json b/homeassistant/components/airnow/translations/pt.json index 7b9af9e8a23..f846047c307 100644 --- a/homeassistant/components/airnow/translations/pt.json +++ b/homeassistant/components/airnow/translations/pt.json @@ -14,10 +14,8 @@ "api_key": "Chave da API", "latitude": "Latitude", "longitude": "Longitude" - }, - "title": "" + } } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/ru.json b/homeassistant/components/airnow/translations/ru.json index c5a9137f4c7..fb8516e6529 100644 --- a/homeassistant/components/airnow/translations/ru.json +++ b/homeassistant/components/airnow/translations/ru.json @@ -17,10 +17,8 @@ "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": "\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" + "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" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/tr.json b/homeassistant/components/airnow/translations/tr.json index 1f80d7805da..25c3cf15d41 100644 --- a/homeassistant/components/airnow/translations/tr.json +++ b/homeassistant/components/airnow/translations/tr.json @@ -17,10 +17,8 @@ "longitude": "Boylam", "radius": "\u0130stasyon Yar\u0131\u00e7ap\u0131 (mil; iste\u011fe ba\u011fl\u0131)" }, - "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://docs.airnowapi.org/account/request/ adresine gidin.", - "title": "AirNow" + "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://docs.airnowapi.org/account/request/ adresine gidin." } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/uk.json b/homeassistant/components/airnow/translations/uk.json index bb872123f54..db8c740234f 100644 --- a/homeassistant/components/airnow/translations/uk.json +++ b/homeassistant/components/airnow/translations/uk.json @@ -17,10 +17,8 @@ "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", "radius": "\u0420\u0430\u0434\u0456\u0443\u0441 \u0441\u0442\u0430\u043d\u0446\u0456\u0457 (\u043c\u0438\u043b\u0456; \u043d\u0435\u043e\u0431\u043e\u0432\u2019\u044f\u0437\u043a\u043e\u0432\u043e)" }, - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u044f\u043a\u043e\u0441\u0442\u0456 \u043f\u043e\u0432\u0456\u0442\u0440\u044f AirNow. \u0429\u043e\u0431 \u0437\u0433\u0435\u043d\u0435\u0440\u0443\u0432\u0430\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0443 https://docs.airnowapi.org/account/request/", - "title": "AirNow" + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u044f\u043a\u043e\u0441\u0442\u0456 \u043f\u043e\u0432\u0456\u0442\u0440\u044f AirNow. \u0429\u043e\u0431 \u0437\u0433\u0435\u043d\u0435\u0440\u0443\u0432\u0430\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0443 https://docs.airnowapi.org/account/request/" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/zh-Hant.json b/homeassistant/components/airnow/translations/zh-Hant.json index bb18a5d8975..e257e43a3e6 100644 --- a/homeassistant/components/airnow/translations/zh-Hant.json +++ b/homeassistant/components/airnow/translations/zh-Hant.json @@ -17,10 +17,8 @@ "longitude": "\u7d93\u5ea6", "radius": "\u89c0\u6e2c\u7ad9\u534a\u5f91\uff08\u82f1\u91cc\uff1b\u9078\u9805\uff09" }, - "description": "\u8acb\u81f3 https://docs.airnowapi.org/account/request/ \u4ee5\u7522\u751f API \u91d1\u9470", - "title": "AirNow" + "description": "\u8acb\u81f3 https://docs.airnowapi.org/account/request/ \u4ee5\u7522\u751f API \u91d1\u9470" } } - }, - "title": "AirNow" + } } \ No newline at end of file diff --git a/homeassistant/components/airthings/translations/es.json b/homeassistant/components/airthings/translations/es.json index 55dd0c10026..5feddd20875 100644 --- a/homeassistant/components/airthings/translations/es.json +++ b/homeassistant/components/airthings/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada" + "already_configured": "La cuenta ya est\u00e1 configurada" }, "error": { "cannot_connect": "No se pudo conectar", diff --git a/homeassistant/components/mill/translations/sv.json b/homeassistant/components/airthings/translations/ko.json similarity index 71% rename from homeassistant/components/mill/translations/sv.json rename to homeassistant/components/airthings/translations/ko.json index 23c825f256f..27863f2e0e4 100644 --- a/homeassistant/components/mill/translations/sv.json +++ b/homeassistant/components/airthings/translations/ko.json @@ -3,7 +3,7 @@ "step": { "user": { "data": { - "username": "Anv\u00e4ndarnamn" + "secret": "\ubcf4\uc548\ud0a4" } } } diff --git a/homeassistant/components/airvisual/translations/es.json b/homeassistant/components/airvisual/translations/es.json index 6e7ea6e6903..b51d3035fe5 100644 --- a/homeassistant/components/airvisual/translations/es.json +++ b/homeassistant/components/airvisual/translations/es.json @@ -7,7 +7,7 @@ "error": { "cannot_connect": "No se pudo conectar", "general_error": "Se ha producido un error desconocido.", - "invalid_api_key": "Se proporciona una clave API no v\u00e1lida.", + "invalid_api_key": "Clave API no v\u00e1lida", "location_not_found": "Ubicaci\u00f3n no encontrada" }, "step": { @@ -32,7 +32,7 @@ }, "node_pro": { "data": { - "ip_address": "Direcci\u00f3n IP/Nombre de host de la Unidad", + "ip_address": "Host", "password": "Contrase\u00f1a" }, "description": "Monitorizar una unidad personal AirVisual. La contrase\u00f1a puede ser recuperada desde la interfaz de la unidad.", diff --git a/homeassistant/components/airvisual/translations/nl.json b/homeassistant/components/airvisual/translations/nl.json index ddbcc6e6009..c857a51ba61 100644 --- a/homeassistant/components/airvisual/translations/nl.json +++ b/homeassistant/components/airvisual/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd. of Node/Pro IDis al geregistreerd.", - "reauth_successful": "Herauthenticatie was succesvol" + "already_configured": "Locatie is al geconfigureerd of Node/Pro IDis al geregistreerd.", + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", - "general_error": "Er is een onbekende fout opgetreden.", + "general_error": "Onverwachte fout", "invalid_api_key": "Ongeldige API-sleutel", "location_not_found": "Locatie niet gevonden" }, @@ -25,15 +25,15 @@ "api_key": "API-sleutel", "city": "Stad", "country": "Land", - "state": "staat" + "state": "status" }, "description": "Gebruik de AirVisual-cloud-API om een stad/staat/land te bewaken.", "title": "Configureer een geografie" }, "node_pro": { "data": { - "ip_address": "IP adres/hostname van component", - "password": "Wachtwoord van component" + "ip_address": "Host", + "password": "Wachtwoord" }, "description": "Monitor een persoonlijke AirVisual-eenheid. Het wachtwoord kan worden opgehaald uit de gebruikersinterface van het apparaat.", "title": "Configureer een AirVisual Node / Pro" diff --git a/homeassistant/components/airvisual/translations/sensor.sk.json b/homeassistant/components/airvisual/translations/sensor.sk.json new file mode 100644 index 00000000000..6d640d965b9 --- /dev/null +++ b/homeassistant/components/airvisual/translations/sensor.sk.json @@ -0,0 +1,7 @@ +{ + "state": { + "airvisual__pollutant_level": { + "good": "Dobr\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airzone/__init__.py b/homeassistant/components/airzone/__init__.py index 39f1fc978c3..cf57a28a5e1 100644 --- a/homeassistant/components/airzone/__init__.py +++ b/homeassistant/components/airzone/__init__.py @@ -4,17 +4,7 @@ from __future__ import annotations import logging from typing import Any -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.const import AZD_MAC, AZD_WEBSERVER, DEFAULT_SYSTEM_ID from aioairzone.localapi import AirzoneLocalApi, ConnectionOptions from homeassistant.config_entries import ConfigEntry @@ -25,10 +15,8 @@ from homeassistant.helpers import ( device_registry as dr, entity_registry as er, ) -from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, MANUFACTURER +from .const import DOMAIN from .coordinator import AirzoneUpdateCoordinator PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SENSOR] @@ -36,52 +24,6 @@ PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform. _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, - entry: ConfigEntry, - system_zone_id: str, - zone_data: dict[str, Any], - ) -> None: - """Initialize.""" - super().__init__(coordinator) - - self.system_id = zone_data[AZD_SYSTEM] - self.system_zone_id = system_zone_id - self.zone_id = zone_data[AZD_ID] - - self._attr_device_info: DeviceInfo = { - "identifiers": {(DOMAIN, f"{entry.entry_id}_{system_zone_id}")}, - "manufacturer": MANUFACTURER, - "model": self.get_airzone_value(AZD_THERMOSTAT_MODEL), - "name": f"Airzone [{system_zone_id}] {zone_data[AZD_NAME]}", - "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_airzone_value(self, key) -> Any: - """Return zone value by key.""" - value = None - if self.system_zone_id in self.coordinator.data[AZD_ZONES]: - zone = self.coordinator.data[AZD_ZONES][self.system_zone_id] - if key in zone: - value = zone[key] - return value - - async def _async_migrate_unique_ids( hass: HomeAssistant, entry: ConfigEntry, @@ -99,7 +41,7 @@ async def _async_migrate_unique_ids( if entity_unique_id.startswith(entry_id): new_unique_id = f"{unique_id}{entity_unique_id[len(entry_id):]}" - _LOGGER.info( + _LOGGER.debug( "Migrating unique_id from [%s] to [%s]", entity_unique_id, new_unique_id, diff --git a/homeassistant/components/airzone/binary_sensor.py b/homeassistant/components/airzone/binary_sensor.py index dd0e4a5b768..7ad3254c080 100644 --- a/homeassistant/components/airzone/binary_sensor.py +++ b/homeassistant/components/airzone/binary_sensor.py @@ -1,7 +1,6 @@ """Support for the Airzone sensors.""" from __future__ import annotations -from collections.abc import Mapping from dataclasses import dataclass from typing import Any, Final @@ -12,6 +11,7 @@ from aioairzone.const import ( AZD_FLOOR_DEMAND, AZD_NAME, AZD_PROBLEMS, + AZD_SYSTEMS, AZD_ZONES, ) @@ -21,13 +21,13 @@ 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 import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import AirzoneEntity, AirzoneZoneEntity from .const import DOMAIN from .coordinator import AirzoneUpdateCoordinator +from .entity import AirzoneEntity, AirzoneSystemEntity, AirzoneZoneEntity @dataclass @@ -37,6 +37,18 @@ class AirzoneBinarySensorEntityDescription(BinarySensorEntityDescription): attributes: dict[str, str] | None = None +SYSTEM_BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]] = ( + AirzoneBinarySensorEntityDescription( + attributes={ + "errors": AZD_ERRORS, + }, + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + key=AZD_PROBLEMS, + name="Problem", + ), +) + ZONE_BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]] = ( AirzoneBinarySensorEntityDescription( device_class=BinarySensorDeviceClass.RUNNING, @@ -72,6 +84,20 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][entry.entry_id] binary_sensors: list[AirzoneBinarySensor] = [] + + for system_id, system_data in coordinator.data[AZD_SYSTEMS].items(): + for description in SYSTEM_BINARY_SENSOR_TYPES: + if description.key in system_data: + binary_sensors.append( + AirzoneSystemBinarySensor( + coordinator, + description, + entry, + system_id, + system_data, + ) + ) + for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items(): for description in ZONE_BINARY_SENSOR_TYPES: if description.key in zone_data: @@ -93,20 +119,40 @@ class AirzoneBinarySensor(AirzoneEntity, BinarySensorEntity): 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() - } + @callback + def _handle_coordinator_update(self) -> None: + """Update attributes when the coordinator updates.""" + self._async_update_attrs() + super()._handle_coordinator_update() - @property - def is_on(self) -> bool | None: - """Return true if the binary sensor is on.""" - return self.get_airzone_value(self.entity_description.key) + @callback + def _async_update_attrs(self) -> None: + """Update binary sensor attributes.""" + self._attr_is_on = self.get_airzone_value(self.entity_description.key) + if self.entity_description.attributes: + self._attr_extra_state_attributes = { + key: self.get_airzone_value(val) + for key, val in self.entity_description.attributes.items() + } + + +class AirzoneSystemBinarySensor(AirzoneSystemEntity, AirzoneBinarySensor): + """Define an Airzone System binary sensor.""" + + def __init__( + self, + coordinator: AirzoneUpdateCoordinator, + description: AirzoneBinarySensorEntityDescription, + entry: ConfigEntry, + system_id: str, + system_data: dict[str, Any], + ) -> None: + """Initialize.""" + super().__init__(coordinator, entry, system_data) + self._attr_name = f"System {system_id} {description.name}" + self._attr_unique_id = f"{self._attr_unique_id}_{system_id}_{description.key}" + self.entity_description = description + self._async_update_attrs() class AirzoneZoneBinarySensor(AirzoneZoneEntity, AirzoneBinarySensor): @@ -128,3 +174,4 @@ class AirzoneZoneBinarySensor(AirzoneZoneEntity, AirzoneBinarySensor): f"{self._attr_unique_id}_{system_zone_id}_{description.key}" ) self.entity_description = description + self._async_update_attrs() diff --git a/homeassistant/components/airzone/climate.py b/homeassistant/components/airzone/climate.py index 9cff1ff393d..ce67142547f 100644 --- a/homeassistant/components/airzone/climate.py +++ b/homeassistant/components/airzone/climate.py @@ -39,9 +39,9 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import AirzoneZoneEntity from .const import API_TEMPERATURE_STEP, DOMAIN, TEMP_UNIT_LIB_TO_HASS from .coordinator import AirzoneUpdateCoordinator +from .entity import AirzoneZoneEntity _LOGGER = logging.getLogger(__name__) @@ -162,7 +162,7 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity): params[API_ON] = 1 await self._async_update_hvac_params(params) - async def async_set_temperature(self, **kwargs) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" params = { API_SET_POINT: kwargs.get(ATTR_TEMPERATURE), diff --git a/homeassistant/components/airzone/coordinator.py b/homeassistant/components/airzone/coordinator.py index 9e1dc44bb6c..ba0296557a1 100644 --- a/homeassistant/components/airzone/coordinator.py +++ b/homeassistant/components/airzone/coordinator.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import timedelta import logging +from typing import Any from aioairzone.exceptions import AirzoneError from aioairzone.localapi import AirzoneLocalApi @@ -18,7 +19,7 @@ SCAN_INTERVAL = timedelta(seconds=60) _LOGGER = logging.getLogger(__name__) -class AirzoneUpdateCoordinator(DataUpdateCoordinator): +class AirzoneUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): """Class to manage fetching data from the Airzone device.""" def __init__(self, hass: HomeAssistant, airzone: AirzoneLocalApi) -> None: @@ -32,7 +33,7 @@ class AirzoneUpdateCoordinator(DataUpdateCoordinator): update_interval=SCAN_INTERVAL, ) - async def _async_update_data(self): + async def _async_update_data(self) -> dict[str, Any]: """Update data via library.""" async with async_timeout.timeout(AIOAIRZONE_DEVICE_TIMEOUT_SEC): try: diff --git a/homeassistant/components/airzone/diagnostics.py b/homeassistant/components/airzone/diagnostics.py index 8baa106beb1..f56a5106b25 100644 --- a/homeassistant/components/airzone/diagnostics.py +++ b/homeassistant/components/airzone/diagnostics.py @@ -3,16 +3,25 @@ from __future__ import annotations from typing import Any -from aioairzone.const import AZD_MAC +from aioairzone.const import API_MAC, AZD_MAC from homeassistant.components.diagnostics.util import async_redact_data from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_UNIQUE_ID from homeassistant.core import HomeAssistant from .const import DOMAIN from .coordinator import AirzoneUpdateCoordinator -TO_REDACT = [ +TO_REDACT_API = [ + API_MAC, +] + +TO_REDACT_CONFIG = [ + CONF_UNIQUE_ID, +] + +TO_REDACT_COORD = [ AZD_MAC, ] @@ -24,6 +33,7 @@ async def async_get_config_entry_diagnostics( 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), + "api_data": async_redact_data(coordinator.airzone.raw_data(), TO_REDACT_API), + "config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT_CONFIG), + "coord_data": async_redact_data(coordinator.data, TO_REDACT_COORD), } diff --git a/homeassistant/components/airzone/entity.py b/homeassistant/components/airzone/entity.py new file mode 100644 index 00000000000..f697a364bc8 --- /dev/null +++ b/homeassistant/components/airzone/entity.py @@ -0,0 +1,132 @@ +"""Entity classes for the Airzone integration.""" +from __future__ import annotations + +from typing import Any + +from aioairzone.const import ( + AZD_FIRMWARE, + AZD_FULL_NAME, + AZD_ID, + AZD_MAC, + AZD_MODEL, + AZD_NAME, + AZD_SYSTEM, + AZD_SYSTEMS, + AZD_THERMOSTAT_FW, + AZD_THERMOSTAT_MODEL, + AZD_WEBSERVER, + AZD_ZONES, +) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, MANUFACTURER +from .coordinator import AirzoneUpdateCoordinator + + +class AirzoneEntity(CoordinatorEntity[AirzoneUpdateCoordinator]): + """Define an Airzone entity.""" + + def get_airzone_value(self, key: str) -> Any: + """Return Airzone entity value by key.""" + raise NotImplementedError() + + +class AirzoneSystemEntity(AirzoneEntity): + """Define an Airzone System entity.""" + + def __init__( + self, + coordinator: AirzoneUpdateCoordinator, + entry: ConfigEntry, + system_data: dict[str, Any], + ) -> None: + """Initialize.""" + super().__init__(coordinator) + + self.system_id = system_data[AZD_ID] + + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, f"{entry.entry_id}_{self.system_id}")}, + manufacturer=MANUFACTURER, + model=self.get_airzone_value(AZD_MODEL), + name=self.get_airzone_value(AZD_FULL_NAME), + sw_version=self.get_airzone_value(AZD_FIRMWARE), + via_device=(DOMAIN, f"{entry.entry_id}_ws"), + ) + self._attr_unique_id = entry.unique_id or entry.entry_id + + def get_airzone_value(self, key: str) -> Any: + """Return system value by key.""" + value = None + if system := self.coordinator.data[AZD_SYSTEMS].get(self.system_id): + if key in system: + value = system[key] + return value + + +class AirzoneWebServerEntity(AirzoneEntity): + """Define an Airzone WebServer entity.""" + + def __init__( + self, + coordinator: AirzoneUpdateCoordinator, + entry: ConfigEntry, + ) -> None: + """Initialize.""" + super().__init__(coordinator) + + mac = self.get_airzone_value(AZD_MAC) + + self._attr_device_info = DeviceInfo( + connections={(dr.CONNECTION_NETWORK_MAC, mac)}, + identifiers={(DOMAIN, f"{entry.entry_id}_ws")}, + manufacturer=MANUFACTURER, + model=self.get_airzone_value(AZD_MODEL), + name=self.get_airzone_value(AZD_FULL_NAME), + sw_version=self.get_airzone_value(AZD_FIRMWARE), + ) + self._attr_unique_id = entry.unique_id or entry.entry_id + + def get_airzone_value(self, key: str) -> Any: + """Return system value by key.""" + return self.coordinator.data[AZD_WEBSERVER].get(key) + + +class AirzoneZoneEntity(AirzoneEntity): + """Define an Airzone Zone entity.""" + + def __init__( + self, + coordinator: AirzoneUpdateCoordinator, + entry: ConfigEntry, + system_zone_id: str, + zone_data: dict[str, Any], + ) -> None: + """Initialize.""" + super().__init__(coordinator) + + self.system_id = zone_data[AZD_SYSTEM] + self.system_zone_id = system_zone_id + self.zone_id = zone_data[AZD_ID] + + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, f"{entry.entry_id}_{system_zone_id}")}, + manufacturer=MANUFACTURER, + model=self.get_airzone_value(AZD_THERMOSTAT_MODEL), + name=f"Airzone [{system_zone_id}] {zone_data[AZD_NAME]}", + sw_version=self.get_airzone_value(AZD_THERMOSTAT_FW), + via_device=(DOMAIN, f"{entry.entry_id}_{self.system_id}"), + ) + self._attr_unique_id = entry.unique_id or entry.entry_id + + def get_airzone_value(self, key: str) -> Any: + """Return zone value by key.""" + value = None + if zone := self.coordinator.data[AZD_ZONES].get(self.system_zone_id): + if key in zone: + value = zone[key] + return value diff --git a/homeassistant/components/airzone/manifest.json b/homeassistant/components/airzone/manifest.json index 7a04b3a78b3..e0d3bd6df09 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.4.3"], + "requirements": ["aioairzone==0.4.4"], "codeowners": ["@Noltari"], "iot_class": "local_polling", "loggers": ["aioairzone"] diff --git a/homeassistant/components/airzone/sensor.py b/homeassistant/components/airzone/sensor.py index f41add5053a..86d88aa5a55 100644 --- a/homeassistant/components/airzone/sensor.py +++ b/homeassistant/components/airzone/sensor.py @@ -3,7 +3,15 @@ from __future__ import annotations from typing import Any, Final -from aioairzone.const import AZD_HUMIDITY, AZD_NAME, AZD_TEMP, AZD_TEMP_UNIT, AZD_ZONES +from aioairzone.const import ( + AZD_HUMIDITY, + AZD_NAME, + AZD_TEMP, + AZD_TEMP_UNIT, + AZD_WEBSERVER, + AZD_WIFI_RSSI, + AZD_ZONES, +) from homeassistant.components.sensor import ( SensorDeviceClass, @@ -12,13 +20,30 @@ from homeassistant.components.sensor import ( SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE, TEMP_CELSIUS -from homeassistant.core import HomeAssistant +from homeassistant.const import ( + PERCENTAGE, + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + TEMP_CELSIUS, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import AirzoneEntity, AirzoneZoneEntity from .const import DOMAIN, TEMP_UNIT_LIB_TO_HASS from .coordinator import AirzoneUpdateCoordinator +from .entity import AirzoneEntity, AirzoneWebServerEntity, AirzoneZoneEntity + +WEBSERVER_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = ( + SensorEntityDescription( + device_class=SensorDeviceClass.SIGNAL_STRENGTH, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + key=AZD_WIFI_RSSI, + name="RSSI", + native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + state_class=SensorStateClass.MEASUREMENT, + ), +) ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = ( SensorEntityDescription( @@ -45,6 +70,19 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][entry.entry_id] sensors: list[AirzoneSensor] = [] + + if AZD_WEBSERVER in coordinator.data: + ws_data = coordinator.data[AZD_WEBSERVER] + for description in WEBSERVER_SENSOR_TYPES: + if description.key in ws_data: + sensors.append( + AirzoneWebServerSensor( + coordinator, + description, + entry, + ) + ) + for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items(): for description in ZONE_SENSOR_TYPES: if description.key in zone_data: @@ -64,10 +102,33 @@ 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) + @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_airzone_value(self.entity_description.key) + + +class AirzoneWebServerSensor(AirzoneWebServerEntity, AirzoneSensor): + """Define an Airzone WebServer sensor.""" + + def __init__( + self, + coordinator: AirzoneUpdateCoordinator, + description: SensorEntityDescription, + entry: ConfigEntry, + ) -> None: + """Initialize.""" + super().__init__(coordinator, entry) + self._attr_name = f"WebServer {description.name}" + self._attr_unique_id = f"{self._attr_unique_id}_ws_{description.key}" + self.entity_description = description + self._async_update_attrs() class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor): @@ -94,3 +155,5 @@ class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor): self._attr_native_unit_of_measurement = TEMP_UNIT_LIB_TO_HASS.get( self.get_airzone_value(AZD_TEMP_UNIT) ) + + self._async_update_attrs() diff --git a/homeassistant/components/airzone/translations/es.json b/homeassistant/components/airzone/translations/es.json new file mode 100644 index 00000000000..fbb00d71bf0 --- /dev/null +++ b/homeassistant/components/airzone/translations/es.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", + "invalid_system_id": "ID de sistema Airzone inv\u00e1lido" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Puerto" + }, + "description": "Configura la integraci\u00f3n Airzone." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airzone/translations/nl.json b/homeassistant/components/airzone/translations/nl.json index 0e84f756de1..e182d71963d 100644 --- a/homeassistant/components/airzone/translations/nl.json +++ b/homeassistant/components/airzone/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_system_id": "Ongeldige Airzone systeem ID" }, "step": { diff --git a/homeassistant/components/aladdin_connect/__init__.py b/homeassistant/components/aladdin_connect/__init__.py index 90196616dc5..048624641bd 100644 --- a/homeassistant/components/aladdin_connect/__init__.py +++ b/homeassistant/components/aladdin_connect/__init__.py @@ -1 +1,37 @@ """The aladdin_connect component.""" +import logging +from typing import Final + +from aladdin_connect import AladdinConnectClient + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed + +from .const import DOMAIN + +_LOGGER: Final = logging.getLogger(__name__) + +PLATFORMS: list[Platform] = [Platform.COVER] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up platform from a ConfigEntry.""" + username = entry.data[CONF_USERNAME] + password = entry.data[CONF_PASSWORD] + acc = AladdinConnectClient(username, password) + if not await hass.async_add_executor_job(acc.login): + raise ConfigEntryAuthFailed("Incorrect Password") + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = acc + 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/aladdin_connect/config_flow.py b/homeassistant/components/aladdin_connect/config_flow.py new file mode 100644 index 00000000000..153a63ffb06 --- /dev/null +++ b/homeassistant/components/aladdin_connect/config_flow.py @@ -0,0 +1,126 @@ +"""Config flow for Aladdin Connect cover integration.""" +from __future__ import annotations + +import logging +from typing import Any + +from aladdin_connect import AladdinConnectClient +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } +) + +REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) + + +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None: + """Validate the user input allows us to connect. + + Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. + """ + acc = AladdinConnectClient(data[CONF_USERNAME], data[CONF_PASSWORD]) + login = await hass.async_add_executor_job(acc.login) + if not login: + raise InvalidAuth + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Aladdin Connect.""" + + VERSION = 1 + entry: config_entries.ConfigEntry | None + + async def async_step_reauth( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle re-authentication with Aladdin Connect.""" + + 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 Aladdin Connect.""" + errors: dict[str, str] = {} + + if user_input: + assert self.entry is not None + password = user_input[CONF_PASSWORD] + data = { + CONF_USERNAME: self.entry.data[CONF_USERNAME], + CONF_PASSWORD: password, + } + + try: + await validate_input(self.hass, data) + except InvalidAuth: + errors["base"] = "invalid_auth" + else: + + self.hass.config_entries.async_update_entry( + self.entry, + data={ + **self.entry.data, + CONF_PASSWORD: password, + }, + ) + 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=REAUTH_SCHEMA, + errors=errors, + ) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA + ) + + errors = {} + + try: + await validate_input(self.hass, user_input) + except InvalidAuth: + errors["base"] = "invalid_auth" + + else: + await self.async_set_unique_id( + user_input["username"].lower(), raise_on_progress=False + ) + self._abort_if_unique_id_configured() + return self.async_create_entry(title="Aladdin Connect", data=user_input) + + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) + + async def async_step_import( + self, import_data: dict[str, Any] | None = None + ) -> FlowResult: + """Import Aladin Connect config from configuration.yaml.""" + return await self.async_step_user(import_data) + + +class InvalidAuth(HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/aladdin_connect/const.py b/homeassistant/components/aladdin_connect/const.py index 3069680c753..7a11cf63a9e 100644 --- a/homeassistant/components/aladdin_connect/const.py +++ b/homeassistant/components/aladdin_connect/const.py @@ -16,4 +16,5 @@ STATES_MAP: Final[dict[str, str]] = { "closing": STATE_CLOSING, } +DOMAIN = "aladdin_connect" SUPPORTED_FEATURES: Final = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE diff --git a/homeassistant/components/aladdin_connect/cover.py b/homeassistant/components/aladdin_connect/cover.py index 05c24fc9a37..da3e6b81663 100644 --- a/homeassistant/components/aladdin_connect/cover.py +++ b/homeassistant/components/aladdin_connect/cover.py @@ -7,12 +7,12 @@ from typing import Any, Final from aladdin_connect import AladdinConnectClient import voluptuous as vol -from homeassistant.components import persistent_notification from homeassistant.components.cover import ( PLATFORM_SCHEMA as BASE_PLATFORM_SCHEMA, CoverDeviceClass, CoverEntity, ) +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, @@ -21,11 +21,12 @@ from homeassistant.const import ( STATE_OPENING, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from .const import NOTIFICATION_ID, NOTIFICATION_TITLE, STATES_MAP, SUPPORTED_FEATURES +from .const import DOMAIN, STATES_MAP, SUPPORTED_FEATURES from .model import DoorDevice _LOGGER: Final = logging.getLogger(__name__) @@ -35,33 +36,41 @@ PLATFORM_SCHEMA: Final = BASE_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 Aladdin Connect devices yaml depreciated.""" + _LOGGER.warning( + "Configuring Aladdin Connect through yaml is deprecated" + "Please remove it from your configuration as it has already been imported to a config entry" + ) + await hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Aladdin Connect platform.""" + acc = hass.data[DOMAIN][config_entry.entry_id] + doors = await hass.async_add_executor_job(acc.get_doors) - username: str = config[CONF_USERNAME] - password: str = config[CONF_PASSWORD] - acc = AladdinConnectClient(username, password) - - try: - if not acc.login(): - raise ValueError("Username or Password is incorrect") - add_entities( - (AladdinDevice(acc, door) for door in acc.get_doors()), - update_before_add=True, - ) - except (TypeError, KeyError, NameError, ValueError) as ex: - _LOGGER.error("%s", ex) - persistent_notification.create( - hass, - f"Error: {ex}
You will need to restart hass after fixing.", - title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID, - ) + if doors is None: + raise PlatformNotReady("Error from Aladdin Connect getting doors") + async_add_entities( + (AladdinDevice(acc, door) for door in doors), + update_before_add=True, + ) class AladdinDevice(CoverEntity): @@ -71,7 +80,7 @@ class AladdinDevice(CoverEntity): _attr_supported_features = SUPPORTED_FEATURES def __init__(self, acc: AladdinConnectClient, device: DoorDevice) -> None: - """Initialize the cover.""" + """Initialize the Aladdin Connect cover.""" self._acc = acc self._device_id = device["device_id"] self._number = device["door_number"] diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index e28b2adff42..b9ea214d996 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -3,7 +3,8 @@ "name": "Aladdin Connect", "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", "requirements": ["aladdin_connect==0.4"], - "codeowners": [], + "codeowners": ["@mkmer"], "iot_class": "cloud_polling", - "loggers": ["aladdin_connect"] + "loggers": ["aladdin_connect"], + "config_flow": true } diff --git a/homeassistant/components/aladdin_connect/strings.json b/homeassistant/components/aladdin_connect/strings.json new file mode 100644 index 00000000000..ff42ca14bc3 --- /dev/null +++ b/homeassistant/components/aladdin_connect/strings.json @@ -0,0 +1,29 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + }, + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "The Aladdin Connect integration needs to re-authenticate your account", + "data": { + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + }, + + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + } + } +} diff --git a/homeassistant/components/aladdin_connect/translations/bg.json b/homeassistant/components/aladdin_connect/translations/bg.json new file mode 100644 index 00000000000..d5babb02c1c --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/bg.json @@ -0,0 +1,26 @@ +{ + "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", + "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": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + }, + "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" + }, + "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" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/ca.json b/homeassistant/components/aladdin_connect/translations/ca.json new file mode 100644 index 00000000000..41cb5c550fa --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/ca.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu 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" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Contrasenya" + }, + "description": "La integraci\u00f3 d'Aladdin Connect ha de tornar a autenticar-se amb el teu compte", + "title": "Reautenticaci\u00f3 de la integraci\u00f3" + }, + "user": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/cs.json b/homeassistant/components/aladdin_connect/translations/cs.json new file mode 100644 index 00000000000..a3144ba2f55 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/cs.json @@ -0,0 +1,22 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Heslo" + }, + "title": "Znovu ov\u011b\u0159it integraci" + }, + "user": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/de.json b/homeassistant/components/aladdin_connect/translations/de.json new file mode 100644 index 00000000000..057f31e9078 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/de.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Passwort" + }, + "description": "Die Aladdin Connect-Integration muss dein Konto erneut authentifizieren", + "title": "Integration erneut authentifizieren" + }, + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/el.json b/homeassistant/components/aladdin_connect/translations/el.json new file mode 100644 index 00000000000..6fa2af154f7 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/el.json @@ -0,0 +1,27 @@ +{ + "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", + "reauth_successful": "\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" + }, + "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": { + "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Aladdin Connect \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \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" + }, + "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" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/en.json b/homeassistant/components/aladdin_connect/translations/en.json new file mode 100644 index 00000000000..959e88fb2ae --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/en.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured", + "reauth_successful": "Re-authentication was successful" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Password" + }, + "description": "The Aladdin Connect integration needs to re-authenticate your account", + "title": "Reauthenticate Integration" + }, + "user": { + "data": { + "password": "Password", + "username": "Username" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/es.json b/homeassistant/components/aladdin_connect/translations/es.json new file mode 100644 index 00000000000..67e509e2626 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/es.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "reauth_successful": "La reautenticaci\u00f3n fue exitosa" + }, + "error": { + "cannot_connect": "Error al conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Contrase\u00f1a" + }, + "title": "Reautenticaci\u00f3n de la integraci\u00f3n" + }, + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/et.json b/homeassistant/components/aladdin_connect/translations/et.json new file mode 100644 index 00000000000..4eb34a1d8bf --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/et.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Salas\u00f5na" + }, + "description": "Aladdin Connecti sidumine peab konto uuesti autentima", + "title": "Taastuvasta sidumine" + }, + "user": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/fr.json b/homeassistant/components/aladdin_connect/translations/fr.json new file mode 100644 index 00000000000..5586c751335 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/fr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil 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" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Mot de passe" + }, + "description": "L'int\u00e9gration Aladdin Connect doit r\u00e9-authentifier votre compte", + "title": "R\u00e9-authentifier l'int\u00e9gration" + }, + "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/he.json b/homeassistant/components/aladdin_connect/translations/he.json new file mode 100644 index 00000000000..a26c20aa4c7 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/he.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "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", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + }, + "title": "\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05e9\u05dc \u05e9\u05d9\u05dc\u05d5\u05d1" + }, + "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/aladdin_connect/translations/hu.json b/homeassistant/components/aladdin_connect/translations/hu.json new file mode 100644 index 00000000000..8d0f979e05a --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/hu.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z 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" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Jelsz\u00f3" + }, + "description": "The Aladdin Connect integration needs to re-authenticate your account", + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + }, + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/id.json b/homeassistant/components/aladdin_connect/translations/id.json new file mode 100644 index 00000000000..37a42d096cf --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Kata Sandi" + }, + "description": "Integrasi Aladdin Connect perlu mengautentikasi ulang akun Anda", + "title": "Autentikasi Ulang Integrasi" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/it.json b/homeassistant/components/aladdin_connect/translations/it.json new file mode 100644 index 00000000000..997d7f36e12 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/it.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Password" + }, + "description": "L'integrazione Aladdin Connect deve autenticare nuovamente il tuo account", + "title": "Autentica nuovamente l'integrazione" + }, + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/ja.json b/homeassistant/components/aladdin_connect/translations/ja.json new file mode 100644 index 00000000000..196b7f1d066 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/ja.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\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" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" + }, + "description": "Aladdin Connect\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", + "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" + }, + "user": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/ko.json b/homeassistant/components/aladdin_connect/translations/ko.json new file mode 100644 index 00000000000..d2e9a9e1375 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/ko.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638" + }, + "description": "Aladdin Connect \ud1b5\ud569\uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" + }, + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/nl.json b/homeassistant/components/aladdin_connect/translations/nl.json new file mode 100644 index 00000000000..bd3384bd0f1 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/nl.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "reauth_successful": "Herauthenticatie geslaagd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Wachtwoord" + }, + "description": "De Aladdin Connect-integratie moet uw account opnieuw verifi\u00ebren", + "title": "Integratie herauthenticeren" + }, + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/no.json b/homeassistant/components/aladdin_connect/translations/no.json new file mode 100644 index 00000000000..c6e6e8413b3 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/no.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Passord" + }, + "description": "Aladdin Connect-integrasjonen m\u00e5 autentisere kontoen din p\u00e5 nytt", + "title": "Godkjenne integrering p\u00e5 nytt" + }, + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/pl.json b/homeassistant/components/aladdin_connect/translations/pl.json new file mode 100644 index 00000000000..14528332e1e --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/pl.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Has\u0142o" + }, + "description": "Integracja Aladdin Connect wymaga ponownego uwierzytelnienia Twojego konta", + "title": "Ponownie uwierzytelnij integracj\u0119" + }, + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/pt-BR.json b/homeassistant/components/aladdin_connect/translations/pt-BR.json new file mode 100644 index 00000000000..2d709bf1125 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falhou ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Senha" + }, + "description": "A integra\u00e7\u00e3o do Aladdin Connect precisa autenticar novamente sua conta", + "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "user": { + "data": { + "password": "Senha", + "username": "Nome de usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/ru.json b/homeassistant/components/aladdin_connect/translations/ru.json new file mode 100644 index 00000000000..d2db9c388ce --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/ru.json @@ -0,0 +1,27 @@ +{ + "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.", + "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." + }, + "step": { + "reauth_confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "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 Aladdin Connect.", + "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": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "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/aladdin_connect/translations/sk.json b/homeassistant/components/aladdin_connect/translations/sk.json new file mode 100644 index 00000000000..c9a7b4c204a --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "password": "Heslo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/tr.json b/homeassistant/components/aladdin_connect/translations/tr.json new file mode 100644 index 00000000000..21e063e21ee --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/tr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz 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" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "Parola" + }, + "description": "Aladdin Connect entegrasyonunun hesab\u0131n\u0131z\u0131 yeniden do\u011frulamas\u0131 gerekiyor", + "title": "Entegrasyonu Yeniden Do\u011frula" + }, + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/zh-Hans.json b/homeassistant/components/aladdin_connect/translations/zh-Hans.json new file mode 100644 index 00000000000..d7088d39951 --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/zh-Hans.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "\u8bbe\u5907\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86", + "reauth_successful": "\u91cd\u65b0\u8ba4\u8bc1\u6210\u529f" + }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "invalid_auth": "\u8eab\u4efd\u8ba4\u8bc1\u65e0\u6548" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "\u5bc6\u7801" + }, + "title": "\u4f7f\u96c6\u6210\u91cd\u65b0\u8fdb\u884c\u8eab\u4efd\u8ba4\u8bc1" + }, + "user": { + "data": { + "password": "\u5bc6\u7801", + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aladdin_connect/translations/zh-Hant.json b/homeassistant/components/aladdin_connect/translations/zh-Hant.json new file mode 100644 index 00000000000..1dcca5b6f9e --- /dev/null +++ b/homeassistant/components/aladdin_connect/translations/zh-Hant.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\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" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "\u5bc6\u78bc" + }, + "description": "Aladdin \u9023\u7dda\u6574\u5408\u9700\u8981\u91cd\u65b0\u8a8d\u8b49\u60a8\u7684\u5e33\u865f", + "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" + }, + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarm_control_panel/device_action.py b/homeassistant/components/alarm_control_panel/device_action.py index 2680b033b03..dd0c3d03a43 100644 --- a/homeassistant/components/alarm_control_panel/device_action.py +++ b/homeassistant/components/alarm_control_panel/device_action.py @@ -24,7 +24,7 @@ from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import get_supported_features -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from . import ATTR_CODE_ARM_REQUIRED, DOMAIN from .const import ( @@ -57,7 +57,7 @@ async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Alarm control panel devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) actions = [] # Get all the integrations entities for this device @@ -90,7 +90,10 @@ async def async_get_actions( async def async_call_action_from_config( - hass: HomeAssistant, config: ConfigType, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} diff --git a/homeassistant/components/alarm_control_panel/device_condition.py b/homeassistant/components/alarm_control_panel/device_condition.py index a378cf262ed..4764d5cfcbe 100644 --- a/homeassistant/components/alarm_control_panel/device_condition.py +++ b/homeassistant/components/alarm_control_panel/device_condition.py @@ -64,7 +64,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Alarm control panel devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) conditions = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/alarm_control_panel/device_trigger.py b/homeassistant/components/alarm_control_panel/device_trigger.py index ce53596fc8d..840b5eba6f3 100644 --- a/homeassistant/components/alarm_control_panel/device_trigger.py +++ b/homeassistant/components/alarm_control_panel/device_trigger.py @@ -1,7 +1,7 @@ """Provides device automations for Alarm control panel.""" from __future__ import annotations -from typing import Any, Final +from typing import Final import voluptuous as vol @@ -58,9 +58,9 @@ TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Alarm control panel devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers: list[dict[str, str]] = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/alarm_control_panel/translations/es.json b/homeassistant/components/alarm_control_panel/translations/es.json index a76c6bd5af9..6255f8cc574 100644 --- a/homeassistant/components/alarm_control_panel/translations/es.json +++ b/homeassistant/components/alarm_control_panel/translations/es.json @@ -40,5 +40,5 @@ "triggered": "Disparada" } }, - "title": "Panel de control de alarmas" + "title": "Panel de control de alarma" } \ No newline at end of file diff --git a/homeassistant/components/alarm_control_panel/translations/nl.json b/homeassistant/components/alarm_control_panel/translations/nl.json index 5527101589b..a2a0eefb149 100644 --- a/homeassistant/components/alarm_control_panel/translations/nl.json +++ b/homeassistant/components/alarm_control_panel/translations/nl.json @@ -28,15 +28,15 @@ "state": { "_": { "armed": "Ingeschakeld", - "armed_away": "Ingeschakeld voor vertrek", + "armed_away": "Ingeschakeld afwezig", "armed_custom_bypass": "Ingeschakeld met overbrugging", - "armed_home": "Ingeschakeld voor thuis", - "armed_night": "Ingeschakeld voor 's nachts", + "armed_home": "Ingeschakeld thuis", + "armed_night": "Ingeschakeld nacht", "armed_vacation": "Vakantie ingeschakeld", "arming": "Schakelt in", "disarmed": "Uitgeschakeld", "disarming": "Schakelt uit", - "pending": "In wacht", + "pending": "In afwachting", "triggered": "Gaat af" } }, diff --git a/homeassistant/components/alarmdecoder/__init__.py b/homeassistant/components/alarmdecoder/__init__.py index 3be3d67e32b..d5c1b88e08e 100644 --- a/homeassistant/components/alarmdecoder/__init__.py +++ b/homeassistant/components/alarmdecoder/__init__.py @@ -15,6 +15,8 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import async_track_point_in_time from homeassistant.util import dt as dt_util from .const import ( @@ -64,8 +66,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.async_add_executor_job(controller.open, baud) except NoDeviceError: _LOGGER.debug("Failed to connect. Retrying in 5 seconds") - hass.helpers.event.async_track_point_in_time( - open_connection, dt_util.utcnow() + timedelta(seconds=5) + async_track_point_in_time( + hass, open_connection, dt_util.utcnow() + timedelta(seconds=5) ) return _LOGGER.debug("Established a connection with the alarmdecoder") @@ -81,23 +83,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: def handle_message(sender, message): """Handle message from AlarmDecoder.""" - hass.helpers.dispatcher.dispatcher_send(SIGNAL_PANEL_MESSAGE, message) + dispatcher_send(hass, SIGNAL_PANEL_MESSAGE, message) def handle_rfx_message(sender, message): """Handle RFX message from AlarmDecoder.""" - hass.helpers.dispatcher.dispatcher_send(SIGNAL_RFX_MESSAGE, message) + dispatcher_send(hass, SIGNAL_RFX_MESSAGE, message) def zone_fault_callback(sender, zone): """Handle zone fault from AlarmDecoder.""" - hass.helpers.dispatcher.dispatcher_send(SIGNAL_ZONE_FAULT, zone) + dispatcher_send(hass, SIGNAL_ZONE_FAULT, zone) def zone_restore_callback(sender, zone): """Handle zone restore from AlarmDecoder.""" - hass.helpers.dispatcher.dispatcher_send(SIGNAL_ZONE_RESTORE, zone) + dispatcher_send(hass, SIGNAL_ZONE_RESTORE, zone) def handle_rel_message(sender, message): """Handle relay or zone expander message from AlarmDecoder.""" - hass.helpers.dispatcher.dispatcher_send(SIGNAL_REL_MESSAGE, message) + dispatcher_send(hass, SIGNAL_REL_MESSAGE, message) baud = ad_connection.get(CONF_DEVICE_BAUD) if protocol == PROTOCOL_SOCKET: diff --git a/homeassistant/components/alexa/auth.py b/homeassistant/components/alexa/auth.py index d888b91a39e..1ce20d154bd 100644 --- a/homeassistant/components/alexa/auth.py +++ b/homeassistant/components/alexa/auth.py @@ -11,6 +11,7 @@ import async_timeout from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import callback from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.storage import Store from homeassistant.util import dt _LOGGER = logging.getLogger(__name__) @@ -37,7 +38,7 @@ class Auth: self.client_secret = client_secret self._prefs = None - self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY) self._get_token_lock = asyncio.Lock() diff --git a/homeassistant/components/alexa/logbook.py b/homeassistant/components/alexa/logbook.py index 65fb410c601..b72b884ec29 100644 --- a/homeassistant/components/alexa/logbook.py +++ b/homeassistant/components/alexa/logbook.py @@ -1,4 +1,9 @@ """Describe logbook events.""" +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_ENTITY_ID, + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) from homeassistant.core import callback from .const import DOMAIN, EVENT_ALEXA_SMART_HOME @@ -22,6 +27,10 @@ def async_describe_events(hass, async_describe_event): f"sent command {data['request']['namespace']}/{data['request']['name']}" ) - return {"name": "Amazon Alexa", "message": message, "entity_id": entity_id} + return { + LOGBOOK_ENTRY_NAME: "Amazon Alexa", + LOGBOOK_ENTRY_MESSAGE: message, + LOGBOOK_ENTRY_ENTITY_ID: entity_id, + } async_describe_event(DOMAIN, EVENT_ALEXA_SMART_HOME, async_describe_logbook_event) diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index 161ac4072b6..d3e476c2e8c 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -12,6 +12,7 @@ import async_timeout from homeassistant.const import MATCH_ALL, STATE_ON from homeassistant.core import HomeAssistant, State, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.significant_change import create_checker import homeassistant.util.dt as dt_util @@ -102,9 +103,7 @@ async def async_enable_proactive_mode(hass, smart_home_config): hass, smart_home_config, alexa_changed_entity, alexa_properties ) - return hass.helpers.event.async_track_state_change( - MATCH_ALL, async_entity_state_listener - ) + return async_track_state_change(hass, MATCH_ALL, async_entity_state_listener) async def async_send_changereport_message( diff --git a/homeassistant/components/almond/translations/es.json b/homeassistant/components/almond/translations/es.json index 4dc5e4ee1c0..83e78317741 100644 --- a/homeassistant/components/almond/translations/es.json +++ b/homeassistant/components/almond/translations/es.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "cannot_connect": "No se puede conectar al servidor Almond.", - "missing_configuration": "Consulte la documentaci\u00f3n sobre c\u00f3mo configurar Almond.", + "cannot_connect": "No se pudo conectar", + "missing_configuration": "El componente no est\u00e1 configurado. Mira su documentaci\u00f3n.", "no_url_available": "No hay URL disponible. Para obtener informaci\u00f3n sobre este error, [consulta la secci\u00f3n de ayuda]({docs_url})", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, diff --git a/homeassistant/components/almond/translations/nl.json b/homeassistant/components/almond/translations/nl.json index 4c507cfab69..e548206e23e 100644 --- a/homeassistant/components/almond/translations/nl.json +++ b/homeassistant/components/almond/translations/nl.json @@ -2,8 +2,8 @@ "config": { "abort": { "cannot_connect": "Kan geen verbinding maken", - "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})", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { diff --git a/homeassistant/components/ambee/translations/ko.json b/homeassistant/components/ambee/translations/ko.json new file mode 100644 index 00000000000..574b7cd0976 --- /dev/null +++ b/homeassistant/components/ambee/translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4", + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4", + "name": "\uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambee/translations/nl.json b/homeassistant/components/ambee/translations/nl.json index 837e39a72d7..5d356037652 100644 --- a/homeassistant/components/ambee/translations/nl.json +++ b/homeassistant/components/ambee/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/amberelectric/config_flow.py b/homeassistant/components/amberelectric/config_flow.py index efb5ddfb931..0258fdf4cb4 100644 --- a/homeassistant/components/amberelectric/config_flow.py +++ b/homeassistant/components/amberelectric/config_flow.py @@ -1,8 +1,6 @@ """Config flow for the Amber Electric integration.""" from __future__ import annotations -from typing import Any - import amberelectric from amberelectric.api import amber_api from amberelectric.model.site import Site @@ -10,6 +8,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_API_TOKEN +from homeassistant.data_entry_flow import FlowResult from .const import CONF_SITE_ID, CONF_SITE_NAME, CONF_SITE_NMI, DOMAIN @@ -44,7 +43,9 @@ class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._errors[CONF_API_TOKEN] = "unknown_error" return None - async def async_step_user(self, user_input: dict[str, Any] | None = None): + async def async_step_user( + self, user_input: dict[str, str] | None = None + ) -> FlowResult: """Step when user initializes a integration.""" self._errors = {} self._sites = None @@ -76,11 +77,14 @@ class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=self._errors, ) - async def async_step_site(self, user_input: dict[str, Any] = None): + async def async_step_site( + self, user_input: dict[str, str] | None = None + ) -> FlowResult: """Step to select site.""" self._errors = {} assert self._sites is not None + assert self._api_token is not None api_token = self._api_token if user_input is not None: diff --git a/homeassistant/components/amberelectric/translations/bg.json b/homeassistant/components/amberelectric/translations/bg.json index 5cfcc05b133..6f035fae4e6 100644 --- a/homeassistant/components/amberelectric/translations/bg.json +++ b/homeassistant/components/amberelectric/translations/bg.json @@ -1,12 +1,8 @@ { "config": { "step": { - "site": { - "title": "Amber Electric" - }, "user": { - "description": "\u041e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 {api_url}, \u0437\u0430 \u0434\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u0442\u0435 API \u043a\u043b\u044e\u0447", - "title": "Amber Electric" + "description": "\u041e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 {api_url}, \u0437\u0430 \u0434\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u0442\u0435 API \u043a\u043b\u044e\u0447" } } } diff --git a/homeassistant/components/amberelectric/translations/ca.json b/homeassistant/components/amberelectric/translations/ca.json index cf9bca64df6..208c7fb9f7c 100644 --- a/homeassistant/components/amberelectric/translations/ca.json +++ b/homeassistant/components/amberelectric/translations/ca.json @@ -6,16 +6,14 @@ "site_name": "Nom del lloc", "site_nmi": "NMI del lloc" }, - "description": "Selecciona l'NMI del lloc que vulguis afegir", - "title": "Amber Electric" + "description": "Selecciona l'NMI del lloc que vulguis afegir" }, "user": { "data": { "api_token": "Token d'API", "site_id": "ID del lloc" }, - "description": "Ves a {api_url} per generar una clau API", - "title": "Amber Electric" + "description": "Ves a {api_url} per generar una clau API" } } } diff --git a/homeassistant/components/amberelectric/translations/de.json b/homeassistant/components/amberelectric/translations/de.json index 2143795f479..34ce233fed8 100644 --- a/homeassistant/components/amberelectric/translations/de.json +++ b/homeassistant/components/amberelectric/translations/de.json @@ -6,16 +6,14 @@ "site_name": "Name des Standorts", "site_nmi": "Standort NMI" }, - "description": "W\u00e4hle die NMI des Standorts, den du hinzuf\u00fcgen m\u00f6chtest", - "title": "Amber Electric" + "description": "W\u00e4hle die NMI des Standorts, den du hinzuf\u00fcgen m\u00f6chtest" }, "user": { "data": { "api_token": "API-Token", "site_id": "Site-ID" }, - "description": "Gehe zu {api_url}, um einen API-Schl\u00fcssel zu generieren", - "title": "Amber Electric" + "description": "Gehe zu {api_url}, um einen API-Schl\u00fcssel zu generieren" } } } diff --git a/homeassistant/components/amberelectric/translations/el.json b/homeassistant/components/amberelectric/translations/el.json index b6e939ea466..3dc8fc9dd78 100644 --- a/homeassistant/components/amberelectric/translations/el.json +++ b/homeassistant/components/amberelectric/translations/el.json @@ -6,16 +6,14 @@ "site_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2", "site_nmi": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 NMI" }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf NMI \u03c4\u03b7\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5.", - "title": "Amber Electric" + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf NMI \u03c4\u03b7\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5." }, "user": { "data": { "api_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API", "site_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2" }, - "description": "\u039c\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf {api_url} \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", - "title": "Amber Electric" + "description": "\u039c\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf {api_url} \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API" } } } diff --git a/homeassistant/components/amberelectric/translations/en.json b/homeassistant/components/amberelectric/translations/en.json index 60c7caae456..b4d30925aa7 100644 --- a/homeassistant/components/amberelectric/translations/en.json +++ b/homeassistant/components/amberelectric/translations/en.json @@ -6,16 +6,14 @@ "site_name": "Site Name", "site_nmi": "Site NMI" }, - "description": "Select the NMI of the site you would like to add", - "title": "Amber Electric" + "description": "Select the NMI of the site you would like to add" }, "user": { "data": { "api_token": "API Token", "site_id": "Site ID" }, - "description": "Go to {api_url} to generate an API key", - "title": "Amber Electric" + "description": "Go to {api_url} to generate an API key" } } } diff --git a/homeassistant/components/amberelectric/translations/es.json b/homeassistant/components/amberelectric/translations/es.json index d4b09e69930..18af819d5d1 100644 --- a/homeassistant/components/amberelectric/translations/es.json +++ b/homeassistant/components/amberelectric/translations/es.json @@ -6,16 +6,14 @@ "site_name": "Nombre del sitio", "site_nmi": "Sitio NMI" }, - "description": "Seleccione el NMI del sitio que le gustar\u00eda agregar", - "title": "Amber Electric" + "description": "Seleccione el NMI del sitio que le gustar\u00eda agregar" }, "user": { "data": { "api_token": "API Token", "site_id": "ID del sitio" }, - "description": "Ve a {api_url} para generar una clave API", - "title": "Amber Electric" + "description": "Ve a {api_url} para generar una clave API" } } } diff --git a/homeassistant/components/amberelectric/translations/et.json b/homeassistant/components/amberelectric/translations/et.json index 05a7e6c6dc2..e48f6f2a749 100644 --- a/homeassistant/components/amberelectric/translations/et.json +++ b/homeassistant/components/amberelectric/translations/et.json @@ -6,16 +6,14 @@ "site_name": "Saidi nimi", "site_nmi": "Saidi NMI" }, - "description": "Vali lisatava saidi NMI", - "title": "Amber Electric" + "description": "Vali lisatava saidi NMI" }, "user": { "data": { "api_token": "API v\u00f5ti", "site_id": "Saidi ID" }, - "description": "API-v\u00f5tme saamiseks ava {api_url}.", - "title": "Amber Electric" + "description": "API-v\u00f5tme saamiseks ava {api_url}." } } } diff --git a/homeassistant/components/amberelectric/translations/fr.json b/homeassistant/components/amberelectric/translations/fr.json index 487ceff33f3..a5b1164924c 100644 --- a/homeassistant/components/amberelectric/translations/fr.json +++ b/homeassistant/components/amberelectric/translations/fr.json @@ -6,16 +6,14 @@ "site_name": "Nom du site", "site_nmi": "Site NMI" }, - "description": "S\u00e9lectionnez le NMI du site que vous souhaitez ajouter", - "title": "Amber Electrique" + "description": "S\u00e9lectionnez le NMI du site que vous souhaitez ajouter" }, "user": { "data": { "api_token": "Jeton d'API", "site_id": "ID du site" }, - "description": "Acc\u00e9dez \u00e0 {api_url} pour g\u00e9n\u00e9rer une cl\u00e9 API", - "title": "Amber Electrique" + "description": "Acc\u00e9dez \u00e0 {api_url} pour g\u00e9n\u00e9rer une cl\u00e9 API" } } } diff --git a/homeassistant/components/amberelectric/translations/hu.json b/homeassistant/components/amberelectric/translations/hu.json index 9811f5a5f8f..2d361e1e76f 100644 --- a/homeassistant/components/amberelectric/translations/hu.json +++ b/homeassistant/components/amberelectric/translations/hu.json @@ -6,16 +6,14 @@ "site_name": "Hely neve", "site_nmi": "Hely NMI" }, - "description": "V\u00e1lassza ki a hozz\u00e1adni k\u00edv\u00e1nt hely NMI-j\u00e9t.", - "title": "Amber Electric" + "description": "V\u00e1lassza ki a hozz\u00e1adni k\u00edv\u00e1nt hely NMI-j\u00e9t." }, "user": { "data": { "api_token": "API Token", "site_id": "Hely ID" }, - "description": "API-kulcs gener\u00e1l\u00e1s\u00e1hoz l\u00e1togasson el ide: {api_url}", - "title": "Amber Electric" + "description": "API-kulcs gener\u00e1l\u00e1s\u00e1hoz l\u00e1togasson el ide: {api_url}" } } } diff --git a/homeassistant/components/amberelectric/translations/id.json b/homeassistant/components/amberelectric/translations/id.json index 4920ee7b177..a88fca7c520 100644 --- a/homeassistant/components/amberelectric/translations/id.json +++ b/homeassistant/components/amberelectric/translations/id.json @@ -6,16 +6,14 @@ "site_name": "Nama Situs", "site_nmi": "Situs NMI" }, - "description": "Pilih NMI dari situs yang ingin ditambahkan", - "title": "Amber Electric" + "description": "Pilih NMI dari situs yang ingin ditambahkan" }, "user": { "data": { "api_token": "Token API", "site_id": "ID Site" }, - "description": "Buka {api_url} untuk membuat kunci API", - "title": "Amber Electric" + "description": "Buka {api_url} untuk membuat kunci API" } } } diff --git a/homeassistant/components/amberelectric/translations/it.json b/homeassistant/components/amberelectric/translations/it.json index 5b061561954..f181f549fff 100644 --- a/homeassistant/components/amberelectric/translations/it.json +++ b/homeassistant/components/amberelectric/translations/it.json @@ -6,16 +6,14 @@ "site_name": "Nome del sito", "site_nmi": "Sito NMI" }, - "description": "Seleziona l'NMI del sito che desideri aggiungere", - "title": "Amber Electric" + "description": "Seleziona l'NMI del sito che desideri aggiungere" }, "user": { "data": { "api_token": "Token API", "site_id": "ID sito" }, - "description": "Vai su {api_url} per generare una chiave API", - "title": "Amber Electric" + "description": "Vai su {api_url} per generare una chiave API" } } } diff --git a/homeassistant/components/amberelectric/translations/ja.json b/homeassistant/components/amberelectric/translations/ja.json index 1fc5f6c58c1..0c061b26112 100644 --- a/homeassistant/components/amberelectric/translations/ja.json +++ b/homeassistant/components/amberelectric/translations/ja.json @@ -6,16 +6,14 @@ "site_name": "\u30b5\u30a4\u30c8\u540d", "site_nmi": "\u30b5\u30a4\u30c8NMI" }, - "description": "\u8ffd\u52a0\u3057\u305f\u3044\u30b5\u30a4\u30c8\u306eNMI\u3092\u9078\u629e", - "title": "Amber Electric" + "description": "\u8ffd\u52a0\u3057\u305f\u3044\u30b5\u30a4\u30c8\u306eNMI\u3092\u9078\u629e" }, "user": { "data": { "api_token": "API\u30c8\u30fc\u30af\u30f3", "site_id": "\u30b5\u30a4\u30c8ID" }, - "description": "API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u305f\u3081\u306b {api_url} \u306b\u30a2\u30af\u30bb\u30b9\u3057\u307e\u3059", - "title": "Amber Electric" + "description": "API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u305f\u3081\u306b {api_url} \u306b\u30a2\u30af\u30bb\u30b9\u3057\u307e\u3059" } } } diff --git a/homeassistant/components/amberelectric/translations/nl.json b/homeassistant/components/amberelectric/translations/nl.json index a874c12f283..263e94962a0 100644 --- a/homeassistant/components/amberelectric/translations/nl.json +++ b/homeassistant/components/amberelectric/translations/nl.json @@ -6,16 +6,14 @@ "site_name": "Sitenaam", "site_nmi": "Site NMI" }, - "description": "Selecteer de NMI van de site die u wilt toevoegen", - "title": "Amber Electric" + "description": "Selecteer de NMI van de site die u wilt toevoegen" }, "user": { "data": { "api_token": "API Token", "site_id": "Site ID" }, - "description": "Ga naar {api_url} om een API sleutel aan te maken", - "title": "Amber Electric" + "description": "Ga naar {api_url} om een API sleutel aan te maken" } } } diff --git a/homeassistant/components/amberelectric/translations/no.json b/homeassistant/components/amberelectric/translations/no.json index 90d4bd930b9..74df9323ce5 100644 --- a/homeassistant/components/amberelectric/translations/no.json +++ b/homeassistant/components/amberelectric/translations/no.json @@ -6,16 +6,14 @@ "site_name": "Side navn", "site_nmi": "Nettsted NMI" }, - "description": "Velg NMI for nettstedet du vil legge til", - "title": "Amber Electric" + "description": "Velg NMI for nettstedet du vil legge til" }, "user": { "data": { "api_token": "API-token", "site_id": "Nettsted -ID" }, - "description": "G\u00e5 til {api_url} \u00e5 generere en API -n\u00f8kkel", - "title": "Amber Electric" + "description": "G\u00e5 til {api_url} \u00e5 generere en API -n\u00f8kkel" } } } diff --git a/homeassistant/components/amberelectric/translations/pl.json b/homeassistant/components/amberelectric/translations/pl.json index 1e9b66e3c3c..2810149273f 100644 --- a/homeassistant/components/amberelectric/translations/pl.json +++ b/homeassistant/components/amberelectric/translations/pl.json @@ -6,16 +6,14 @@ "site_name": "Nazwa obiektu", "site_nmi": "Numer identyfikacyjny (NMI) obiektu" }, - "description": "Wybierz NMI obiektu, kt\u00f3ry chcesz doda\u0107", - "title": "Amber Electric" + "description": "Wybierz NMI obiektu, kt\u00f3ry chcesz doda\u0107" }, "user": { "data": { "api_token": "Token API", "site_id": "Identyfikator obiektu" }, - "description": "Przejd\u017a do {api_url}, aby wygenerowa\u0107 klucz API", - "title": "Amber Electric" + "description": "Przejd\u017a do {api_url}, aby wygenerowa\u0107 klucz API" } } } diff --git a/homeassistant/components/amberelectric/translations/pt-BR.json b/homeassistant/components/amberelectric/translations/pt-BR.json index 6e8eb4d0ac8..35541dbf290 100644 --- a/homeassistant/components/amberelectric/translations/pt-BR.json +++ b/homeassistant/components/amberelectric/translations/pt-BR.json @@ -6,16 +6,14 @@ "site_name": "Nome do site", "site_nmi": "Site NMI" }, - "description": "Selecione o NMI do site que voc\u00ea gostaria de adicionar", - "title": "\u00c2mbar el\u00e9trico" + "description": "Selecione o NMI do site que voc\u00ea gostaria de adicionar" }, "user": { "data": { "api_token": "Token de API", "site_id": "ID do site" }, - "description": "V\u00e1 para {api_url} para gerar uma chave de API", - "title": "\u00c2mbar el\u00e9trico" + "description": "V\u00e1 para {api_url} para gerar uma chave de API" } } } diff --git a/homeassistant/components/amberelectric/translations/ru.json b/homeassistant/components/amberelectric/translations/ru.json index 4b8caee72ee..793f8bcae9d 100644 --- a/homeassistant/components/amberelectric/translations/ru.json +++ b/homeassistant/components/amberelectric/translations/ru.json @@ -6,16 +6,14 @@ "site_name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0447\u0430\u0441\u0442\u043a\u0430", "site_nmi": "NMI \u0443\u0447\u0430\u0441\u0442\u043a\u0430" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 NMI \u0443\u0447\u0430\u0441\u0442\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c", - "title": "Amber Electric" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 NMI \u0443\u0447\u0430\u0441\u0442\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c" }, "user": { "data": { "api_token": "\u0422\u043e\u043a\u0435\u043d API", "site_id": "ID \u0443\u0447\u0430\u0441\u0442\u043a\u0430" }, - "description": "\u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 {api_url} \u0447\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API.", - "title": "Amber Electric" + "description": "\u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 {api_url} \u0447\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API." } } } diff --git a/homeassistant/components/amberelectric/translations/tr.json b/homeassistant/components/amberelectric/translations/tr.json index 274a347e578..393b2cf08ee 100644 --- a/homeassistant/components/amberelectric/translations/tr.json +++ b/homeassistant/components/amberelectric/translations/tr.json @@ -6,16 +6,14 @@ "site_name": "Site Ad\u0131", "site_nmi": "Site NMI" }, - "description": "Eklemek istedi\u011finiz sitenin NMI'sini se\u00e7in", - "title": "Amber Electric" + "description": "Eklemek istedi\u011finiz sitenin NMI'sini se\u00e7in" }, "user": { "data": { "api_token": "API Anahtar\u0131", "site_id": "Site Kimli\u011fi" }, - "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in {api_url} konumuna gidin", - "title": "Amber Electric" + "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in {api_url} konumuna gidin" } } } diff --git a/homeassistant/components/amberelectric/translations/zh-Hant.json b/homeassistant/components/amberelectric/translations/zh-Hant.json index c2cbef2778b..42b671d3530 100644 --- a/homeassistant/components/amberelectric/translations/zh-Hant.json +++ b/homeassistant/components/amberelectric/translations/zh-Hant.json @@ -6,16 +6,14 @@ "site_name": "\u4f4d\u5740\u540d\u7a31", "site_nmi": "\u4f4d\u5740 NMI" }, - "description": "\u9078\u64c7\u6240\u8981\u65b0\u589e\u7684\u4f4d\u5740 NMI", - "title": "Amber Electric" + "description": "\u9078\u64c7\u6240\u8981\u65b0\u589e\u7684\u4f4d\u5740 NMI" }, "user": { "data": { "api_token": "API \u6b0a\u6756", "site_id": "\u4f4d\u5740 ID" }, - "description": "\u9023\u7dda\u81f3 {api_url} \u4ee5\u7522\u751f API \u91d1\u9470", - "title": "Amber Electric" + "description": "\u9023\u7dda\u81f3 {api_url} \u4ee5\u7522\u751f API \u91d1\u9470" } } } diff --git a/homeassistant/components/ambiclimate/climate.py b/homeassistant/components/ambiclimate/climate.py index 0bf4ec35526..93d9348655f 100644 --- a/homeassistant/components/ambiclimate/climate.py +++ b/homeassistant/components/ambiclimate/climate.py @@ -23,6 +23,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import ( @@ -63,7 +64,7 @@ async def async_setup_entry( """Set up the Ambiclimate device from config entry.""" config = entry.data websession = async_get_clientsession(hass) - store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + store = Store(hass, STORAGE_VERSION, STORAGE_KEY) token_info = await store.async_load() oauth = ambiclimate.AmbiclimateOAuth( diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index e93c21ce5ba..bde5e41e392 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -10,6 +10,7 @@ from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.network import get_url +from homeassistant.helpers.storage import Store from .const import ( AUTH_CALLBACK_NAME, @@ -102,7 +103,7 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): _LOGGER.error("Failed to get access token", exc_info=True) return None - store = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + store = Store(self.hass, STORAGE_VERSION, STORAGE_KEY) await store.async_save(token_info) return token_info diff --git a/homeassistant/components/ambiclimate/translations/nl.json b/homeassistant/components/ambiclimate/translations/nl.json index 6d3b3822224..e0679131de4 100644 --- a/homeassistant/components/ambiclimate/translations/nl.json +++ b/homeassistant/components/ambiclimate/translations/nl.json @@ -3,10 +3,10 @@ "abort": { "access_token": "Onbekende fout bij het genereren van een toegangstoken.", "already_configured": "Account is al geconfigureerd", - "missing_configuration": "De component is niet geconfigureerd. Gelieve de documentatie volgen." + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie." }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "error": { "follow_link": "Gelieve de link te volgen en te verifi\u00ebren voordat u op Verzenden drukt.", diff --git a/homeassistant/components/ambient_station/translations/nl.json b/homeassistant/components/ambient_station/translations/nl.json index 008bc10e084..332ff9f0a33 100644 --- a/homeassistant/components/ambient_station/translations/nl.json +++ b/homeassistant/components/ambient_station/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "invalid_key": "Ongeldige API-sleutel", diff --git a/homeassistant/components/analytics/analytics.py b/homeassistant/components/analytics/analytics.py index 39c813b2696..802aa33585a 100644 --- a/homeassistant/components/analytics/analytics.py +++ b/homeassistant/components/analytics/analytics.py @@ -71,7 +71,7 @@ class Analytics: ATTR_ONBOARDED: False, ATTR_UUID: None, } - self._store: Store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY) @property def preferences(self) -> dict: diff --git a/homeassistant/components/androidtv/__init__.py b/homeassistant/components/androidtv/__init__.py index c1875a883e3..8a34b8aa858 100644 --- a/homeassistant/components/androidtv/__init__.py +++ b/homeassistant/components/androidtv/__init__.py @@ -1,9 +1,17 @@ """Support for functionality to interact with Android TV/Fire TV devices.""" +from __future__ import annotations + +from collections.abc import Mapping import os +from typing import Any from adb_shell.auth.keygen import keygen -from androidtv.adb_manager.adb_manager_sync import ADBPythonSync -from androidtv.setup_async import setup as async_androidtv_setup +from androidtv.adb_manager.adb_manager_sync import ADBPythonSync, PythonRSASigner +from androidtv.setup_async import ( + AndroidTVAsync, + FireTVAsync, + setup as async_androidtv_setup, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -41,7 +49,7 @@ RELOAD_OPTIONS = [CONF_STATE_DETECTION_RULES] _INVALID_MACS = {"ff:ff:ff:ff:ff:ff"} -def get_androidtv_mac(dev_props): +def get_androidtv_mac(dev_props: dict[str, Any]) -> str | None: """Return formatted mac from device properties.""" for prop_mac in (PROP_ETHMAC, PROP_WIFIMAC): if if_mac := dev_props.get(prop_mac): @@ -51,9 +59,13 @@ def get_androidtv_mac(dev_props): return None -def _setup_androidtv(hass, config): +def _setup_androidtv( + hass: HomeAssistant, config: dict[str, Any] +) -> tuple[str, PythonRSASigner | None, str]: """Generate an ADB key (if needed) and load it.""" - adbkey = config.get(CONF_ADBKEY, hass.config.path(STORAGE_DIR, "androidtv_adbkey")) + adbkey: str = config.get( + CONF_ADBKEY, hass.config.path(STORAGE_DIR, "androidtv_adbkey") + ) if CONF_ADB_SERVER_IP not in config: # Use "adb_shell" (Python ADB implementation) if not os.path.isfile(adbkey): @@ -73,8 +85,12 @@ def _setup_androidtv(hass, config): async def async_connect_androidtv( - hass, config, *, state_detection_rules=None, timeout=30.0 -): + hass: HomeAssistant, + config: Mapping[str, Any], + *, + state_detection_rules: dict[str, Any] | None = None, + timeout: float = 30.0, +) -> tuple[AndroidTVAsync | FireTVAsync | None, str | None]: """Connect to Android device.""" address = f"{config[CONF_HOST]}:{config[CONF_PORT]}" diff --git a/homeassistant/components/androidtv/config_flow.py b/homeassistant/components/androidtv/config_flow.py index 9df87eaffe2..bdc067c4275 100644 --- a/homeassistant/components/androidtv/config_flow.py +++ b/homeassistant/components/androidtv/config_flow.py @@ -1,14 +1,18 @@ """Config flow to configure the Android TV integration.""" +from __future__ import annotations + import json import logging import os +from typing import Any from androidtv import state_detection_rules_validator import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_DEVICE_CLASS, CONF_HOST, CONF_PORT from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from . import async_connect_androidtv, get_androidtv_mac @@ -51,24 +55,28 @@ RESULT_UNKNOWN = "unknown" _LOGGER = logging.getLogger(__name__) -def _is_file(value): +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) -class AndroidTVFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AndroidTVFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @callback - def _show_setup_form(self, user_input=None, error=None): + def _show_setup_form( + self, + user_input: dict[str, Any] | None = None, + error: str | None = None, + ) -> FlowResult: """Show the setup form to the user.""" - user_input = user_input or {} + host = user_input.get(CONF_HOST, "") if user_input else "" data_schema = vol.Schema( { - vol.Required(CONF_HOST, default=user_input.get(CONF_HOST, "")): str, + vol.Required(CONF_HOST, default=host): str, vol.Required(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): vol.In( DEVICE_CLASSES ), @@ -90,10 +98,12 @@ class AndroidTVFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id="user", data_schema=data_schema, - errors={"base": error}, + errors={"base": error} if error else None, ) - async def _async_check_connection(self, user_input): + async def _async_check_connection( + self, user_input: dict[str, Any] + ) -> tuple[str | None, str | None]: """Attempt to connect the Android TV.""" try: @@ -121,7 +131,9 @@ class AndroidTVFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): await aftv.adb_close() return None, unique_id - 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.""" error = None @@ -152,32 +164,31 @@ class AndroidTVFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): data=user_input, ) - user_input = user_input or {} return self._show_setup_form(user_input, error) @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 an option flow for Android TV.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry apps = config_entry.options.get(CONF_APPS, {}) det_rules = config_entry.options.get(CONF_STATE_DETECTION_RULES, {}) - self._apps = apps.copy() - self._state_det_rules = det_rules.copy() - self._conf_app_id = None - self._conf_rule_id = None + self._apps: dict[str, Any] = apps.copy() + self._state_det_rules: dict[str, Any] = det_rules.copy() + self._conf_app_id: str | None = None + self._conf_rule_id: str | None = None @callback - def _save_config(self, data): + def _save_config(self, data: dict[str, Any]) -> FlowResult: """Save the updated options.""" new_data = { k: v @@ -191,7 +202,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_create_entry(title="", data=new_data) - 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: if sel_app := user_input.get(CONF_APPS): @@ -203,7 +216,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self._async_init_form() @callback - def _async_init_form(self): + def _async_init_form(self) -> FlowResult: """Return initial configuration form.""" apps_list = {k: f"{v} ({k})" if v else k for k, v in self._apps.items()} @@ -246,7 +259,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=data_schema) - async def async_step_apps(self, user_input=None, app_id=None): + async def async_step_apps( + self, user_input: dict[str, Any] | None = None, app_id: str | None = None + ) -> FlowResult: """Handle options flow for apps list.""" if app_id is not None: self._conf_app_id = app_id if app_id != APPS_NEW_ID else None @@ -263,28 +278,32 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return await self.async_step_init() @callback - def _async_apps_form(self, app_id): + def _async_apps_form(self, app_id: str) -> FlowResult: """Return configuration form for apps.""" - data_schema = { + app_schema = { vol.Optional( CONF_APP_NAME, description={"suggested_value": self._apps.get(app_id, "")}, ): str, } if app_id == APPS_NEW_ID: - data_schema[vol.Optional(CONF_APP_ID)] = str + data_schema = vol.Schema({**app_schema, vol.Optional(CONF_APP_ID): str}) else: - data_schema[vol.Optional(CONF_APP_DELETE, default=False)] = bool + data_schema = vol.Schema( + {**app_schema, vol.Optional(CONF_APP_DELETE, default=False): bool} + ) return self.async_show_form( step_id="apps", - data_schema=vol.Schema(data_schema), + data_schema=data_schema, description_placeholders={ "app_id": f"`{app_id}`" if app_id != APPS_NEW_ID else "", }, ) - async def async_step_rules(self, user_input=None, rule_id=None): + async def async_step_rules( + self, user_input: dict[str, Any] | None = None, rule_id: str | None = None + ) -> FlowResult: """Handle options flow for detection rules.""" if rule_id is not None: self._conf_rule_id = rule_id if rule_id != RULES_NEW_ID else None @@ -308,21 +327,26 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return await self.async_step_init() @callback - def _async_rules_form(self, rule_id, default_id="", errors=None): + def _async_rules_form( + self, rule_id: str, default_id: str = "", errors: dict[str, str] | None = None + ) -> FlowResult: """Return configuration form for detection rules.""" state_det_rule = self._state_det_rules.get(rule_id) str_det_rule = json.dumps(state_det_rule) if state_det_rule else "" - data_schema = {} + rule_schema = {vol.Optional(CONF_RULE_VALUES, default=str_det_rule): str} if rule_id == RULES_NEW_ID: - data_schema[vol.Optional(CONF_RULE_ID, default=default_id)] = str - data_schema[vol.Optional(CONF_RULE_VALUES, default=str_det_rule)] = str - if rule_id != RULES_NEW_ID: - data_schema[vol.Optional(CONF_RULE_DELETE, default=False)] = bool + data_schema = vol.Schema( + {vol.Optional(CONF_RULE_ID, default=default_id): str, **rule_schema} + ) + else: + data_schema = vol.Schema( + {**rule_schema, vol.Optional(CONF_RULE_DELETE, default=False): bool} + ) return self.async_show_form( step_id="rules", - data_schema=vol.Schema(data_schema), + data_schema=data_schema, description_placeholders={ "rule_id": f"`{rule_id}`" if rule_id != RULES_NEW_ID else "", }, @@ -330,7 +354,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ) -def _validate_state_det_rules(state_det_rules): +def _validate_state_det_rules(state_det_rules: str) -> list[Any] | None: """Validate a string that contain state detection rules and return a dict.""" try: json_rules = json.loads(state_det_rules) diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index d2a0b97ed6b..0d43ada7bc0 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -1,9 +1,11 @@ """Support for functionality to interact with Android TV / Fire TV devices.""" from __future__ import annotations +from collections.abc import Awaitable, Callable, Coroutine from datetime import datetime import functools import logging +from typing import Any, TypeVar from adb_shell.exceptions import ( AdbTimeoutError, @@ -14,6 +16,7 @@ from adb_shell.exceptions import ( ) from androidtv.constants import APPS, KEYS from androidtv.exceptions import LockNotAcquiredException +from typing_extensions import Concatenate, ParamSpec import voluptuous as vol from homeassistant.components import persistent_notification @@ -61,6 +64,10 @@ from .const import ( SIGNAL_CONFIG_ENTITY, ) +_ADBDeviceT = TypeVar("_ADBDeviceT", bound="ADBDevice") +_R = TypeVar("_R") +_P = ParamSpec("_P") + _LOGGER = logging.getLogger(__name__) ATTR_ADB_RESPONSE = "adb_response" @@ -144,18 +151,27 @@ async def async_setup_entry( ) -def adb_decorator(override_available=False): +def adb_decorator( + override_available: bool = False, +) -> Callable[ + [Callable[Concatenate[_ADBDeviceT, _P], Awaitable[_R]]], + Callable[Concatenate[_ADBDeviceT, _P], Coroutine[Any, Any, _R | None]], +]: """Wrap ADB methods and catch exceptions. Allows for overriding the available status of the ADB connection via the `override_available` parameter. """ - def _adb_decorator(func): + def _adb_decorator( + func: Callable[Concatenate[_ADBDeviceT, _P], Awaitable[_R]] + ) -> Callable[Concatenate[_ADBDeviceT, _P], Coroutine[Any, Any, _R | None]]: """Wrap the provided ADB method and catch exceptions.""" @functools.wraps(func) - async def _adb_exception_catcher(self, *args, **kwargs): + async def _adb_exception_catcher( + self: _ADBDeviceT, *args: _P.args, **kwargs: _P.kwargs + ) -> _R | None: """Call an ADB-related method and catch exceptions.""" # pylint: disable=protected-access if not self.available and not override_available: @@ -168,7 +184,7 @@ def adb_decorator(override_available=False): _LOGGER.info( "ADB command not executed because the connection is currently in use" ) - return + return None except self.exceptions as err: _LOGGER.error( "Failed to execute an ADB command. ADB connection re-" diff --git a/homeassistant/components/androidtv/translations/bg.json b/homeassistant/components/androidtv/translations/bg.json index 9dd3bde62ad..f912f17d257 100644 --- a/homeassistant/components/androidtv/translations/bg.json +++ b/homeassistant/components/androidtv/translations/bg.json @@ -13,8 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" - }, - "title": "Android TV" + } } } } diff --git a/homeassistant/components/androidtv/translations/ca.json b/homeassistant/components/androidtv/translations/ca.json index 3fccdc45771..d640a250fb0 100644 --- a/homeassistant/components/androidtv/translations/ca.json +++ b/homeassistant/components/androidtv/translations/ca.json @@ -20,9 +20,7 @@ "device_class": "Tipus de dispositiu", "host": "Amfitri\u00f3", "port": "Port" - }, - "description": "Estableix els par\u00e0metres necessaris per connectar al teu dispositiu Android TV", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Configura les regles de detecci\u00f3 d'estat", "turn_off_command": "Comanda d'apagada de shell ADB (deixa-ho buit per utilitzar la predeterminada)", "turn_on_command": "Comanda d'engegada de shell ADB (deixa-ho buit per utilitzar la predeterminada)" - }, - "title": "Opcions d'Android TV" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/de.json b/homeassistant/components/androidtv/translations/de.json index e69e9ac2183..55a3b75d76c 100644 --- a/homeassistant/components/androidtv/translations/de.json +++ b/homeassistant/components/androidtv/translations/de.json @@ -20,9 +20,7 @@ "device_class": "Der Typ des Ger\u00e4ts", "host": "Host", "port": "Port" - }, - "description": "Stelle die erforderlichen Parameter f\u00fcr die Verbindung mit deinem Android TV-Ger\u00e4t ein", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Regeln zur Statuserkennung konfigurieren", "turn_off_command": "ADB-Shell-Abschaltbefehl (f\u00fcr Standard leer lassen)", "turn_on_command": "ADB-Shell-Einschaltbefehl (f\u00fcr Standard leer lassen)" - }, - "title": "Android TV-Optionen" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/el.json b/homeassistant/components/androidtv/translations/el.json index 67db15ff170..a831586fc41 100644 --- a/homeassistant/components/androidtv/translations/el.json +++ b/homeassistant/components/androidtv/translations/el.json @@ -20,9 +20,7 @@ "device_class": "\u039f \u03c4\u03cd\u03c0\u03bf\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", "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": "\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2 Android TV", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03b1\u03bd\u03cc\u03bd\u03c9\u03bd \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2", "turn_off_command": "\u0395\u03bd\u03c4\u03bf\u03bb\u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03ba\u03b5\u03bb\u03cd\u03c6\u03bf\u03c5\u03c2 ADB (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae)", "turn_on_command": "\u0395\u03bd\u03c4\u03bf\u03bb\u03ae \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bb\u03cd\u03c6\u03bf\u03c5\u03c2 ADB (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae)" - }, - "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 Android TV" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/en.json b/homeassistant/components/androidtv/translations/en.json index f3232b4b229..638ba7fbcd5 100644 --- a/homeassistant/components/androidtv/translations/en.json +++ b/homeassistant/components/androidtv/translations/en.json @@ -20,9 +20,7 @@ "device_class": "The type of device", "host": "Host", "port": "Port" - }, - "description": "Set required parameters to connect to your Android TV device", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Configure state detection rules", "turn_off_command": "ADB shell turn off command (leave empty for default)", "turn_on_command": "ADB shell turn on command (leave empty for default)" - }, - "title": "Android TV Options" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/es-419.json b/homeassistant/components/androidtv/translations/es-419.json index 0e97b8c1265..d8f04b2e7c4 100644 --- a/homeassistant/components/androidtv/translations/es-419.json +++ b/homeassistant/components/androidtv/translations/es-419.json @@ -7,9 +7,7 @@ "adb_server_port": "Puerto del servidor ADB", "adbkey": "Ruta a su archivo de clave ADB (d\u00e9jelo en blanco para generarlo autom\u00e1ticamente)", "device_class": "El tipo de dispositivo" - }, - "description": "Establezca los par\u00e1metros requeridos para conectarse a su dispositivo Android TV", - "title": "Android TV" + } } } }, @@ -36,8 +34,7 @@ "state_detection_rules": "Configurar reglas de detecci\u00f3n de estado", "turn_off_command": "Comando de apagado de shell ADB (d\u00e9jelo vac\u00edo por defecto)", "turn_on_command": "Comando de activaci\u00f3n de shell ADB (d\u00e9jelo vac\u00edo por defecto)" - }, - "title": "Opciones de Android TV" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/es.json b/homeassistant/components/androidtv/translations/es.json index 333910a2b64..74c7484257c 100644 --- a/homeassistant/components/androidtv/translations/es.json +++ b/homeassistant/components/androidtv/translations/es.json @@ -20,9 +20,7 @@ "device_class": "Tipo de dispositivo", "host": "Host", "port": "Puerto" - }, - "description": "Establezca los par\u00e1metros necesarios para conectarse a su Android TV", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Configurar reglas de detecci\u00f3n de estado", "turn_off_command": "Comando de apagado del shell de ADB (dejar vac\u00edo por defecto)", "turn_on_command": "Comando de activaci\u00f3n del shell ADB (dejar vac\u00edo por defecto)" - }, - "title": "Opciones de Android TV" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/et.json b/homeassistant/components/androidtv/translations/et.json index 292b01fa849..1704f450482 100644 --- a/homeassistant/components/androidtv/translations/et.json +++ b/homeassistant/components/androidtv/translations/et.json @@ -20,9 +20,7 @@ "device_class": "Seadme t\u00fc\u00fcp", "host": "Host", "port": "Port" - }, - "description": "Seadista Android TV seadmega \u00fchenduse loomiseks vajalikud parameetrid", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "M\u00e4\u00e4ra oleku tuvastamise reeglid", "turn_off_command": "ADB shell turn off k\u00e4sk (vaikimisi j\u00e4ta t\u00fchjaks)", "turn_on_command": "ADB shell turn on k\u00e4sk (vaikimisi j\u00e4ta t\u00fchjaks)" - }, - "title": "Android TV suvandid" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/fr.json b/homeassistant/components/androidtv/translations/fr.json index ea79fddb98a..76124671f2c 100644 --- a/homeassistant/components/androidtv/translations/fr.json +++ b/homeassistant/components/androidtv/translations/fr.json @@ -20,9 +20,7 @@ "device_class": "Le type d'appareil", "host": "H\u00f4te", "port": "Port" - }, - "description": "D\u00e9finissez les param\u00e8tres requis pour vous connecter \u00e0 votre appareil Android TV", - "title": "T\u00e9l\u00e9vision Android" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Configurer les r\u00e8gles de d\u00e9tection d'\u00e9tat", "turn_off_command": "Commande de d\u00e9sactivation du shell ADB (laisser vide par d\u00e9faut)", "turn_on_command": "Commande d'activation du shell ADB (laisser vide par d\u00e9faut)" - }, - "title": "Options de t\u00e9l\u00e9vision Android" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/he.json b/homeassistant/components/androidtv/translations/he.json index 81870f85c75..557e868298f 100644 --- a/homeassistant/components/androidtv/translations/he.json +++ b/homeassistant/components/androidtv/translations/he.json @@ -20,9 +20,7 @@ "device_class": "\u05e1\u05d5\u05d2 \u05d4\u05d4\u05ea\u05e7\u05df", "host": "\u05de\u05d0\u05e8\u05d7", "port": "\u05e4\u05ea\u05d7\u05d4" - }, - "description": "\u05d4\u05d2\u05d3\u05e8\u05ea \u05d4\u05e4\u05e8\u05de\u05d8\u05e8\u05d9\u05dd \u05d4\u05e0\u05d3\u05e8\u05e9\u05d9\u05dd \u05db\u05d3\u05d9 \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05d9\u05ea \u05d4\u05d0\u05e0\u05d3\u05e8\u05d5\u05d0\u05d9\u05d3 \u05e9\u05dc\u05da", - "title": "\u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05ea \u05d0\u05e0\u05d3\u05e8\u05d5\u05d0\u05d9\u05d3" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "\u05e7\u05d1\u05d9\u05e2\u05ea \u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc \u05db\u05dc\u05dc\u05d9 \u05d6\u05d9\u05d4\u05d5\u05d9 \u05de\u05e6\u05d1\u05d9\u05dd", "turn_off_command": "\u05d4\u05e4\u05e7\u05d5\u05d3\u05d4 \u05db\u05d9\u05d1\u05d5\u05d9 \u05de\u05e2\u05d8\u05e4\u05ea ADB (\u05d4\u05e9\u05d0\u05e8 \u05e8\u05d9\u05e7\u05d4 \u05db\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc)", "turn_on_command": "\u05d4\u05e4\u05e7\u05d5\u05d3\u05d4 \u05d4\u05e4\u05e2\u05dc \u05de\u05e2\u05d8\u05e4\u05ea ADB (\u05d4\u05e9\u05d0\u05e8 \u05e8\u05d9\u05e7\u05d4 \u05db\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc)" - }, - "title": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05ea \u05d0\u05e0\u05d3\u05e8\u05d5\u05d0\u05d9\u05d3" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/hu.json b/homeassistant/components/androidtv/translations/hu.json index 3b75153a351..98c82a9779b 100644 --- a/homeassistant/components/androidtv/translations/hu.json +++ b/homeassistant/components/androidtv/translations/hu.json @@ -20,9 +20,7 @@ "device_class": "Az eszk\u00f6z t\u00edpusa", "host": "C\u00edm", "port": "Port" - }, - "description": "Az Android TV k\u00e9sz\u00fcl\u00e9khez val\u00f3 csatlakoz\u00e1shoz sz\u00fcks\u00e9ges param\u00e9terek be\u00e1ll\u00edt\u00e1sa", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "\u00c1llapotfelismer\u00e9si szab\u00e1lyok konfigur\u00e1l\u00e1sa", "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" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/id.json b/homeassistant/components/androidtv/translations/id.json index 2b754860c5d..2c81a6b2b96 100644 --- a/homeassistant/components/androidtv/translations/id.json +++ b/homeassistant/components/androidtv/translations/id.json @@ -20,9 +20,7 @@ "device_class": "Jenis perangkat", "host": "Host", "port": "Port" - }, - "description": "Setel parameter yang diperlukan untuk terhubung ke perangkat Android TV Anda", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Konfigurasikan aturan deteksi status", "turn_off_command": "Perintah mematikan shell ADB (kosongkan untuk default)", "turn_on_command": "Perintah nyalakan shell ADB (kosongkan untuk default)" - }, - "title": "Opsi Android TV" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/it.json b/homeassistant/components/androidtv/translations/it.json index ce6d3c92bca..91f56c48853 100644 --- a/homeassistant/components/androidtv/translations/it.json +++ b/homeassistant/components/androidtv/translations/it.json @@ -20,9 +20,7 @@ "device_class": "Il tipo di dispositivo", "host": "Host", "port": "Porta" - }, - "description": "Imposta i parametri richiesti per connetterti al tuo dispositivo Android TV", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Configura le regole di rilevamento dello stato", "turn_off_command": "Comando di disattivazione della shell ADB (lascia vuoto per impostazione predefinita)", "turn_on_command": "Comando di attivazione della shell ADB (lascia vuoto per impostazione predefinita)" - }, - "title": "Opzioni Android TV" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/ja.json b/homeassistant/components/androidtv/translations/ja.json index f3139df9819..26b83a2643a 100644 --- a/homeassistant/components/androidtv/translations/ja.json +++ b/homeassistant/components/androidtv/translations/ja.json @@ -14,15 +14,13 @@ "step": { "user": { "data": { - "adb_server_ip": "ADB\u30b5\u30fc\u30d0\u30fc\u306eIP\u30a2\u30c9\u30ec\u30b9(\u4f7f\u7528\u3057\u306a\u3044\u5834\u5408\u306f\u7a7a\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", + "adb_server_ip": "ADB\u30b5\u30fc\u30d0\u30fc\u306eIP\u30a2\u30c9\u30ec\u30b9(\u4f7f\u7528\u3057\u306a\u3044\u5834\u5408\u306f\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", "adb_server_port": "ADB\u30b5\u30fc\u30d0\u30fc\u306e\u30dd\u30fc\u30c8", - "adbkey": "ADB\u30ad\u30fc\u30d5\u30a1\u30a4\u30eb\u3078\u306e\u30d1\u30b9(\u7a7a\u306b\u3059\u308b\u3068\u81ea\u52d5\u751f\u6210\u3055\u308c\u307e\u3059)", + "adbkey": "ADB\u30ad\u30fc\u30d5\u30a1\u30a4\u30eb\u3078\u306e\u30d1\u30b9(\u7a7a\u767d\u306b\u3059\u308b\u3068\u81ea\u52d5\u751f\u6210\u3055\u308c\u307e\u3059)", "device_class": "\u30c7\u30d0\u30a4\u30b9\u306e\u7a2e\u985e", "host": "\u30db\u30b9\u30c8", "port": "\u30dd\u30fc\u30c8" - }, - "description": "Android TV\u30c7\u30d0\u30a4\u30b9\u306b\u63a5\u7d9a\u3059\u308b\u305f\u3081\u306b\u5fc5\u8981\u306a\u30d1\u30e9\u30e1\u30fc\u30bf\u30fc\u3092\u8a2d\u5b9a\u3057\u307e\u3059", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "\u72b6\u614b\u691c\u51fa\u30eb\u30fc\u30eb\u3092\u8a2d\u5b9a", "turn_off_command": "\u30c7\u30d5\u30a9\u30eb\u30c8\u306eturn_off\u30b3\u30de\u30f3\u30c9\u3092\u4e0a\u66f8\u304d\u3059\u308bADB\u30b7\u30a7\u30eb\u30b3\u30de\u30f3\u30c9", "turn_on_command": "\u30c7\u30d5\u30a9\u30eb\u30c8\u306eturn_on\u30b3\u30de\u30f3\u30c9\u3092\u4e0a\u66f8\u304d\u3059\u308bADB\u30b7\u30a7\u30eb\u30b3\u30de\u30f3\u30c9" - }, - "title": "Android TV\u306e\u30aa\u30d7\u30b7\u30e7\u30f3" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/nl.json b/homeassistant/components/androidtv/translations/nl.json index e8fdf44387c..40df125d384 100644 --- a/homeassistant/components/androidtv/translations/nl.json +++ b/homeassistant/components/androidtv/translations/nl.json @@ -20,9 +20,7 @@ "device_class": "Het type apparaat", "host": "Host", "port": "Poort" - }, - "description": "Stel de vereiste parameters in om verbinding te maken met uw Android TV-apparaat", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Regels voor statusdetectie van Android TV configureren", "turn_off_command": "ADB shell-uitschakelcommando(laat standaard leeg)", "turn_on_command": "ADB shell-inschakelcommando (laat standaard leeg)" - }, - "title": "Android TV-opties" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/no.json b/homeassistant/components/androidtv/translations/no.json index 92d74ad5e0b..707352476a2 100644 --- a/homeassistant/components/androidtv/translations/no.json +++ b/homeassistant/components/androidtv/translations/no.json @@ -20,9 +20,7 @@ "device_class": "Type enhet", "host": "Vert", "port": "Port" - }, - "description": "Angi n\u00f8dvendige parametere for \u00e5 koble til Android TV-enheten din", - "title": "" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Konfigurere regler for tilstandsgjenkjenning", "turn_off_command": "ADB shell sl\u00e5 av kommando (la st\u00e5 tomt som standard)", "turn_on_command": "ADB-skall sl\u00e5 p\u00e5 kommando (la st\u00e5 tomt som standard)" - }, - "title": "Android TV-alternativer" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/pl.json b/homeassistant/components/androidtv/translations/pl.json index 5a4f7e39130..2f8dd47ee23 100644 --- a/homeassistant/components/androidtv/translations/pl.json +++ b/homeassistant/components/androidtv/translations/pl.json @@ -20,9 +20,7 @@ "device_class": "Typ urz\u0105dzenia", "host": "Nazwa hosta lub adres IP", "port": "Port" - }, - "description": "Ustaw wymagane parametry, aby po\u0142\u0105czy\u0107 si\u0119 z Android TV", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Skonfiguruj regu\u0142y wykrywania stanu", "turn_off_command": "Polecenie wy\u0142\u0105czania z pow\u0142oki ADB (pozostaw puste dla warto\u015bci domy\u015blnej)", "turn_on_command": "Polecenie w\u0142\u0105czenia z pow\u0142oki ADB (pozostaw puste dla warto\u015bci domy\u015blnej)" - }, - "title": "Opcje Android TV" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/pt-BR.json b/homeassistant/components/androidtv/translations/pt-BR.json index 86e9a29dd12..496f7ea0013 100644 --- a/homeassistant/components/androidtv/translations/pt-BR.json +++ b/homeassistant/components/androidtv/translations/pt-BR.json @@ -20,9 +20,7 @@ "device_class": "O tipo de dispositivo", "host": "Nome do host", "port": "Porta" - }, - "description": "Defina os par\u00e2metros necess\u00e1rios para se conectar ao seu dispositivo Android TV", - "title": "AndroidTV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Configurar regras de detec\u00e7\u00e3o de estado", "turn_off_command": "Comando de desligamento do shell ADB (deixe vazio por padr\u00e3o)", "turn_on_command": "Comando de ativa\u00e7\u00e3o do shell ADB (deixe vazio por padr\u00e3o)" - }, - "title": "Op\u00e7\u00f5es de TV Android" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/ru.json b/homeassistant/components/androidtv/translations/ru.json index 0803a16c986..61eb431fbf4 100644 --- a/homeassistant/components/androidtv/translations/ru.json +++ b/homeassistant/components/androidtv/translations/ru.json @@ -20,9 +20,7 @@ "device_class": "\u0422\u0438\u043f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" - }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Android TV.", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439", "turn_off_command": "\u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u043e\u0431\u043e\u043b\u043e\u0447\u043a\u0438 ADB \u0434\u043b\u044f \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c)", "turn_on_command": "\u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u043e\u0431\u043e\u043b\u043e\u0447\u043a\u0438 ADB \u0434\u043b\u044f \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c)" - }, - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 Android TV" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/tr.json b/homeassistant/components/androidtv/translations/tr.json index bb77c35ed1b..6e52e9b3200 100644 --- a/homeassistant/components/androidtv/translations/tr.json +++ b/homeassistant/components/androidtv/translations/tr.json @@ -20,9 +20,7 @@ "device_class": "Cihaz\u0131n t\u00fcr\u00fc", "host": "Sunucu", "port": "Port" - }, - "description": "Android TV cihaz\u0131n\u0131za ba\u011flanmak i\u00e7in gerekli parametreleri ayarlay\u0131n", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "Durum alg\u0131lama kurallar\u0131n\u0131 yap\u0131land\u0131r\u0131n", "turn_off_command": "ADB kabu\u011fu kapatma komutu (varsay\u0131lan olarak bo\u015f b\u0131rak\u0131n)", "turn_on_command": "ADB kabu\u011fu a\u00e7ma komutu (varsay\u0131lan olarak bo\u015f b\u0131rak\u0131n)" - }, - "title": "Android TV Se\u00e7enekleri" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/zh-Hans.json b/homeassistant/components/androidtv/translations/zh-Hans.json index b3aa722d8a5..03bdfd8851d 100644 --- a/homeassistant/components/androidtv/translations/zh-Hans.json +++ b/homeassistant/components/androidtv/translations/zh-Hans.json @@ -19,9 +19,7 @@ "device_class": "\u8bbe\u5907\u7c7b\u578b", "host": "\u4e3b\u673a", "port": "\u7aef\u53e3" - }, - "description": "\u8bf7\u586b\u5199\u4ee5\u4e0b\u53c2\u6570\uff0c\u8fde\u63a5\u5230 Android TV \u8bbe\u5907\u3002", - "title": "Android TV" + } } } }, @@ -48,8 +46,7 @@ "state_detection_rules": "\u914d\u7f6e\u72b6\u6001\u68c0\u6d4b\u89c4\u5219", "turn_off_command": "ADB shell \u5173\u673a\u547d\u4ee4\uff08\u7559\u7a7a\u4ee5\u4f7f\u7528\u9ed8\u8ba4\u503c\uff09", "turn_on_command": "ADB shell \u5f00\u673a\u547d\u4ee4\uff08\u7559\u7a7a\u4ee5\u4f7f\u7528\u9ed8\u8ba4\u503c\uff09" - }, - "title": "Android TV \u9009\u9879" + } }, "rules": { "data": { diff --git a/homeassistant/components/androidtv/translations/zh-Hant.json b/homeassistant/components/androidtv/translations/zh-Hant.json index 9e9d78f0629..3e572ea509d 100644 --- a/homeassistant/components/androidtv/translations/zh-Hant.json +++ b/homeassistant/components/androidtv/translations/zh-Hant.json @@ -20,9 +20,7 @@ "device_class": "\u88dd\u7f6e\u985e\u5225", "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0" - }, - "description": "\u8a2d\u5b9a\u9023\u7dda\u81f3 Android TV \u88dd\u7f6e\u6240\u9700\u53c3\u6578", - "title": "Android TV" + } } } }, @@ -49,8 +47,7 @@ "state_detection_rules": "\u8a2d\u5b9a\u72c0\u614b\u5075\u6e2c\u898f\u5247", "turn_off_command": "ADB shell turn off \u6307\u4ee4\uff08\u9810\u8a2d\u7a7a\u767d\uff09", "turn_on_command": "ADB shell turn on \u6307\u4ee4\uff08\u9810\u8a2d\u7a7a\u767d\uff09" - }, - "title": "Android TV \u9078\u9805" + } }, "rules": { "data": { diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index 5a7298dcbee..30a397d953c 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -284,7 +284,9 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity): await self.atv.apps.launch_app(media_id) if media_source.is_media_source_id(media_id): - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = play_item.url media_type = MEDIA_TYPE_MUSIC diff --git a/homeassistant/components/apple_tv/translations/bg.json b/homeassistant/components/apple_tv/translations/bg.json index b1923cab650..cd0488141c4 100644 --- a/homeassistant/components/apple_tv/translations/bg.json +++ b/homeassistant/components/apple_tv/translations/bg.json @@ -2,7 +2,6 @@ "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", - "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", @@ -36,6 +35,5 @@ } } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/ca.json b/homeassistant/components/apple_tv/translations/ca.json index c672733193a..ca0ca27bcfe 100644 --- a/homeassistant/components/apple_tv/translations/ca.json +++ b/homeassistant/components/apple_tv/translations/ca.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "already_configured_device": "El dispositiu ja est\u00e0 configurat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "backoff": "En aquests moments el dispositiu no accepta sol\u00b7licituds de vinculaci\u00f3 (\u00e9s possible que hagis introdu\u00eft un codi PIN inv\u00e0lid massa vegades), torna-ho a provar m\u00e9s tard.", "device_did_not_pair": "No s'ha fet cap intent d'acabar el proc\u00e9s de vinculaci\u00f3 des del dispositiu.", "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", @@ -19,7 +17,6 @@ "already_configured": "El dispositiu ja est\u00e0 configurat", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "no_devices_found": "No s'han trobat dispositius a la xarxa", - "no_usable_service": "S'ha trobat un dispositiu per\u00f2 no ha pogut identificar cap manera d'establir-hi una connexi\u00f3. Si continues veient aquest missatge, prova d'especificar-ne l'adre\u00e7a IP o reinicia l'Apple TV.", "unknown": "Error inesperat" }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "Configuraci\u00f3 dels par\u00e0metres generals del dispositiu" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/cs.json b/homeassistant/components/apple_tv/translations/cs.json index 27b9e1dfeee..b7c44b60f6b 100644 --- a/homeassistant/components/apple_tv/translations/cs.json +++ b/homeassistant/components/apple_tv/translations/cs.json @@ -2,9 +2,7 @@ "config": { "abort": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", - "already_configured_device": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", - "invalid_config": "Nastaven\u00ed tohoto za\u0159\u00edzen\u00ed je ne\u00fapln\u00e9. Zkuste jej p\u0159idat znovu.", "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed", "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" @@ -57,6 +55,5 @@ "description": "Konfigurace obecn\u00fdch mo\u017enost\u00ed za\u0159\u00edzen\u00ed" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/de.json b/homeassistant/components/apple_tv/translations/de.json index 48149ce8394..27ecc3452f0 100644 --- a/homeassistant/components/apple_tv/translations/de.json +++ b/homeassistant/components/apple_tv/translations/de.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "already_configured_device": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "backoff": "Das Ger\u00e4t akzeptiert zur Zeit keine Kopplungsanfragen (Du hast m\u00f6glicherweise zu oft einen ung\u00fcltigen PIN-Code eingegeben), versuche es sp\u00e4ter erneut.", "device_did_not_pair": "Es wurde kein Versuch unternommen, den Kopplungsvorgang vom Ger\u00e4t aus abzuschlie\u00dfen.", "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", @@ -19,7 +17,6 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "invalid_auth": "Ung\u00fcltige Authentifizierung", "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", - "no_usable_service": "Es wurde ein Ger\u00e4t gefunden, aber es konnte keine M\u00f6glichkeit gefunden werden, eine Verbindung zu diesem Ger\u00e4t herzustellen. Wenn diese Meldung weiterhin erscheint, versuche, die IP-Adresse anzugeben oder den Apple TV neu zu starten.", "unknown": "Unerwarteter Fehler" }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "Konfiguriere die allgemeinen Ger\u00e4teeinstellungen" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/el.json b/homeassistant/components/apple_tv/translations/el.json index cbe36bf56fb..a017d67b834 100644 --- a/homeassistant/components/apple_tv/translations/el.json +++ b/homeassistant/components/apple_tv/translations/el.json @@ -2,13 +2,11 @@ "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_device": "\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", "backoff": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae (\u03af\u03c3\u03c9\u03c2 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03b5\u03b9 \u03ac\u03ba\u03c5\u03c1\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc PIN \u03c0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03ad\u03c2 \u03c6\u03bf\u03c1\u03ad\u03c2), \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1.", "device_did_not_pair": "\u0394\u03b5\u03bd \u03ad\u03b3\u03b9\u03bd\u03b5 \u03ba\u03b1\u03bc\u03af\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1\u03c2 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae.", "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", @@ -19,7 +17,6 @@ "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_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_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", - "no_usable_service": "\u0392\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae, \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03bc\u03c0\u03cc\u03c1\u03b5\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03c4\u03b5\u03af \u03ba\u03b1\u03bd\u03ad\u03bd\u03b1\u03c2 \u03c4\u03c1\u03cc\u03c0\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd. \u0391\u03bd \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b2\u03bb\u03ad\u03c0\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b4\u03b9\u03bf\u03c1\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Apple TV.", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b3\u03b5\u03bd\u03b9\u03ba\u03ad\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/en.json b/homeassistant/components/apple_tv/translations/en.json index 792eaf32c29..f455d590d79 100644 --- a/homeassistant/components/apple_tv/translations/en.json +++ b/homeassistant/components/apple_tv/translations/en.json @@ -2,13 +2,11 @@ "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", @@ -19,7 +17,6 @@ "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})", @@ -73,6 +70,5 @@ "description": "Configure general device settings" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/es-419.json b/homeassistant/components/apple_tv/translations/es-419.json index 75e6fb43ff2..f8035822cb6 100644 --- a/homeassistant/components/apple_tv/translations/es-419.json +++ b/homeassistant/components/apple_tv/translations/es-419.json @@ -2,11 +2,7 @@ "config": { "abort": { "backoff": "El dispositivo no acepta solicitudes de emparejamiento en este momento (es posible que haya ingresado un c\u00f3digo PIN no v\u00e1lido demasiadas veces), vuelva a intentarlo m\u00e1s tarde.", - "device_did_not_pair": "No se intent\u00f3 finalizar el proceso de emparejamiento desde el dispositivo.", - "invalid_config": "La configuraci\u00f3n de este dispositivo est\u00e1 incompleta. Intente agregarlo nuevamente." - }, - "error": { - "no_usable_service": "Se encontr\u00f3 un dispositivo, pero no se pudo identificar ninguna forma de establecer una conexi\u00f3n con \u00e9l. Si sigue viendo este mensaje, intente especificar su direcci\u00f3n IP o reinicie su Apple TV." + "device_did_not_pair": "No se intent\u00f3 finalizar el proceso de emparejamiento desde el dispositivo." }, "step": { "confirm": { @@ -47,6 +43,5 @@ "description": "Configurar los ajustes generales del dispositivo" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/es.json b/homeassistant/components/apple_tv/translations/es.json index f72b7ec4cff..1a0ed773169 100644 --- a/homeassistant/components/apple_tv/translations/es.json +++ b/homeassistant/components/apple_tv/translations/es.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "El dispositivo ya est\u00e1 configurado", - "already_configured_device": "El dispositivo ya est\u00e1 configurado", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "backoff": "El dispositivo no acepta solicitudes de emparejamiento en este momento (es posible que hayas introducido un c\u00f3digo PIN no v\u00e1lido demasiadas veces), int\u00e9ntalo de nuevo m\u00e1s tarde.", "device_did_not_pair": "No se ha intentado finalizar el proceso de emparejamiento desde el dispositivo.", "device_not_found": "No se ha encontrado el dispositivo durante la detecci\u00f3n, por favor, intente a\u00f1adirlo de nuevo.", "inconsistent_device": "No se encontraron los protocolos esperados durante el descubrimiento. Esto normalmente indica un problema con el DNS de multidifusi\u00f3n (Zeroconf). Por favor, intente a\u00f1adir el dispositivo de nuevo.", - "invalid_config": "La configuraci\u00f3n para este dispositivo est\u00e1 incompleta. Intenta a\u00f1adirlo de nuevo.", "no_devices_found": "No se encontraron dispositivos en la red", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente", "setup_failed": "No se ha podido configurar el dispositivo.", @@ -18,13 +16,12 @@ "already_configured": "El dispositivo ya est\u00e1 configurado", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "no_devices_found": "No se encontraron dispositivos en la red", - "no_usable_service": "Se encontr\u00f3 un dispositivo, pero no se pudo identificar ninguna manera de establecer una conexi\u00f3n con \u00e9l. Si sigues viendo este mensaje, intenta especificar su direcci\u00f3n IP o reiniciar el Apple TV.", "unknown": "Error inesperado" }, - "flow_title": "Apple TV: {name}", + "flow_title": "{name} ({type})", "step": { "confirm": { - "description": "Est\u00e1s a punto de a\u00f1adir el Apple TV con nombre `{name}` a Home Assistant.\n\n**Para completar el proceso, puede que tengas que introducir varios c\u00f3digos PIN.**\n\nTen en cuenta que *no* podr\u00e1s apagar tu Apple TV con esta integraci\u00f3n. \u00a1S\u00f3lo se apagar\u00e1 el reproductor de medios de Home Assistant!", + "description": "Est\u00e1s a punto de a\u00f1adir `{name}` con el tipo `{type}` en Home Assistant.\n\n**Para completar el proceso, puede que tengas que introducir varios c\u00f3digos PIN.**\n\nTen en cuenta que *no* podr\u00e1s apagar tu Apple TV con esta integraci\u00f3n. \u00a1S\u00f3lo se apagar\u00e1 el reproductor de medios de Home Assistant!", "title": "Confirma la adici\u00f3n del Apple TV" }, "pair_no_pin": { @@ -47,7 +44,7 @@ "title": "No es posible el emparejamiento" }, "reconfigure": { - "description": "Este Apple TV est\u00e1 experimentando algunos problemas de conexi\u00f3n y debe ser reconfigurado.", + "description": "Vuelve a configurar este dispositivo para restablecer su funcionamiento.", "title": "Reconfiguraci\u00f3n del dispositivo" }, "service_problem": { @@ -72,6 +69,5 @@ "description": "Configurar los ajustes generales del dispositivo" } } - }, - "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 8180b95fe12..81c5183ece1 100644 --- a/homeassistant/components/apple_tv/translations/et.json +++ b/homeassistant/components/apple_tv/translations/et.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "Seade on juba h\u00e4\u00e4lestatud", - "already_configured_device": "Seade on juba h\u00e4\u00e4lestatud", "already_in_progress": "Seadistamine on juba k\u00e4imas", "backoff": "Seade ei aktsepteeri praegu sidumisn\u00f5udeid (v\u00f5ib-olla oled liiga palju kordi vale PIN-koodi sisestanud), proovi hiljem uuesti.", "device_did_not_pair": "Seade ei \u00fcritatud sidumisprotsessi l\u00f5pule viia.", "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", @@ -19,7 +17,6 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "invalid_auth": "Vigane autentimine", "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi seadet", - "no_usable_service": "Leiti seade kuid ei suudetud tuvastada moodust \u00fchenduse loomiseks. Kui n\u00e4ed seda teadet pidevalt, proovi m\u00e4\u00e4rata seadme IP-aadress v\u00f5i taask\u00e4ivita Apple TV.", "unknown": "Ootamatu t\u00f5rge" }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "Seadme \u00fclds\u00e4tete seadistamine" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/fr.json b/homeassistant/components/apple_tv/translations/fr.json index 6d7deea6e5a..92075463f4d 100644 --- a/homeassistant/components/apple_tv/translations/fr.json +++ b/homeassistant/components/apple_tv/translations/fr.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", - "already_configured_device": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "backoff": "L'appareil n'accepte pas les demandes d'appariement pour le moment (vous avez peut-\u00eatre saisi un code PIN non valide trop de fois), r\u00e9essayez plus tard.", "device_did_not_pair": "Aucune tentative pour terminer l'appairage n'a \u00e9t\u00e9 effectu\u00e9e \u00e0 partir de l'appareil.", "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", @@ -19,7 +17,6 @@ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "invalid_auth": "Authentification non valide", "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau", - "no_usable_service": "Un dispositif a \u00e9t\u00e9 trouv\u00e9, mais aucun moyen d\u2019\u00e9tablir un lien avec lui. Si vous continuez \u00e0 voir ce message, essayez de sp\u00e9cifier son adresse IP ou de red\u00e9marrer votre Apple TV.", "unknown": "Erreur inattendue" }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "Configurer les param\u00e8tres g\u00e9n\u00e9raux de l'appareil" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/he.json b/homeassistant/components/apple_tv/translations/he.json index 61ca863bd66..26ec85e8dc7 100644 --- a/homeassistant/components/apple_tv/translations/he.json +++ b/homeassistant/components/apple_tv/translations/he.json @@ -2,7 +2,6 @@ "config": { "abort": { "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", diff --git a/homeassistant/components/apple_tv/translations/hu.json b/homeassistant/components/apple_tv/translations/hu.json index 73a30fbdd9a..4c3a8cdee94 100644 --- a/homeassistant/components/apple_tv/translations/hu.json +++ b/homeassistant/components/apple_tv/translations/hu.json @@ -2,13 +2,11 @@ "config": { "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 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.", @@ -19,7 +17,6 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", - "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} ({type})", @@ -73,6 +70,5 @@ "description": "Konfigur\u00e1lja az eszk\u00f6z \u00e1ltal\u00e1nos be\u00e1ll\u00edt\u00e1sait" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/id.json b/homeassistant/components/apple_tv/translations/id.json index 7120d0671b8..fcb77511abe 100644 --- a/homeassistant/components/apple_tv/translations/id.json +++ b/homeassistant/components/apple_tv/translations/id.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "Perangkat sudah dikonfigurasi", - "already_configured_device": "Perangkat sudah dikonfigurasi", "already_in_progress": "Alur konfigurasi sedang berlangsung", "backoff": "Perangkat tidak bisa menerima permintaan pemasangan saat ini (Anda mungkin telah berulang kali memasukkan kode PIN yang salah). Coba lagi nanti.", "device_did_not_pair": "Tidak ada upaya untuk menyelesaikan proses pemasangan dari sisi perangkat.", "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", @@ -19,7 +17,6 @@ "already_configured": "Perangkat sudah dikonfigurasi", "invalid_auth": "Autentikasi tidak valid", "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", - "no_usable_service": "Perangkat ditemukan tetapi kami tidak dapat mengidentifikasi berbagai cara untuk membuat koneksi ke perangkat tersebut. Jika Anda terus melihat pesan ini, coba tentukan alamat IP-nya atau mulai ulang Apple TV Anda.", "unknown": "Kesalahan yang tidak diharapkan" }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "Konfigurasikan pengaturan umum perangkat" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/it.json b/homeassistant/components/apple_tv/translations/it.json index b1e3a06440f..25bcbeaf9fe 100644 --- a/homeassistant/components/apple_tv/translations/it.json +++ b/homeassistant/components/apple_tv/translations/it.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "already_configured_device": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "backoff": "Il dispositivo non accetta richieste di abbinamento in questo momento (potresti aver inserito un codice PIN non valido troppe volte), riprova pi\u00f9 tardi.", "device_did_not_pair": "Nessun tentativo di completare il processo di abbinamento \u00e8 stato effettuato dal dispositivo.", "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", @@ -19,7 +17,6 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "invalid_auth": "Autenticazione non valida", "no_devices_found": "Nessun dispositivo trovato sulla rete", - "no_usable_service": "\u00c8 stato trovato un dispositivo ma non \u00e8 stato possibile identificare alcun modo per stabilire una connessione ad esso. Se continui a vedere questo messaggio, prova a specificarne l'indirizzo IP o a riavviare l'Apple TV.", "unknown": "Errore imprevisto" }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "Configura le impostazioni generali del dispositivo" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/ja.json b/homeassistant/components/apple_tv/translations/ja.json index 9984006365e..3661cbeedb3 100644 --- a/homeassistant/components/apple_tv/translations/ja.json +++ b/homeassistant/components/apple_tv/translations/ja.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", - "already_configured_device": "\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", "backoff": "\u73fe\u5728\u3001\u30c7\u30d0\u30a4\u30b9\u306f\u30da\u30a2\u30ea\u30f3\u30b0\u8981\u6c42\u3092\u53d7\u3051\u4ed8\u3051\u3066\u3044\u307e\u305b\u3093(\u7121\u52b9\u306aPIN\u30b3\u30fc\u30c9\u3092\u4f55\u5ea6\u3082\u5165\u529b\u3057\u305f\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059)\u3001\u5f8c\u3067\u3082\u3046\u4e00\u5ea6\u8a66\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "device_did_not_pair": "\u30c7\u30d0\u30a4\u30b9\u304b\u3089\u30da\u30a2\u30ea\u30f3\u30b0\u30d7\u30ed\u30bb\u30b9\u3092\u7d42\u4e86\u3059\u308b\u8a66\u307f\u306f\u884c\u308f\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002", "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", @@ -19,7 +17,6 @@ "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093", - "no_usable_service": "\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u304c\u3001\u30c7\u30d0\u30a4\u30b9\u3078\u306e\u63a5\u7d9a\u3092\u78ba\u7acb\u3059\u308b\u65b9\u6cd5\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u3053\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u304c\u5f15\u304d\u7d9a\u304d\u8868\u793a\u3055\u308c\u308b\u5834\u5408\u306f\u3001IP\u30a2\u30c9\u30ec\u30b9\u3092\u6307\u5b9a\u3059\u308b\u304b\u3001Apple TV\u3092\u518d\u8d77\u52d5\u3057\u3066\u307f\u3066\u304f\u3060\u3055\u3044\u3002", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "flow_title": "{name}", @@ -73,6 +70,5 @@ "description": "\u30c7\u30d0\u30a4\u30b9\u306e\u4e00\u822c\u7684\u306a\u8a2d\u5b9a" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/ko.json b/homeassistant/components/apple_tv/translations/ko.json index 278dbec04e4..9bddc31c56f 100644 --- a/homeassistant/components/apple_tv/translations/ko.json +++ b/homeassistant/components/apple_tv/translations/ko.json @@ -1,11 +1,10 @@ { "config": { "abort": { - "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "backoff": "\uae30\uae30\uac00 \ud604\uc7ac \ud398\uc5b4\ub9c1 \uc694\uccad\uc744 \uc218\ub77d\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4(\uc798\ubabb\ub41c PIN \ucf54\ub4dc\ub97c \ub108\ubb34 \ub9ce\uc774 \uc785\ub825\ud588\uc744 \uc218 \uc788\uc74c). \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "device_did_not_pair": "\uae30\uae30\uc5d0\uc11c \ud398\uc5b4\ub9c1 \ud504\ub85c\uc138\uc2a4\ub97c \uc644\ub8cc\ud558\ub824\uace0 \uc2dc\ub3c4\ud558\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "invalid_config": "\uc774 \uae30\uae30\uc5d0 \ub300\ud55c \uad6c\uc131\uc774 \ubd88\uc644\uc804\ud569\ub2c8\ub2e4. \ub2e4\uc2dc \ucd94\uac00\ud574\uc8fc\uc138\uc694.", + "inconsistent_device": "\uc7a5\uce58\uac80\uc0c9 \uc911\uc5d0 \ud574\ub2f9 \ud504\ub85c\ud1a0\ucf5c\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774\ub294 \uc77c\ubc18\uc801\uc73c\ub85c \uba40\ud2f0\uce90\uc2a4\ud2b8 DNS(Zeroconf)\uc5d0 \ubb38\uc81c\uac00 \uc788\uc74c\uc744 \ub098\ud0c0\ub0c5\ub2c8\ub2e4. \uc7a5\uce58\ub97c \ub2e4\uc2dc \ucd94\uac00\ud574 \ubcf4\uc2ed\uc2dc\uc624.", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, @@ -13,7 +12,6 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "no_usable_service": "\uae30\uae30\ub97c \ucc3e\uc558\uc9c0\ub9cc \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\ub294 \ubc29\ubc95\uc744 \uc2dd\ubcc4\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uba54\uc2dc\uc9c0\uac00 \uacc4\uc18d \ud45c\uc2dc\ub418\uba74 \ud574\ub2f9 IP \uc8fc\uc18c\ub97c \uc9c1\uc811 \uc9c0\uc815\ud574\uc8fc\uc2dc\uac70\ub098 Apple TV\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud574\uc8fc\uc138\uc694.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "Apple TV: {name}", @@ -59,6 +57,5 @@ "description": "\uc77c\ubc18 \uae30\uae30 \uc124\uc815 \uad6c\uc131" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/lb.json b/homeassistant/components/apple_tv/translations/lb.json index 2354033b577..0950ff8235a 100644 --- a/homeassistant/components/apple_tv/translations/lb.json +++ b/homeassistant/components/apple_tv/translations/lb.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured_device": "Apparat ass scho konfigur\u00e9iert", "already_in_progress": "Konfiguratioun's Oflaf ass schon am gaang", - "invalid_config": "Konfiguratioun fir d\u00ebsen Apparat ass net komplett. Prob\u00e9ier fir et nach emol dob\u00e4i ze setzen.", "no_devices_found": "Keng Apparater am Netzwierk fonnt", "unknown": "Onerwaarte Feeler" }, @@ -55,6 +53,5 @@ "description": "Allgemeng Apparat Astellungen konfigur\u00e9ieren" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/nl.json b/homeassistant/components/apple_tv/translations/nl.json index 8aaf120403c..c7adfa6d756 100644 --- a/homeassistant/components/apple_tv/translations/nl.json +++ b/homeassistant/components/apple_tv/translations/nl.json @@ -2,16 +2,14 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_configured_device": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "backoff": "Het apparaat accepteert op dit moment geen koppelingsverzoeken (u heeft mogelijk te vaak een ongeldige pincode ingevoerd), probeer het later opnieuw.", "device_did_not_pair": "Er is geen poging gedaan om het koppelingsproces te voltooien vanaf het apparaat.", "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", + "reauth_successful": "Herauthenticatie geslaagd", "setup_failed": "Kan het apparaat niet instellen.", "unknown": "Onverwachte fout" }, @@ -19,7 +17,6 @@ "already_configured": "Apparaat is al geconfigureerd", "invalid_auth": "Ongeldige authenticatie", "no_devices_found": "Geen apparaten gevonden op het netwerk", - "no_usable_service": "Er is een apparaat gevonden, maar er kon geen manier worden gevonden om er verbinding mee te maken. Als u dit bericht blijft zien, probeert u het IP-adres in te voeren of uw Apple TV opnieuw op te starten.", "unknown": "Onverwachte fout" }, "flow_title": "{name} ( {type} )", @@ -34,7 +31,7 @@ }, "pair_with_pin": { "data": { - "pin": "PIN-code" + "pin": "Pincode" }, "description": "Koppelen is vereist voor het `{protocol}` protocol. Voer de PIN-code in die op het scherm wordt getoond. Beginnende nullen moeten worden weggelaten, d.w.z. voer 123 in als de getoonde code 0123 is.", "title": "Koppelen" @@ -73,6 +70,5 @@ "description": "Algemene apparaatinstellingen configureren" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/no.json b/homeassistant/components/apple_tv/translations/no.json index e364633597e..97f80c7dfe7 100644 --- a/homeassistant/components/apple_tv/translations/no.json +++ b/homeassistant/components/apple_tv/translations/no.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", - "already_configured_device": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "backoff": "Enheten godtar ikke sammenkoblingsforesp\u00f8rsler for \u00f8yeblikket (du kan ha skrevet inn en ugyldig PIN-kode for mange ganger), pr\u00f8v igjen senere.", "device_did_not_pair": "Ingen fors\u00f8k p\u00e5 \u00e5 fullf\u00f8re paringsprosessen ble gjort fra enheten", "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", @@ -19,7 +17,6 @@ "already_configured": "Enheten er allerede konfigurert", "invalid_auth": "Ugyldig godkjenning", "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", - "no_usable_service": "En enhet ble funnet, men kunne ikke identifisere noen m\u00e5te \u00e5 etablere en tilkobling til den. Hvis du fortsetter \u00e5 se denne meldingen, kan du pr\u00f8ve \u00e5 angi IP-adressen eller starte Apple TV p\u00e5 nytt.", "unknown": "Uventet feil" }, "flow_title": "{name} ( {type} )", @@ -73,6 +70,5 @@ "description": "Konfigurer generelle enhetsinnstillinger" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/pl.json b/homeassistant/components/apple_tv/translations/pl.json index 303de09c1dd..6c236c7b3ea 100644 --- a/homeassistant/components/apple_tv/translations/pl.json +++ b/homeassistant/components/apple_tv/translations/pl.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", - "already_configured_device": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", "backoff": "Urz\u0105dzenie w tej chwili nie akceptuje \u017c\u0105da\u0144 parowania (by\u0107 mo\u017ce zbyt wiele razy wpisa\u0142e\u015b nieprawid\u0142owy kod PIN), spr\u00f3buj ponownie p\u00f3\u017aniej.", "device_did_not_pair": "Nie podj\u0119to pr\u00f3by zako\u0144czenia procesu parowania z urz\u0105dzenia.", "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", @@ -19,7 +17,6 @@ "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "invalid_auth": "Niepoprawne uwierzytelnienie", "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci", - "no_usable_service": "Znaleziono urz\u0105dzenie, ale nie uda\u0142o si\u0119 zidentyfikowa\u0107 \u017cadnego sposobu na nawi\u0105zanie z nim po\u0142\u0105czenia. Je\u015bli nadal widzisz t\u0119 wiadomo\u015b\u0107, spr\u00f3buj poda\u0107 jego adres IP lub uruchom ponownie Apple TV.", "unknown": "Nieoczekiwany b\u0142\u0105d" }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "Skonfiguruj og\u00f3lne ustawienia urz\u0105dzenia" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/pt-BR.json b/homeassistant/components/apple_tv/translations/pt-BR.json index 539335c17d3..801f6df094b 100644 --- a/homeassistant/components/apple_tv/translations/pt-BR.json +++ b/homeassistant/components/apple_tv/translations/pt-BR.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "already_configured_device": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "backoff": "O dispositivo n\u00e3o aceita solicita\u00e7\u00f5es de emparelhamento neste momento (voc\u00ea pode ter digitado um c\u00f3digo PIN inv\u00e1lido muitas vezes), tente novamente mais tarde.", "device_did_not_pair": "Nenhuma tentativa de concluir o processo de emparelhamento foi feita a partir do dispositivo.", "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", @@ -19,7 +17,6 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "no_devices_found": "Nenhum dispositivo encontrado na rede", - "no_usable_service": "Um dispositivo foi encontrado, mas n\u00e3o foi poss\u00edvel identificar nenhuma maneira de estabelecer uma conex\u00e3o com ele. Se voc\u00ea continuar vendo esta mensagem, tente especificar o endere\u00e7o IP ou reiniciar a Apple TV.", "unknown": "Erro inesperado" }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "Definir as configura\u00e7\u00f5es gerais do dispositivo" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/pt.json b/homeassistant/components/apple_tv/translations/pt.json index 486ff0c51e4..deec60a19ea 100644 --- a/homeassistant/components/apple_tv/translations/pt.json +++ b/homeassistant/components/apple_tv/translations/pt.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_configured_device": "O dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O processo de configura\u00e7\u00e3o j\u00e1 est\u00e1 a decorrer", "no_devices_found": "Nenhum dispositivo encontrado na rede", "unknown": "Erro inesperado" @@ -10,7 +9,6 @@ "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "no_devices_found": "Nenhum dispositivo encontrado na rede", - "no_usable_service": "Foi encontrado um dispositivo, mas n\u00e3o foi poss\u00edvel identificar nenhuma forma de estabelecer uma liga\u00e7\u00e3o com ele. Se continuar a ver esta mensagem, tente especificar o endere\u00e7o IP ou reiniciar a sua Apple TV.", "unknown": "Erro inesperado" }, "flow_title": "Apple TV: {name}", @@ -56,6 +54,5 @@ "description": "Definir as configura\u00e7\u00f5es gerais do dispositivo" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/ru.json b/homeassistant/components/apple_tv/translations/ru.json index 88516d45af3..024103258a8 100644 --- a/homeassistant/components/apple_tv/translations/ru.json +++ b/homeassistant/components/apple_tv/translations/ru.json @@ -2,13 +2,11 @@ "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_device": "\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.", "backoff": "\u0412 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043d\u0430 \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 (\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0412\u044b \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0437 \u0432\u0432\u043e\u0434\u0438\u043b\u0438 \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 PIN-\u043a\u043e\u0434), \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", "device_did_not_pair": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043f\u044b\u0442\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f.", "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.", @@ -19,7 +17,6 @@ "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_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "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.", - "no_usable_service": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u043c\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443. \u0415\u0441\u043b\u0438 \u0412\u044b \u0443\u0436\u0435 \u0432\u0438\u0434\u0435\u043b\u0438 \u044d\u0442\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u0435\u0433\u043e.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/sl.json b/homeassistant/components/apple_tv/translations/sl.json index 997d60402ca..da9af2347c0 100644 --- a/homeassistant/components/apple_tv/translations/sl.json +++ b/homeassistant/components/apple_tv/translations/sl.json @@ -1,11 +1,9 @@ { "config": { "abort": { - "already_configured_device": "Naprava je \u017ee name\u0161\u010dena", "already_in_progress": "Name\u0161\u010danje se \u017ee izvaja", "backoff": "Naprav v tem trenutku ne sprejema zahtev za seznanitev (morda ste preve\u010dkrat vnesli napa\u010den PIN). Pokusitve znova kasneje.", "device_did_not_pair": "Iz te naprave ni bilo poskusov zaklju\u010diti seznanjanja.", - "invalid_config": "Namestitev te naprave ni bila zaklju\u010dena. Poskusite ponovno.", "no_devices_found": "Ni najdenih naprav v omre\u017eju", "unknown": "Nepri\u010dakovana napaka" }, @@ -13,7 +11,6 @@ "already_configured": "Naprava je \u017ee name\u0161\u010dena", "invalid_auth": "Napaka pri overjanju", "no_devices_found": "Ni najdenih naprav v omre\u017eju", - "no_usable_service": "Najdena je bila naprava, za katero ni znan na\u010din povezovanja. \u010ce boste \u0161e vedno videli to sporo\u010dilo, poskusite dolo\u010diti IP naslov ali pa ponovno za\u017eenite Apple TV.", "unknown": "Nepri\u010dakovana napaka" }, "flow_title": "Apple TV: {name}", @@ -59,6 +56,5 @@ "description": "Konfiguracija splo\u0161nih nastavitev naprave" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/tr.json b/homeassistant/components/apple_tv/translations/tr.json index cee9fcce81e..4919d48c15c 100644 --- a/homeassistant/components/apple_tv/translations/tr.json +++ b/homeassistant/components/apple_tv/translations/tr.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", - "already_configured_device": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "backoff": "Cihaz \u015fu anda e\u015fle\u015ftirme isteklerini kabul etmiyor (\u00e7ok say\u0131da ge\u00e7ersiz PIN kodu girmi\u015f olabilirsiniz), daha sonra tekrar deneyin.", "device_did_not_pair": "Cihazdan e\u015fle\u015ftirme i\u015flemini bitirmek i\u00e7in herhangi bir giri\u015fimde bulunulmad\u0131.", "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", @@ -19,7 +17,6 @@ "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "no_devices_found": "A\u011fda cihaz bulunamad\u0131", - "no_usable_service": "Bir ayg\u0131t bulundu, ancak ba\u011flant\u0131 kurman\u0131n herhangi bir yolunu tan\u0131mlayamad\u0131. Bu iletiyi g\u00f6rmeye devam ederseniz, IP adresini belirtmeye veya Apple TV'nizi yeniden ba\u015flatmaya \u00e7al\u0131\u015f\u0131n.", "unknown": "Beklenmeyen hata" }, "flow_title": "{name} ( {type} )", @@ -73,6 +70,5 @@ "description": "Genel cihaz ayarlar\u0131n\u0131 yap\u0131land\u0131r\u0131n" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/uk.json b/homeassistant/components/apple_tv/translations/uk.json index a1ae2259ada..984aeceaaeb 100644 --- a/homeassistant/components/apple_tv/translations/uk.json +++ b/homeassistant/components/apple_tv/translations/uk.json @@ -1,11 +1,9 @@ { "config": { "abort": { - "already_configured_device": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0442\u0440\u0438\u0432\u0430\u0454.", "backoff": "\u0412 \u0434\u0430\u043d\u0438\u0439 \u0447\u0430\u0441 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u043f\u0440\u0438\u0439\u043c\u0430\u0454 \u0437\u0430\u043f\u0438\u0442\u0438 \u043d\u0430 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438 (\u043c\u043e\u0436\u043b\u0438\u0432\u043e, \u0412\u0438 \u0437\u0430\u043d\u0430\u0434\u0442\u043e \u0431\u0430\u0433\u0430\u0442\u043e \u0440\u0430\u0437 \u0432\u0432\u043e\u0434\u0438\u043b\u0438 \u043d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 PIN-\u043a\u043e\u0434), \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437 \u043f\u0456\u0437\u043d\u0456\u0448\u0435.", "device_did_not_pair": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u043d\u0430\u043c\u0430\u0433\u0430\u0432\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043f\u0440\u043e\u0446\u0435\u0441 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438.", - "invalid_config": "\u041a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0439\u043e\u0433\u043e \u0449\u0435 \u0440\u0430\u0437.", "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" }, @@ -13,7 +11,6 @@ "already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "no_usable_service": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0441\u043f\u043e\u0441\u0456\u0431 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u042f\u043a\u0449\u043e \u0412\u0438 \u0432\u0436\u0435 \u0431\u0430\u0447\u0438\u043b\u0438 \u0446\u0435 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0432\u043a\u0430\u0437\u0430\u0442\u0438 IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0430\u0431\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0456\u0442\u044c \u0439\u043e\u0433\u043e.", "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" }, "flow_title": "Apple TV: {name}", @@ -59,6 +56,5 @@ "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/zh-Hans.json b/homeassistant/components/apple_tv/translations/zh-Hans.json index b08ccafc837..365bf6cf6c0 100644 --- a/homeassistant/components/apple_tv/translations/zh-Hans.json +++ b/homeassistant/components/apple_tv/translations/zh-Hans.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "\u8bbe\u5907\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86", - "already_configured_device": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e", "already_in_progress": "\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c\u4e2d", "backoff": "\u8bbe\u5907\u76ee\u524d\u6682\u4e0d\u63a5\u53d7\u914d\u5bf9\u8bf7\u6c42\uff0c\u53ef\u80fd\u662f\u56e0\u4e3a\u591a\u6b21 PIN \u7801\u8f93\u5165\u9519\u8bef\u3002\u8bf7\u7a0d\u540e\u91cd\u8bd5\u3002", "device_did_not_pair": "\u672a\u5c1d\u8bd5\u4ece\u8bbe\u5907\u5b8c\u6210\u914d\u5bf9\u8fc7\u7a0b\u3002", "device_not_found": "\u672a\u627e\u5230\u8bbe\u5907\uff0c\u8bf7\u5c1d\u8bd5\u91cd\u65b0\u6dfb\u52a0\u3002", "inconsistent_device": "\u641c\u7d22\u671f\u95f4\u672a\u53d1\u73b0\u914d\u7f6e\u8bbe\u5907\u6240\u5fc5\u9700\u7684\u534f\u8bae\u3002\u8fd9\u901a\u5e38\u662f\u56e0\u4e3a mDNS \u534f\u8bae\uff08zeroconf\uff09\u5b58\u5728\u95ee\u9898\u3002\u8bf7\u7a0d\u540e\u518d\u91cd\u65b0\u5c1d\u8bd5\u6dfb\u52a0\u8bbe\u5907\u3002", - "invalid_config": "\u6b64\u8bbe\u5907\u7684\u914d\u7f6e\u4fe1\u606f\u4e0d\u5b8c\u6574\u3002\u8bf7\u5c1d\u8bd5\u91cd\u65b0\u6dfb\u52a0\u3002", "no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230\u8bbe\u5907", "reauth_successful": "\u91cd\u65b0\u8ba4\u8bc1\u6210\u529f", "setup_failed": "\u8bbe\u7f6e\u8bbe\u5907\u5931\u8d25\u3002", @@ -18,7 +16,6 @@ "already_configured": "\u8bbe\u5907\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86", "invalid_auth": "\u8eab\u4efd\u8ba4\u8bc1\u65e0\u6548", "no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230\u8bbe\u5907", - "no_usable_service": "\u5df2\u627e\u5230\u8bbe\u5907\uff0c\u4f46\u672a\u8bc6\u522b\u51fa\u4e0e\u5176\u5efa\u7acb\u8fde\u63a5\u7684\u65b9\u5f0f\u3002\u5982\u679c\u6b64\u95ee\u9898\u6301\u7eed\u5b58\u5728\uff0c\u8bf7\u5c1d\u8bd5\u6307\u5b9a\u5176 IP \u5730\u5740\uff0c\u6216\u91cd\u65b0\u542f\u52a8\u8bbe\u5907\u3002", "unknown": "\u975e\u9884\u671f\u7684\u9519\u8bef" }, "flow_title": "{name} ({type})", @@ -72,6 +69,5 @@ "description": "\u914d\u7f6e\u8bbe\u5907\u901a\u7528\u8bbe\u7f6e" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/zh-Hant.json b/homeassistant/components/apple_tv/translations/zh-Hant.json index b4e5108d474..be2bfe08fdd 100644 --- a/homeassistant/components/apple_tv/translations/zh-Hant.json +++ b/homeassistant/components/apple_tv/translations/zh-Hant.json @@ -2,13 +2,11 @@ "config": { "abort": { "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_configured_device": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "backoff": "\u88dd\u7f6e\u4e0d\u63a5\u53d7\u6b64\u6b21\u914d\u5c0d\u8acb\u6c42\uff08\u53ef\u80fd\u8f38\u5165\u592a\u591a\u6b21\u7121\u6548\u7684 PIN \u78bc\uff09\uff0c\u8acb\u7a0d\u5f8c\u518d\u8a66\u3002", "device_did_not_pair": "\u88dd\u7f6e\u6c92\u6709\u5617\u8a66\u914d\u5c0d\u5b8c\u6210\u904e\u7a0b\u3002", "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", @@ -19,7 +17,6 @@ "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "no_usable_service": "\u627e\u5230\u7684\u88dd\u7f6e\u7121\u6cd5\u8b58\u5225\u4ee5\u9032\u884c\u9023\u7dda\u3002\u5047\u5982\u6b64\u8a0a\u606f\u91cd\u8907\u767c\u751f\u3002\u8acb\u8a66\u8457\u6307\u5b9a\u7279\u5b9a IP \u4f4d\u5740\u6216\u91cd\u555f Apple TV\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "flow_title": "{name} ({type})", @@ -73,6 +70,5 @@ "description": "\u8a2d\u5b9a\u4e00\u822c\u88dd\u7f6e\u8a2d\u5b9a" } } - }, - "title": "Apple TV" + } } \ No newline at end of file diff --git a/homeassistant/components/application_credentials/__init__.py b/homeassistant/components/application_credentials/__init__.py new file mode 100644 index 00000000000..1a128c5c378 --- /dev/null +++ b/homeassistant/components/application_credentials/__init__.py @@ -0,0 +1,295 @@ +"""The Application Credentials integration. + +This integration provides APIs for managing local OAuth credentials on behalf +of other integrations. Integrations register an authorization server, and then +the APIs are used to add one or more client credentials. Integrations may also +provide credentials from yaml for backwards compatibility. +""" +from __future__ import annotations + +from dataclasses import dataclass +import logging +from typing import Any, Protocol + +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.components.websocket_api.connection import ActiveConnection +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_DOMAIN, + CONF_ID, + CONF_NAME, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import collection, config_entry_oauth2_flow +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.storage import Store +from homeassistant.helpers.typing import ConfigType +from homeassistant.loader import ( + IntegrationNotFound, + async_get_application_credentials, + async_get_integration, +) +from homeassistant.util import slugify + +__all__ = ["ClientCredential", "AuthorizationServer", "async_import_client_credential"] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "application_credentials" + +STORAGE_KEY = DOMAIN +STORAGE_VERSION = 1 +DATA_STORAGE = "storage" +CONF_AUTH_DOMAIN = "auth_domain" +DEFAULT_IMPORT_NAME = "Import from configuration.yaml" + +CREATE_FIELDS = { + vol.Required(CONF_DOMAIN): cv.string, + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + vol.Optional(CONF_AUTH_DOMAIN): cv.string, + vol.Optional(CONF_NAME): cv.string, +} +UPDATE_FIELDS: dict = {} # Not supported + + +@dataclass +class ClientCredential: + """Represent an OAuth client credential.""" + + client_id: str + client_secret: str + name: str | None = None + + +@dataclass +class AuthorizationServer: + """Represent an OAuth2 Authorization Server.""" + + authorize_url: str + token_url: str + + +class ApplicationCredentialsStorageCollection(collection.StorageCollection): + """Application credential collection stored in storage.""" + + CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) + + async def _process_create_data(self, data: dict[str, str]) -> dict[str, str]: + """Validate the config is valid.""" + result = self.CREATE_SCHEMA(data) + domain = result[CONF_DOMAIN] + if not await _get_platform(self.hass, domain): + raise ValueError(f"No application_credentials platform for {domain}") + return result + + @callback + def _get_suggested_id(self, info: dict[str, str]) -> str: + """Suggest an ID based on the config.""" + return f"{info[CONF_DOMAIN]}.{info[CONF_CLIENT_ID]}" + + async def _update_data( + self, data: dict[str, str], update_data: dict[str, str] + ) -> dict[str, str]: + """Return a new updated data object.""" + raise ValueError("Updates not supported") + + async def async_delete_item(self, item_id: str) -> None: + """Delete item, verifying credential is not in use.""" + if item_id not in self.data: + raise collection.ItemNotFound(item_id) + + # Cannot delete a credential currently in use by a ConfigEntry + current = self.data[item_id] + entries = self.hass.config_entries.async_entries(current[CONF_DOMAIN]) + for entry in entries: + if entry.data.get("auth_implementation") == item_id: + raise HomeAssistantError( + f"Cannot delete credential in use by integration {entry.domain}" + ) + + await super().async_delete_item(item_id) + + async def async_import_item(self, info: dict[str, str]) -> None: + """Import an yaml credential if it does not already exist.""" + suggested_id = self._get_suggested_id(info) + if self.id_manager.has_id(slugify(suggested_id)): + return + await self.async_create_item(info) + + def async_client_credentials(self, domain: str) -> dict[str, ClientCredential]: + """Return ClientCredentials in storage for the specified domain.""" + credentials = {} + for item in self.async_items(): + if item[CONF_DOMAIN] != domain: + continue + auth_domain = ( + item[CONF_AUTH_DOMAIN] if CONF_AUTH_DOMAIN in item else item[CONF_ID] + ) + credentials[auth_domain] = ClientCredential( + client_id=item[CONF_CLIENT_ID], + client_secret=item[CONF_CLIENT_SECRET], + name=item.get(CONF_NAME), + ) + return credentials + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up Application Credentials.""" + hass.data[DOMAIN] = {} + + id_manager = collection.IDManager() + storage_collection = ApplicationCredentialsStorageCollection( + Store(hass, STORAGE_VERSION, STORAGE_KEY), + logging.getLogger(f"{__name__}.storage_collection"), + id_manager, + ) + await storage_collection.async_load() + hass.data[DOMAIN][DATA_STORAGE] = storage_collection + + collection.StorageCollectionWebsocket( + storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS + ).async_setup(hass) + + websocket_api.async_register_command(hass, handle_integration_list) + + config_entry_oauth2_flow.async_add_implementation_provider( + hass, DOMAIN, _async_provide_implementation + ) + + return True + + +async def async_import_client_credential( + hass: HomeAssistant, + domain: str, + credential: ClientCredential, + auth_domain: str = None, +) -> None: + """Import an existing credential from configuration.yaml.""" + if DOMAIN not in hass.data: + raise ValueError("Integration 'application_credentials' not setup") + storage_collection = hass.data[DOMAIN][DATA_STORAGE] + item = { + CONF_DOMAIN: domain, + CONF_CLIENT_ID: credential.client_id, + CONF_CLIENT_SECRET: credential.client_secret, + CONF_AUTH_DOMAIN: auth_domain if auth_domain else domain, + } + item[CONF_NAME] = credential.name if credential.name else DEFAULT_IMPORT_NAME + await storage_collection.async_import_item(item) + + +class AuthImplementation(config_entry_oauth2_flow.LocalOAuth2Implementation): + """Application Credentials local oauth2 implementation.""" + + def __init__( + self, + hass: HomeAssistant, + auth_domain: str, + credential: ClientCredential, + authorization_server: AuthorizationServer, + ) -> None: + """Initialize AuthImplementation.""" + super().__init__( + hass, + auth_domain, + credential.client_id, + credential.client_secret, + authorization_server.authorize_url, + authorization_server.token_url, + ) + self._name = credential.name + + @property + def name(self) -> str: + """Name of the implementation.""" + return self._name or self.client_id + + +async def _async_provide_implementation( + hass: HomeAssistant, domain: str +) -> list[config_entry_oauth2_flow.AbstractOAuth2Implementation]: + """Return registered OAuth implementations.""" + + platform = await _get_platform(hass, domain) + if not platform: + return [] + + storage_collection = hass.data[DOMAIN][DATA_STORAGE] + credentials = storage_collection.async_client_credentials(domain) + if hasattr(platform, "async_get_auth_implementation"): + return [ + await platform.async_get_auth_implementation(hass, auth_domain, credential) + for auth_domain, credential in credentials.items() + ] + authorization_server = await platform.async_get_authorization_server(hass) + return [ + AuthImplementation(hass, auth_domain, credential, authorization_server) + for auth_domain, credential in credentials.items() + ] + + +class ApplicationCredentialsProtocol(Protocol): + """Define the format that application_credentials platforms may have. + + Most platforms typically just implement async_get_authorization_server, and + the default oauth implementation will be used. Otherwise a platform may + implement async_get_auth_implementation to give their use a custom + AbstractOAuth2Implementation. + """ + + async def async_get_authorization_server( + self, hass: HomeAssistant + ) -> AuthorizationServer: + """Return authorization server, for the default auth implementation.""" + + async def async_get_auth_implementation( + self, hass: HomeAssistant, auth_domain: str, credential: ClientCredential + ) -> config_entry_oauth2_flow.AbstractOAuth2Implementation: + """Return a custom auth implementation.""" + + +async def _get_platform( + hass: HomeAssistant, integration_domain: str +) -> ApplicationCredentialsProtocol | None: + """Register an application_credentials platform.""" + try: + integration = await async_get_integration(hass, integration_domain) + except IntegrationNotFound as err: + _LOGGER.debug("Integration '%s' does not exist: %s", integration_domain, err) + return None + try: + platform = integration.get_platform("application_credentials") + except ImportError as err: + _LOGGER.debug( + "Integration '%s' does not provide application_credentials: %s", + integration_domain, + err, + ) + return None + if not hasattr(platform, "async_get_authorization_server") and not hasattr( + platform, "async_get_auth_implementation" + ): + raise ValueError( + f"Integration '{integration_domain}' platform {DOMAIN} did not " + f"implement 'async_get_authorization_server' or 'async_get_auth_implementation'" + ) + return platform + + +@websocket_api.websocket_command( + {vol.Required("type"): "application_credentials/config"} +) +@websocket_api.async_response +async def handle_integration_list( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Handle integrations command.""" + connection.send_result( + msg["id"], {"domains": await async_get_application_credentials(hass)} + ) diff --git a/homeassistant/components/application_credentials/manifest.json b/homeassistant/components/application_credentials/manifest.json new file mode 100644 index 00000000000..9a8abc16c36 --- /dev/null +++ b/homeassistant/components/application_credentials/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "application_credentials", + "name": "Application Credentials", + "config_flow": false, + "documentation": "https://www.home-assistant.io/integrations/application_credentials", + "dependencies": ["auth", "websocket_api"], + "codeowners": ["@home-assistant/core"], + "quality_scale": "internal" +} diff --git a/homeassistant/components/application_credentials/strings.json b/homeassistant/components/application_credentials/strings.json new file mode 100644 index 00000000000..48d74bc75e4 --- /dev/null +++ b/homeassistant/components/application_credentials/strings.json @@ -0,0 +1,3 @@ +{ + "title": "Application Credentials" +} diff --git a/homeassistant/components/application_credentials/translations/ca.json b/homeassistant/components/application_credentials/translations/ca.json new file mode 100644 index 00000000000..073fb242e72 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/ca.json @@ -0,0 +1,3 @@ +{ + "title": "Credencials de l'aplicaci\u00f3" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/de.json b/homeassistant/components/application_credentials/translations/de.json new file mode 100644 index 00000000000..95198ef66cc --- /dev/null +++ b/homeassistant/components/application_credentials/translations/de.json @@ -0,0 +1,3 @@ +{ + "title": "Anmeldeinformationen" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/el.json b/homeassistant/components/application_credentials/translations/el.json new file mode 100644 index 00000000000..79c30a8b969 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/el.json @@ -0,0 +1,3 @@ +{ + "title": "\u0394\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/en.json b/homeassistant/components/application_credentials/translations/en.json new file mode 100644 index 00000000000..2a8755ad9d8 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/en.json @@ -0,0 +1,3 @@ +{ + "title": "Application Credentials" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/es.json b/homeassistant/components/application_credentials/translations/es.json new file mode 100644 index 00000000000..bf7462ead66 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/es.json @@ -0,0 +1,3 @@ +{ + "title": "Credenciales de la aplicaci\u00f3n" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/et.json b/homeassistant/components/application_credentials/translations/et.json new file mode 100644 index 00000000000..af619b0fb8d --- /dev/null +++ b/homeassistant/components/application_credentials/translations/et.json @@ -0,0 +1,3 @@ +{ + "title": "Rakenduse mandaadid" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/fr.json b/homeassistant/components/application_credentials/translations/fr.json new file mode 100644 index 00000000000..859fa965023 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/fr.json @@ -0,0 +1,3 @@ +{ + "title": "Informations d'identification de l'application" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/hu.json b/homeassistant/components/application_credentials/translations/hu.json new file mode 100644 index 00000000000..d24ea488f08 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/hu.json @@ -0,0 +1,3 @@ +{ + "title": "Alkalmaz\u00e1s hiteles\u00edt\u0151 adatai" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/id.json b/homeassistant/components/application_credentials/translations/id.json new file mode 100644 index 00000000000..b4ce4f295fd --- /dev/null +++ b/homeassistant/components/application_credentials/translations/id.json @@ -0,0 +1,3 @@ +{ + "title": "Kredensial Aplikasi" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/it.json b/homeassistant/components/application_credentials/translations/it.json new file mode 100644 index 00000000000..144f96c6342 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/it.json @@ -0,0 +1,3 @@ +{ + "title": "Credenziali dell'applicazione" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/ja.json b/homeassistant/components/application_credentials/translations/ja.json new file mode 100644 index 00000000000..ee195408093 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/ja.json @@ -0,0 +1,3 @@ +{ + "title": "\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u8a8d\u8a3c" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/ko.json b/homeassistant/components/application_credentials/translations/ko.json new file mode 100644 index 00000000000..3beeea8e2e4 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/ko.json @@ -0,0 +1,3 @@ +{ + "title": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \uc790\uaca9 \uc99d\uba85" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/nl.json b/homeassistant/components/application_credentials/translations/nl.json new file mode 100644 index 00000000000..3638594ac2e --- /dev/null +++ b/homeassistant/components/application_credentials/translations/nl.json @@ -0,0 +1,3 @@ +{ + "title": "Applicatie inloggegevens" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/no.json b/homeassistant/components/application_credentials/translations/no.json new file mode 100644 index 00000000000..5e55d3d6538 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/no.json @@ -0,0 +1,3 @@ +{ + "title": "S\u00f8knadslegitimasjon" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/pl.json b/homeassistant/components/application_credentials/translations/pl.json new file mode 100644 index 00000000000..93c2ddb7534 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/pl.json @@ -0,0 +1,3 @@ +{ + "title": "Po\u015bwiadczenia aplikacji" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/pt-BR.json b/homeassistant/components/application_credentials/translations/pt-BR.json new file mode 100644 index 00000000000..cc21106d6c5 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/pt-BR.json @@ -0,0 +1,3 @@ +{ + "title": "Credenciais do aplicativo" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/ru.json b/homeassistant/components/application_credentials/translations/ru.json new file mode 100644 index 00000000000..e0edbab4c74 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/ru.json @@ -0,0 +1,3 @@ +{ + "title": "\u0423\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/sv.json b/homeassistant/components/application_credentials/translations/sv.json new file mode 100644 index 00000000000..1127f807c9f --- /dev/null +++ b/homeassistant/components/application_credentials/translations/sv.json @@ -0,0 +1,3 @@ +{ + "title": "Autentiseringsuppgifter f\u00f6r applikationer" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/tr.json b/homeassistant/components/application_credentials/translations/tr.json new file mode 100644 index 00000000000..70da28ed814 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/tr.json @@ -0,0 +1,3 @@ +{ + "title": "Uygulama Kimlik Bilgileri" +} \ No newline at end of file diff --git a/homeassistant/components/application_credentials/translations/zh-Hant.json b/homeassistant/components/application_credentials/translations/zh-Hant.json new file mode 100644 index 00000000000..88ad70a7d10 --- /dev/null +++ b/homeassistant/components/application_credentials/translations/zh-Hant.json @@ -0,0 +1,3 @@ +{ + "title": "\u61c9\u7528\u6191\u8b49" +} \ No newline at end of file diff --git a/homeassistant/components/apprise/manifest.json b/homeassistant/components/apprise/manifest.json index 21e2ed7c94d..b4422a49ef4 100644 --- a/homeassistant/components/apprise/manifest.json +++ b/homeassistant/components/apprise/manifest.json @@ -2,7 +2,7 @@ "domain": "apprise", "name": "Apprise", "documentation": "https://www.home-assistant.io/integrations/apprise", - "requirements": ["apprise==0.9.7"], + "requirements": ["apprise==0.9.8.3"], "codeowners": ["@caronc"], "iot_class": "cloud_push", "loggers": ["apprise"] diff --git a/homeassistant/components/aqualogic/__init__.py b/homeassistant/components/aqualogic/__init__.py index 0c4ecaa1683..94941b30713 100644 --- a/homeassistant/components/aqualogic/__init__.py +++ b/homeassistant/components/aqualogic/__init__.py @@ -15,6 +15,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -70,7 +71,7 @@ class AquaLogicProcessor(threading.Thread): def data_changed(self, panel): """Aqualogic data changed callback.""" - self._hass.helpers.dispatcher.dispatcher_send(UPDATE_TOPIC) + dispatcher_send(self._hass, UPDATE_TOPIC) def run(self): """Event thread.""" diff --git a/homeassistant/components/arcam_fmj/__init__.py b/homeassistant/components/arcam_fmj/__init__.py index ee86fc8c4b5..a82e0239842 100644 --- a/homeassistant/components/arcam_fmj/__init__.py +++ b/homeassistant/components/arcam_fmj/__init__.py @@ -11,6 +11,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import ConfigType from .const import ( @@ -81,7 +82,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _run_client(hass, client, interval): def _listen(_): - hass.helpers.dispatcher.async_dispatcher_send(SIGNAL_CLIENT_DATA, client.host) + async_dispatcher_send(hass, SIGNAL_CLIENT_DATA, client.host) while True: try: @@ -89,9 +90,7 @@ async def _run_client(hass, client, interval): await client.start() _LOGGER.debug("Client connected %s", client.host) - hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_CLIENT_STARTED, client.host - ) + async_dispatcher_send(hass, SIGNAL_CLIENT_STARTED, client.host) try: with client.listen(_listen): @@ -100,9 +99,7 @@ async def _run_client(hass, client, interval): await client.stop() _LOGGER.debug("Client disconnected %s", client.host) - hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_CLIENT_STOPPED, client.host - ) + async_dispatcher_send(hass, SIGNAL_CLIENT_STOPPED, client.host) except ConnectionFailed: await asyncio.sleep(interval) diff --git a/homeassistant/components/arcam_fmj/device_trigger.py b/homeassistant/components/arcam_fmj/device_trigger.py index 2b1a3bf3a19..593250e4983 100644 --- a/homeassistant/components/arcam_fmj/device_trigger.py +++ b/homeassistant/components/arcam_fmj/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for Arcam FMJ Receiver control.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -35,9 +33,9 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Arcam FMJ Receiver control devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/arcam_fmj/media_player.py b/homeassistant/components/arcam_fmj/media_player.py index 245e309af9c..731eb0b0352 100644 --- a/homeassistant/components/arcam_fmj/media_player.py +++ b/homeassistant/components/arcam_fmj/media_player.py @@ -18,6 +18,7 @@ from homeassistant.components.media_player.errors import BrowseError from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -127,21 +128,15 @@ class ArcamFmj(MediaPlayerEntity): self.async_schedule_update_ha_state(force_refresh=True) self.async_on_remove( - self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_CLIENT_DATA, _data - ) + async_dispatcher_connect(self.hass, SIGNAL_CLIENT_DATA, _data) ) self.async_on_remove( - self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_CLIENT_STARTED, _started - ) + async_dispatcher_connect(self.hass, SIGNAL_CLIENT_STARTED, _started) ) self.async_on_remove( - self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_CLIENT_STOPPED, _stopped - ) + async_dispatcher_connect(self.hass, SIGNAL_CLIENT_STOPPED, _stopped) ) async def async_update(self): diff --git a/homeassistant/components/arcam_fmj/translations/nl.json b/homeassistant/components/arcam_fmj/translations/nl.json index 45a7be867b9..76d3c93739a 100644 --- a/homeassistant/components/arcam_fmj/translations/nl.json +++ b/homeassistant/components/arcam_fmj/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken" }, "error": { diff --git a/homeassistant/components/aseko_pool_live/translations/es.json b/homeassistant/components/aseko_pool_live/translations/es.json index 419359a942c..79c199d012d 100644 --- a/homeassistant/components/aseko_pool_live/translations/es.json +++ b/homeassistant/components/aseko_pool_live/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada" + "already_configured": "La cuenta ya est\u00e1 configurada" }, "error": { "cannot_connect": "No se pudo conectar", diff --git a/homeassistant/components/asuswrt/config_flow.py b/homeassistant/components/asuswrt/config_flow.py index ab62b879f75..ec5cccb9a71 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -25,6 +25,7 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.device_registry import format_mac from .const import ( CONF_DNSMASQ, @@ -41,11 +42,13 @@ from .const import ( PROTOCOL_SSH, PROTOCOL_TELNET, ) -from .router import get_api +from .router import get_api, get_nvram_info + +LABEL_MAC = "LABEL_MAC" RESULT_CONN_ERROR = "cannot_connect" -RESULT_UNKNOWN = "unknown" RESULT_SUCCESS = "success" +RESULT_UNKNOWN = "unknown" _LOGGER = logging.getLogger(__name__) @@ -107,7 +110,9 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN): ) @staticmethod - async def _async_check_connection(user_input: dict[str, Any]) -> str: + async def _async_check_connection( + user_input: dict[str, Any] + ) -> tuple[str, str | None]: """Attempt to connect the AsusWrt router.""" host: str = user_input[CONF_HOST] @@ -117,29 +122,37 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN): except OSError: _LOGGER.error("Error connecting to the AsusWrt router at %s", host) - return RESULT_CONN_ERROR + return RESULT_CONN_ERROR, None except Exception: # pylint: disable=broad-except _LOGGER.exception( "Unknown error connecting with AsusWrt router at %s", host ) - return RESULT_UNKNOWN + return RESULT_UNKNOWN, None if not api.is_connected: _LOGGER.error("Error connecting to the AsusWrt router at %s", host) - return RESULT_CONN_ERROR + return RESULT_CONN_ERROR, None + label_mac = await get_nvram_info(api, LABEL_MAC) conf_protocol = user_input[CONF_PROTOCOL] if conf_protocol == PROTOCOL_TELNET: api.connection.disconnect() - return RESULT_SUCCESS + + unique_id = None + if label_mac and "label_mac" in label_mac: + unique_id = format_mac(label_mac["label_mac"]) + return RESULT_SUCCESS, unique_id 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") + + # if there's one entry without unique ID, we abort config flow + for unique_id in self._async_current_ids(): + if unique_id is None: + return self.async_abort(reason="no_unique_id") if user_input is None: return self._show_setup_form(user_input) @@ -166,17 +179,27 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN): errors["base"] = "invalid_host" if not errors: - result = await self._async_check_connection(user_input) - if result != RESULT_SUCCESS: - errors["base"] = result + result, unique_id = await self._async_check_connection(user_input) + if result == RESULT_SUCCESS: + if unique_id: + await self.async_set_unique_id(unique_id) + # we allow configure a single instance without unique id + elif self._async_current_entries(): + return self.async_abort(reason="invalid_unique_id") + else: + _LOGGER.warning( + "This device does not provide a valid Unique ID." + " Configuration of multiple instance will not be possible" + ) - if errors: - return self._show_setup_form(user_input, errors) + return self.async_create_entry( + title=host, + data=user_input, + ) - return self.async_create_entry( - title=host, - data=user_input, - ) + errors["base"] = result + + return self._show_setup_form(user_input, errors) @staticmethod @callback diff --git a/homeassistant/components/asuswrt/diagnostics.py b/homeassistant/components/asuswrt/diagnostics.py index 16f5468f87d..61de4c866db 100644 --- a/homeassistant/components/asuswrt/diagnostics.py +++ b/homeassistant/components/asuswrt/diagnostics.py @@ -11,6 +11,7 @@ from homeassistant.const import ( ATTR_CONNECTIONS, ATTR_IDENTIFIERS, CONF_PASSWORD, + CONF_UNIQUE_ID, CONF_USERNAME, ) from homeassistant.core import HomeAssistant @@ -19,7 +20,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from .const import DATA_ASUSWRT, DOMAIN from .router import AsusWrtRouter -TO_REDACT = {CONF_PASSWORD, CONF_USERNAME} +TO_REDACT = {CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME} TO_REDACT_DEV = {ATTR_CONNECTIONS, ATTR_IDENTIFIERS} diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 2484b9880c3..f68c77a2a66 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -51,6 +51,7 @@ from .const import ( ) CONF_REQ_RELOAD = [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP] +DEFAULT_NAME = "Asuswrt" KEY_COORDINATOR = "coordinator" KEY_SENSORS = "sensors" @@ -260,10 +261,10 @@ class AsusWrtRouter: raise ConfigEntryNotReady # System - model = await _get_nvram_info(self._api, "MODEL") + model = await get_nvram_info(self._api, "MODEL") if model and "model" in model: self._model = model["model"] - firmware = await _get_nvram_info(self._api, "FIRMWARE") + firmware = await get_nvram_info(self._api, "FIRMWARE") if firmware and "firmver" in firmware and "buildno" in firmware: self._sw_v = f"{firmware['firmver']} (build {firmware['buildno']})" @@ -441,7 +442,7 @@ class AsusWrtRouter: def device_info(self) -> DeviceInfo: """Return the device information.""" return DeviceInfo( - identifiers={(DOMAIN, "AsusWRT")}, + identifiers={(DOMAIN, self.unique_id or "AsusWRT")}, name=self._host, model=self._model, manufacturer="Asus", @@ -464,6 +465,16 @@ class AsusWrtRouter: """Return router hostname.""" return self._host + @property + def unique_id(self) -> str | None: + """Return router unique id.""" + return self._entry.unique_id + + @property + def name(self) -> str: + """Return router name.""" + return self._host if self.unique_id else DEFAULT_NAME + @property def devices(self) -> dict[str, AsusWrtDevInfo]: """Return devices.""" @@ -475,7 +486,7 @@ class AsusWrtRouter: return self._sensors_coordinator -async def _get_nvram_info(api: AsusWrt, info_type: str) -> dict[str, Any]: +async def get_nvram_info(api: AsusWrt, info_type: str) -> dict[str, Any]: """Get AsusWrt router info from nvram.""" info = {} try: diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 5c46b3e693c..e66874e4137 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -43,7 +43,6 @@ class AsusWrtSensorEntityDescription(SensorEntityDescription): precision: int = 2 -DEFAULT_PREFIX = "Asuswrt" UNIT_DEVICES = "Devices" CONNECTION_SENSORS: tuple[AsusWrtSensorEntityDescription, ...] = ( @@ -190,8 +189,11 @@ class AsusWrtSensor(CoordinatorEntity, SensorEntity): super().__init__(coordinator) self.entity_description: AsusWrtSensorEntityDescription = description - self._attr_name = f"{DEFAULT_PREFIX} {description.name}" - self._attr_unique_id = f"{DOMAIN} {self.name}" + self._attr_name = f"{router.name} {description.name}" + if router.unique_id: + self._attr_unique_id = f"{DOMAIN} {router.unique_id} {description.name}" + else: + self._attr_unique_id = f"{DOMAIN} {self.name}" self._attr_device_info = router.device_info self._attr_extra_state_attributes = {"hostname": router.host} diff --git a/homeassistant/components/asuswrt/strings.json b/homeassistant/components/asuswrt/strings.json index 56f3c659c0b..bd0c706e74a 100644 --- a/homeassistant/components/asuswrt/strings.json +++ b/homeassistant/components/asuswrt/strings.json @@ -25,7 +25,8 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + "invalid_unique_id": "Impossible to determine a valid unique id for the device", + "no_unique_id": "A device without a valid unique id is already configured. Configuration of multiple instance is not possible" } }, "options": { diff --git a/homeassistant/components/asuswrt/translations/ca.json b/homeassistant/components/asuswrt/translations/ca.json index 9149cd1ae7a..8cded456ee9 100644 --- a/homeassistant/components/asuswrt/translations/ca.json +++ b/homeassistant/components/asuswrt/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + "invalid_unique_id": "No s'ha pogut determinar cap identificador \u00fanic v\u00e0lid del dispositiu", + "no_unique_id": "Ja s'ha configurat un dispositiu sense un identificador \u00fanic v\u00e0lid. La configuraci\u00f3 de diverses inst\u00e0ncies no \u00e9s possible" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", diff --git a/homeassistant/components/asuswrt/translations/cs.json b/homeassistant/components/asuswrt/translations/cs.json index d9766e9a6d0..26358d8c4bd 100644 --- a/homeassistant/components/asuswrt/translations/cs.json +++ b/homeassistant/components/asuswrt/translations/cs.json @@ -1,8 +1,5 @@ { "config": { - "abort": { - "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." - }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "invalid_host": "Neplatn\u00fd hostitel nebo IP adresa", diff --git a/homeassistant/components/asuswrt/translations/de.json b/homeassistant/components/asuswrt/translations/de.json index af4beb984a4..be17c242d58 100644 --- a/homeassistant/components/asuswrt/translations/de.json +++ b/homeassistant/components/asuswrt/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + "invalid_unique_id": "Unm\u00f6glich, eine g\u00fcltige eindeutige Kennung f\u00fcr das Ger\u00e4t zu ermitteln", + "no_unique_id": "Ein Ger\u00e4t ohne g\u00fcltige, eindeutige ID ist bereits konfiguriert. Die Konfiguration mehrerer Instanzen ist nicht m\u00f6glich" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", diff --git a/homeassistant/components/asuswrt/translations/el.json b/homeassistant/components/asuswrt/translations/el.json index ea926f10bd2..6a380b23152 100644 --- a/homeassistant/components/asuswrt/translations/el.json +++ b/homeassistant/components/asuswrt/translations/el.json @@ -1,7 +1,8 @@ { "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." + "invalid_unique_id": "\u0391\u03b4\u03cd\u03bd\u03b1\u03c4\u03bf\u03c2 \u03bf \u03c0\u03c1\u03bf\u03c3\u03b4\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b5\u03bd\u03cc\u03c2 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c5 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03bf\u03cd \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", + "no_unique_id": "\u039c\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c7\u03c9\u03c1\u03af\u03c2 \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 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af. \u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ce\u03bd \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ce\u03bd" }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", diff --git a/homeassistant/components/asuswrt/translations/en.json b/homeassistant/components/asuswrt/translations/en.json index 4bef898ff35..f93eae80e6d 100644 --- a/homeassistant/components/asuswrt/translations/en.json +++ b/homeassistant/components/asuswrt/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Already configured. Only a single configuration possible." + "invalid_unique_id": "Impossible to determine a valid unique id for the device", + "no_unique_id": "A device without a valid unique id is already configured. Configuration of multiple instance is not possible" }, "error": { "cannot_connect": "Failed to connect", diff --git a/homeassistant/components/asuswrt/translations/es.json b/homeassistant/components/asuswrt/translations/es.json index c5792babf00..9a2e0485aa7 100644 --- a/homeassistant/components/asuswrt/translations/es.json +++ b/homeassistant/components/asuswrt/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Ya configurado. Solo es posible una \u00fanica configuraci\u00f3n." + "invalid_unique_id": "No se pudo determinar ning\u00fan identificador \u00fanico v\u00e1lido del dispositivo" }, "error": { "cannot_connect": "No se pudo conectar", @@ -18,7 +18,7 @@ "mode": "Modo", "name": "Nombre", "password": "Contrase\u00f1a", - "port": "Puerto", + "port": "Puerto (d\u00e9jalo vac\u00edo por el predeterminado del protocolo)", "protocol": "Protocolo de comunicaci\u00f3n a utilizar", "ssh_key": "Ruta de acceso a su archivo de clave SSH (en lugar de la contrase\u00f1a)", "username": "Nombre de usuario" diff --git a/homeassistant/components/asuswrt/translations/et.json b/homeassistant/components/asuswrt/translations/et.json index 7b7a0869061..e76f5006de7 100644 --- a/homeassistant/components/asuswrt/translations/et.json +++ b/homeassistant/components/asuswrt/translations/et.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + "invalid_unique_id": "Seadme kehtivat kordumatut ID-d on v\u00f5imatu m\u00e4\u00e4rata", + "no_unique_id": "Seade, millel puudub kehtiv kordumatu ID, on juba seadistatud. Mitme eksemplari seadistamine pole v\u00f5imalik" }, "error": { "cannot_connect": "\u00dchendamine nurjus", diff --git a/homeassistant/components/asuswrt/translations/fr.json b/homeassistant/components/asuswrt/translations/fr.json index 5c8882d5813..7ef4eafc6b3 100644 --- a/homeassistant/components/asuswrt/translations/fr.json +++ b/homeassistant/components/asuswrt/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + "invalid_unique_id": "Impossible de d\u00e9terminer un identifiant unique valide pour l'appareil", + "no_unique_id": "Un appareil sans identifiant unique valide est d\u00e9j\u00e0 configur\u00e9. La configuration de plusieurs instances n'est pas possible" }, "error": { "cannot_connect": "\u00c9chec de connexion", diff --git a/homeassistant/components/asuswrt/translations/he.json b/homeassistant/components/asuswrt/translations/he.json index 867cc6e4f5c..fdc894892fa 100644 --- a/homeassistant/components/asuswrt/translations/he.json +++ b/homeassistant/components/asuswrt/translations/he.json @@ -1,8 +1,5 @@ { "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." - }, "error": { "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", "invalid_host": "\u05e9\u05dd \u05de\u05d0\u05e8\u05d7 \u05d0\u05d5 \u05db\u05ea\u05d5\u05d1\u05ea IP \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9\u05d9\u05dd", diff --git a/homeassistant/components/asuswrt/translations/hu.json b/homeassistant/components/asuswrt/translations/hu.json index d8133061380..34581ec6b7a 100644 --- a/homeassistant/components/asuswrt/translations/hu.json +++ b/homeassistant/components/asuswrt/translations/hu.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + "invalid_unique_id": "Lehetetlen \u00e9rv\u00e9nyes egyedi azonos\u00edt\u00f3t meghat\u00e1rozni az eszk\u00f6zh\u00f6z", + "no_unique_id": "Az \u00e9rv\u00e9nyes egyedi azonos\u00edt\u00f3 n\u00e9lk\u00fcli eszk\u00f6z m\u00e1r konfigur\u00e1lva van. T\u00f6bb p\u00e9ld\u00e1ny konfigur\u00e1l\u00e1sa nem lehets\u00e9ges" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", diff --git a/homeassistant/components/asuswrt/translations/id.json b/homeassistant/components/asuswrt/translations/id.json index 83ade7b6462..758d88b9f48 100644 --- a/homeassistant/components/asuswrt/translations/id.json +++ b/homeassistant/components/asuswrt/translations/id.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + "invalid_unique_id": "Penentuan ID unik yang valid untuk perangkat tidak dimungkinkan", + "no_unique_id": "Perangkat tanpa ID unik yang valid sudah dikonfigurasi. Konfigurasi beberapa instans tidak dimungkinkan" }, "error": { "cannot_connect": "Gagal terhubung", diff --git a/homeassistant/components/asuswrt/translations/it.json b/homeassistant/components/asuswrt/translations/it.json index 0cf06679d83..38225900691 100644 --- a/homeassistant/components/asuswrt/translations/it.json +++ b/homeassistant/components/asuswrt/translations/it.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + "invalid_unique_id": "Impossibile determinare un ID univoco valido per il dispositivo", + "no_unique_id": "Un dispositivo senza un ID univoco valido \u00e8 gi\u00e0 configurato. La configurazione di pi\u00f9 istanze non \u00e8 possibile" }, "error": { "cannot_connect": "Impossibile connettersi", diff --git a/homeassistant/components/asuswrt/translations/ja.json b/homeassistant/components/asuswrt/translations/ja.json index ab253e324ce..6bbe87fbf7e 100644 --- a/homeassistant/components/asuswrt/translations/ja.json +++ b/homeassistant/components/asuswrt/translations/ja.json @@ -1,7 +1,8 @@ { "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" + "invalid_unique_id": "\u30c7\u30d0\u30a4\u30b9\u306e\u6709\u52b9\u306a\u30e6\u30cb\u30fc\u30afID\u3092\u6c7a\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093", + "no_unique_id": "\u6709\u52b9\u306a\u30e6\u30cb\u30fc\u30afID\u3092\u6301\u305f\u306a\u3044\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u69cb\u6210\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u8907\u6570\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u3092\u69cb\u6210\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", diff --git a/homeassistant/components/asuswrt/translations/ko.json b/homeassistant/components/asuswrt/translations/ko.json index 4e60a0d15b0..3825ed92b2e 100644 --- a/homeassistant/components/asuswrt/translations/ko.json +++ b/homeassistant/components/asuswrt/translations/ko.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "invalid_unique_id": "\uae30\uae30\uc758 \uc720\ud6a8\ud55c \uace0\uc720 ID\ub97c \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "no_unique_id": "\uc720\ud6a8\ud55c \uace0\uc720 ID\uac00 \uc5c6\ub294 \uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.\n\uc5ec\ub7ec \uc778\uc2a4\ud134\uc2a4\ub97c \uad6c\uc131\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/asuswrt/translations/nl.json b/homeassistant/components/asuswrt/translations/nl.json index e6db5fae621..5e85888843a 100644 --- a/homeassistant/components/asuswrt/translations/nl.json +++ b/homeassistant/components/asuswrt/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "invalid_unique_id": "Onmogelijk om een geldige unieke id voor het apparaat te bepalen", + "no_unique_id": "Een apparaat zonder geldige unieke id is al geconfigureerd. Configuratie van meerdere instanties is niet mogelijk" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -15,7 +16,7 @@ "user": { "data": { "host": "Host", - "mode": "Mode", + "mode": "Modus", "name": "Naam", "password": "Wachtwoord", "port": "Poort (leeg laten voor protocol standaard)", diff --git a/homeassistant/components/asuswrt/translations/no.json b/homeassistant/components/asuswrt/translations/no.json index 84ece53ed42..39539e74f1c 100644 --- a/homeassistant/components/asuswrt/translations/no.json +++ b/homeassistant/components/asuswrt/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + "invalid_unique_id": "Umulig \u00e5 bestemme en gyldig unik ID for enheten", + "no_unique_id": "En enhet uten en gyldig unik ID er allerede konfigurert. Konfigurasjon av flere forekomster er ikke mulig" }, "error": { "cannot_connect": "Tilkobling mislyktes", diff --git a/homeassistant/components/asuswrt/translations/pl.json b/homeassistant/components/asuswrt/translations/pl.json index 76d8f30d950..7a03225633d 100644 --- a/homeassistant/components/asuswrt/translations/pl.json +++ b/homeassistant/components/asuswrt/translations/pl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + "invalid_unique_id": "Nie mo\u017cna okre\u015bli\u0107 prawid\u0142owego unikalnego identyfikatora urz\u0105dzenia", + "no_unique_id": "Urz\u0105dzenie bez prawid\u0142owego unikalnego identyfikatora jest ju\u017c skonfigurowane. Konfiguracja wielu instancji nie jest mo\u017cliwa." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", diff --git a/homeassistant/components/asuswrt/translations/pt-BR.json b/homeassistant/components/asuswrt/translations/pt-BR.json index 88999128895..a42cab6fe0d 100644 --- a/homeassistant/components/asuswrt/translations/pt-BR.json +++ b/homeassistant/components/asuswrt/translations/pt-BR.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "invalid_unique_id": "Imposs\u00edvel determinar um ID exclusivo v\u00e1lido para o dispositivo", + "no_unique_id": "[%key:component::asuswrt::config::abort::not_unique_id_exist%]" }, "error": { "cannot_connect": "Falha ao conectar", diff --git a/homeassistant/components/asuswrt/translations/ru.json b/homeassistant/components/asuswrt/translations/ru.json index 8edc9786f5b..0253cd20d1b 100644 --- a/homeassistant/components/asuswrt/translations/ru.json +++ b/homeassistant/components/asuswrt/translations/ru.json @@ -1,7 +1,8 @@ { "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." + "invalid_unique_id": "\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", + "no_unique_id": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0431\u0435\u0437 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u043e\u0433\u043e \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u043e. \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u043e\u0432 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\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.", diff --git a/homeassistant/components/asuswrt/translations/sv.json b/homeassistant/components/asuswrt/translations/sv.json index 9189aba6208..057a107356a 100644 --- a/homeassistant/components/asuswrt/translations/sv.json +++ b/homeassistant/components/asuswrt/translations/sv.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Redan konfigurerad. Bara en konfiguration \u00e4r m\u00f6jlig." + "invalid_unique_id": "Om\u00f6jligt att fastst\u00e4lla ett giltigt unikt ID f\u00f6r enheten", + "no_unique_id": "En enhet utan ett giltigt unikt ID \u00e4r redan konfigurerad. Konfiguration av flera instanser \u00e4r inte m\u00f6jlig" }, "error": { "cannot_connect": "Kunde inte ansluta", diff --git a/homeassistant/components/asuswrt/translations/tr.json b/homeassistant/components/asuswrt/translations/tr.json index 3225c78b40a..177c069298f 100644 --- a/homeassistant/components/asuswrt/translations/tr.json +++ b/homeassistant/components/asuswrt/translations/tr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + "invalid_unique_id": "Cihaz i\u00e7in ge\u00e7erli bir benzersiz kimlik belirlemek imkans\u0131z", + "no_unique_id": "Ge\u00e7erli bir benzersiz kimli\u011fi olmayan bir cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Birden \u00e7ok \u00f6rne\u011fin yap\u0131land\u0131r\u0131lmas\u0131 m\u00fcmk\u00fcn de\u011fil" }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", diff --git a/homeassistant/components/asuswrt/translations/zh-Hans.json b/homeassistant/components/asuswrt/translations/zh-Hans.json index 69f7bf98df3..a8e6dbf8c10 100644 --- a/homeassistant/components/asuswrt/translations/zh-Hans.json +++ b/homeassistant/components/asuswrt/translations/zh-Hans.json @@ -1,8 +1,5 @@ { "config": { - "abort": { - "single_instance_allowed": "\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86\uff0c\u4e14\u53ea\u80fd\u914d\u7f6e\u4e00\u6b21\u3002" - }, "error": { "cannot_connect": "\u8fde\u63a5\u5931\u8d25", "invalid_host": "\u65e0\u6548\u7684\u4e3b\u673a\u5730\u5740\u6216 IP \u5730\u5740", diff --git a/homeassistant/components/asuswrt/translations/zh-Hant.json b/homeassistant/components/asuswrt/translations/zh-Hant.json index 17c5cd698cd..a8c4d18023a 100644 --- a/homeassistant/components/asuswrt/translations/zh-Hant.json +++ b/homeassistant/components/asuswrt/translations/zh-Hant.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + "invalid_unique_id": "\u7121\u6cd5\u78ba\u8a8d\u88dd\u7f6e\u6709\u6548\u552f\u4e00 ID", + "no_unique_id": "\u5df2\u8a2d\u5b9a\u4e0d\u5177\u6709\u6548\u552f\u4e00 ID \u7684\u88dd\u7f6e\uff0c\u7121\u6cd5\u8a2d\u5b9a\u591a\u500b\u5be6\u4f8b" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/atag/translations/nl.json b/homeassistant/components/atag/translations/nl.json index 98200dd3f6e..da53d96e88f 100644 --- a/homeassistant/components/atag/translations/nl.json +++ b/homeassistant/components/atag/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Er kan slechts \u00e9\u00e9n Atag-apparaat worden toegevoegd aan Home Assistant " + "already_configured": "Apparaat is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -11,7 +11,7 @@ "user": { "data": { "host": "Host", - "port": "Poort " + "port": "Poort" }, "title": "Verbinding maken met het apparaat" } diff --git a/homeassistant/components/atag/translations/sk.json b/homeassistant/components/atag/translations/sk.json index 892b8b2cd91..5b7f75998d7 100644 --- a/homeassistant/components/atag/translations/sk.json +++ b/homeassistant/components/atag/translations/sk.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Zariadenie je u\u017e nakonfigurovan\u00e9" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 329522bef28..34bd4843f32 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -2,7 +2,7 @@ "domain": "august", "name": "August", "documentation": "https://www.home-assistant.io/integrations/august", - "requirements": ["yalexs==1.1.23"], + "requirements": ["yalexs==1.1.25"], "codeowners": ["@bdraco"], "dhcp": [ { diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 29f17c69661..8a6d169fcb0 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -20,9 +20,9 @@ from homeassistant.components.sensor import ( from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ENTITY_PICTURE, PERCENTAGE, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.restore_state import RestoreEntity from . import AugustData @@ -154,7 +154,7 @@ async def async_setup_entry( async def _async_migrate_old_unique_ids(hass, devices): """Keypads now have their own serial number.""" - registry = await async_get_registry(hass) + registry = er.async_get(hass) for device in devices: old_entity_id = registry.async_get_entity_id( "sensor", DOMAIN, device.old_unique_id diff --git a/homeassistant/components/august/translations/es.json b/homeassistant/components/august/translations/es.json index a660d11f996..cd50020bb2a 100644 --- a/homeassistant/components/august/translations/es.json +++ b/homeassistant/components/august/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { diff --git a/homeassistant/components/august/translations/it.json b/homeassistant/components/august/translations/it.json index aebfc1b14cd..7a121915768 100644 --- a/homeassistant/components/august/translations/it.json +++ b/homeassistant/components/august/translations/it.json @@ -24,7 +24,7 @@ "username": "Nome utente" }, "description": "Se il metodo di accesso \u00e8 'email', il nome utente \u00e8 l'indirizzo email. Se il metodo di accesso \u00e8 'phone', il nome utente \u00e8 il numero di telefono nel formato '+NNNNNNNNNN'.", - "title": "Configura un account di August" + "title": "Configura un account August" }, "validation": { "data": { diff --git a/homeassistant/components/august/translations/nl.json b/homeassistant/components/august/translations/nl.json index 2d9a4202f74..e2bbc4d636d 100644 --- a/homeassistant/components/august/translations/nl.json +++ b/homeassistant/components/august/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/aurora/translations/nl.json b/homeassistant/components/aurora/translations/nl.json index fe7b4809f13..423e722921d 100644 --- a/homeassistant/components/aurora/translations/nl.json +++ b/homeassistant/components/aurora/translations/nl.json @@ -17,7 +17,7 @@ "step": { "init": { "data": { - "threshold": "Drempel (%)" + "threshold": "Drempelwaarde (%)" } } } diff --git a/homeassistant/components/aurora_abb_powerone/config_flow.py b/homeassistant/components/aurora_abb_powerone/config_flow.py index 643d3b3f865..07741bd4e3c 100644 --- a/homeassistant/components/aurora_abb_powerone/config_flow.py +++ b/homeassistant/components/aurora_abb_powerone/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Aurora ABB PowerOne integration.""" from __future__ import annotations +from collections.abc import Mapping import logging from typing import Any @@ -27,7 +28,7 @@ _LOGGER = logging.getLogger(__name__) def validate_and_connect( - hass: core.HomeAssistant, data: dict[str, Any] + hass: core.HomeAssistant, data: Mapping[str, Any] ) -> dict[str, str]: """Validate the user input allows us to connect. diff --git a/homeassistant/components/aurora_abb_powerone/translations/bg.json b/homeassistant/components/aurora_abb_powerone/translations/bg.json index 88f52d84269..37b6f40c82e 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/bg.json +++ b/homeassistant/components/aurora_abb_powerone/translations/bg.json @@ -2,9 +2,6 @@ "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": { - "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/aurora_abb_powerone/translations/ca.json b/homeassistant/components/aurora_abb_powerone/translations/ca.json index 98f77dad13b..43b4acd25ea 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/ca.json +++ b/homeassistant/components/aurora_abb_powerone/translations/ca.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "No s'ha pogut connectar, comprova el port s\u00e8rie, l'adre\u00e7a, la connexi\u00f3 el\u00e8ctrica i que l'inversor estigui enc\u00e8s", "cannot_open_serial_port": "No s'ha pogut obrir el port s\u00e8rie, comprova'l i torna-ho a provar", - "invalid_serial_port": "El port s\u00e8rie no t\u00e9 un dispositiu v\u00e0lid o no s'ha pogut obrir", - "unknown": "Error inesperat" + "invalid_serial_port": "El port s\u00e8rie no t\u00e9 un dispositiu v\u00e0lid o no s'ha pogut obrir" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/cs.json b/homeassistant/components/aurora_abb_powerone/translations/cs.json deleted file mode 100644 index e1bf8e7f45f..00000000000 --- a/homeassistant/components/aurora_abb_powerone/translations/cs.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "config": { - "error": { - "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/aurora_abb_powerone/translations/de.json b/homeassistant/components/aurora_abb_powerone/translations/de.json index a60138da16d..fab4d1f6e31 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/de.json +++ b/homeassistant/components/aurora_abb_powerone/translations/de.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "Verbindung kann nicht hergestellt werden, bitte \u00fcberpr\u00fcfe den seriellen Anschluss, die Adresse, die elektrische Verbindung und ob der Wechselrichter eingeschaltet ist (bei Tageslicht)", "cannot_open_serial_port": "Serielle Schnittstelle kann nicht ge\u00f6ffnet werden, bitte pr\u00fcfen und erneut versuchen", - "invalid_serial_port": "Serielle Schnittstelle ist kein g\u00fcltiges Ger\u00e4t oder konnte nicht ge\u00f6ffnet werden", - "unknown": "Unerwarteter Fehler" + "invalid_serial_port": "Serielle Schnittstelle ist kein g\u00fcltiges Ger\u00e4t oder konnte nicht ge\u00f6ffnet werden" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/el.json b/homeassistant/components/aurora_abb_powerone/translations/el.json index 834c5794861..52b43a668be 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/el.json +++ b/homeassistant/components/aurora_abb_powerone/translations/el.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1, \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7, \u03c4\u03b7\u03bd \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03b9\u03ba\u03ae \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03bf \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2 (\u03c3\u03c4\u03bf \u03c6\u03c9\u03c2 \u03c4\u03b7\u03c2 \u03b7\u03bc\u03ad\u03c1\u03b1\u03c2)", "cannot_open_serial_port": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03c4\u03bf \u03ac\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac", - "invalid_serial_port": "\u0397 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ae \u03b4\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03b9", - "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + "invalid_serial_port": "\u0397 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ae \u03b4\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03b9" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/en.json b/homeassistant/components/aurora_abb_powerone/translations/en.json index fe5d668f573..748a570f291 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/en.json +++ b/homeassistant/components/aurora_abb_powerone/translations/en.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "Unable to connect, please check serial port, address, electrical connection and that inverter is on (in daylight)", "cannot_open_serial_port": "Cannot open serial port, please check and try again", - "invalid_serial_port": "Serial port is not a valid device or could not be openned", - "unknown": "Unexpected error" + "invalid_serial_port": "Serial port is not a valid device or could not be openned" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/en_GB.json b/homeassistant/components/aurora_abb_powerone/translations/en_GB.json index 59c6263bd14..25f797239b9 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/en_GB.json +++ b/homeassistant/components/aurora_abb_powerone/translations/en_GB.json @@ -1,8 +1,5 @@ { "config": { - "error": { - "unknown": "Unexpected error" - }, "step": { "user": { "data": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/es.json b/homeassistant/components/aurora_abb_powerone/translations/es.json index f71039bda56..4e1d95a126d 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/es.json +++ b/homeassistant/components/aurora_abb_powerone/translations/es.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "No se puede conectar, por favor, compruebe el puerto serie, la direcci\u00f3n, la conexi\u00f3n el\u00e9ctrica y que el inversor est\u00e1 encendido", "cannot_open_serial_port": "No se puede abrir el puerto serie, por favor, compruebe y vuelva a intentarlo", - "invalid_serial_port": "El puerto serie no es v\u00e1lido para este dispositivo o no se ha podido abrir", - "unknown": "Error inesperado" + "invalid_serial_port": "El puerto serie no es v\u00e1lido para este dispositivo o no se ha podido abrir" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/et.json b/homeassistant/components/aurora_abb_powerone/translations/et.json index b7764fa0f33..115bab56eca 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/et.json +++ b/homeassistant/components/aurora_abb_powerone/translations/et.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "\u00dchendust ei saa luua, palun kontrolli jadaporti, aadressi, elektri\u00fchendust ja et inverter on sisse l\u00fclitatud (p\u00e4evavalguses)", "cannot_open_serial_port": "Jadaporti ei saa avada, kontrolli ja proovi uuesti", - "invalid_serial_port": "Jadaport pole sobiv seade v\u00f5i seda ei saa avada", - "unknown": "Ootamatu t\u00f5rge" + "invalid_serial_port": "Jadaport pole sobiv seade v\u00f5i seda ei saa avada" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/fr.json b/homeassistant/components/aurora_abb_powerone/translations/fr.json index 206167a5669..c36cc62196f 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/fr.json +++ b/homeassistant/components/aurora_abb_powerone/translations/fr.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "Connexion impossible, veuillez v\u00e9rifier le port s\u00e9rie, l'adresse, la connexion \u00e9lectrique et que l'onduleur est allum\u00e9 (\u00e0 la lumi\u00e8re du jour)", "cannot_open_serial_port": "Impossible d'ouvrir le port s\u00e9rie, veuillez v\u00e9rifier et r\u00e9essayer", - "invalid_serial_port": "Le port s\u00e9rie n'est pas un p\u00e9riph\u00e9rique valide ou n'a pas pu \u00eatre ouvert", - "unknown": "Erreur inattendue" + "invalid_serial_port": "Le port s\u00e9rie n'est pas un p\u00e9riph\u00e9rique valide ou n'a pas pu \u00eatre ouvert" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/he.json b/homeassistant/components/aurora_abb_powerone/translations/he.json index ea40181bd9a..cdb921611c4 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/he.json +++ b/homeassistant/components/aurora_abb_powerone/translations/he.json @@ -2,9 +2,6 @@ "config": { "abort": { "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" - }, - "error": { - "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" } } } \ No newline at end of file diff --git a/homeassistant/components/aurora_abb_powerone/translations/hu.json b/homeassistant/components/aurora_abb_powerone/translations/hu.json index ffc812dfd4b..6342466bffe 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/hu.json +++ b/homeassistant/components/aurora_abb_powerone/translations/hu.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "A csatlakoz\u00e1s sikertelen. Ellen\u0151rizze a soros portot, a c\u00edmet, az elektromos csatlakoz\u00e1st \u00e9s azt, hogy az inverter be van-e kapcsolva (nappal).", "cannot_open_serial_port": "A soros port nem nyithat\u00f3 meg, k\u00e9rem ellen\u0151rizze \u00e9s pr\u00f3b\u00e1lkozzon \u00fajra", - "invalid_serial_port": "A soros port nem \u00e9rv\u00e9nyes eszk\u00f6z, vagy nem nyithat\u00f3 meg", - "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + "invalid_serial_port": "A soros port nem \u00e9rv\u00e9nyes eszk\u00f6z, vagy nem nyithat\u00f3 meg" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/id.json b/homeassistant/components/aurora_abb_powerone/translations/id.json index 4157502c2ad..70811e170a1 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/id.json +++ b/homeassistant/components/aurora_abb_powerone/translations/id.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "Tidak dapat terhubung, periksa port serial, alamat, koneksi listrik dan apakah inverter sedang nyala (di siang hari)", "cannot_open_serial_port": "Tidak dapat membuka port serial, periksa dan coba lagi", - "invalid_serial_port": "Port serial bukan perangkat yang valid atau tidak dapat dibuka", - "unknown": "Kesalahan yang tidak diharapkan" + "invalid_serial_port": "Port serial bukan perangkat yang valid atau tidak dapat dibuka" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/it.json b/homeassistant/components/aurora_abb_powerone/translations/it.json index bb534d079cc..765aaf5e9ed 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/it.json +++ b/homeassistant/components/aurora_abb_powerone/translations/it.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "Impossibile connettersi, controllare la porta seriale, l'indirizzo, la connessione elettrica e che l'inverter sia acceso (alla luce del giorno)", "cannot_open_serial_port": "Impossibile aprire la porta seriale, controllare e riprovare", - "invalid_serial_port": "La porta seriale non \u00e8 un dispositivo valido o non pu\u00f2 essere aperta", - "unknown": "Errore imprevisto" + "invalid_serial_port": "La porta seriale non \u00e8 un dispositivo valido o non pu\u00f2 essere aperta" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/ja.json b/homeassistant/components/aurora_abb_powerone/translations/ja.json index a558f088f07..5977b1513b0 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/ja.json +++ b/homeassistant/components/aurora_abb_powerone/translations/ja.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "\u63a5\u7d9a\u3067\u304d\u306a\u3044\u5834\u5408\u306f\u3001\u30b7\u30ea\u30a2\u30eb\u30dd\u30fc\u30c8\u3001\u30a2\u30c9\u30ec\u30b9\u3001\u96fb\u6c17\u7684\u63a5\u7d9a\u3092\u78ba\u8a8d\u3057\u3001\u30a4\u30f3\u30d0\u30fc\u30bf\u30fc\u306e\u96fb\u6e90\u304c\u5165\u3063\u3066\u3044\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044(\u663c\u9593)", "cannot_open_serial_port": "\u30b7\u30ea\u30a2\u30eb\u30dd\u30fc\u30c8\u3092\u958b\u3051\u307e\u305b\u3093\u3002\u78ba\u8a8d\u3057\u3066\u304b\u3089\u3082\u3046\u4e00\u5ea6\u8a66\u3057\u3066\u304f\u3060\u3055\u3044", - "invalid_serial_port": "\u30b7\u30ea\u30a2\u30eb\u30dd\u30fc\u30c8\u304c\u6709\u52b9\u306a\u30c7\u30d0\u30a4\u30b9\u3067\u306f\u306a\u3044\u3001\u3082\u3057\u304f\u306f\u958b\u304f\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f", - "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + "invalid_serial_port": "\u30b7\u30ea\u30a2\u30eb\u30dd\u30fc\u30c8\u304c\u6709\u52b9\u306a\u30c7\u30d0\u30a4\u30b9\u3067\u306f\u306a\u3044\u3001\u3082\u3057\u304f\u306f\u958b\u304f\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/nl.json b/homeassistant/components/aurora_abb_powerone/translations/nl.json index d70113e9c19..cb5cd511f81 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/nl.json +++ b/homeassistant/components/aurora_abb_powerone/translations/nl.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "Kan geen verbinding maken, controleer de seri\u00eble poort, het adres, de elektrische aansluiting en of de omvormer aan staat (bij daglicht)", "cannot_open_serial_port": "Kan seri\u00eble poort niet openen, controleer en probeer het opnieuw", - "invalid_serial_port": "Seri\u00eble poort is geen geldig apparaat of kan niet worden geopend", - "unknown": "Onverwachte fout" + "invalid_serial_port": "Seri\u00eble poort is geen geldig apparaat of kan niet worden geopend" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/no.json b/homeassistant/components/aurora_abb_powerone/translations/no.json index 9d4cd656f45..38cef3a2c96 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/no.json +++ b/homeassistant/components/aurora_abb_powerone/translations/no.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "Kan ikke koble til, sjekk seriell port, adresse, elektrisk tilkobling og at omformeren er p\u00e5 (i dagslys)", "cannot_open_serial_port": "Kan ikke \u00e5pne serieporten, sjekk og pr\u00f8v igjen", - "invalid_serial_port": "Seriell port er ikke en gyldig enhet eller kunne ikke \u00e5pnes", - "unknown": "Uventet feil" + "invalid_serial_port": "Seriell port er ikke en gyldig enhet eller kunne ikke \u00e5pnes" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/pl.json b/homeassistant/components/aurora_abb_powerone/translations/pl.json index d6131cfa195..3b773295256 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/pl.json +++ b/homeassistant/components/aurora_abb_powerone/translations/pl.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, sprawd\u017a port szeregowy, adres, po\u0142\u0105czenie elektryczne i czy falownik jest w\u0142\u0105czony (w \u015bwietle dziennym)", "cannot_open_serial_port": "Nie mo\u017cna otworzy\u0107 portu szeregowego, sprawd\u017a i spr\u00f3buj ponownie", - "invalid_serial_port": "Port szeregowy nie jest prawid\u0142owym urz\u0105dzeniem lub nie mo\u017cna go otworzy\u0107", - "unknown": "Nieoczekiwany b\u0142\u0105d" + "invalid_serial_port": "Port szeregowy nie jest prawid\u0142owym urz\u0105dzeniem lub nie mo\u017cna go otworzy\u0107" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json b/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json index 8fb79bcefa2..82dbcb466ec 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json +++ b/homeassistant/components/aurora_abb_powerone/translations/pt-BR.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel conectar, verifique a porta serial, endere\u00e7o, conex\u00e3o el\u00e9trica e se o inversor est\u00e1 ligado (\u00e0 luz do dia)", "cannot_open_serial_port": "N\u00e3o \u00e9 poss\u00edvel abrir a porta serial, verifique e tente novamente", - "invalid_serial_port": "A porta serial n\u00e3o \u00e9 um dispositivo v\u00e1lido ou n\u00e3o p\u00f4de ser aberta", - "unknown": "Erro inesperado" + "invalid_serial_port": "A porta serial n\u00e3o \u00e9 um dispositivo v\u00e1lido ou n\u00e3o p\u00f4de ser aberta" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/ru.json b/homeassistant/components/aurora_abb_powerone/translations/ru.json index 6baec9324d2..22e73931232 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/ru.json +++ b/homeassistant/components/aurora_abb_powerone/translations/ru.json @@ -7,8 +7,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. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442, \u0430\u0434\u0440\u0435\u0441, \u044d\u043b\u0435\u043a\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0438 \u0447\u0442\u043e \u0438\u043d\u0432\u0435\u0440\u0442\u043e\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d (\u043f\u0440\u0438 \u0434\u043d\u0435\u0432\u043d\u043e\u043c \u0441\u0432\u0435\u0442\u0435).", "cannot_open_serial_port": "\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", - "invalid_serial_port": "\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442 \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u043c \u0438\u043b\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u0442\u043a\u0440\u044b\u0442.", - "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + "invalid_serial_port": "\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442 \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u043c \u0438\u043b\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u0442\u043a\u0440\u044b\u0442." }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/sl.json b/homeassistant/components/aurora_abb_powerone/translations/sl.json index 87fce100c77..16a4bd0b1c1 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/sl.json +++ b/homeassistant/components/aurora_abb_powerone/translations/sl.json @@ -2,9 +2,6 @@ "config": { "abort": { "already_configured": "Naprava je \u017ee konfigurirana" - }, - "error": { - "unknown": "Nepri\u010dakovana napaka" } } } \ No newline at end of file diff --git a/homeassistant/components/aurora_abb_powerone/translations/tr.json b/homeassistant/components/aurora_abb_powerone/translations/tr.json index ec8ee6da4a3..39a046adf43 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/tr.json +++ b/homeassistant/components/aurora_abb_powerone/translations/tr.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "Ba\u011flant\u0131 kurulam\u0131yor, l\u00fctfen seri portu, adresi, elektrik ba\u011flant\u0131s\u0131n\u0131 ve invert\u00f6r\u00fcn a\u00e7\u0131k oldu\u011funu (g\u00fcn \u0131\u015f\u0131\u011f\u0131nda) kontrol edin.", "cannot_open_serial_port": "Seri ba\u011flant\u0131 noktas\u0131 a\u00e7\u0131lam\u0131yor, l\u00fctfen kontrol edip tekrar deneyin", - "invalid_serial_port": "Seri ba\u011flant\u0131 noktas\u0131 ge\u00e7erli bir ayg\u0131t de\u011fil veya a\u00e7\u0131lamad\u0131", - "unknown": "Beklenmeyen hata" + "invalid_serial_port": "Seri ba\u011flant\u0131 noktas\u0131 ge\u00e7erli bir ayg\u0131t de\u011fil veya a\u00e7\u0131lamad\u0131" }, "step": { "user": { diff --git a/homeassistant/components/aurora_abb_powerone/translations/zh-Hant.json b/homeassistant/components/aurora_abb_powerone/translations/zh-Hant.json index 0a05e640994..bec84f72b80 100644 --- a/homeassistant/components/aurora_abb_powerone/translations/zh-Hant.json +++ b/homeassistant/components/aurora_abb_powerone/translations/zh-Hant.json @@ -7,8 +7,7 @@ "error": { "cannot_connect": "\u7121\u6cd5\u9023\u63a5\uff0c\u8acb\u6aa2\u67e5\u5e8f\u5217\u57e0\u3001\u4f4d\u5740\u3001\u96fb\u529b\u9023\u63a5\uff0c\u4e26\u78ba\u5b9a\u8a72\u8b8a\u6d41\u5668\u70ba\u958b\u555f\u72c0\u614b\uff08\u767d\u5929\uff09", "cannot_open_serial_port": "\u7121\u6cd5\u958b\u555f\u5e8f\u5217\u57e0\u3001\u8acb\u6aa2\u67e5\u5f8c\u518d\u8a66\u4e00\u6b21", - "invalid_serial_port": "\u5e8f\u5217\u57e0\u70ba\u7121\u6548\u88dd\u7f6e\u6216\u7121\u6cd5\u958b\u555f", - "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + "invalid_serial_port": "\u5e8f\u5217\u57e0\u70ba\u7121\u6548\u88dd\u7f6e\u6216\u7121\u6cd5\u958b\u555f" }, "step": { "user": { diff --git a/homeassistant/components/aussie_broadband/translations/bg.json b/homeassistant/components/aussie_broadband/translations/bg.json index 8c36ef66120..8098935e86c 100644 --- a/homeassistant/components/aussie_broadband/translations/bg.json +++ b/homeassistant/components/aussie_broadband/translations/bg.json @@ -11,13 +11,6 @@ "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, "step": { - "reauth": { - "data": { - "password": "\u041f\u0430\u0440\u043e\u043b\u0430" - }, - "description": "\u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u0437\u0430 {username}", - "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" - }, "reauth_confirm": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u0430" diff --git a/homeassistant/components/aussie_broadband/translations/ca.json b/homeassistant/components/aussie_broadband/translations/ca.json index 83ec53ad5a4..4688357a3c8 100644 --- a/homeassistant/components/aussie_broadband/translations/ca.json +++ b/homeassistant/components/aussie_broadband/translations/ca.json @@ -11,13 +11,6 @@ "unknown": "Error inesperat" }, "step": { - "reauth": { - "data": { - "password": "Contrasenya" - }, - "description": "Actualitza la contrasenya de {username}", - "title": "Reautenticaci\u00f3 de la integraci\u00f3" - }, "reauth_confirm": { "data": { "password": "Contrasenya" diff --git a/homeassistant/components/aussie_broadband/translations/cs.json b/homeassistant/components/aussie_broadband/translations/cs.json index 8ea2970a132..131dddca93a 100644 --- a/homeassistant/components/aussie_broadband/translations/cs.json +++ b/homeassistant/components/aussie_broadband/translations/cs.json @@ -10,12 +10,6 @@ "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "step": { - "reauth": { - "data": { - "password": "Heslo" - }, - "title": "Znovu ov\u011b\u0159it integraci" - }, "user": { "data": { "password": "Heslo", diff --git a/homeassistant/components/aussie_broadband/translations/de.json b/homeassistant/components/aussie_broadband/translations/de.json index ed5cd5fd02c..e4c5bd327ff 100644 --- a/homeassistant/components/aussie_broadband/translations/de.json +++ b/homeassistant/components/aussie_broadband/translations/de.json @@ -11,13 +11,6 @@ "unknown": "Unerwarteter Fehler" }, "step": { - "reauth": { - "data": { - "password": "Passwort" - }, - "description": "Passwort f\u00fcr {username} aktualisieren", - "title": "Integration erneut authentifizieren" - }, "reauth_confirm": { "data": { "password": "Passwort" diff --git a/homeassistant/components/aussie_broadband/translations/el.json b/homeassistant/components/aussie_broadband/translations/el.json index 0b78eacb826..c1e36e01654 100644 --- a/homeassistant/components/aussie_broadband/translations/el.json +++ b/homeassistant/components/aussie_broadband/translations/el.json @@ -11,13 +11,6 @@ "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { - "reauth": { - "data": { - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" - }, - "description": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 {username}", - "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" - }, "reauth_confirm": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" diff --git a/homeassistant/components/aussie_broadband/translations/en.json b/homeassistant/components/aussie_broadband/translations/en.json index 2843916df2e..4d18251f270 100644 --- a/homeassistant/components/aussie_broadband/translations/en.json +++ b/homeassistant/components/aussie_broadband/translations/en.json @@ -11,13 +11,6 @@ "unknown": "Unexpected error" }, "step": { - "reauth": { - "data": { - "password": "Password" - }, - "description": "Update password for {username}", - "title": "Reauthenticate Integration" - }, "reauth_confirm": { "data": { "password": "Password" diff --git a/homeassistant/components/aussie_broadband/translations/es-419.json b/homeassistant/components/aussie_broadband/translations/es-419.json index df9322bf01d..f90e4891935 100644 --- a/homeassistant/components/aussie_broadband/translations/es-419.json +++ b/homeassistant/components/aussie_broadband/translations/es-419.json @@ -4,9 +4,6 @@ "no_services_found": "No se encontraron servicios para esta cuenta" }, "step": { - "reauth": { - "description": "Actualizar contrase\u00f1a para {username}" - }, "service": { "data": { "services": "Servicios" diff --git a/homeassistant/components/aussie_broadband/translations/es.json b/homeassistant/components/aussie_broadband/translations/es.json index ff13c88c598..1410af6ad76 100644 --- a/homeassistant/components/aussie_broadband/translations/es.json +++ b/homeassistant/components/aussie_broadband/translations/es.json @@ -1,12 +1,14 @@ { "config": { "abort": { - "no_services_found": "No se han encontrado servicios para esta cuenta" + "no_services_found": "No se han encontrado servicios para esta cuenta", + "reauth_successful": "Re-autenticaci\u00f3n realizada correctamente" + }, + "error": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" }, "step": { - "reauth": { - "description": "Actualizar la contrase\u00f1a de {username}" - }, "reauth_confirm": { "data": { "password": "Contrase\u00f1a" @@ -19,6 +21,19 @@ "services": "Servicios" }, "title": "Seleccionar Servicios" + }, + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Selecciona servicios" } } } diff --git a/homeassistant/components/aussie_broadband/translations/et.json b/homeassistant/components/aussie_broadband/translations/et.json index 7dbf2d26b59..0d0d97b6ff2 100644 --- a/homeassistant/components/aussie_broadband/translations/et.json +++ b/homeassistant/components/aussie_broadband/translations/et.json @@ -11,13 +11,6 @@ "unknown": "Ootamatu t\u00f5rge" }, "step": { - "reauth": { - "data": { - "password": "Salas\u00f5na" - }, - "description": "{username} salas\u00f5na v\u00e4rskendamine", - "title": "Taastuvasta sidumine" - }, "reauth_confirm": { "data": { "password": "Salas\u00f5na" diff --git a/homeassistant/components/aussie_broadband/translations/fr.json b/homeassistant/components/aussie_broadband/translations/fr.json index d540e5fd2f9..37d570e77d6 100644 --- a/homeassistant/components/aussie_broadband/translations/fr.json +++ b/homeassistant/components/aussie_broadband/translations/fr.json @@ -11,13 +11,6 @@ "unknown": "Erreur inattendue" }, "step": { - "reauth": { - "data": { - "password": "Mot de passe" - }, - "description": "Mettre \u00e0 jour le mot de passe pour {username}", - "title": "R\u00e9-authentifier l'int\u00e9gration" - }, "reauth_confirm": { "data": { "password": "Mot de passe" diff --git a/homeassistant/components/aussie_broadband/translations/he.json b/homeassistant/components/aussie_broadband/translations/he.json index 5861357a6d4..7a1ef5f9cc5 100644 --- a/homeassistant/components/aussie_broadband/translations/he.json +++ b/homeassistant/components/aussie_broadband/translations/he.json @@ -10,12 +10,6 @@ "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" }, "step": { - "reauth": { - "data": { - "password": "\u05e1\u05d9\u05e1\u05de\u05d4" - }, - "title": "\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05e9\u05dc \u05e9\u05d9\u05dc\u05d5\u05d1" - }, "reauth_confirm": { "data": { "password": "\u05e1\u05d9\u05e1\u05de\u05d4" diff --git a/homeassistant/components/aussie_broadband/translations/hu.json b/homeassistant/components/aussie_broadband/translations/hu.json index a8c3543873d..2ad64c2155d 100644 --- a/homeassistant/components/aussie_broadband/translations/hu.json +++ b/homeassistant/components/aussie_broadband/translations/hu.json @@ -11,13 +11,6 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { - "reauth": { - "data": { - "password": "Jelsz\u00f3" - }, - "description": "Jelsz\u00f3 friss\u00edt\u00e9se {username} sz\u00e1m\u00e1ra", - "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" - }, "reauth_confirm": { "data": { "password": "Jelsz\u00f3" diff --git a/homeassistant/components/aussie_broadband/translations/id.json b/homeassistant/components/aussie_broadband/translations/id.json index 18020014268..d3353b46f3c 100644 --- a/homeassistant/components/aussie_broadband/translations/id.json +++ b/homeassistant/components/aussie_broadband/translations/id.json @@ -11,13 +11,6 @@ "unknown": "Kesalahan yang tidak diharapkan" }, "step": { - "reauth": { - "data": { - "password": "Kata Sandi" - }, - "description": "Perbarui kata sandi untuk {username}", - "title": "Autentikasi Ulang Integrasi" - }, "reauth_confirm": { "data": { "password": "Kata Sandi" diff --git a/homeassistant/components/aussie_broadband/translations/it.json b/homeassistant/components/aussie_broadband/translations/it.json index a29e10db60a..6c53ec28dff 100644 --- a/homeassistant/components/aussie_broadband/translations/it.json +++ b/homeassistant/components/aussie_broadband/translations/it.json @@ -11,13 +11,6 @@ "unknown": "Errore imprevisto" }, "step": { - "reauth": { - "data": { - "password": "Password" - }, - "description": "Aggiorna la password per {username}", - "title": "Autentica nuovamente l'integrazione" - }, "reauth_confirm": { "data": { "password": "Password" diff --git a/homeassistant/components/aussie_broadband/translations/ja.json b/homeassistant/components/aussie_broadband/translations/ja.json index f08e02f73c1..d8351bbc98b 100644 --- a/homeassistant/components/aussie_broadband/translations/ja.json +++ b/homeassistant/components/aussie_broadband/translations/ja.json @@ -11,13 +11,6 @@ "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { - "reauth": { - "data": { - "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" - }, - "description": "{username} \u306e\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u66f4\u65b0\u3057\u307e\u3059", - "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" - }, "reauth_confirm": { "data": { "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" diff --git a/homeassistant/components/aussie_broadband/translations/nl.json b/homeassistant/components/aussie_broadband/translations/nl.json index 21da52666bc..77e22db0a48 100644 --- a/homeassistant/components/aussie_broadband/translations/nl.json +++ b/homeassistant/components/aussie_broadband/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Account is al geconfigureerd", "no_services_found": "Er zijn geen services gevonden voor dit account", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -11,19 +11,12 @@ "unknown": "Onverwachte fout" }, "step": { - "reauth": { - "data": { - "password": "Wachtwoord" - }, - "description": "Update wachtwoord voor {username}", - "title": "Verifieer de integratie opnieuw" - }, "reauth_confirm": { "data": { "password": "Wachtwoord" }, "description": "Update wachtwoord voor {username}", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "service": { "data": { diff --git a/homeassistant/components/aussie_broadband/translations/no.json b/homeassistant/components/aussie_broadband/translations/no.json index 8129a8c9cc2..00e911bbfba 100644 --- a/homeassistant/components/aussie_broadband/translations/no.json +++ b/homeassistant/components/aussie_broadband/translations/no.json @@ -11,13 +11,6 @@ "unknown": "Uventet feil" }, "step": { - "reauth": { - "data": { - "password": "Passord" - }, - "description": "Oppdater passordet for {username}", - "title": "Godkjenne integrering p\u00e5 nytt" - }, "reauth_confirm": { "data": { "password": "Passord" diff --git a/homeassistant/components/aussie_broadband/translations/pl.json b/homeassistant/components/aussie_broadband/translations/pl.json index c2f686c11dc..ed6860b31f8 100644 --- a/homeassistant/components/aussie_broadband/translations/pl.json +++ b/homeassistant/components/aussie_broadband/translations/pl.json @@ -11,13 +11,6 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { - "reauth": { - "data": { - "password": "Has\u0142o" - }, - "description": "Zaktualizuj has\u0142o dla {username}", - "title": "Ponownie uwierzytelnij integracj\u0119" - }, "reauth_confirm": { "data": { "password": "Has\u0142o" diff --git a/homeassistant/components/aussie_broadband/translations/pt-BR.json b/homeassistant/components/aussie_broadband/translations/pt-BR.json index efe1fca7c80..884d426a04b 100644 --- a/homeassistant/components/aussie_broadband/translations/pt-BR.json +++ b/homeassistant/components/aussie_broadband/translations/pt-BR.json @@ -11,13 +11,6 @@ "unknown": "Erro inesperado" }, "step": { - "reauth": { - "data": { - "password": "Senha" - }, - "description": "Atualizar senha para {username}", - "title": "Reautenticar Integra\u00e7\u00e3o" - }, "reauth_confirm": { "data": { "password": "Senha" diff --git a/homeassistant/components/aussie_broadband/translations/ru.json b/homeassistant/components/aussie_broadband/translations/ru.json index 15a9f44a98a..24a3c3d67fb 100644 --- a/homeassistant/components/aussie_broadband/translations/ru.json +++ b/homeassistant/components/aussie_broadband/translations/ru.json @@ -11,13 +11,6 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { - "reauth": { - "data": { - "password": "\u041f\u0430\u0440\u043e\u043b\u044c" - }, - "description": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f {username}.", - "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" - }, "reauth_confirm": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c" diff --git a/homeassistant/components/aussie_broadband/translations/sk.json b/homeassistant/components/aussie_broadband/translations/sk.json index a8cf7db8bbf..2d028b5f36c 100644 --- a/homeassistant/components/aussie_broadband/translations/sk.json +++ b/homeassistant/components/aussie_broadband/translations/sk.json @@ -5,11 +5,21 @@ }, "error": { "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "service": { + "title": "Vyberte slu\u017eby" + } } }, "options": { "abort": { "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "init": { + "title": "Vyberte slu\u017eby" + } } } } \ No newline at end of file diff --git a/homeassistant/components/aussie_broadband/translations/sv.json b/homeassistant/components/aussie_broadband/translations/sv.json index a82ed912c19..b159fb71b87 100644 --- a/homeassistant/components/aussie_broadband/translations/sv.json +++ b/homeassistant/components/aussie_broadband/translations/sv.json @@ -11,13 +11,6 @@ "unknown": "Ov\u00e4ntat fel" }, "step": { - "reauth": { - "data": { - "password": "L\u00f6senord" - }, - "description": "Uppdatera l\u00f6senord f\u00f6r {username}", - "title": "\u00c5terautentisera integration" - }, "service": { "data": { "services": "Tj\u00e4nster" diff --git a/homeassistant/components/aussie_broadband/translations/tr.json b/homeassistant/components/aussie_broadband/translations/tr.json index 28eae33719d..e9d656acb26 100644 --- a/homeassistant/components/aussie_broadband/translations/tr.json +++ b/homeassistant/components/aussie_broadband/translations/tr.json @@ -11,13 +11,6 @@ "unknown": "Beklenmeyen hata" }, "step": { - "reauth": { - "data": { - "password": "Parola" - }, - "description": "{username} i\u00e7in \u015fifreyi g\u00fcncelleyin", - "title": "Entegrasyonu Yeniden Do\u011frula" - }, "reauth_confirm": { "data": { "password": "Parola" diff --git a/homeassistant/components/aussie_broadband/translations/zh-Hant.json b/homeassistant/components/aussie_broadband/translations/zh-Hant.json index f7beefb2962..5f248360de1 100644 --- a/homeassistant/components/aussie_broadband/translations/zh-Hant.json +++ b/homeassistant/components/aussie_broadband/translations/zh-Hant.json @@ -11,13 +11,6 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { - "reauth": { - "data": { - "password": "\u5bc6\u78bc" - }, - "description": "\u66f4\u65b0 {username} \u5bc6\u78bc", - "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" - }, "reauth_confirm": { "data": { "password": "\u5bc6\u78bc" diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 30b36a40f32..10f974faa28 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -19,13 +19,15 @@ Exchange the authorization code retrieved from the login flow for tokens. Return value will be the access and refresh tokens. The access token will have a limited expiration. New access tokens can be requested using the refresh -token. +token. The value ha_auth_provider will contain the auth provider type that was +used to authorize the refresh token. { "access_token": "ABCDEFGH", "expires_in": 1800, "refresh_token": "IJKLMNOPQRST", - "token_type": "Bearer" + "token_type": "Bearer", + "ha_auth_provider": "homeassistant" } ## Grant type refresh_token @@ -342,7 +344,12 @@ class TokenView(HomeAssistantView): "expires_in": int( refresh_token.access_token_expiration.total_seconds() ), - } + "ha_auth_provider": credential.auth_provider_type, + }, + headers={ + "Cache-Control": "no-store", + "Pragma": "no-cache", + }, ) async def _async_handle_refresh_token(self, hass, data, remote_addr): @@ -399,7 +406,11 @@ class TokenView(HomeAssistantView): "expires_in": int( refresh_token.access_token_expiration.total_seconds() ), - } + }, + headers={ + "Cache-Control": "no-store", + "Pragma": "no-cache", + }, ) diff --git a/homeassistant/components/auth/translations/id.json b/homeassistant/components/auth/translations/id.json index ed7bede5fff..22b562e7c97 100644 --- a/homeassistant/components/auth/translations/id.json +++ b/homeassistant/components/auth/translations/id.json @@ -5,7 +5,7 @@ "no_available_service": "Tidak ada layanan notifikasi yang tersedia." }, "error": { - "invalid_code": "Kode tifak valid, coba lagi." + "invalid_code": "Kode tidak valid, coba lagi." }, "step": { "init": { diff --git a/homeassistant/components/auth/translations/it.json b/homeassistant/components/auth/translations/it.json index e8091faf757..82d69b007ff 100644 --- a/homeassistant/components/auth/translations/it.json +++ b/homeassistant/components/auth/translations/it.json @@ -25,7 +25,7 @@ }, "step": { "init": { - "description": "Per attivare l'autenticazione a due fattori utilizzando le password monouso basate sul tempo, esegui la scansione del codice QR con l'app di autenticazione. Se non ne hai uno, ti consigliamo [Google Authenticator](https://support.google.com/accounts/answer/1066447) o [Authy](https://authy.com/). \n\n {qr_code} \n \nDopo la scansione, inserisci il codice a sei cifre dalla tua app per verificare la configurazione. Se riscontri problemi con la scansione del codice QR, esegui una configurazione manuale con il codice **`{code}`**.", + "description": "Per attivare l'autenticazione a due fattori utilizzando le password monouso basate sul tempo, esegui la scansione del codice QR con l'applicazione di autenticazione. Se non ne hai uno, ti consigliamo [Google Authenticator](https://support.google.com/accounts/answer/1066447) o [Authy](https://authy.com/). \n\n {qr_code} \n \nDopo la scansione, inserisci il codice a sei cifre dalla tua app per verificare la configurazione. Se riscontri problemi con la scansione del codice QR, esegui una configurazione manuale con il codice **`{code}`**.", "title": "Imposta l'autenticazione a due fattori usando TOTP" } }, diff --git a/homeassistant/components/automation/logbook.py b/homeassistant/components/automation/logbook.py index 86fb797ea31..529fed80d26 100644 --- a/homeassistant/components/automation/logbook.py +++ b/homeassistant/components/automation/logbook.py @@ -1,5 +1,12 @@ """Describe logbook events.""" from homeassistant.components.logbook import LazyEventPartialState +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_CONTEXT_ID, + LOGBOOK_ENTRY_ENTITY_ID, + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, + LOGBOOK_ENTRY_SOURCE, +) from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME from homeassistant.core import HomeAssistant, callback @@ -15,16 +22,16 @@ def async_describe_events(hass: HomeAssistant, async_describe_event): # type: i def async_describe_logbook_event(event: LazyEventPartialState): # type: ignore[no-untyped-def] """Describe a logbook event.""" data = event.data - message = "has been triggered" + message = "triggered" if ATTR_SOURCE in data: message = f"{message} by {data[ATTR_SOURCE]}" return { - "name": data.get(ATTR_NAME), - "message": message, - "source": data.get(ATTR_SOURCE), - "entity_id": data.get(ATTR_ENTITY_ID), - "context_id": event.context_id, + LOGBOOK_ENTRY_NAME: data.get(ATTR_NAME), + LOGBOOK_ENTRY_MESSAGE: message, + LOGBOOK_ENTRY_SOURCE: data.get(ATTR_SOURCE), + LOGBOOK_ENTRY_ENTITY_ID: data.get(ATTR_ENTITY_ID), + LOGBOOK_ENTRY_CONTEXT_ID: event.context_id, } async_describe_event( diff --git a/homeassistant/components/awair/translations/es.json b/homeassistant/components/awair/translations/es.json index e87ce031b95..1a3240d3802 100644 --- a/homeassistant/components/awair/translations/es.json +++ b/homeassistant/components/awair/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "no_devices_found": "No se encontraron dispositivos en la red", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, diff --git a/homeassistant/components/awair/translations/nl.json b/homeassistant/components/awair/translations/nl.json index d41b85cc09b..1b58405af32 100644 --- a/homeassistant/components/awair/translations/nl.json +++ b/homeassistant/components/awair/translations/nl.json @@ -2,8 +2,8 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "no_devices_found": "Geen apparaten op het netwerk gevonden", - "reauth_successful": "Herauthenticatie was succesvol" + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_access_token": "Ongeldig toegangstoken", diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 23b1ba4c753..2c5239a8fb3 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -1,13 +1,16 @@ """Config flow to configure Axis devices.""" +from __future__ import annotations +from collections.abc import Mapping from ipaddress import ip_address +from typing import Any from urllib.parse import urlsplit import voluptuous as vol from homeassistant import config_entries from homeassistant.components import dhcp, ssdp, zeroconf -from homeassistant.config_entries import SOURCE_IGNORE +from homeassistant.config_entries import SOURCE_IGNORE, ConfigEntry from homeassistant.const import ( CONF_HOST, CONF_MAC, @@ -29,7 +32,7 @@ from .const import ( DEFAULT_VIDEO_SOURCE, DOMAIN as AXIS_DOMAIN, ) -from .device import get_device +from .device import AxisNetworkDevice, get_device from .errors import AuthenticationRequired, CannotConnect AXIS_OUI = {"00:40:8c", "ac:cc:8e", "b8:a4:4f"} @@ -43,18 +46,18 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry): + def async_get_options_flow(config_entry: ConfigEntry) -> AxisOptionsFlowHandler: """Get the options flow for this handler.""" return AxisOptionsFlowHandler(config_entry) - def __init__(self): + def __init__(self) -> None: """Initialize the Axis config flow.""" - self.device_config = {} - self.discovery_schema = {} - self.import_schema = {} - self.serial = None + self.device_config: dict[str, Any] = {} + self.discovery_schema: dict[vol.Required, type[str | int]] | None = None - 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 Axis config flow start. Manage device specific parameters. @@ -71,8 +74,8 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): password=user_input[CONF_PASSWORD], ) - self.serial = device.vapix.serial_number - await self.async_set_unique_id(format_mac(self.serial)) + serial = device.vapix.serial_number + await self.async_set_unique_id(format_mac(serial)) self._abort_if_unique_id_configured( updates={ @@ -91,7 +94,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): CONF_MODEL: device.vapix.product_number, } - return await self._create_entry() + return await self._create_entry(serial) except AuthenticationRequired: errors["base"] = "invalid_auth" @@ -113,7 +116,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): errors=errors, ) - async def _create_entry(self): + async def _create_entry(self, serial: str) -> FlowResult: """Create entry for device. Generate a name to be used as a prefix for device entities. @@ -133,10 +136,10 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): self.device_config[CONF_NAME] = name - title = f"{model} - {self.serial}" + title = f"{model} - {serial}" return self.async_create_entry(title=title, data=self.device_config) - async def async_step_reauth(self, device_config: dict): + async def async_step_reauth(self, device_config: Mapping[str, Any]) -> FlowResult: """Trigger a reauthentication flow.""" self.context["title_placeholders"] = { CONF_NAME: device_config[CONF_NAME], @@ -188,7 +191,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): } ) - async def _process_discovered_device(self, device: dict): + async def _process_discovered_device(self, device: dict[str, Any]) -> FlowResult: """Prepare configuration for a discovered Axis device.""" if device[CONF_MAC][:8] not in AXIS_OUI: return self.async_abort(reason="not_axis_device") @@ -228,18 +231,22 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): class AxisOptionsFlowHandler(config_entries.OptionsFlow): """Handle Axis device options.""" - def __init__(self, config_entry): + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Axis device options flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) - self.device = None + self.device: AxisNetworkDevice | None = None - 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 Axis device options.""" self.device = self.hass.data[AXIS_DOMAIN][self.config_entry.unique_id] return await self.async_step_configure_stream() - async def async_step_configure_stream(self, user_input=None): + async def async_step_configure_stream( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Manage the Axis device stream options.""" if user_input is not None: self.options.update(user_input) @@ -247,6 +254,7 @@ class AxisOptionsFlowHandler(config_entries.OptionsFlow): schema = {} + assert self.device vapix = self.device.api.vapix # Stream profiles diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index 2338a90d562..d0d5e230d2f 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -274,7 +274,9 @@ class AxisNetworkDevice: ) -async def get_device(hass, host, port, username, password): +async def get_device( + hass: HomeAssistant, host: str, port: int, username: str, password: str +) -> axis.AxisDevice: """Create a Axis device.""" session = get_async_client(hass, verify_ssl=False) diff --git a/homeassistant/components/axis/translations/nl.json b/homeassistant/components/axis/translations/nl.json index 3b41c1184ba..f51f59f775f 100644 --- a/homeassistant/components/axis/translations/nl.json +++ b/homeassistant/components/axis/translations/nl.json @@ -7,7 +7,7 @@ }, "error": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie" }, diff --git a/homeassistant/components/axis/translations/sv.json b/homeassistant/components/axis/translations/sv.json index 22e9344b64d..e04267cb5d7 100644 --- a/homeassistant/components/axis/translations/sv.json +++ b/homeassistant/components/axis/translations/sv.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Enheten \u00e4r redan konfigurerad", - "link_local_address": "Link local addresses are not supported", + "link_local_address": "Lokala l\u00e4nkadresser st\u00f6ds inte", "not_axis_device": "Uppt\u00e4ckte enhet som inte \u00e4r en Axis enhet" }, "error": { diff --git a/homeassistant/components/azure_devops/translations/es.json b/homeassistant/components/azure_devops/translations/es.json index 1055fdebf41..c71fc22b1bd 100644 --- a/homeassistant/components/azure_devops/translations/es.json +++ b/homeassistant/components/azure_devops/translations/es.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", - "reauth_successful": "Token de acceso actualizado correctamente " + "already_configured": "La cuenta ya est\u00e1 configurada", + "reauth_successful": "Re-autenticaci\u00f3n realizada correctamente" }, "error": { "cannot_connect": "No se pudo conectar", diff --git a/homeassistant/components/azure_devops/translations/nl.json b/homeassistant/components/azure_devops/translations/nl.json index a57dd85c495..3267ea89c19 100644 --- a/homeassistant/components/azure_devops/translations/nl.json +++ b/homeassistant/components/azure_devops/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/azure_event_hub/translations/ko.json b/homeassistant/components/azure_event_hub/translations/ko.json new file mode 100644 index 00000000000..814b5cba06f --- /dev/null +++ b/homeassistant/components/azure_event_hub/translations/ko.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "cannot_connect": "configuration.yaml\uc758 \uc815\ubcf4\ub85c \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. yaml\uc5d0\uc11c \uc81c\uac70\ud558\uace0 \uad6c\uc131 \ud750\ub984\uc744 \uc0ac\uc6a9\ud558\uc138\uc694.", + "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\ub85c \uc778\ud574 configuration.yaml\uc758 \uc815\ubcf4\ub85c \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. yaml\uc5d0\uc11c \uc81c\uac70\ud558\uace0 \uad6c\uc131 \ud750\ub984\uc744 \uc0ac\uc6a9\ud558\uc138\uc694." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/azure_event_hub/translations/nl.json b/homeassistant/components/azure_event_hub/translations/nl.json index 646d820c2ad..e75443dbc88 100644 --- a/homeassistant/components/azure_event_hub/translations/nl.json +++ b/homeassistant/components/azure_event_hub/translations/nl.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", + "already_configured": "Dienst is al geconfigureerd", "cannot_connect": "Verbinding maken met de credentials uit de configuration.yaml is mislukt, verwijder deze uit yaml en gebruik de config flow.", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "unknown": "Verbinding maken met de credentials uit de configuration.yaml is mislukt met een onbekende fout, verwijder deze uit yaml en gebruik de config flow." }, "error": { diff --git a/homeassistant/components/azure_event_hub/translations/sk.json b/homeassistant/components/azure_event_hub/translations/sk.json new file mode 100644 index 00000000000..3f20d345b26 --- /dev/null +++ b/homeassistant/components/azure_event_hub/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/__init__.py b/homeassistant/components/baf/__init__.py new file mode 100644 index 00000000000..601215d4c61 --- /dev/null +++ b/homeassistant/components/baf/__init__.py @@ -0,0 +1,53 @@ +"""The Big Ass Fans integration.""" +from __future__ import annotations + +import asyncio + +from aiobafi6 import Device, Service +from aiobafi6.discovery import PORT + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_IP_ADDRESS, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady + +from .const import DOMAIN, QUERY_INTERVAL, RUN_TIMEOUT +from .models import BAFData + +PLATFORMS: list[Platform] = [ + Platform.CLIMATE, + Platform.FAN, + Platform.LIGHT, + Platform.NUMBER, + Platform.SENSOR, + Platform.SWITCH, +] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Big Ass Fans from a config entry.""" + ip_address = entry.data[CONF_IP_ADDRESS] + + service = Service(ip_addresses=[ip_address], uuid=entry.unique_id, port=PORT) + device = Device(service, query_interval_seconds=QUERY_INTERVAL) + run_future = device.async_run() + + try: + await asyncio.wait_for(device.async_wait_available(), timeout=RUN_TIMEOUT) + except asyncio.TimeoutError as ex: + run_future.cancel() + raise ConfigEntryNotReady(f"Timed out connecting to {ip_address}") from ex + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = BAFData(device, run_future) + 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): + data: BAFData = hass.data[DOMAIN].pop(entry.entry_id) + data.run_future.cancel() + + return unload_ok diff --git a/homeassistant/components/baf/climate.py b/homeassistant/components/baf/climate.py new file mode 100644 index 00000000000..f785d18e06f --- /dev/null +++ b/homeassistant/components/baf/climate.py @@ -0,0 +1,60 @@ +"""Support for Big Ass Fans auto comfort.""" +from __future__ import annotations + +from typing import Any + +from homeassistant import config_entries +from homeassistant.components.climate import ( + ClimateEntity, + ClimateEntityFeature, + HVACAction, + HVACMode, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import BAFEntity +from .models import BAFData + + +async def async_setup_entry( + hass: HomeAssistant, + entry: config_entries.ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up BAF fan auto comfort.""" + data: BAFData = hass.data[DOMAIN][entry.entry_id] + if data.device.has_fan: + async_add_entities( + [BAFAutoComfort(data.device, f"{data.device.name} Auto Comfort")] + ) + + +class BAFAutoComfort(BAFEntity, ClimateEntity): + """BAF climate auto comfort.""" + + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + _attr_temperature_unit = TEMP_CELSIUS + _attr_hvac_modes = [HVACMode.OFF, HVACMode.FAN_ONLY] + + @callback + def _async_update_attrs(self) -> None: + """Update attrs from device.""" + device = self._device + auto_on = device.auto_comfort_enable + self._attr_hvac_mode = HVACMode.FAN_ONLY if auto_on else HVACMode.OFF + self._attr_hvac_action = HVACAction.FAN if device.speed else HVACAction.OFF + self._attr_target_temperature = device.comfort_ideal_temperature + self._attr_current_temperature = device.temperature + + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set the HVAC mode.""" + self._device.auto_comfort_enable = hvac_mode == HVACMode.FAN_ONLY + + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set the target temperature.""" + if not self._device.auto_comfort_enable: + self._device.auto_comfort_enable = True + self._device.comfort_ideal_temperature = kwargs[ATTR_TEMPERATURE] diff --git a/homeassistant/components/baf/config_flow.py b/homeassistant/components/baf/config_flow.py new file mode 100644 index 00000000000..2326d30937b --- /dev/null +++ b/homeassistant/components/baf/config_flow.py @@ -0,0 +1,122 @@ +"""Config flow for baf.""" +from __future__ import annotations + +import asyncio +import logging +from typing import Any + +from aiobafi6 import Device, Service +from aiobafi6.discovery import PORT +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components import zeroconf +from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.data_entry_flow import FlowResult +from homeassistant.util.network import is_ipv6_address + +from .const import DOMAIN, RUN_TIMEOUT +from .models import BAFDiscovery + +_LOGGER = logging.getLogger(__name__) + + +async def async_try_connect(ip_address: str) -> Device: + """Validate we can connect to a device.""" + device = Device(Service(ip_addresses=[ip_address], port=PORT)) + run_future = device.async_run() + try: + await asyncio.wait_for(device.async_wait_available(), timeout=RUN_TIMEOUT) + except asyncio.TimeoutError as ex: + raise CannotConnect from ex + finally: + run_future.cancel() + return device + + +class BAFFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle BAF discovery config flow.""" + + VERSION = 1 + + def __init__(self) -> None: + """Initialize the BAF config flow.""" + self.discovery: BAFDiscovery | None = None + + async def async_step_zeroconf( + self, discovery_info: zeroconf.ZeroconfServiceInfo + ) -> FlowResult: + """Handle zeroconf discovery.""" + properties = discovery_info.properties + ip_address = discovery_info.host + if is_ipv6_address(ip_address): + return self.async_abort(reason="ipv6_not_supported") + uuid = properties["uuid"] + model = properties["model"] + name = properties["name"] + await self.async_set_unique_id(uuid) + self._abort_if_unique_id_configured(updates={CONF_IP_ADDRESS: ip_address}) + self.discovery = BAFDiscovery(ip_address, name, uuid, model) + return await self.async_step_discovery_confirm() + + async def async_step_discovery_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Confirm discovery.""" + assert self.discovery is not None + discovery = self.discovery + if user_input is not None: + return self.async_create_entry( + title=discovery.name, + data={CONF_IP_ADDRESS: discovery.ip_address}, + ) + placeholders = { + "name": discovery.name, + "model": discovery.model, + "ip_address": discovery.ip_address, + } + self.context["title_placeholders"] = placeholders + self._set_confirm_only() + return self.async_show_form( + step_id="discovery_confirm", description_placeholders=placeholders + ) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + errors = {} + ip_address = (user_input or {}).get(CONF_IP_ADDRESS, "") + if user_input is not None: + try: + device = await async_try_connect(ip_address) + except CannotConnect: + errors[CONF_IP_ADDRESS] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception( + "Unknown exception during connection test to %s", ip_address + ) + errors["base"] = "unknown" + else: + await self.async_set_unique_id( + device.dns_sd_uuid, raise_on_progress=False + ) + self._abort_if_unique_id_configured( + updates={CONF_IP_ADDRESS: ip_address} + ) + return self.async_create_entry( + title=device.name, + data={CONF_IP_ADDRESS: ip_address}, + ) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + {vol.Required(CONF_IP_ADDRESS, default=ip_address): str} + ), + errors=errors, + ) + + +class CannotConnect(Exception): + """Exception to raise when we cannot connect.""" diff --git a/homeassistant/components/baf/const.py b/homeassistant/components/baf/const.py new file mode 100644 index 00000000000..9876d7ffec3 --- /dev/null +++ b/homeassistant/components/baf/const.py @@ -0,0 +1,19 @@ +"""Constants for the Big Ass Fans integration.""" + +DOMAIN = "baf" + +# Most properties are pushed, only the +# query every 5 minutes so we keep the RPM +# sensors up to date +QUERY_INTERVAL = 300 + +RUN_TIMEOUT = 20 + +PRESET_MODE_AUTO = "Auto" + +SPEED_COUNT = 7 +SPEED_RANGE = (1, SPEED_COUNT) + +ONE_MIN_SECS = 60 +ONE_DAY_SECS = 86400 +HALF_DAY_SECS = 43200 diff --git a/homeassistant/components/baf/entity.py b/homeassistant/components/baf/entity.py new file mode 100644 index 00000000000..22054d0b16d --- /dev/null +++ b/homeassistant/components/baf/entity.py @@ -0,0 +1,48 @@ +"""The baf integration entities.""" +from __future__ import annotations + +from aiobafi6 import Device + +from homeassistant.core import callback +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.device_registry import format_mac +from homeassistant.helpers.entity import DeviceInfo, Entity + + +class BAFEntity(Entity): + """Base class for baf entities.""" + + _attr_should_poll = False + + def __init__(self, device: Device, name: str) -> None: + """Initialize the entity.""" + self._device = device + self._attr_unique_id = format_mac(self._device.mac_address) + self._attr_name = name + self._attr_device_info = DeviceInfo( + connections={(dr.CONNECTION_NETWORK_MAC, self._device.mac_address)}, + name=self._device.name, + manufacturer="Big Ass Fans", + model=self._device.model, + sw_version=self._device.firmware_version, + ) + self._async_update_attrs() + + @callback + def _async_update_attrs(self) -> None: + """Update attrs from device.""" + self._attr_available = self._device.available + + @callback + def _async_update_from_device(self, device: Device) -> None: + """Process an update from the device.""" + self._async_update_attrs() + self.async_write_ha_state() + + async def async_added_to_hass(self) -> None: + """Add data updated listener after this object has been initialized.""" + self._device.add_callback(self._async_update_from_device) + + async def async_will_remove_from_hass(self) -> None: + """Remove data updated listener after this object has been initialized.""" + self._device.remove_callback(self._async_update_from_device) diff --git a/homeassistant/components/baf/fan.py b/homeassistant/components/baf/fan.py new file mode 100644 index 00000000000..360926363a5 --- /dev/null +++ b/homeassistant/components/baf/fan.py @@ -0,0 +1,97 @@ +"""Support for Big Ass Fans fan.""" +from __future__ import annotations + +import math +from typing import Any + +from aiobafi6 import OffOnAuto + +from homeassistant import config_entries +from homeassistant.components.fan import ( + DIRECTION_FORWARD, + DIRECTION_REVERSE, + FanEntity, + FanEntityFeature, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util.percentage import ( + percentage_to_ranged_value, + ranged_value_to_percentage, +) + +from .const import DOMAIN, PRESET_MODE_AUTO, SPEED_COUNT, SPEED_RANGE +from .entity import BAFEntity +from .models import BAFData + + +async def async_setup_entry( + hass: HomeAssistant, + entry: config_entries.ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up SenseME fans.""" + data: BAFData = hass.data[DOMAIN][entry.entry_id] + if data.device.has_fan: + async_add_entities([BAFFan(data.device, data.device.name)]) + + +class BAFFan(BAFEntity, FanEntity): + """BAF ceiling fan component.""" + + _attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.DIRECTION + _attr_preset_modes = [PRESET_MODE_AUTO] + _attr_speed_count = SPEED_COUNT + + @callback + def _async_update_attrs(self) -> None: + """Update attrs from device.""" + self._attr_is_on = self._device.fan_mode == OffOnAuto.ON + self._attr_current_direction = DIRECTION_FORWARD + if self._device.reverse_enable: + self._attr_current_direction = DIRECTION_REVERSE + if self._device.speed is not None: + self._attr_percentage = ranged_value_to_percentage( + SPEED_RANGE, self._device.speed + ) + else: + self._attr_percentage = None + auto = self._device.fan_mode == OffOnAuto.AUTO + self._attr_preset_mode = PRESET_MODE_AUTO if auto else None + super()._async_update_attrs() + + async def async_set_percentage(self, percentage: int) -> None: + """Set the speed of the fan, as a percentage.""" + device = self._device + if device.fan_mode != OffOnAuto.ON: + device.fan_mode = OffOnAuto.ON + device.speed = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + + async def async_turn_on( + self, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, + ) -> None: + """Turn the fan on with a percentage or preset mode.""" + if preset_mode is not None: + await self.async_set_preset_mode(preset_mode) + return + if percentage is None: + self._device.fan_mode = OffOnAuto.ON + return + await self.async_set_percentage(percentage) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the fan off.""" + self._device.fan_mode = OffOnAuto.OFF + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the preset mode of the fan.""" + if preset_mode != PRESET_MODE_AUTO: + raise ValueError(f"Invalid preset mode: {preset_mode}") + self._device.fan_mode = OffOnAuto.AUTO + + async def async_set_direction(self, direction: str) -> None: + """Set the direction of the fan.""" + self._device.reverse_enable = direction == DIRECTION_REVERSE diff --git a/homeassistant/components/baf/light.py b/homeassistant/components/baf/light.py new file mode 100644 index 00000000000..b177d383cd5 --- /dev/null +++ b/homeassistant/components/baf/light.py @@ -0,0 +1,102 @@ +"""Support for Big Ass Fans lights.""" +from __future__ import annotations + +from typing import Any + +from aiobafi6 import Device, OffOnAuto + +from homeassistant import config_entries +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ColorMode, + LightEntity, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util.color import ( + color_temperature_kelvin_to_mired, + color_temperature_mired_to_kelvin, +) + +from .const import DOMAIN +from .entity import BAFEntity +from .models import BAFData + + +async def async_setup_entry( + hass: HomeAssistant, + entry: config_entries.ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up BAF lights.""" + data: BAFData = hass.data[DOMAIN][entry.entry_id] + if data.device.has_light: + klass = BAFFanLight if data.device.has_fan else BAFStandaloneLight + async_add_entities([klass(data.device)]) + + +class BAFLight(BAFEntity, LightEntity): + """Representation of a Big Ass Fans light.""" + + @callback + def _async_update_attrs(self) -> None: + """Update attrs from device.""" + self._attr_is_on = self._device.light_mode == OffOnAuto.ON + if self._device.light_brightness_level is not None: + self._attr_brightness = round( + self._device.light_brightness_level / 16 * 255 + ) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the light.""" + if (brightness := kwargs.get(ATTR_BRIGHTNESS)) is not None: + self._device.light_brightness_level = max(int(brightness / 255 * 16), 1) + else: + self._device.light_mode = OffOnAuto.ON + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the light.""" + self._device.light_mode = OffOnAuto.OFF + + +class BAFFanLight(BAFLight): + """Representation of a Big Ass Fans light on a fan.""" + + def __init__(self, device: Device) -> None: + """Init a fan light.""" + super().__init__(device, device.name) + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} + self._attr_color_mode = ColorMode.BRIGHTNESS + + +class BAFStandaloneLight(BAFLight): + """Representation of a Big Ass Fans light.""" + + def __init__(self, device: Device) -> None: + """Init a standalone light.""" + super().__init__(device, f"{device.name} Light") + 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_warmest_color_temperature + ) + self._attr_max_mireds = color_temperature_kelvin_to_mired( + device.light_coolest_color_temperature + ) + + @callback + def _async_update_attrs(self) -> None: + """Update attrs from device.""" + super()._async_update_attrs() + self._attr_color_temp = color_temperature_kelvin_to_mired( + self._device.light_color_temperature + ) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the light.""" + if (color_temp := kwargs.get(ATTR_COLOR_TEMP)) is not None: + self._device.light_color_temperature = color_temperature_mired_to_kelvin( + color_temp + ) + await super().async_turn_on(**kwargs) diff --git a/homeassistant/components/baf/manifest.json b/homeassistant/components/baf/manifest.json new file mode 100644 index 00000000000..9dfc35685e3 --- /dev/null +++ b/homeassistant/components/baf/manifest.json @@ -0,0 +1,13 @@ +{ + "domain": "baf", + "name": "Big Ass Fans", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/baf", + "requirements": ["aiobafi6==0.3.0"], + "codeowners": ["@bdraco", "@jfroy"], + "iot_class": "local_push", + "zeroconf": [ + { "type": "_api._tcp.local.", "properties": { "model": "haiku*" } }, + { "type": "_api._tcp.local.", "properties": { "model": "i6*" } } + ] +} diff --git a/homeassistant/components/baf/models.py b/homeassistant/components/baf/models.py new file mode 100644 index 00000000000..de5c4a3498b --- /dev/null +++ b/homeassistant/components/baf/models.py @@ -0,0 +1,25 @@ +"""The baf integration models.""" +from __future__ import annotations + +import asyncio +from dataclasses import dataclass + +from aiobafi6 import Device + + +@dataclass +class BAFData: + """Data for the baf integration.""" + + device: Device + run_future: asyncio.Future + + +@dataclass +class BAFDiscovery: + """A BAF Discovery.""" + + ip_address: str + name: str + uuid: str + model: str diff --git a/homeassistant/components/baf/number.py b/homeassistant/components/baf/number.py new file mode 100644 index 00000000000..84358e79669 --- /dev/null +++ b/homeassistant/components/baf/number.py @@ -0,0 +1,151 @@ +"""Support for Big Ass Fans number.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Optional, cast + +from aiobafi6 import Device + +from homeassistant import config_entries +from homeassistant.components.number import ( + NumberEntity, + NumberEntityDescription, + NumberMode, +) +from homeassistant.const import TIME_SECONDS +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, HALF_DAY_SECS, ONE_DAY_SECS, ONE_MIN_SECS, SPEED_RANGE +from .entity import BAFEntity +from .models import BAFData + + +@dataclass +class BAFNumberDescriptionMixin: + """Required values for BAF sensors.""" + + value_fn: Callable[[Device], int | None] + mode: NumberMode + + +@dataclass +class BAFNumberDescription(NumberEntityDescription, BAFNumberDescriptionMixin): + """Class describing BAF sensor entities.""" + + +FAN_NUMBER_DESCRIPTIONS = ( + BAFNumberDescription( + key="return_to_auto_timeout", + name="Return to Auto Timeout", + min_value=ONE_MIN_SECS, + max_value=HALF_DAY_SECS, + entity_category=EntityCategory.CONFIG, + unit_of_measurement=TIME_SECONDS, + value_fn=lambda device: cast(Optional[int], device.return_to_auto_timeout), + mode=NumberMode.SLIDER, + ), + BAFNumberDescription( + key="motion_sense_timeout", + name="Motion Sense Timeout", + min_value=ONE_MIN_SECS, + max_value=ONE_DAY_SECS, + entity_category=EntityCategory.CONFIG, + unit_of_measurement=TIME_SECONDS, + value_fn=lambda device: cast(Optional[int], device.motion_sense_timeout), + mode=NumberMode.SLIDER, + ), + BAFNumberDescription( + key="comfort_min_speed", + name="Auto Comfort Minimum Speed", + min_value=0, + max_value=SPEED_RANGE[1] - 1, + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[int], device.comfort_min_speed), + mode=NumberMode.BOX, + ), + BAFNumberDescription( + key="comfort_max_speed", + name="Auto Comfort Maximum Speed", + min_value=1, + max_value=SPEED_RANGE[1], + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[int], device.comfort_max_speed), + mode=NumberMode.BOX, + ), + BAFNumberDescription( + key="comfort_heat_assist_speed", + name="Auto Comfort Heat Assist Speed", + min_value=SPEED_RANGE[0], + max_value=SPEED_RANGE[1], + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[int], device.comfort_heat_assist_speed), + mode=NumberMode.BOX, + ), +) + +LIGHT_NUMBER_DESCRIPTIONS = ( + BAFNumberDescription( + key="light_return_to_auto_timeout", + name="Light Return to Auto Timeout", + min_value=ONE_MIN_SECS, + max_value=HALF_DAY_SECS, + entity_category=EntityCategory.CONFIG, + unit_of_measurement=TIME_SECONDS, + value_fn=lambda device: cast( + Optional[int], device.light_return_to_auto_timeout + ), + mode=NumberMode.SLIDER, + ), + BAFNumberDescription( + key="light_auto_motion_timeout", + name="Light Motion Sense Timeout", + min_value=ONE_MIN_SECS, + max_value=ONE_DAY_SECS, + entity_category=EntityCategory.CONFIG, + unit_of_measurement=TIME_SECONDS, + value_fn=lambda device: cast(Optional[int], device.light_auto_motion_timeout), + mode=NumberMode.SLIDER, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: config_entries.ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up BAF numbers.""" + data: BAFData = hass.data[DOMAIN][entry.entry_id] + device = data.device + descriptions: list[BAFNumberDescription] = [] + if device.has_fan: + descriptions.extend(FAN_NUMBER_DESCRIPTIONS) + if device.has_light: + descriptions.extend(LIGHT_NUMBER_DESCRIPTIONS) + async_add_entities(BAFNumber(device, description) for description in descriptions) + + +class BAFNumber(BAFEntity, NumberEntity): + """BAF number.""" + + entity_description: BAFNumberDescription + + def __init__(self, device: Device, description: BAFNumberDescription) -> None: + """Initialize the entity.""" + self.entity_description = description + super().__init__(device, f"{device.name} {description.name}") + self._attr_unique_id = f"{self._device.mac_address}-{description.key}" + self._attr_mode = description.mode + + @callback + def _async_update_attrs(self) -> None: + """Update attrs from device.""" + if (value := self.entity_description.value_fn(self._device)) is not None: + self._attr_value = float(value) + + async def async_set_value(self, value: float) -> None: + """Set the value.""" + setattr(self._device, self.entity_description.key, int(value)) diff --git a/homeassistant/components/baf/sensor.py b/homeassistant/components/baf/sensor.py new file mode 100644 index 00000000000..0f4239962cf --- /dev/null +++ b/homeassistant/components/baf/sensor.py @@ -0,0 +1,132 @@ +"""Support for Big Ass Fans sensors.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Optional, cast + +from aiobafi6 import Device + +from homeassistant import config_entries +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.const import PERCENTAGE, TEMP_CELSIUS +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import BAFEntity +from .models import BAFData + + +@dataclass +class BAFSensorDescriptionMixin: + """Required values for BAF sensors.""" + + value_fn: Callable[[Device], int | float | str | None] + + +@dataclass +class BAFSensorDescription( + SensorEntityDescription, + BAFSensorDescriptionMixin, +): + """Class describing BAF sensor entities.""" + + +BASE_SENSORS = ( + BAFSensorDescription( + key="temperature", + name="Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device: cast(Optional[float], device.temperature), + ), +) + +DEFINED_ONLY_SENSORS = ( + BAFSensorDescription( + key="humidity", + name="Humidity", + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device: cast(Optional[float], device.humidity), + ), +) + +FAN_SENSORS = ( + BAFSensorDescription( + key="current_rpm", + name="Current RPM", + native_unit_of_measurement="RPM", + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: cast(Optional[int], device.current_rpm), + ), + BAFSensorDescription( + key="target_rpm", + name="Target RPM", + native_unit_of_measurement="RPM", + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: cast(Optional[int], device.target_rpm), + ), + BAFSensorDescription( + key="wifi_ssid", + name="WiFi SSID", + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: cast(Optional[int], device.wifi_ssid), + ), + BAFSensorDescription( + key="ip_address", + name="IP Address", + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda device: cast(Optional[str], device.ip_address), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: config_entries.ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up BAF fan sensors.""" + data: BAFData = hass.data[DOMAIN][entry.entry_id] + device = data.device + sensors_descriptions = list(BASE_SENSORS) + for description in DEFINED_ONLY_SENSORS: + if getattr(device, description.key): + sensors_descriptions.append(description) + if device.has_fan: + sensors_descriptions.extend(FAN_SENSORS) + async_add_entities( + BAFSensor(device, description) for description in sensors_descriptions + ) + + +class BAFSensor(BAFEntity, SensorEntity): + """BAF sensor.""" + + entity_description: BAFSensorDescription + + def __init__(self, device: Device, description: BAFSensorDescription) -> None: + """Initialize the entity.""" + self.entity_description = description + super().__init__(device, f"{device.name} {description.name}") + self._attr_unique_id = f"{self._device.mac_address}-{description.key}" + + @callback + def _async_update_attrs(self) -> None: + """Update attrs from device.""" + description = self.entity_description + self._attr_native_value = description.value_fn(self._device) diff --git a/homeassistant/components/baf/strings.json b/homeassistant/components/baf/strings.json new file mode 100644 index 00000000000..a26e3152326 --- /dev/null +++ b/homeassistant/components/baf/strings.json @@ -0,0 +1,23 @@ +{ + "config": { + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "user": { + "data": { + "ip_address": "[%key:common::config_flow::data::ip%]" + } + }, + "discovery_confirm": { + "description": "Do you want to setup {name} - {model} ({ip_address})?" + } + }, + "abort": { + "ipv6_not_supported": "IPv6 is not supported.", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + } +} diff --git a/homeassistant/components/baf/switch.py b/homeassistant/components/baf/switch.py new file mode 100644 index 00000000000..6cefa0db65d --- /dev/null +++ b/homeassistant/components/baf/switch.py @@ -0,0 +1,148 @@ +"""Support for Big Ass Fans switch.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any, Optional, cast + +from aiobafi6 import Device + +from homeassistant import config_entries +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import BAFEntity +from .models import BAFData + + +@dataclass +class BAFSwitchDescriptionMixin: + """Required values for BAF sensors.""" + + value_fn: Callable[[Device], bool | None] + + +@dataclass +class BAFSwitchDescription( + SwitchEntityDescription, + BAFSwitchDescriptionMixin, +): + """Class describing BAF switch entities.""" + + +BASE_SWITCHES = [ + BAFSwitchDescription( + key="legacy_ir_remote_enable", + name="Legacy IR Remote", + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[bool], device.legacy_ir_remote_enable), + ), + BAFSwitchDescription( + key="led_indicators_enable", + name="Led Indicators", + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[bool], device.led_indicators_enable), + ), +] + +FAN_SWITCHES = [ + BAFSwitchDescription( + key="comfort_heat_assist_enable", + name="Auto Comfort Heat Assist", + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[bool], device.comfort_heat_assist_enable), + ), + BAFSwitchDescription( + key="fan_beep_enable", + name="Beep", + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[bool], device.fan_beep_enable), + ), + BAFSwitchDescription( + key="eco_enable", + name="Eco Mode", + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[bool], device.eco_enable), + ), + BAFSwitchDescription( + key="motion_sense_enable", + name="Motion Sense", + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[bool], device.motion_sense_enable), + ), + BAFSwitchDescription( + key="return_to_auto_enable", + name="Return to Auto", + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[bool], device.return_to_auto_enable), + ), + BAFSwitchDescription( + key="whoosh_enable", + name="Whoosh", + # Not a configuration switch + value_fn=lambda device: cast(Optional[bool], device.whoosh_enable), + ), +] + + +LIGHT_SWITCHES = [ + BAFSwitchDescription( + key="light_dim_to_warm_enable", + name="Dim to Warm", + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast(Optional[bool], device.light_dim_to_warm_enable), + ), + BAFSwitchDescription( + key="light_return_to_auto_enable", + name="Light Return to Auto", + entity_category=EntityCategory.CONFIG, + value_fn=lambda device: cast( + Optional[bool], device.light_return_to_auto_enable + ), + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: config_entries.ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up BAF fan switches.""" + data: BAFData = hass.data[DOMAIN][entry.entry_id] + device = data.device + descriptions: list[BAFSwitchDescription] = [] + descriptions.extend(BASE_SWITCHES) + if device.has_fan: + descriptions.extend(FAN_SWITCHES) + if device.has_light: + descriptions.extend(LIGHT_SWITCHES) + async_add_entities(BAFSwitch(device, description) for description in descriptions) + + +class BAFSwitch(BAFEntity, SwitchEntity): + """BAF switch component.""" + + entity_description: BAFSwitchDescription + + def __init__(self, device: Device, description: BAFSwitchDescription) -> None: + """Initialize the entity.""" + self.entity_description = description + super().__init__(device, f"{device.name} {description.name}") + self._attr_unique_id = f"{self._device.mac_address}-{description.key}" + + @callback + def _async_update_attrs(self) -> None: + """Update attrs from device.""" + self._attr_is_on = self.entity_description.value_fn(self._device) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the switch.""" + setattr(self._device, self.entity_description.key, True) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the switch.""" + setattr(self._device, self.entity_description.key, False) diff --git a/homeassistant/components/baf/translations/bg.json b/homeassistant/components/baf/translations/bg.json new file mode 100644 index 00000000000..c5e33f4c079 --- /dev/null +++ b/homeassistant/components/baf/translations/bg.json @@ -0,0 +1,23 @@ +{ + "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", + "ipv6_not_supported": "IPv6 \u043d\u0435 \u0441\u0435 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430." + }, + "error": { + "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": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 {name} - {model} ({ip_address})?" + }, + "user": { + "data": { + "ip_address": "IP \u0430\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/ca.json b/homeassistant/components/baf/translations/ca.json new file mode 100644 index 00000000000..f471d071017 --- /dev/null +++ b/homeassistant/components/baf/translations/ca.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "ipv6_not_supported": "IPv6 no est\u00e0 suportat." + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "unknown": "Error inesperat" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "Vols configurar {name} - {model} ({ip_address})?" + }, + "user": { + "data": { + "ip_address": "Adre\u00e7a IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/cs.json b/homeassistant/components/baf/translations/cs.json new file mode 100644 index 00000000000..04f18366eaf --- /dev/null +++ b/homeassistant/components/baf/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", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "ip_address": "IP adresa" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/de.json b/homeassistant/components/baf/translations/de.json new file mode 100644 index 00000000000..8e5344cf9b0 --- /dev/null +++ b/homeassistant/components/baf/translations/de.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "ipv6_not_supported": "IPv6 wird nicht unterst\u00fctzt." + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "M\u00f6chtest du {name} - {model} ({ip_address}) einrichten?" + }, + "user": { + "data": { + "ip_address": "IP-Adresse" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/el.json b/homeassistant/components/baf/translations/el.json new file mode 100644 index 00000000000..a73fe3aa490 --- /dev/null +++ b/homeassistant/components/baf/translations/el.json @@ -0,0 +1,23 @@ +{ + "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", + "ipv6_not_supported": "\u03a4\u03bf IPv6 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9." + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} - {model} ({ip_address});" + }, + "user": { + "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/en.json b/homeassistant/components/baf/translations/en.json new file mode 100644 index 00000000000..4bb7256a692 --- /dev/null +++ b/homeassistant/components/baf/translations/en.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured", + "ipv6_not_supported": "IPv6 is not supported." + }, + "error": { + "cannot_connect": "Failed to connect", + "unknown": "Unexpected error" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "Do you want to setup {name} - {model} ({ip_address})?" + }, + "user": { + "data": { + "ip_address": "IP Address" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/es.json b/homeassistant/components/baf/translations/es.json new file mode 100644 index 00000000000..4e2800090d2 --- /dev/null +++ b/homeassistant/components/baf/translations/es.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "ipv6_not_supported": "IPv6 no est\u00e1 soportado." + }, + "error": { + "cannot_connect": "Error al conectar", + "unknown": "Error Inesperado" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "\u00bfQuieres configurar {name} - {model} ({ip_address})?" + }, + "user": { + "data": { + "ip_address": "Direcci\u00f3n IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/et.json b/homeassistant/components/baf/translations/et.json new file mode 100644 index 00000000000..e958eb4545d --- /dev/null +++ b/homeassistant/components/baf/translations/et.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "ipv6_not_supported": "IPv6 ei ole toetatud." + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "Kas seadistada {name} \u2013 {model} ( {ip_address} )?" + }, + "user": { + "data": { + "ip_address": "IP aadress" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/fr.json b/homeassistant/components/baf/translations/fr.json new file mode 100644 index 00000000000..8088de223c3 --- /dev/null +++ b/homeassistant/components/baf/translations/fr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "ipv6_not_supported": "IPv6 n'est pas pris en charge." + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "unknown": "Erreur inattendue" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "Voulez-vous configurer {name} - {model} ({ip_address})\u00a0?" + }, + "user": { + "data": { + "ip_address": "Adresse IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/he.json b/homeassistant/components/baf/translations/he.json new file mode 100644 index 00000000000..61151ab6737 --- /dev/null +++ b/homeassistant/components/baf/translations/he.json @@ -0,0 +1,19 @@ +{ + "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", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "user": { + "data": { + "ip_address": "\u05db\u05ea\u05d5\u05d1\u05ea IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/hu.json b/homeassistant/components/baf/translations/hu.json new file mode 100644 index 00000000000..82e7d6aae75 --- /dev/null +++ b/homeassistant/components/baf/translations/hu.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "ipv6_not_supported": "Az IPv6 nem t\u00e1mogatott." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name} - {model} ({ip_address})?" + }, + "user": { + "data": { + "ip_address": "IP c\u00edm" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/id.json b/homeassistant/components/baf/translations/id.json new file mode 100644 index 00000000000..116742e613c --- /dev/null +++ b/homeassistant/components/baf/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "ipv6_not_supported": "IPv6 tidak didukung." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "Ingin menyiapkan {name} - {model} ({ip_address})?" + }, + "user": { + "data": { + "ip_address": "Alamat IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/it.json b/homeassistant/components/baf/translations/it.json new file mode 100644 index 00000000000..86fcdab723b --- /dev/null +++ b/homeassistant/components/baf/translations/it.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "ipv6_not_supported": "IPv6 non \u00e8 supportato." + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "unknown": "Errore imprevisto" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "Vuoi configurare {name} - {model} ({ip_address})?" + }, + "user": { + "data": { + "ip_address": "Indirizzo IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/ja.json b/homeassistant/components/baf/translations/ja.json new file mode 100644 index 00000000000..0e256a2f92c --- /dev/null +++ b/homeassistant/components/baf/translations/ja.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "ipv6_not_supported": "IPv6\u306b\u306f\u5bfe\u5fdc\u3057\u3066\u3044\u307e\u305b\u3093\u3002" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "{name} - {model} ({ip_address}) \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" + }, + "user": { + "data": { + "ip_address": "IP\u30a2\u30c9\u30ec\u30b9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/ko.json b/homeassistant/components/baf/translations/ko.json new file mode 100644 index 00000000000..9bf0efc5136 --- /dev/null +++ b/homeassistant/components/baf/translations/ko.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "ipv6_not_supported": "IPv6\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "flow_title": "{name} - {model} ( {ip_address} )", + "step": { + "discovery_confirm": { + "description": "{name} - {model} ({ip_address}) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + }, + "user": { + "data": { + "ip_address": "IP \uc8fc\uc18c" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/nl.json b/homeassistant/components/baf/translations/nl.json new file mode 100644 index 00000000000..a05aedd9381 --- /dev/null +++ b/homeassistant/components/baf/translations/nl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "ipv6_not_supported": "IPv6 wordt niet ondersteund." + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "unknown": "Onverwachte fout" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "Wilt je {name} - {model} ({ip_address}) instellen?" + }, + "user": { + "data": { + "ip_address": "IP-adres" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/no.json b/homeassistant/components/baf/translations/no.json new file mode 100644 index 00000000000..fa65c5ca21f --- /dev/null +++ b/homeassistant/components/baf/translations/no.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "ipv6_not_supported": "IPv6 st\u00f8ttes ikke." + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "unknown": "Uventet feil" + }, + "flow_title": "{name} \u2013 {model} ( {ip_address} )", + "step": { + "discovery_confirm": { + "description": "Vil du konfigurere {name} - {model} ( {ip_address} )?" + }, + "user": { + "data": { + "ip_address": "IP adresse" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/pl.json b/homeassistant/components/baf/translations/pl.json new file mode 100644 index 00000000000..b0347c8318c --- /dev/null +++ b/homeassistant/components/baf/translations/pl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "ipv6_not_supported": "IPv6 nie jest obs\u0142ugiwany." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "Czy chcesz skonfigurowa\u0107 {name} - {model} ({ip_address})?" + }, + "user": { + "data": { + "ip_address": "Adres IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/pt-BR.json b/homeassistant/components/baf/translations/pt-BR.json new file mode 100644 index 00000000000..72ce0dd06fc --- /dev/null +++ b/homeassistant/components/baf/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "ipv6_not_supported": "IPv6 n\u00e3o \u00e9 suportado." + }, + "error": { + "cannot_connect": "Falhou ao se conectar", + "unknown": "Erro inesperado" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "Deseja configurar {name} - {model} ({ip_address})?" + }, + "user": { + "data": { + "ip_address": "Endere\u00e7o IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/ru.json b/homeassistant/components/baf/translations/ru.json new file mode 100644 index 00000000000..e2e5d43123e --- /dev/null +++ b/homeassistant/components/baf/translations/ru.json @@ -0,0 +1,23 @@ +{ + "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.", + "ipv6_not_supported": "IPv6 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} - {model} ({ip_address})?" + }, + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/sk.json b/homeassistant/components/baf/translations/sk.json new file mode 100644 index 00000000000..5ca1b9820a9 --- /dev/null +++ b/homeassistant/components/baf/translations/sk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie je u\u017e nakonfigurovan\u00e9", + "ipv6_not_supported": "IPv6 nie je podporovan\u00e9" + }, + "error": { + "cannot_connect": "Nepodarilo sa pripoji\u0165", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "ip_address": "IP adresa" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/tr.json b/homeassistant/components/baf/translations/tr.json new file mode 100644 index 00000000000..ffa458b7366 --- /dev/null +++ b/homeassistant/components/baf/translations/tr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "ipv6_not_supported": "IPv6 desteklenmiyor." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "{name} - {model} ( {ip_address} ) kurulumunu yapmak istiyor musunuz?" + }, + "user": { + "data": { + "ip_address": "IP Adresi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/baf/translations/zh-Hant.json b/homeassistant/components/baf/translations/zh-Hant.json new file mode 100644 index 00000000000..4d191cb6d87 --- /dev/null +++ b/homeassistant/components/baf/translations/zh-Hant.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "ipv6_not_supported": "\u4e0d\u652f\u63f4 IPv6\u3002" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "flow_title": "{name} - {model} ({ip_address})", + "step": { + "discovery_confirm": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} - {model} ({ip_address})\uff1f" + }, + "user": { + "data": { + "ip_address": "IP \u4f4d\u5740" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/balboa/__init__.py b/homeassistant/components/balboa/__init__.py index 731f7b2c2d1..60989ecc6d6 100644 --- a/homeassistant/components/balboa/__init__.py +++ b/homeassistant/components/balboa/__init__.py @@ -22,6 +22,7 @@ from .const import ( SIGNAL_UPDATE, ) +KEEP_ALIVE_INTERVAL = timedelta(minutes=1) SYNC_TIME_INTERVAL = timedelta(days=1) @@ -31,7 +32,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.debug("Attempting to connect to %s", host) spa = BalboaSpaWifi(host) - connected = await spa.connect() if not connected: _LOGGER.error("Failed to connect to spa at %s", host) @@ -39,11 +39,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {})[entry.entry_id] = spa - # send config requests, and then listen until we are configured. - await spa.send_mod_ident_req() - await spa.send_panel_req(0, 1) - - async def _async_balboa_update_cb(): + async def _async_balboa_update_cb() -> None: """Primary update callback called from pybalboa.""" _LOGGER.debug("Primary update callback triggered") async_dispatcher_send(hass, SIGNAL_UPDATE.format(entry.entry_id)) @@ -52,13 +48,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: spa.new_data_cb = _async_balboa_update_cb _LOGGER.debug("Starting listener and monitor tasks") - asyncio.create_task(spa.listen()) + monitoring_tasks = [asyncio.create_task(spa.listen())] await spa.spa_configured() - asyncio.create_task(spa.check_connection_status()) + monitoring_tasks.append(asyncio.create_task(spa.check_connection_status())) + + def stop_monitoring() -> None: + """Stop monitoring the spa connection.""" + _LOGGER.debug("Canceling listener and monitor tasks") + for task in monitoring_tasks: + task.cancel() + + entry.async_on_unload(stop_monitoring) # At this point we have a configured spa. hass.config_entries.async_setup_platforms(entry, PLATFORMS) + async def keep_alive(now: datetime) -> None: + """Keep alive task.""" + _LOGGER.debug("Keep alive") + await spa.send_mod_ident_req() + + entry.async_on_unload( + async_track_time_interval(hass, keep_alive, KEEP_ALIVE_INTERVAL) + ) + # call update_listener on startup and for options change as well. await async_setup_time_sync(hass, entry) entry.async_on_unload(entry.add_update_listener(update_listener)) @@ -68,14 +81,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - _LOGGER.debug("Disconnecting from spa") - spa = hass.data[DOMAIN][entry.entry_id] - await spa.disconnect() + spa: BalboaSpaWifi = hass.data[DOMAIN][entry.entry_id] if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass.data[DOMAIN].pop(entry.entry_id) + await spa.disconnect() + return unload_ok @@ -90,9 +103,9 @@ async def async_setup_time_sync(hass: HomeAssistant, entry: ConfigEntry) -> None return _LOGGER.debug("Setting up daily time sync") - spa = hass.data[DOMAIN][entry.entry_id] + spa: BalboaSpaWifi = hass.data[DOMAIN][entry.entry_id] - async def sync_time(now: datetime): + async def sync_time(now: datetime) -> None: _LOGGER.debug("Syncing time with Home Assistant") await spa.set_time(time.strptime(str(dt_util.now()), "%Y-%m-%d %H:%M:%S.%f%z")) diff --git a/homeassistant/components/balboa/config_flow.py b/homeassistant/components/balboa/config_flow.py index 42895e5ccd6..c0301bc9892 100644 --- a/homeassistant/components/balboa/config_flow.py +++ b/homeassistant/components/balboa/config_flow.py @@ -1,12 +1,16 @@ """Config flow for Balboa Spa Client integration.""" +from __future__ import annotations + import asyncio +from typing import Any from pybalboa import BalboaSpaWifi import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant import config_entries, exceptions from homeassistant.const import CONF_HOST from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from .const import _LOGGER, CONF_SYNC_TIME, DOMAIN @@ -14,9 +18,8 @@ from .const import _LOGGER, CONF_SYNC_TIME, DOMAIN DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str}) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows us to connect.""" - _LOGGER.debug("Attempting to connect to %s", data[CONF_HOST]) spa = BalboaSpaWifi(data[CONF_HOST]) connected = await spa.connect() @@ -24,16 +27,12 @@ async def validate_input(hass: core.HomeAssistant, data): if not connected: raise CannotConnect - # send config requests, and then listen until we are configured. - await spa.send_mod_ident_req() - await spa.send_panel_req(0, 1) - - asyncio.create_task(spa.listen()) - + task = asyncio.create_task(spa.listen()) await spa.spa_configured() mac_addr = format_mac(spa.get_macaddr()) model = spa.get_model_name() + task.cancel() await spa.disconnect() return {"title": model, "formatted_mac": mac_addr} @@ -46,17 +45,21 @@ class BalboaSpaClientFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry): + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> config_entries.OptionsFlow: """Get the options flow for this handler.""" return BalboaSpaClientOptionsFlowHandler(config_entry) - 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.""" errors = {} if user_input is not None: self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]}) try: - info = await validate_input(self.hass, user_input) + info = await validate_input(user_input) except CannotConnect: errors["base"] = "cannot_connect" except Exception: # pylint: disable=broad-except @@ -79,11 +82,13 @@ class CannotConnect(exceptions.HomeAssistantError): class BalboaSpaClientOptionsFlowHandler(config_entries.OptionsFlow): """Handle Balboa Spa Client options.""" - def __init__(self, config_entry): + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize Balboa Spa Client 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: """Manage Balboa Spa Client options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/binary_sensor/device_condition.py b/homeassistant/components/binary_sensor/device_condition.py index 1f16956ff41..4b3aa70d716 100644 --- a/homeassistant/components/binary_sensor/device_condition.py +++ b/homeassistant/components/binary_sensor/device_condition.py @@ -12,12 +12,12 @@ from homeassistant.const import ( CONF_TYPE, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import condition, config_validation as cv -from homeassistant.helpers.entity import get_device_class -from homeassistant.helpers.entity_registry import ( - async_entries_for_device, - async_get_registry, +from homeassistant.helpers import ( + condition, + config_validation as cv, + entity_registry as er, ) +from homeassistant.helpers.entity import get_device_class from homeassistant.helpers.typing import ConfigType from . import DOMAIN, BinarySensorDeviceClass @@ -268,10 +268,10 @@ async def async_get_conditions( ) -> list[dict[str, str]]: """List device conditions.""" conditions: list[dict[str, str]] = [] - entity_registry = await async_get_registry(hass) + entity_registry = er.async_get(hass) entries = [ entry - for entry in async_entries_for_device(entity_registry, device_id) + for entry in er.async_entries_for_device(entity_registry, device_id) if entry.domain == DOMAIN ] diff --git a/homeassistant/components/binary_sensor/device_trigger.py b/homeassistant/components/binary_sensor/device_trigger.py index 9989e415242..12b620b8c4f 100644 --- a/homeassistant/components/binary_sensor/device_trigger.py +++ b/homeassistant/components/binary_sensor/device_trigger.py @@ -1,6 +1,10 @@ """Provides device triggers for binary sensors.""" import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation.const import ( CONF_TURNED_OFF, @@ -8,9 +12,10 @@ from homeassistant.components.device_automation.const import ( ) from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.const import CONF_ENTITY_ID, CONF_FOR, CONF_TYPE -from homeassistant.helpers import config_validation as cv +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity import get_device_class -from homeassistant.helpers.entity_registry import async_entries_for_device +from homeassistant.helpers.typing import ConfigType from . import DOMAIN, BinarySensorDeviceClass @@ -255,7 +260,12 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( ) -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" trigger_type = config[CONF_TYPE] if trigger_type in TURNED_ON: @@ -277,14 +287,16 @@ async def async_attach_trigger(hass, config, action, automation_info): ) -async def async_get_triggers(hass, device_id): +async def async_get_triggers( + hass: HomeAssistant, device_id: str +) -> list[dict[str, str]]: """List device triggers.""" - triggers = [] - entity_registry = await hass.helpers.entity_registry.async_get_registry() + triggers: list[dict[str, str]] = [] + entity_registry = er.async_get(hass) entries = [ entry - for entry in async_entries_for_device(entity_registry, device_id) + for entry in er.async_entries_for_device(entity_registry, device_id) if entry.domain == DOMAIN ] @@ -309,7 +321,9 @@ async def async_get_triggers(hass, device_id): return triggers -async def async_get_trigger_capabilities(hass, config): +async def async_get_trigger_capabilities( + hass: HomeAssistant, config: ConfigType +) -> dict[str, vol.Schema]: """List trigger capabilities.""" return { "extra_fields": vol.Schema( diff --git a/homeassistant/components/binary_sensor/translations/ca.json b/homeassistant/components/binary_sensor/translations/ca.json index 4c27c1e2966..10d705e797b 100644 --- a/homeassistant/components/binary_sensor/translations/ca.json +++ b/homeassistant/components/binary_sensor/translations/ca.json @@ -59,8 +59,6 @@ "connected": "{entity_name} est\u00e0 connectat", "gas": "{entity_name} ha comen\u00e7at a detectar gas", "hot": "{entity_name} es torna calent", - "is_not_tampered": "{entity_name} ha deixat de detectar manipulaci\u00f3", - "is_tampered": "{entity_name} ha comen\u00e7at a detectar manipulaci\u00f3", "light": "{entity_name} ha comen\u00e7at a detectar llum", "locked": "{entity_name} est\u00e0 bloquejat", "moist": "{entity_name} es torna humit", @@ -138,10 +136,6 @@ "off": "Lliure", "on": "Detectat" }, - "co": { - "off": "Lliure", - "on": "Detectat" - }, "cold": { "off": "Normal", "on": "Fred" diff --git a/homeassistant/components/binary_sensor/translations/cs.json b/homeassistant/components/binary_sensor/translations/cs.json index ced02ffc326..b8793a2c087 100644 --- a/homeassistant/components/binary_sensor/translations/cs.json +++ b/homeassistant/components/binary_sensor/translations/cs.json @@ -53,8 +53,6 @@ "connected": "{entity_name} p\u0159ipojeno", "gas": "{entity_name} za\u010dalo detekovat plyn", "hot": "{entity_name} se zah\u0159\u00e1l", - "is_not_tampered": "{entity_name} p\u0159estalo detekovat neopr\u00e1vn\u011bnou manipulaci", - "is_tampered": "{entity_name} za\u010dalo detekovat neopr\u00e1vn\u011bnou manipulaci", "light": "{entity_name} za\u010dalo detekovat sv\u011btlo", "locked": "{entity_name} zam\u010deno", "moist": "{entity_name} zvlhnul", diff --git a/homeassistant/components/binary_sensor/translations/de.json b/homeassistant/components/binary_sensor/translations/de.json index 1d6257c48c6..85163bce0a3 100644 --- a/homeassistant/components/binary_sensor/translations/de.json +++ b/homeassistant/components/binary_sensor/translations/de.json @@ -59,8 +59,6 @@ "connected": "{entity_name} verbunden", "gas": "{entity_name} hat Gas detektiert", "hot": "{entity_name} wurde hei\u00df", - "is_not_tampered": "{entity_name} hat aufgeh\u00f6rt, Manipulationen zu erkennen", - "is_tampered": "{entity_name} hat begonnen, Manipulationen zu erkennen", "light": "{entity_name} hat Licht detektiert", "locked": "{entity_name} gesperrt", "moist": "{entity_name} wurde feucht", @@ -138,10 +136,6 @@ "off": "Normal", "on": "Erkannt" }, - "co": { - "off": "Normal", - "on": "Erkannt" - }, "cold": { "off": "Normal", "on": "Kalt" diff --git a/homeassistant/components/binary_sensor/translations/el.json b/homeassistant/components/binary_sensor/translations/el.json index ec78f1cd575..ed291a70f0d 100644 --- a/homeassistant/components/binary_sensor/translations/el.json +++ b/homeassistant/components/binary_sensor/translations/el.json @@ -59,8 +59,6 @@ "connected": "{entity_name} \u03c3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5", "gas": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03b1\u03ad\u03c1\u03b9\u03bf", "hot": "{entity_name} \u03b6\u03b5\u03c3\u03c4\u03ac\u03b8\u03b7\u03ba\u03b5", - "is_not_tampered": "{entity_name} \u03c3\u03c4\u03b1\u03bc\u03ac\u03c4\u03b7\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", - "is_tampered": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03b6\u03b5\u03b9 \u03c0\u03b1\u03c1\u03b1\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "light": "{entity_name} \u03ac\u03c1\u03c7\u03b9\u03c3\u03b5 \u03bd\u03b1 \u03b1\u03bd\u03b9\u03c7\u03bd\u03b5\u03cd\u03b5\u03b9 \u03c6\u03c9\u03c2", "locked": "{entity_name} \u03ba\u03bb\u03b5\u03b9\u03b4\u03ce\u03b8\u03b7\u03ba\u03b5", "moist": "{entity_name} \u03ad\u03b3\u03b9\u03bd\u03b5 \u03c5\u03b3\u03c1\u03cc", @@ -138,10 +136,6 @@ "off": "\u0394\u03b5\u03bd \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5", "on": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5" }, - "co": { - "off": "\u0394\u03b5\u03bd \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5", - "on": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5" - }, "cold": { "off": "\u03a6\u03c5\u03c3\u03b9\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03cc", "on": "\u039a\u03c1\u03cd\u03bf" diff --git a/homeassistant/components/binary_sensor/translations/en.json b/homeassistant/components/binary_sensor/translations/en.json index 1d4f30fef52..1dc6cf2caa1 100644 --- a/homeassistant/components/binary_sensor/translations/en.json +++ b/homeassistant/components/binary_sensor/translations/en.json @@ -59,8 +59,6 @@ "connected": "{entity_name} connected", "gas": "{entity_name} started detecting gas", "hot": "{entity_name} became hot", - "is_not_tampered": "{entity_name} stopped detecting tampering", - "is_tampered": "{entity_name} started detecting tampering", "light": "{entity_name} started detecting light", "locked": "{entity_name} locked", "moist": "{entity_name} became moist", @@ -138,10 +136,6 @@ "off": "Clear", "on": "Detected" }, - "co": { - "off": "Clear", - "on": "Detected" - }, "cold": { "off": "Normal", "on": "Cold" diff --git a/homeassistant/components/binary_sensor/translations/es-419.json b/homeassistant/components/binary_sensor/translations/es-419.json index 2951e71b114..bc77a635165 100644 --- a/homeassistant/components/binary_sensor/translations/es-419.json +++ b/homeassistant/components/binary_sensor/translations/es-419.json @@ -121,10 +121,6 @@ "off": "No esta cargando", "on": "Cargando" }, - "co": { - "off": "Despejado", - "on": "Detectado" - }, "cold": { "off": "Normal", "on": "Fr\u00edo" diff --git a/homeassistant/components/binary_sensor/translations/es.json b/homeassistant/components/binary_sensor/translations/es.json index 1e328150c39..005f2669e50 100644 --- a/homeassistant/components/binary_sensor/translations/es.json +++ b/homeassistant/components/binary_sensor/translations/es.json @@ -59,8 +59,6 @@ "connected": "{entity_name} conectado", "gas": "{entity_name} empez\u00f3 a detectar gas", "hot": "{entity_name} se est\u00e1 calentando", - "is_not_tampered": "{entity_name} dej\u00f3 de detectar alteraciones", - "is_tampered": "{entity_name} comenz\u00f3 a detectar alteraciones", "light": "{entity_name} empez\u00f3 a detectar la luz", "locked": "{entity_name} bloqueado", "moist": "{entity_name} se humedece", @@ -83,7 +81,7 @@ "not_moist": "{entity_name} se sec\u00f3", "not_moving": "{entity_name} dej\u00f3 de moverse", "not_occupied": "{entity_name} no est\u00e1 ocupado", - "not_opened": "{entity_name} cerrado", + "not_opened": "{entity_name} se cierra", "not_plugged_in": "{entity_name} desconectado", "not_powered": "{entity_name} no est\u00e1 activado", "not_present": "{entity_name} no est\u00e1 presente", @@ -92,7 +90,7 @@ "not_unsafe": "{entity_name} se volvi\u00f3 seguro", "occupied": "{entity_name} se convirti\u00f3 en ocupado", "opened": "{entity_name} abierto", - "plugged_in": "{entity_name} conectado", + "plugged_in": "{entity_name} se ha enchufado", "powered": "{entity_name} alimentado", "present": "{entity_name} presente", "problem": "{entity_name} empez\u00f3 a detectar problemas", @@ -135,10 +133,7 @@ "on": "Cargando" }, "carbon_monoxide": { - "on": "Detectado" - }, - "co": { - "off": "No detectado", + "off": "Libre", "on": "Detectado" }, "cold": { @@ -198,7 +193,7 @@ "on": "Enchufado" }, "presence": { - "off": "Fuera de casa", + "off": "Fuera", "on": "En casa" }, "problem": { diff --git a/homeassistant/components/binary_sensor/translations/et.json b/homeassistant/components/binary_sensor/translations/et.json index 92b4e52952d..6e16d5da000 100644 --- a/homeassistant/components/binary_sensor/translations/et.json +++ b/homeassistant/components/binary_sensor/translations/et.json @@ -59,8 +59,6 @@ "connected": "{entity_name} on \u00fchendatud", "gas": "{entity_name} tuvastas gaasi(leket)", "hot": "{entity_name} muutus kuumaks", - "is_not_tampered": "{entity_name} l\u00f5petas omavolilise muutmise tuvastamise", - "is_tampered": "{entity_name} alustas omavolilise muutmise tuvastamist", "light": "{entity_name} tuvastas valgust", "locked": "{entity_name} on lukus", "moist": "{entity_name} muutus niiskeks", @@ -138,10 +136,6 @@ "off": "Korras", "on": "Tuvastatud" }, - "co": { - "off": "Puudub", - "on": "Tuvastatud" - }, "cold": { "off": "Normaalne", "on": "Jahe" diff --git a/homeassistant/components/binary_sensor/translations/fr.json b/homeassistant/components/binary_sensor/translations/fr.json index a9bd4ba30b1..b5280a897cc 100644 --- a/homeassistant/components/binary_sensor/translations/fr.json +++ b/homeassistant/components/binary_sensor/translations/fr.json @@ -59,8 +59,6 @@ "connected": "{entity_name} s'est connect\u00e9", "gas": "{entity_name} a commenc\u00e9 \u00e0 d\u00e9tecter du gaz", "hot": "{entity_name} est devenu chaud", - "is_not_tampered": "{entity_name} a cess\u00e9 de d\u00e9tecter une manipulation", - "is_tampered": "{entity_name} a commenc\u00e9 \u00e0 d\u00e9tecter une manipulation", "light": "{entity_name} a commenc\u00e9 \u00e0 d\u00e9tecter de la lumi\u00e8re", "locked": "{entity_name} s'est verrouill\u00e9", "moist": "{entity_name} est devenu humide", @@ -138,10 +136,6 @@ "off": "Non d\u00e9tect\u00e9", "on": "D\u00e9tect\u00e9" }, - "co": { - "off": "Non d\u00e9tect\u00e9", - "on": "D\u00e9tect\u00e9" - }, "cold": { "off": "Normal", "on": "Froid" diff --git a/homeassistant/components/binary_sensor/translations/he.json b/homeassistant/components/binary_sensor/translations/he.json index 5f0e14ccac1..cd3b24ed9a3 100644 --- a/homeassistant/components/binary_sensor/translations/he.json +++ b/homeassistant/components/binary_sensor/translations/he.json @@ -59,8 +59,6 @@ "connected": "{entity_name} \u05de\u05d7\u05d5\u05d1\u05e8", "gas": "{entity_name} \u05d4\u05d7\u05dc \u05dc\u05d6\u05d4\u05d5\u05ea \u05d2\u05d6", "hot": "{entity_name} \u05e0\u05e2\u05e9\u05d4 \u05d7\u05dd", - "is_not_tampered": "{entity_name} \u05d4\u05e4\u05e1\u05d9\u05e7 \u05dc\u05d6\u05d4\u05d5\u05ea \u05d7\u05d1\u05dc\u05d4", - "is_tampered": "{entity_name} \u05d4\u05d7\u05dc \u05dc\u05d6\u05d4\u05d5\u05ea \u05d7\u05d1\u05dc\u05d4", "light": "{entity_name} \u05d4\u05ea\u05d7\u05d9\u05dc \u05dc\u05d6\u05d4\u05d5\u05ea \u05d0\u05d5\u05e8", "locked": "{entity_name} \u05e0\u05e2\u05d5\u05dc", "moist": "{entity_name} \u05d4\u05e4\u05da \u05dc\u05d7", @@ -138,10 +136,6 @@ "off": "\u05e0\u05e7\u05d9", "on": "\u05d6\u05d5\u05d4\u05d4" }, - "co": { - "off": "\u05e0\u05e7\u05d9", - "on": "\u05d6\u05d5\u05d4\u05d4" - }, "cold": { "off": "\u05e0\u05d5\u05e8\u05de\u05dc\u05d9", "on": "\u05e7\u05e8" diff --git a/homeassistant/components/binary_sensor/translations/hu.json b/homeassistant/components/binary_sensor/translations/hu.json index 3690c1def1e..56df2994766 100644 --- a/homeassistant/components/binary_sensor/translations/hu.json +++ b/homeassistant/components/binary_sensor/translations/hu.json @@ -59,8 +59,6 @@ "connected": "{entity_name} csatlakozik", "gas": "{entity_name} g\u00e1zt \u00e9rz\u00e9kel", "hot": "{entity_name} felforr\u00f3sodik", - "is_not_tampered": "{entity_name} nem \u00e9szlelt manipul\u00e1l\u00e1st", - "is_tampered": "{entity_name} manipul\u00e1l\u00e1st \u00e9szlelt", "light": "{entity_name} f\u00e9nyt \u00e9rz\u00e9kel", "locked": "{entity_name} be lett z\u00e1rva", "moist": "{entity_name} nedves lett", @@ -138,10 +136,6 @@ "off": "Norm\u00e1l", "on": "\u00c9szlelve" }, - "co": { - "off": "Tiszta", - "on": "\u00c9rz\u00e9kelve" - }, "cold": { "off": "Norm\u00e1l", "on": "Hideg" diff --git a/homeassistant/components/binary_sensor/translations/id.json b/homeassistant/components/binary_sensor/translations/id.json index 5215f57814a..e01ee10cc3c 100644 --- a/homeassistant/components/binary_sensor/translations/id.json +++ b/homeassistant/components/binary_sensor/translations/id.json @@ -59,8 +59,6 @@ "connected": "{entity_name} terhubung", "gas": "{entity_name} mulai mendeteksi gas", "hot": "{entity_name} menjadi panas", - "is_not_tampered": "{entity_name} berhenti mendeteksi gangguan", - "is_tampered": "{entity_name} mulai mendeteksi gangguan", "light": "{entity_name} mulai mendeteksi cahaya", "locked": "{entity_name} terkunci", "moist": "{entity_name} menjadi lembab", @@ -138,10 +136,6 @@ "off": "Tidak ada", "on": "Terdeteksi" }, - "co": { - "off": "Tidak ada", - "on": "Terdeteksi" - }, "cold": { "off": "Normal", "on": "Dingin" diff --git a/homeassistant/components/binary_sensor/translations/it.json b/homeassistant/components/binary_sensor/translations/it.json index 6054fd9d9fe..933e72a285a 100644 --- a/homeassistant/components/binary_sensor/translations/it.json +++ b/homeassistant/components/binary_sensor/translations/it.json @@ -59,8 +59,6 @@ "connected": "{entity_name} \u00e8 connesso", "gas": "{entity_name} ha iniziato a rilevare il gas", "hot": "{entity_name} \u00e8 diventato caldo", - "is_not_tampered": "{entity_name} ha smesso di rilevare manomissioni", - "is_tampered": "{entity_name} ha iniziato a rilevare manomissioni", "light": "{entity_name} ha iniziato a rilevare la luce", "locked": "{entity_name} bloccato", "moist": "{entity_name} diventato umido", @@ -138,10 +136,6 @@ "off": "Assente", "on": "Rilevato" }, - "co": { - "off": "Non rilevato", - "on": "Rilevato" - }, "cold": { "off": "Normale", "on": "Freddo" diff --git a/homeassistant/components/binary_sensor/translations/ja.json b/homeassistant/components/binary_sensor/translations/ja.json index 541c5073961..a49c683b00b 100644 --- a/homeassistant/components/binary_sensor/translations/ja.json +++ b/homeassistant/components/binary_sensor/translations/ja.json @@ -59,8 +59,6 @@ "connected": "{entity_name} \u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u3059", "gas": "{entity_name} \u304c\u30ac\u30b9\u306e\u691c\u51fa\u3092\u958b\u59cb\u3057\u307e\u3057\u305f", "hot": "{entity_name} \u6e29\u307e\u3063\u3066\u3044\u307e\u3059", - "is_not_tampered": "{entity_name} \u304c\u6539\u7ac4(tampering)\u306e\u691c\u51fa\u3092\u505c\u6b62\u3057\u307e\u3057\u305f", - "is_tampered": "{entity_name} \u304c\u6539\u7ac4(tampering)\u306e\u691c\u51fa\u3092\u958b\u59cb\u3057\u307e\u3057\u305f", "light": "{entity_name} \u306f\u3001\u5149\u306e\u691c\u51fa\u3092\u958b\u59cb\u3057\u307e\u3057\u305f", "locked": "{entity_name} \u306f\u30ed\u30c3\u30af\u3055\u308c\u3066\u3044\u307e\u3059", "moist": "{entity_name} \u304c\u6e7f\u3063\u305f", @@ -138,10 +136,6 @@ "off": "\u30af\u30ea\u30a2", "on": "\u691c\u51fa" }, - "co": { - "off": "\u30af\u30ea\u30a2", - "on": "\u691c\u51fa\u3055\u308c\u307e\u3057\u305f" - }, "cold": { "off": "\u901a\u5e38", "on": "\u4f4e\u6e29" diff --git a/homeassistant/components/binary_sensor/translations/ko.json b/homeassistant/components/binary_sensor/translations/ko.json index 7a725fc6719..3d26bdd0193 100644 --- a/homeassistant/components/binary_sensor/translations/ko.json +++ b/homeassistant/components/binary_sensor/translations/ko.json @@ -89,6 +89,9 @@ "vibration": "{entity_name}\uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c" } }, + "device_class": { + "co": "\uc77c\uc0b0\ud654\ud0c4\uc18c" + }, "state": { "_": { "off": "\uaebc\uc9d0", diff --git a/homeassistant/components/binary_sensor/translations/nl.json b/homeassistant/components/binary_sensor/translations/nl.json index 7afbcaf616f..eb830fdd450 100644 --- a/homeassistant/components/binary_sensor/translations/nl.json +++ b/homeassistant/components/binary_sensor/translations/nl.json @@ -59,8 +59,6 @@ "connected": "{entity_name} verbonden", "gas": "{entity_name} begon gas te detecteren", "hot": "{entity_name} werd heet", - "is_not_tampered": "{entity_name} gestopt met het detecteren van sabotage", - "is_tampered": "{entity_name} begonnen met het detecteren van sabotage", "light": "{entity_name} begon licht te detecteren", "locked": "{entity_name} vergrendeld", "moist": "{entity_name} werd vochtig", @@ -138,10 +136,6 @@ "off": "Niet gedetecteerd", "on": "Gedetecteerd" }, - "co": { - "off": "Niet gedetecteerd", - "on": "Gedetecteerd" - }, "cold": { "off": "Normaal", "on": "Koud" diff --git a/homeassistant/components/binary_sensor/translations/no.json b/homeassistant/components/binary_sensor/translations/no.json index 62cf2d7cc1b..e29fab0a709 100644 --- a/homeassistant/components/binary_sensor/translations/no.json +++ b/homeassistant/components/binary_sensor/translations/no.json @@ -59,8 +59,6 @@ "connected": "{entity_name} tilkoblet", "gas": "{entity_name} begynte \u00e5 registrere gass", "hot": "{entity_name} ble varm", - "is_not_tampered": "{entity_name} sluttet \u00e5 oppdage manipulering", - "is_tampered": "{entity_name} begynte \u00e5 oppdage manipulering", "light": "{entity_name} begynte \u00e5 registrere lys", "locked": "{entity_name} l\u00e5st", "moist": "{entity_name} ble fuktig", @@ -138,10 +136,6 @@ "off": "Klart", "on": "Oppdaget" }, - "co": { - "off": "Klart", - "on": "Oppdaget" - }, "cold": { "off": "Normal", "on": "Kald" diff --git a/homeassistant/components/binary_sensor/translations/pl.json b/homeassistant/components/binary_sensor/translations/pl.json index d318968da56..d27d6443b55 100644 --- a/homeassistant/components/binary_sensor/translations/pl.json +++ b/homeassistant/components/binary_sensor/translations/pl.json @@ -59,8 +59,6 @@ "connected": "nast\u0105pi pod\u0142\u0105czenie {entity_name}", "gas": "sensor {entity_name} wykryje gaz", "hot": "sensor {entity_name} wykryje gor\u0105co", - "is_not_tampered": "sensor {entity_name} przestanie wykrywa\u0107 naruszenie", - "is_tampered": "sensor {entity_name} wykryje naruszenie", "light": "sensor {entity_name} wykryje \u015bwiat\u0142o", "locked": "nast\u0105pi zamkni\u0119cie {entity_name}", "moist": "nast\u0105pi wykrycie wilgoci {entity_name}", @@ -138,10 +136,6 @@ "off": "brak", "on": "wykryto" }, - "co": { - "off": "brak", - "on": "wykryto" - }, "cold": { "off": "normalnie", "on": "zimno" diff --git a/homeassistant/components/binary_sensor/translations/pt-BR.json b/homeassistant/components/binary_sensor/translations/pt-BR.json index 5b1ba2a1abf..5bd166f9367 100644 --- a/homeassistant/components/binary_sensor/translations/pt-BR.json +++ b/homeassistant/components/binary_sensor/translations/pt-BR.json @@ -59,8 +59,6 @@ "connected": "{entity_name} conectado", "gas": "{entity_name} come\u00e7ou a detectar g\u00e1s", "hot": "{entity_name} tornou-se quente", - "is_not_tampered": "{entity_name} parar de detectar adultera\u00e7\u00e3o", - "is_tampered": "{entity_name} come\u00e7ar a detectar adultera\u00e7\u00e3o", "light": "{entity_name} come\u00e7ou a detectar luz", "locked": "{entity_name} bloqueado", "moist": "{entity_name} ficar \u00famido", @@ -138,10 +136,6 @@ "off": "Normal", "on": "Detectado" }, - "co": { - "off": "Limpo", - "on": "Detectado" - }, "cold": { "off": "Normal", "on": "Frio" diff --git a/homeassistant/components/binary_sensor/translations/ru.json b/homeassistant/components/binary_sensor/translations/ru.json index cc9573e2b14..9522720571b 100644 --- a/homeassistant/components/binary_sensor/translations/ru.json +++ b/homeassistant/components/binary_sensor/translations/ru.json @@ -59,8 +59,6 @@ "connected": "{entity_name} \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "gas": "{entity_name} \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0442\u044c \u0433\u0430\u0437", "hot": "{entity_name} \u043d\u0430\u0433\u0440\u0435\u0432\u0430\u0435\u0442\u0441\u044f", - "is_not_tampered": "{entity_name} \u043f\u0440\u0435\u043a\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u043d\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u0438\u0435", - "is_tampered": "{entity_name} \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u043d\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u0438\u0435", "light": "{entity_name} \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0442\u044c \u0441\u0432\u0435\u0442", "locked": "{entity_name} \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u0442\u0441\u044f", "moist": "{entity_name} \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0432\u043b\u0430\u0436\u043d\u044b\u043c", @@ -138,10 +136,6 @@ "off": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d", "on": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d" }, - "co": { - "off": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d", - "on": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d" - }, "cold": { "off": "\u041d\u043e\u0440\u043c\u0430", "on": "\u041e\u0445\u043b\u0430\u0436\u0434\u0435\u043d\u0438\u0435" diff --git a/homeassistant/components/binary_sensor/translations/sk.json b/homeassistant/components/binary_sensor/translations/sk.json index 5cff82615ae..89e6288a979 100644 --- a/homeassistant/components/binary_sensor/translations/sk.json +++ b/homeassistant/components/binary_sensor/translations/sk.json @@ -1,4 +1,8 @@ { + "device_class": { + "motion": "pohyb", + "vibration": "vibr\u00e1cia" + }, "state": { "_": { "off": "Neakt\u00edvny", diff --git a/homeassistant/components/binary_sensor/translations/sl.json b/homeassistant/components/binary_sensor/translations/sl.json index cc34982ad0a..6b47f4e9e7e 100644 --- a/homeassistant/components/binary_sensor/translations/sl.json +++ b/homeassistant/components/binary_sensor/translations/sl.json @@ -52,8 +52,6 @@ "connected": "{entity_name} povezan", "gas": "{entity_name} za\u010del zaznavati plin", "hot": "{entity_name} je postal vro\u010d", - "is_not_tampered": "{entity_name} je prenehal zaznavati nedovoljena dejanja", - "is_tampered": "{entity_name} je za\u010del zaznavati nedovoljeno poseganje", "light": "{entity_name} za\u010del zaznavati svetlobo", "locked": "{entity_name} zaklenjen", "moist": "{entity_name} postal vla\u017een", diff --git a/homeassistant/components/binary_sensor/translations/tr.json b/homeassistant/components/binary_sensor/translations/tr.json index 2b7eb33e6f0..c54454b2230 100644 --- a/homeassistant/components/binary_sensor/translations/tr.json +++ b/homeassistant/components/binary_sensor/translations/tr.json @@ -59,8 +59,6 @@ "connected": "{entity_name} ba\u011fland\u0131", "gas": "{entity_name} gaz alg\u0131lamaya ba\u015flad\u0131", "hot": "{entity_name} \u0131s\u0131nd\u0131", - "is_not_tampered": "{entity_name} kurcalamay\u0131 alg\u0131lamay\u0131 durdurdu", - "is_tampered": "{entity_name} , kurcalamay\u0131 alg\u0131lamaya ba\u015flad\u0131", "light": "{entity_name} \u0131\u015f\u0131\u011f\u0131 alg\u0131lamaya ba\u015flad\u0131", "locked": "{entity_name} kilitlendi", "moist": "{entity_name} nemli oldu", @@ -138,10 +136,6 @@ "off": "Temiz", "on": "Alg\u0131land\u0131" }, - "co": { - "off": "Temiz", - "on": "Alg\u0131land\u0131" - }, "cold": { "off": "Normal", "on": "So\u011fuk" diff --git a/homeassistant/components/binary_sensor/translations/zh-Hans.json b/homeassistant/components/binary_sensor/translations/zh-Hans.json index 202a51740de..70ff33a6e53 100644 --- a/homeassistant/components/binary_sensor/translations/zh-Hans.json +++ b/homeassistant/components/binary_sensor/translations/zh-Hans.json @@ -59,8 +59,6 @@ "connected": "{entity_name} \u5df2\u8fde\u63a5", "gas": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u71c3\u6c14\u6cc4\u6f0f", "hot": "{entity_name} \u53d8\u70ed", - "is_not_tampered": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u81ea\u8eab\u88ab\u62c6\u89e3", - "is_tampered": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u81ea\u8eab\u88ab\u62c6\u89e3", "light": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u5149\u7ebf", "locked": "{entity_name} \u88ab\u9501\u5b9a", "moist": "{entity_name} \u53d8\u6e7f", @@ -134,10 +132,6 @@ "off": "\u672a\u5728\u5145\u7535", "on": "\u6b63\u5728\u5145\u7535" }, - "co": { - "off": "\u672a\u89e6\u53d1", - "on": "\u89e6\u53d1" - }, "cold": { "off": "\u6b63\u5e38", "on": "\u8fc7\u51b7" diff --git a/homeassistant/components/binary_sensor/translations/zh-Hant.json b/homeassistant/components/binary_sensor/translations/zh-Hant.json index 417ca652cb5..32c1aab1cd1 100644 --- a/homeassistant/components/binary_sensor/translations/zh-Hant.json +++ b/homeassistant/components/binary_sensor/translations/zh-Hant.json @@ -59,8 +59,6 @@ "connected": "{entity_name}\u5df2\u9023\u7dda", "gas": "{entity_name}\u5df2\u958b\u59cb\u5075\u6e2c\u6c23\u9ad4", "hot": "{entity_name}\u5df2\u8b8a\u71b1", - "is_not_tampered": "{entity_name}\u5df2\u505c\u6b62\u5075\u6e2c\u6e1b\u5f31", - "is_tampered": "{entity_name}\u5df2\u5075\u6e2c\u5230\u6e1b\u5f31", "light": "{entity_name}\u5df2\u958b\u59cb\u5075\u6e2c\u5149\u7dda", "locked": "{entity_name}\u5df2\u4e0a\u9396", "moist": "{entity_name}\u5df2\u8b8a\u6f6e\u6fd5", @@ -138,10 +136,6 @@ "off": "\u672a\u89f8\u767c", "on": "\u5df2\u89f8\u767c" }, - "co": { - "off": "\u672a\u5075\u6e2c", - "on": "\u5075\u6e2c" - }, "cold": { "off": "\u6b63\u5e38", "on": "\u51b7" diff --git a/homeassistant/components/blebox/translations/es.json b/homeassistant/components/blebox/translations/es.json index a415edd9809..c62caa44eec 100644 --- a/homeassistant/components/blebox/translations/es.json +++ b/homeassistant/components/blebox/translations/es.json @@ -9,7 +9,7 @@ "unknown": "Error inesperado", "unsupported_version": "El dispositivo BleBox tiene un firmware anticuado. Por favor, actual\u00edzalo primero." }, - "flow_title": "Dispositivo BleBox: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/blebox/translations/nl.json b/homeassistant/components/blebox/translations/nl.json index 65a775e6f72..a9acfc5f71e 100644 --- a/homeassistant/components/blebox/translations/nl.json +++ b/homeassistant/components/blebox/translations/nl.json @@ -5,7 +5,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout", "unsupported_version": "BleBox-apparaat heeft verouderde firmware. Upgrade het eerst." }, diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index 32f74743972..7f1c6b6553f 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -24,10 +24,7 @@ from homeassistant.components.media_player import ( 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, -) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, @@ -1023,25 +1020,20 @@ class BluesoundPlayer(MediaPlayerEntity): return await self.send_bluesound_command(f"Play?seek={float(position)}") async def async_play_media(self, media_type, media_id, **kwargs): - """ - Send the play_media command to the media player. - - If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the queue. - """ + """Send the play_media command to the media player.""" if self.is_grouped and not self.is_master: return if media_source.is_media_source_id(media_id): - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = play_item.url media_id = async_process_play_media_url(self.hass, media_id) url = f"Play?url={media_id}" - if kwargs.get(ATTR_MEDIA_ENQUEUE): - return await self.send_bluesound_command(url) - return await self.send_bluesound_command(url) async def async_volume_up(self): diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index dae211f91a2..7023dd7481a 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -1,31 +1,27 @@ -"""Reads vehicle status from BMW connected drive portal.""" +"""Reads vehicle status from MyBMW portal.""" from __future__ import annotations +import logging from typing import Any -from bimmer_connected.vehicle import ConnectedDriveVehicle +from bimmer_connected.vehicle import MyBMWVehicle import voluptuous as vol 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.const import CONF_DEVICE_ID, CONF_ENTITY_ID, CONF_NAME, Platform from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import discovery +from homeassistant.helpers import discovery, entity_registry as er import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ATTR_VIN, ATTRIBUTION, CONF_READ_ONLY, DATA_HASS_CONFIG, DOMAIN from .coordinator import BMWDataUpdateCoordinator +_LOGGER = logging.getLogger(__name__) + + CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) SERVICE_SCHEMA = vol.Schema( @@ -74,18 +70,56 @@ def _async_migrate_options_from_data_if_missing( hass.config_entries.async_update_entry(entry, data=data, options=options) +async def _async_migrate_entries( + hass: HomeAssistant, config_entry: ConfigEntry +) -> bool: + """Migrate old entry.""" + entity_registry = er.async_get(hass) + + @callback + def update_unique_id(entry: er.RegistryEntry) -> dict[str, str] | None: + replacements = { + "charging_level_hv": "remaining_battery_percent", + "fuel_percent": "remaining_fuel_percent", + } + if (key := entry.unique_id.split("-")[-1]) in replacements: + new_unique_id = entry.unique_id.replace(key, replacements[key]) + _LOGGER.debug( + "Migrating entity '%s' unique_id from '%s' to '%s'", + entry.entity_id, + entry.unique_id, + new_unique_id, + ) + if existing_entity_id := entity_registry.async_get_entity_id( + entry.domain, entry.platform, new_unique_id + ): + _LOGGER.debug( + "Cannot migrate to unique_id '%s', already exists for '%s'", + new_unique_id, + existing_entity_id, + ) + return None + return { + "new_unique_id": new_unique_id, + } + return None + + await er.async_migrate_entries(hass, config_entry.entry_id, update_unique_id) + + return True + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up BMW Connected Drive from a config entry.""" _async_migrate_options_from_data_if_missing(hass, entry) + await _async_migrate_entries(hass, entry) + # 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], + entry=entry, ) await coordinator.async_config_entry_first_refresh() @@ -109,9 +143,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) ) - # Add event listener for option flow changes - entry.async_on_unload(entry.add_update_listener(async_update_options)) - return True @@ -127,20 +158,16 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(config_entry.entry_id) - - -class BMWConnectedDriveBaseEntity(CoordinatorEntity[BMWDataUpdateCoordinator], Entity): +class BMWBaseEntity(CoordinatorEntity[BMWDataUpdateCoordinator]): """Common base for BMW entities.""" + coordinator: BMWDataUpdateCoordinator _attr_attribution = ATTRIBUTION def __init__( self, coordinator: BMWDataUpdateCoordinator, - vehicle: ConnectedDriveVehicle, + vehicle: MyBMWVehicle, ) -> None: """Initialize entity.""" super().__init__(coordinator) diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index cae70f6de4b..a19ccc8f715 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -1,18 +1,15 @@ -"""Reads vehicle status from BMW connected drive portal.""" +"""Reads vehicle status from BMW MyBMW portal.""" from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass import logging -from typing import Any, cast +from typing import Any -from bimmer_connected.vehicle import ConnectedDriveVehicle -from bimmer_connected.vehicle_status import ( - ChargingState, - ConditionBasedServiceReport, - LockState, - VehicleStatus, -) +from bimmer_connected.vehicle import MyBMWVehicle +from bimmer_connected.vehicle.doors_windows import LockState +from bimmer_connected.vehicle.fuel_and_battery import ChargingState +from bimmer_connected.vehicle.reports import ConditionBasedService from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, @@ -24,7 +21,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.unit_system import UnitSystem -from . import BMWConnectedDriveBaseEntity +from . import BMWBaseEntity from .const import DOMAIN, UNIT_MAP from .coordinator import BMWDataUpdateCoordinator @@ -32,20 +29,20 @@ _LOGGER = logging.getLogger(__name__) def _condition_based_services( - vehicle_state: VehicleStatus, unit_system: UnitSystem + vehicle: MyBMWVehicle, unit_system: UnitSystem ) -> dict[str, Any]: extra_attributes = {} - for report in vehicle_state.condition_based_services: + for report in vehicle.condition_based_services.messages: extra_attributes.update(_format_cbs_report(report, unit_system)) return extra_attributes -def _check_control_messages(vehicle_state: VehicleStatus) -> dict[str, Any]: +def _check_control_messages(vehicle: MyBMWVehicle) -> dict[str, Any]: extra_attributes: dict[str, Any] = {} - if vehicle_state.has_check_control_messages: + if vehicle.check_control_messages.has_check_control_messages: cbs_list = [ message.description_short - for message in vehicle_state.check_control_messages + for message in vehicle.check_control_messages.messages ] extra_attributes["check_control_messages"] = cbs_list else: @@ -54,18 +51,18 @@ def _check_control_messages(vehicle_state: VehicleStatus) -> dict[str, Any]: def _format_cbs_report( - report: ConditionBasedServiceReport, unit_system: UnitSystem + report: ConditionBasedService, unit_system: UnitSystem ) -> dict[str, Any]: result: dict[str, Any] = {} service_type = report.service_type.lower().replace("_", " ") result[f"{service_type} status"] = report.state.value if report.due_date is not None: result[f"{service_type} date"] = report.due_date.strftime("%Y-%m-%d") - if report.due_distance is not None: + if report.due_distance.value and report.due_distance.unit: distance = round( unit_system.length( - report.due_distance[0], - UNIT_MAP.get(report.due_distance[1], report.due_distance[1]), + report.due_distance.value, + UNIT_MAP.get(report.due_distance.unit, report.due_distance.unit), ) ) result[f"{service_type} distance"] = f"{distance} {unit_system.length_unit}" @@ -76,7 +73,7 @@ def _format_cbs_report( class BMWRequiredKeysMixin: """Mixin for required keys.""" - value_fn: Callable[[VehicleStatus], bool] + value_fn: Callable[[MyBMWVehicle], bool] @dataclass @@ -85,7 +82,7 @@ class BMWBinarySensorEntityDescription( ): """Describes BMW binary_sensor entity.""" - attr_fn: Callable[[VehicleStatus, UnitSystem], dict[str, Any]] | None = None + attr_fn: Callable[[MyBMWVehicle, UnitSystem], dict[str, Any]] | None = None SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = ( @@ -95,8 +92,10 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = ( device_class=BinarySensorDeviceClass.OPENING, icon="mdi:car-door-lock", # 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}, + value_fn=lambda v: not v.doors_and_windows.all_lids_closed, + attr_fn=lambda v, u: { + lid.name: lid.state.value for lid in v.doors_and_windows.lids + }, ), BMWBinarySensorEntityDescription( key="windows", @@ -104,8 +103,10 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = ( device_class=BinarySensorDeviceClass.OPENING, icon="mdi:car-door", # 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}, + value_fn=lambda v: not v.doors_and_windows.all_windows_closed, + attr_fn=lambda v, u: { + window.name: window.state.value for window in v.doors_and_windows.windows + }, ), BMWBinarySensorEntityDescription( key="door_lock_state", @@ -114,29 +115,19 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = ( icon="mdi:car-key", # device class lock: On means unlocked, Off means locked # Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED - value_fn=lambda s: s.door_lock_state + value_fn=lambda v: v.doors_and_windows.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, + attr_fn=lambda v, u: { + "door_lock_state": v.doors_and_windows.door_lock_state.value }, ), - BMWBinarySensorEntityDescription( - key="lights_parking", - name="Parking lights", - device_class=BinarySensorDeviceClass.LIGHT, - icon="mdi:car-parking-lights", - # 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", # device class problem: On means problem detected, Off means no problem - value_fn=lambda s: not s.are_all_cbs_ok, + value_fn=lambda v: v.condition_based_services.is_service_required, attr_fn=_condition_based_services, ), BMWBinarySensorEntityDescription( @@ -145,8 +136,8 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = ( device_class=BinarySensorDeviceClass.PROBLEM, icon="mdi:car-tire-alert", # 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), + value_fn=lambda v: v.check_control_messages.has_check_control_messages, + attr_fn=lambda v, u: _check_control_messages(v), ), # electric BMWBinarySensorEntityDescription( @@ -155,10 +146,9 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = ( device_class=BinarySensorDeviceClass.BATTERY_CHARGING, icon="mdi:ev-station", # 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, + value_fn=lambda v: v.fuel_and_battery.charging_status == ChargingState.CHARGING, + attr_fn=lambda v, u: { + "charging_status": str(v.fuel_and_battery.charging_status), }, ), BMWBinarySensorEntityDescription( @@ -166,8 +156,7 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = ( name="Connection status", device_class=BinarySensorDeviceClass.PLUG, icon="mdi:car-electric", - value_fn=lambda s: cast(str, s.connection_status) == "CONNECTED", - attr_fn=lambda s, u: {"connection_status": s.connection_status}, + value_fn=lambda v: v.fuel_and_battery.is_charger_connected, ), ) @@ -177,11 +166,11 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the BMW ConnectedDrive binary sensors from config entry.""" + """Set up the BMW binary sensors from config entry.""" coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] entities = [ - BMWConnectedDriveSensor(coordinator, vehicle, description, hass.config.units) + BMWBinarySensor(coordinator, vehicle, description, hass.config.units) for vehicle in coordinator.account.vehicles for description in SENSOR_TYPES if description.key in vehicle.available_attributes @@ -189,7 +178,7 @@ async def async_setup_entry( async_add_entities(entities) -class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, BinarySensorEntity): +class BMWBinarySensor(BMWBaseEntity, BinarySensorEntity): """Representation of a BMW vehicle binary sensor.""" entity_description: BMWBinarySensorEntityDescription @@ -197,7 +186,7 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, BinarySensorEntity): def __init__( self, coordinator: BMWDataUpdateCoordinator, - vehicle: ConnectedDriveVehicle, + vehicle: MyBMWVehicle, description: BMWBinarySensorEntityDescription, unit_system: UnitSystem, ) -> None: @@ -217,14 +206,12 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, BinarySensorEntity): self.entity_description.key, self.vehicle.name, ) - vehicle_state = self.vehicle.status - - self._attr_is_on = self.entity_description.value_fn(vehicle_state) + self._attr_is_on = self.entity_description.value_fn(self.vehicle) if self.entity_description.attr_fn: self._attr_extra_state_attributes = dict( self._attrs, - **self.entity_description.attr_fn(vehicle_state, self._unit_system), + **self.entity_description.attr_fn(self.vehicle, 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 254fbebfdac..9cec9a73ce7 100644 --- a/homeassistant/components/bmw_connected_drive/button.py +++ b/homeassistant/components/bmw_connected_drive/button.py @@ -1,19 +1,20 @@ -"""Support for BMW connected drive button entities.""" +"""Support for MyBMW button entities.""" from __future__ import annotations from collections.abc import Callable, Coroutine from dataclasses import dataclass import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from bimmer_connected.vehicle import ConnectedDriveVehicle +from bimmer_connected.vehicle import MyBMWVehicle +from bimmer_connected.vehicle.remote_services import RemoteServiceStatus from homeassistant.components.button import ButtonEntity, ButtonEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import BMWConnectedDriveBaseEntity +from . import BMWBaseEntity from .const import DOMAIN if TYPE_CHECKING: @@ -27,7 +28,9 @@ class BMWButtonEntityDescription(ButtonEntityDescription): """Class describing BMW button entities.""" enabled_when_read_only: bool = False - remote_function: str | None = None + remote_function: Callable[ + [MyBMWVehicle], Coroutine[Any, Any, RemoteServiceStatus] + ] | None = None account_function: Callable[[BMWDataUpdateCoordinator], Coroutine] | None = None @@ -36,31 +39,31 @@ BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = ( key="light_flash", icon="mdi:car-light-alert", name="Flash Lights", - remote_function="trigger_remote_light_flash", + remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_light_flash(), ), BMWButtonEntityDescription( key="sound_horn", icon="mdi:bullhorn", name="Sound Horn", - remote_function="trigger_remote_horn", + remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_horn(), ), BMWButtonEntityDescription( key="activate_air_conditioning", icon="mdi:hvac", name="Activate Air Conditioning", - remote_function="trigger_remote_air_conditioning", + remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning(), ), BMWButtonEntityDescription( key="deactivate_air_conditioning", icon="mdi:hvac-off", name="Deactivate Air Conditioning", - remote_function="trigger_remote_air_conditioning_stop", + remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning_stop(), ), BMWButtonEntityDescription( key="find_vehicle", icon="mdi:crosshairs-question", name="Find Vehicle", - remote_function="trigger_remote_vehicle_finder", + remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_vehicle_finder(), ), BMWButtonEntityDescription( key="refresh", @@ -77,7 +80,7 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the BMW ConnectedDrive buttons from config entry.""" + """Set up the BMW buttons from config entry.""" coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] entities: list[BMWButton] = [] @@ -95,15 +98,15 @@ async def async_setup_entry( async_add_entities(entities) -class BMWButton(BMWConnectedDriveBaseEntity, ButtonEntity): - """Representation of a BMW Connected Drive button.""" +class BMWButton(BMWBaseEntity, ButtonEntity): + """Representation of a MyBMW button.""" entity_description: BMWButtonEntityDescription def __init__( self, coordinator: BMWDataUpdateCoordinator, - vehicle: ConnectedDriveVehicle, + vehicle: MyBMWVehicle, description: BMWButtonEntityDescription, ) -> None: """Initialize BMW vehicle sensor.""" @@ -116,12 +119,7 @@ class BMWButton(BMWConnectedDriveBaseEntity, ButtonEntity): async def async_press(self) -> None: """Press the button.""" if self.entity_description.remote_function: - await self.hass.async_add_executor_job( - getattr( - self.vehicle.remote_services, - self.entity_description.remote_function, - ) - ) + await self.entity_description.remote_function(self.vehicle) elif self.entity_description.account_function: _LOGGER.warning( "The 'Refresh from cloud' button is deprecated. Use the 'homeassistant.update_entity' " diff --git a/homeassistant/components/bmw_connected_drive/config_flow.py b/homeassistant/components/bmw_connected_drive/config_flow.py index fec25390ff4..c07be4c8849 100644 --- a/homeassistant/components/bmw_connected_drive/config_flow.py +++ b/homeassistant/components/bmw_connected_drive/config_flow.py @@ -3,8 +3,9 @@ from __future__ import annotations from typing import Any -from bimmer_connected.account import ConnectedDriveAccount -from bimmer_connected.country_selector import get_region_from_name +from bimmer_connected.account import MyBMWAccount +from bimmer_connected.api.regions import get_region_from_name +from httpx import HTTPError import voluptuous as vol from homeassistant import config_entries, core, exceptions @@ -31,22 +32,23 @@ async def validate_input( Data has the keys from DATA_SCHEMA with values provided by the user. """ + account = MyBMWAccount( + data[CONF_USERNAME], + data[CONF_PASSWORD], + get_region_from_name(data[CONF_REGION]), + ) + try: - await hass.async_add_executor_job( - ConnectedDriveAccount, - data[CONF_USERNAME], - data[CONF_PASSWORD], - get_region_from_name(data[CONF_REGION]), - ) - except OSError as ex: + await account.get_vehicles() + except HTTPError as ex: raise CannotConnect from ex # Return info that you want to store in the config entry. return {"title": f"{data[CONF_USERNAME]}{data.get(CONF_SOURCE, '')}"} -class BMWConnectedDriveConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for BMW ConnectedDrive.""" +class BMWConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for MyBMW.""" VERSION = 1 @@ -78,16 +80,16 @@ class BMWConnectedDriveConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @callback def async_get_options_flow( config_entry: config_entries.ConfigEntry, - ) -> BMWConnectedDriveOptionsFlow: - """Return a BWM ConnectedDrive option flow.""" - return BMWConnectedDriveOptionsFlow(config_entry) + ) -> BMWOptionsFlow: + """Return a MyBMW option flow.""" + return BMWOptionsFlow(config_entry) -class BMWConnectedDriveOptionsFlow(config_entries.OptionsFlow): - """Handle a option flow for BMW ConnectedDrive.""" +class BMWOptionsFlow(config_entries.OptionsFlow): + """Handle a option flow for MyBMW.""" def __init__(self, config_entry: config_entries.ConfigEntry) -> None: - """Initialize BMW ConnectedDrive option flow.""" + """Initialize MyBMW option flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) @@ -102,6 +104,16 @@ class BMWConnectedDriveOptionsFlow(config_entries.OptionsFlow): ) -> FlowResult: """Handle the initial step.""" if user_input is not None: + # Manually update & reload the config entry after options change. + # Required as each successful login will store the latest refresh_token + # using async_update_entry, which would otherwise trigger a full reload + # if the options would be refreshed using a listener. + changed = self.hass.config_entries.async_update_entry( + self.config_entry, + options=user_input, + ) + if changed: + await self.hass.config_entries.async_reload(self.config_entry.entry_id) return self.async_create_entry(title="", data=user_input) return self.async_show_form( step_id="account_options", diff --git a/homeassistant/components/bmw_connected_drive/const.py b/homeassistant/components/bmw_connected_drive/const.py index a2082c0bede..6a8f82ae22d 100644 --- a/homeassistant/components/bmw_connected_drive/const.py +++ b/homeassistant/components/bmw_connected_drive/const.py @@ -1,4 +1,4 @@ -"""Const file for the BMW Connected Drive integration.""" +"""Const file for the MyBMW integration.""" from homeassistant.const import ( LENGTH_KILOMETERS, LENGTH_MILES, @@ -7,7 +7,7 @@ from homeassistant.const import ( ) DOMAIN = "bmw_connected_drive" -ATTRIBUTION = "Data provided by BMW Connected Drive" +ATTRIBUTION = "Data provided by MyBMW" ATTR_DIRECTION = "direction" ATTR_VIN = "vin" @@ -15,6 +15,7 @@ ATTR_VIN = "vin" CONF_ALLOWED_REGIONS = ["china", "north_america", "rest_of_world"] CONF_READ_ONLY = "read_only" CONF_ACCOUNT = "account" +CONF_REFRESH_TOKEN = "refresh_token" DATA_HASS_CONFIG = "hass_config" diff --git a/homeassistant/components/bmw_connected_drive/coordinator.py b/homeassistant/components/bmw_connected_drive/coordinator.py index a02b4bdd27c..47d1f358686 100644 --- a/homeassistant/components/bmw_connected_drive/coordinator.py +++ b/homeassistant/components/bmw_connected_drive/coordinator.py @@ -4,14 +4,17 @@ 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 bimmer_connected.account import MyBMWAccount +from bimmer_connected.api.regions import get_region_from_name +from bimmer_connected.models import GPSPosition +from httpx import HTTPError, TimeoutException +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN +from .const import CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN SCAN_INTERVAL = timedelta(seconds=300) _LOGGER = logging.getLogger(__name__) @@ -20,53 +23,57 @@ _LOGGER = logging.getLogger(__name__) class BMWDataUpdateCoordinator(DataUpdateCoordinator): """Class to manage fetching BMW data.""" - account: ConnectedDriveAccount + account: MyBMWAccount - def __init__( - self, - hass: HomeAssistant, - *, - username: str, - password: str, - region: str, - read_only: bool = False, - ) -> None: + def __init__(self, hass: HomeAssistant, *, entry: ConfigEntry) -> 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 = MyBMWAccount( + entry.data[CONF_USERNAME], + entry.data[CONF_PASSWORD], + get_region_from_name(entry.data[CONF_REGION]), + observer_position=GPSPosition(hass.config.latitude, hass.config.longitude), + use_metric_units=hass.config.units.is_metric, + ) + self.read_only = entry.options[CONF_READ_ONLY] + self._entry = entry - self.account = None - self.read_only = read_only + if CONF_REFRESH_TOKEN in entry.data: + self.account.set_refresh_token(entry.data[CONF_REFRESH_TOKEN]) super().__init__( hass, _LOGGER, - name=f"{DOMAIN}-{username}", + name=f"{DOMAIN}-{entry.data['username']}", update_interval=SCAN_INTERVAL, ) async def _async_update_data(self) -> None: """Fetch data from BMW.""" + old_refresh_token = self.account.refresh_token + 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 + await self.account.get_vehicles() + except (HTTPError, TimeoutException) as err: + self._update_config_entry_refresh_token(None) + raise UpdateFailed(f"Error communicating with BMW API: {err}") from err + + if self.account.refresh_token != old_refresh_token: + self._update_config_entry_refresh_token(self.account.refresh_token) + _LOGGER.debug( + "bimmer_connected: refresh token %s > %s", + old_refresh_token, + self.account.refresh_token, + ) + + def _update_config_entry_refresh_token(self, refresh_token: str | None) -> None: + """Update or delete the refresh_token in the Config Entry.""" + data = { + **self._entry.data, + CONF_REFRESH_TOKEN: refresh_token, + } + if not refresh_token: + data.pop(CONF_REFRESH_TOKEN) + self.hass.config_entries.async_update_entry(self._entry, data=data) def notify_listeners(self) -> None: """Notify all listeners to refresh HA state machine.""" diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index b1fa429f5b9..dc71100455d 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -1,10 +1,10 @@ -"""Device tracker for BMW Connected Drive vehicles.""" +"""Device tracker for MyBMW vehicles.""" from __future__ import annotations import logging from typing import Literal -from bimmer_connected.vehicle import ConnectedDriveVehicle +from bimmer_connected.vehicle import MyBMWVehicle from homeassistant.components.device_tracker import SOURCE_TYPE_GPS from homeassistant.components.device_tracker.config_entry import TrackerEntity @@ -12,7 +12,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import BMWConnectedDriveBaseEntity +from . import BMWBaseEntity from .const import ATTR_DIRECTION, DOMAIN from .coordinator import BMWDataUpdateCoordinator @@ -24,7 +24,7 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the BMW ConnectedDrive tracker from config entry.""" + """Set up the MyBMW tracker from config entry.""" coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] entities: list[BMWDeviceTracker] = [] @@ -39,8 +39,8 @@ async def async_setup_entry( async_add_entities(entities) -class BMWDeviceTracker(BMWConnectedDriveBaseEntity, TrackerEntity): - """BMW Connected Drive device tracker.""" +class BMWDeviceTracker(BMWBaseEntity, TrackerEntity): + """MyBMW device tracker.""" _attr_force_update = False _attr_icon = "mdi:car" @@ -48,7 +48,7 @@ class BMWDeviceTracker(BMWConnectedDriveBaseEntity, TrackerEntity): def __init__( self, coordinator: BMWDataUpdateCoordinator, - vehicle: ConnectedDriveVehicle, + vehicle: MyBMWVehicle, ) -> None: """Initialize the Tracker.""" super().__init__(coordinator, vehicle) @@ -59,13 +59,13 @@ class BMWDeviceTracker(BMWConnectedDriveBaseEntity, TrackerEntity): @property def extra_state_attributes(self) -> dict: """Return entity specific state attributes.""" - return dict(self._attrs, **{ATTR_DIRECTION: self.vehicle.status.gps_heading}) + return {**self._attrs, ATTR_DIRECTION: self.vehicle.vehicle_location.heading} @property def latitude(self) -> float | None: """Return latitude value of the device.""" return ( - self.vehicle.status.gps_position[0] + self.vehicle.vehicle_location.location[0] if self.vehicle.is_vehicle_tracking_enabled else None ) @@ -74,7 +74,7 @@ class BMWDeviceTracker(BMWConnectedDriveBaseEntity, TrackerEntity): def longitude(self) -> float | None: """Return longitude value of the device.""" return ( - self.vehicle.status.gps_position[1] + self.vehicle.vehicle_location.location[1] 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 a395c80ebcc..0c2c5a1e832 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -4,15 +4,15 @@ from __future__ import annotations import logging from typing import Any -from bimmer_connected.vehicle import ConnectedDriveVehicle -from bimmer_connected.vehicle_status import LockState +from bimmer_connected.vehicle import MyBMWVehicle +from bimmer_connected.vehicle.doors_windows import LockState from homeassistant.components.lock import LockEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import BMWConnectedDriveBaseEntity +from . import BMWBaseEntity from .const import DOMAIN from .coordinator import BMWDataUpdateCoordinator @@ -25,7 +25,7 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the BMW ConnectedDrive binary sensors from config entry.""" + """Set up the MyBMW lock from config entry.""" coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] entities: list[BMWLock] = [] @@ -36,13 +36,13 @@ async def async_setup_entry( async_add_entities(entities) -class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): - """Representation of a BMW vehicle lock.""" +class BMWLock(BMWBaseEntity, LockEntity): + """Representation of a MyBMW vehicle lock.""" def __init__( self, coordinator: BMWDataUpdateCoordinator, - vehicle: ConnectedDriveVehicle, + vehicle: MyBMWVehicle, attribute: str, sensor_name: str, ) -> None: @@ -55,7 +55,7 @@ class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): self._sensor_name = sensor_name self.door_lock_state_available = DOOR_LOCK_STATE in vehicle.available_attributes - def lock(self, **kwargs: Any) -> None: + async def async_lock(self, **kwargs: Any) -> None: """Lock the car.""" _LOGGER.debug("%s: locking doors", self.vehicle.name) # Only update the HA state machine if the vehicle reliably reports its lock state @@ -63,10 +63,10 @@ class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): # 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() + self.async_write_ha_state() + await self.vehicle.remote_services.trigger_remote_door_lock() - def unlock(self, **kwargs: Any) -> None: + async def async_unlock(self, **kwargs: Any) -> None: """Unlock the car.""" _LOGGER.debug("%s: unlocking doors", self.vehicle.name) # Only update the HA state machine if the vehicle reliably reports its lock state @@ -74,8 +74,8 @@ class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): # 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() + self.async_write_ha_state() + await self.vehicle.remote_services.trigger_remote_door_unlock() @callback def _handle_coordinator_update(self) -> None: @@ -83,16 +83,14 @@ class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): _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 { + self._attr_is_locked = self.vehicle.doors_and_windows.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, + "door_lock_state": self.vehicle.doors_and_windows.door_lock_state.value, }, ) diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index 2d1b5e43984..75ac3e982e8 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.12"], + "requirements": ["bimmer_connected==0.9.3"], "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 42e9c834459..14f6c94dff6 100644 --- a/homeassistant/components/bmw_connected_drive/notify.py +++ b/homeassistant/components/bmw_connected_drive/notify.py @@ -4,7 +4,7 @@ from __future__ import annotations import logging from typing import Any, cast -from bimmer_connected.vehicle import ConnectedDriveVehicle +from bimmer_connected.vehicle import MyBMWVehicle from homeassistant.components.notify import ( ATTR_DATA, @@ -52,14 +52,14 @@ def get_service( class BMWNotificationService(BaseNotificationService): """Send Notifications to BMW.""" - def __init__(self, targets: dict[str, ConnectedDriveVehicle]) -> None: + def __init__(self, targets: dict[str, MyBMWVehicle]) -> None: """Set up the notification service.""" - self.targets: dict[str, ConnectedDriveVehicle] = targets + self.targets: dict[str, MyBMWVehicle] = targets def send_message(self, message: str = "", **kwargs: Any) -> None: """Send a message or POI to the car.""" for vehicle in kwargs[ATTR_TARGET]: - vehicle = cast(ConnectedDriveVehicle, vehicle) + vehicle = cast(MyBMWVehicle, vehicle) _LOGGER.debug("Sending message to %s", vehicle.name) # Extract params from data dict diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 4a928870eb2..9f19673c398 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -1,4 +1,4 @@ -"""Support for reading vehicle status from BMW connected drive portal.""" +"""Support for reading vehicle status from MyBMW portal.""" from __future__ import annotations from collections.abc import Callable @@ -6,7 +6,8 @@ from dataclasses import dataclass import logging from typing import cast -from bimmer_connected.vehicle import ConnectedDriveVehicle +from bimmer_connected.models import ValueWithUnit +from bimmer_connected.vehicle import MyBMWVehicle from homeassistant.components.sensor import ( SensorDeviceClass, @@ -27,7 +28,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.util.unit_system import UnitSystem -from . import BMWConnectedDriveBaseEntity +from . import BMWBaseEntity from .const import DOMAIN, UNIT_MAP from .coordinator import BMWDataUpdateCoordinator @@ -38,44 +39,54 @@ _LOGGER = logging.getLogger(__name__) class BMWSensorEntityDescription(SensorEntityDescription): """Describes BMW sensor entity.""" + key_class: str | None = None unit_metric: str | None = None unit_imperial: str | None = None value: Callable = lambda x, y: x def convert_and_round( - state: tuple, + state: ValueWithUnit, converter: Callable[[float | None, str], float], precision: int, ) -> float | None: - """Safely convert and round a value from a Tuple[value, unit].""" - if state[0] is None: - return None - return round(converter(state[0], UNIT_MAP.get(state[1], state[1])), precision) + """Safely convert and round a value from ValueWithUnit.""" + if state.value and state.unit: + return round( + converter(state.value, UNIT_MAP.get(state.unit, state.unit)), precision + ) + if state.value: + return state.value + return None SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { # --- Generic --- "charging_start_time": BMWSensorEntityDescription( key="charging_start_time", + key_class="fuel_and_battery", device_class=SensorDeviceClass.TIMESTAMP, entity_registry_enabled_default=False, ), "charging_end_time": BMWSensorEntityDescription( key="charging_end_time", + key_class="fuel_and_battery", device_class=SensorDeviceClass.TIMESTAMP, ), "charging_time_label": BMWSensorEntityDescription( key="charging_time_label", + key_class="fuel_and_battery", entity_registry_enabled_default=False, ), "charging_status": BMWSensorEntityDescription( key="charging_status", + key_class="fuel_and_battery", icon="mdi:ev-station", value=lambda x, y: x.value, ), - "charging_level_hv": BMWSensorEntityDescription( - key="charging_level_hv", + "remaining_battery_percent": BMWSensorEntityDescription( + key="remaining_battery_percent", + key_class="fuel_and_battery", unit_metric=PERCENTAGE, unit_imperial=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, @@ -90,6 +101,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { ), "remaining_range_total": BMWSensorEntityDescription( key="remaining_range_total", + key_class="fuel_and_battery", icon="mdi:map-marker-distance", unit_metric=LENGTH_KILOMETERS, unit_imperial=LENGTH_MILES, @@ -97,6 +109,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { ), "remaining_range_electric": BMWSensorEntityDescription( key="remaining_range_electric", + key_class="fuel_and_battery", icon="mdi:map-marker-distance", unit_metric=LENGTH_KILOMETERS, unit_imperial=LENGTH_MILES, @@ -104,6 +117,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { ), "remaining_range_fuel": BMWSensorEntityDescription( key="remaining_range_fuel", + key_class="fuel_and_battery", icon="mdi:map-marker-distance", unit_metric=LENGTH_KILOMETERS, unit_imperial=LENGTH_MILES, @@ -111,13 +125,15 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { ), "remaining_fuel": BMWSensorEntityDescription( key="remaining_fuel", + key_class="fuel_and_battery", icon="mdi:gas-station", unit_metric=VOLUME_LITERS, unit_imperial=VOLUME_GALLONS, value=lambda x, hass: convert_and_round(x, hass.config.units.volume, 2), ), - "fuel_percent": BMWSensorEntityDescription( - key="fuel_percent", + "remaining_fuel_percent": BMWSensorEntityDescription( + key="remaining_fuel_percent", + key_class="fuel_and_battery", icon="mdi:gas-station", unit_metric=PERCENTAGE, unit_imperial=PERCENTAGE, @@ -130,16 +146,16 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the BMW ConnectedDrive sensors from config entry.""" + """Set up the MyBMW sensors from config entry.""" unit_system = hass.config.units coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - entities: list[BMWConnectedDriveSensor] = [] + entities: list[BMWSensor] = [] for vehicle in coordinator.account.vehicles: entities.extend( [ - BMWConnectedDriveSensor(coordinator, vehicle, description, unit_system) + BMWSensor(coordinator, vehicle, description, unit_system) for attribute_name in vehicle.available_attributes if (description := SENSOR_TYPES.get(attribute_name)) ] @@ -148,7 +164,7 @@ async def async_setup_entry( async_add_entities(entities) -class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): +class BMWSensor(BMWBaseEntity, SensorEntity): """Representation of a BMW vehicle sensor.""" entity_description: BMWSensorEntityDescription @@ -156,7 +172,7 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): def __init__( self, coordinator: BMWDataUpdateCoordinator, - vehicle: ConnectedDriveVehicle, + vehicle: MyBMWVehicle, description: BMWSensorEntityDescription, unit_system: UnitSystem, ) -> None: @@ -178,7 +194,13 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): _LOGGER.debug( "Updating sensor '%s' of %s", self.entity_description.key, self.vehicle.name ) - state = getattr(self.vehicle.status, self.entity_description.key) + if self.entity_description.key_class is None: + state = getattr(self.vehicle, self.entity_description.key) + else: + state = getattr( + getattr(self.vehicle, self.entity_description.key_class), + self.entity_description.key, + ) self._attr_native_value = cast( StateType, self.entity_description.value(state, self.hass) ) diff --git a/homeassistant/components/bmw_connected_drive/translations/ca.json b/homeassistant/components/bmw_connected_drive/translations/ca.json index eb12ac6fc3b..b6129aa4de8 100644 --- a/homeassistant/components/bmw_connected_drive/translations/ca.json +++ b/homeassistant/components/bmw_connected_drive/translations/ca.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Nom\u00e9s de lectura (nom\u00e9s sensors i notificacions, sense execuci\u00f3 de serveis, sense bloqueig)", - "use_location": "Utilitza la ubicaci\u00f3 de Home Assistant per a les crides de localitzaci\u00f3 del cotxe (obligatori per a vehicles que no siguin i3/i8 produ\u00efts abans del 7/2014)" + "read_only": "Nom\u00e9s de lectura (nom\u00e9s sensors i notificacions, sense execuci\u00f3 de serveis, sense bloqueig)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/de.json b/homeassistant/components/bmw_connected_drive/translations/de.json index 85e27b5b3e4..0ee49c105f4 100644 --- a/homeassistant/components/bmw_connected_drive/translations/de.json +++ b/homeassistant/components/bmw_connected_drive/translations/de.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Schreibgesch\u00fctzt (nur Sensoren und Notify, keine Ausf\u00fchrung von Diensten, kein Abschlie\u00dfen)", - "use_location": "Standort des Home Assistant f\u00fcr die Abfrage des Fahrzeugstandorts verwenden (erforderlich f\u00fcr nicht i3/i8 Fahrzeuge, die vor 7/2014 produziert wurden)" + "read_only": "Schreibgesch\u00fctzt (nur Sensoren und Notify, keine Ausf\u00fchrung von Diensten, kein Abschlie\u00dfen)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/el.json b/homeassistant/components/bmw_connected_drive/translations/el.json index bb20b0a61a2..8ba712e7373 100644 --- a/homeassistant/components/bmw_connected_drive/translations/el.json +++ b/homeassistant/components/bmw_connected_drive/translations/el.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "\u039c\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7 (\u03bc\u03cc\u03bd\u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2, \u03cc\u03c7\u03b9 \u03b5\u03ba\u03c4\u03ad\u03bb\u03b5\u03c3\u03b7 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03b9\u03ce\u03bd, \u03cc\u03c7\u03b9 \u03ba\u03bb\u03b5\u03af\u03b4\u03c9\u03bc\u03b1)", - "use_location": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03b4\u03b7\u03bc\u03bf\u03c3\u03ba\u03bf\u03c0\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b8\u03ad\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b1\u03c5\u03c4\u03bf\u03ba\u03b9\u03bd\u03ae\u03c4\u03bf\u03c5 (\u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03bf\u03c7\u03ae\u03bc\u03b1\u03c4\u03b1 \u03c0\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 i3/i8 \u03ba\u03b1\u03b9 \u03ad\u03c7\u03bf\u03c5\u03bd \u03c0\u03b1\u03c1\u03b1\u03c7\u03b8\u03b5\u03af \u03c0\u03c1\u03b9\u03bd \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 7/2014)" + "read_only": "\u039c\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7 (\u03bc\u03cc\u03bd\u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2, \u03cc\u03c7\u03b9 \u03b5\u03ba\u03c4\u03ad\u03bb\u03b5\u03c3\u03b7 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03b9\u03ce\u03bd, \u03cc\u03c7\u03b9 \u03ba\u03bb\u03b5\u03af\u03b4\u03c9\u03bc\u03b1)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/en.json b/homeassistant/components/bmw_connected_drive/translations/en.json index dedd84d070b..db98df61d28 100644 --- a/homeassistant/components/bmw_connected_drive/translations/en.json +++ b/homeassistant/components/bmw_connected_drive/translations/en.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Read-only (only sensors and notify, no execution of services, no lock)", - "use_location": "Use Home Assistant location for car location polls (required for non i3/i8 vehicles produced before 7/2014)" + "read_only": "Read-only (only sensors and notify, no execution of services, no lock)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/es-419.json b/homeassistant/components/bmw_connected_drive/translations/es-419.json index bb19124b55c..0bce46abd97 100644 --- a/homeassistant/components/bmw_connected_drive/translations/es-419.json +++ b/homeassistant/components/bmw_connected_drive/translations/es-419.json @@ -12,8 +12,7 @@ "step": { "account_options": { "data": { - "read_only": "Solo lectura (solo sensores y notificaci\u00f3n, sin ejecuci\u00f3n de servicios, sin bloqueo)", - "use_location": "Use la ubicaci\u00f3n de Home Assistant para encuestas de ubicaci\u00f3n de autom\u00f3viles (obligatorio para veh\u00edculos que no sean i3/i8 fabricados antes de julio de 2014)" + "read_only": "Solo lectura (solo sensores y notificaci\u00f3n, sin ejecuci\u00f3n de servicios, sin bloqueo)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/es.json b/homeassistant/components/bmw_connected_drive/translations/es.json index 65ed9643f89..2a4fd84f708 100644 --- a/homeassistant/components/bmw_connected_drive/translations/es.json +++ b/homeassistant/components/bmw_connected_drive/translations/es.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "S\u00f3lo lectura (s\u00f3lo sensores y notificaci\u00f3n, sin ejecuci\u00f3n de servicios, sin bloqueo)", - "use_location": "Usar la ubicaci\u00f3n de Home Assistant para las encuestas de localizaci\u00f3n de autom\u00f3viles (necesario para los veh\u00edculos no i3/i8 producidos antes del 7/2014)" + "read_only": "S\u00f3lo lectura (s\u00f3lo sensores y notificaci\u00f3n, sin ejecuci\u00f3n de servicios, sin bloqueo)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/et.json b/homeassistant/components/bmw_connected_drive/translations/et.json index f28209a1e7a..adee392525e 100644 --- a/homeassistant/components/bmw_connected_drive/translations/et.json +++ b/homeassistant/components/bmw_connected_drive/translations/et.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Kirjutuskaitstud (ainult andurid ja teavitused, ei k\u00e4ivita teenuseid, kood puudub)", - "use_location": "Kasuta HA asukohta auto asukoha k\u00fcsitluste jaoks (n\u00f5utav enne 7/2014 toodetud muude kui i3 / i8 s\u00f5idukite jaoks)" + "read_only": "Kirjutuskaitstud (ainult andurid ja teavitused, ei k\u00e4ivita teenuseid, kood puudub)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/fr.json b/homeassistant/components/bmw_connected_drive/translations/fr.json index 5181620d465..afed216c64c 100644 --- a/homeassistant/components/bmw_connected_drive/translations/fr.json +++ b/homeassistant/components/bmw_connected_drive/translations/fr.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Lecture seule (uniquement capteurs et notification, pas d'ex\u00e9cution de services, pas de verrouillage)", - "use_location": "Utilisez la localisation de Home Assistant pour les sondages de localisation de voiture (obligatoire pour les v\u00e9hicules non i3 / i8 produits avant 7/2014)" + "read_only": "Lecture seule (uniquement capteurs et notification, pas d'ex\u00e9cution de services, pas de verrouillage)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/hu.json b/homeassistant/components/bmw_connected_drive/translations/hu.json index fa3fcf2df57..637083154bd 100644 --- a/homeassistant/components/bmw_connected_drive/translations/hu.json +++ b/homeassistant/components/bmw_connected_drive/translations/hu.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Csak olvashat\u00f3 (csak \u00e9rz\u00e9kel\u0151k \u00e9s \u00e9rtes\u00edt\u00e9sek, szolg\u00e1ltat\u00e1sok v\u00e9grehajt\u00e1sa, z\u00e1rol\u00e1s n\u00e9lk\u00fcl)", - "use_location": "Haszn\u00e1lja a Home Assistant hely\u00e9t az aut\u00f3 helymeghat\u00e1roz\u00e1si lek\u00e9rdez\u00e9seihez (a 2014.07.07. el\u0151tt gy\u00e1rtott nem i3/i8 j\u00e1rm\u0171vekhez sz\u00fcks\u00e9ges)" + "read_only": "Csak olvashat\u00f3 (csak \u00e9rz\u00e9kel\u0151k \u00e9s \u00e9rtes\u00edt\u00e9sek, szolg\u00e1ltat\u00e1sok v\u00e9grehajt\u00e1sa, z\u00e1rol\u00e1s n\u00e9lk\u00fcl)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/id.json b/homeassistant/components/bmw_connected_drive/translations/id.json index 3701a49ccfb..23e57c8e301 100644 --- a/homeassistant/components/bmw_connected_drive/translations/id.json +++ b/homeassistant/components/bmw_connected_drive/translations/id.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Hanya baca (hanya sensor dan notifikasi, tidak ada eksekusi layanan, tidak ada fitur penguncian)", - "use_location": "Gunakan lokasi Home Assistant untuk polling lokasi mobil (diperlukan untuk kendaraan non i3/i8 yang diproduksi sebelum Juli 2014)" + "read_only": "Hanya baca (hanya sensor dan notifikasi, tidak ada eksekusi layanan, tidak ada fitur penguncian)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/it.json b/homeassistant/components/bmw_connected_drive/translations/it.json index eeca1909ea7..3c0bb4ee830 100644 --- a/homeassistant/components/bmw_connected_drive/translations/it.json +++ b/homeassistant/components/bmw_connected_drive/translations/it.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Sola lettura (solo sensori e notifica, nessuna esecuzione di servizi, nessun blocco)", - "use_location": "Usa la posizione di Home Assistant per le richieste di posizione dell'auto (richiesto per veicoli non i3/i8 prodotti prima del 7/2014)" + "read_only": "Sola lettura (solo sensori e notifica, nessuna esecuzione di servizi, nessun blocco)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/ja.json b/homeassistant/components/bmw_connected_drive/translations/ja.json index a3fa86348fe..643e3b70cc3 100644 --- a/homeassistant/components/bmw_connected_drive/translations/ja.json +++ b/homeassistant/components/bmw_connected_drive/translations/ja.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "\u30ea\u30fc\u30c9\u30aa\u30f3\u30ea\u30fc(\u30bb\u30f3\u30b5\u30fc\u3068\u901a\u77e5\u306e\u307f\u3001\u30b5\u30fc\u30d3\u30b9\u306e\u5b9f\u884c\u306f\u4e0d\u53ef\u3001\u30ed\u30c3\u30af\u4e0d\u53ef)", - "use_location": "Home Assistant\u306e\u5834\u6240\u3092\u3001\u8eca\u306e\u4f4d\u7f6e\u3068\u3057\u3066\u30dd\u30fc\u30ea\u30f3\u30b0\u306b\u4f7f\u7528\u3059\u308b(2014\u5e747\u67087\u65e5\u4ee5\u524d\u306b\u751f\u7523\u3055\u308c\u305f\u3001i3/i8\u4ee5\u5916\u306e\u8eca\u4e21\u3067\u306f\u5fc5\u9808)" + "read_only": "\u30ea\u30fc\u30c9\u30aa\u30f3\u30ea\u30fc(\u30bb\u30f3\u30b5\u30fc\u3068\u901a\u77e5\u306e\u307f\u3001\u30b5\u30fc\u30d3\u30b9\u306e\u5b9f\u884c\u306f\u4e0d\u53ef\u3001\u30ed\u30c3\u30af\u4e0d\u53ef)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/ko.json b/homeassistant/components/bmw_connected_drive/translations/ko.json index 4c9872573be..588c5111afc 100644 --- a/homeassistant/components/bmw_connected_drive/translations/ko.json +++ b/homeassistant/components/bmw_connected_drive/translations/ko.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "\uc77d\uae30 \uc804\uc6a9(\uc13c\uc11c \ubc0f \uc54c\ub9bc\ub9cc \uac00\ub2a5, \uc11c\ube44\uc2a4 \uc2e4\ud589 \ubc0f \uc7a0\uae08 \uc5c6\uc74c)", - "use_location": "\ucc28\ub7c9 \uc704\uce58 \ud3f4\ub9c1\uc5d0 Home Assistant \uc704\uce58\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4(2014\ub144 7\uc6d4 \uc774\uc804\uc5d0 \uc0dd\uc0b0\ub41c i3/i8\uc774 \uc544\ub2cc \ucc28\ub7c9\uc758 \uacbd\uc6b0 \ud544\uc694)" + "read_only": "\uc77d\uae30 \uc804\uc6a9(\uc13c\uc11c \ubc0f \uc54c\ub9bc\ub9cc \uac00\ub2a5, \uc11c\ube44\uc2a4 \uc2e4\ud589 \ubc0f \uc7a0\uae08 \uc5c6\uc74c)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/nl.json b/homeassistant/components/bmw_connected_drive/translations/nl.json index 8fa6c839112..a566afd7ad8 100644 --- a/homeassistant/components/bmw_connected_drive/translations/nl.json +++ b/homeassistant/components/bmw_connected_drive/translations/nl.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Alleen-lezen (alleen sensoren en notificatie, geen uitvoering van diensten, geen vergrendeling)", - "use_location": "Gebruik Home Assistant locatie voor auto locatie peilingen (vereist voor niet i3/i8 voertuigen geproduceerd voor 7/2014)" + "read_only": "Alleen-lezen (alleen sensoren en notificatie, geen uitvoering van diensten, geen vergrendeling)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/no.json b/homeassistant/components/bmw_connected_drive/translations/no.json index f1715c550db..ed6acfeac38 100644 --- a/homeassistant/components/bmw_connected_drive/translations/no.json +++ b/homeassistant/components/bmw_connected_drive/translations/no.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Skrivebeskyttet (bare sensorer og varsler, ingen utf\u00f8relse av tjenester, ingen l\u00e5s)", - "use_location": "Bruk Home Assistant plassering for avstemningssteder for biler (p\u00e5krevd for ikke i3 / i8-kj\u00f8ret\u00f8y produsert f\u00f8r 7/2014)" + "read_only": "Skrivebeskyttet (bare sensorer og varsler, ingen utf\u00f8relse av tjenester, ingen l\u00e5s)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/pl.json b/homeassistant/components/bmw_connected_drive/translations/pl.json index 146916edd5b..00602b2c14e 100644 --- a/homeassistant/components/bmw_connected_drive/translations/pl.json +++ b/homeassistant/components/bmw_connected_drive/translations/pl.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Tylko odczyt (tylko sensory i powiadomienia, brak wykonywania us\u0142ug, brak blokady)", - "use_location": "U\u017cyj lokalizacji Home Assistant do sondowania lokalizacji samochodu (wymagane w przypadku pojazd\u00f3w innych ni\u017c i3/i8 wyprodukowanych przed 7/2014)" + "read_only": "Tylko odczyt (tylko sensory i powiadomienia, brak wykonywania us\u0142ug, brak blokady)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/pt-BR.json b/homeassistant/components/bmw_connected_drive/translations/pt-BR.json index b4b82c1fe18..a30ede043bd 100644 --- a/homeassistant/components/bmw_connected_drive/translations/pt-BR.json +++ b/homeassistant/components/bmw_connected_drive/translations/pt-BR.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Somente leitura (somente sensores e notificar, sem execu\u00e7\u00e3o de servi\u00e7os, sem bloqueio)", - "use_location": "Use a localiza\u00e7\u00e3o do Home Assistant para pesquisas de localiza\u00e7\u00e3o de carros (necess\u00e1rio para ve\u00edculos n\u00e3o i3/i8 produzidos antes de 7/2014)" + "read_only": "Somente leitura (somente sensores e notificar, sem execu\u00e7\u00e3o de servi\u00e7os, sem bloqueio)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/ru.json b/homeassistant/components/bmw_connected_drive/translations/ru.json index 8ab4e4e1207..7a398c8ca55 100644 --- a/homeassistant/components/bmw_connected_drive/translations/ru.json +++ b/homeassistant/components/bmw_connected_drive/translations/ru.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "\u0422\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u0435\u043d\u0438\u0435 (\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f, \u0431\u0435\u0437 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0436\u0431, \u0431\u0435\u0437 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438)", - "use_location": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Home Assistant \u0434\u043b\u044f \u043e\u043f\u0440\u043e\u0441\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0435\u0439 (\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0435\u0439 \u043d\u0435 i3/i8, \u0432\u044b\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u0434\u043e 7/2014)" + "read_only": "\u0422\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u0435\u043d\u0438\u0435 (\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f, \u0431\u0435\u0437 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0436\u0431, \u0431\u0435\u0437 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/sv.json b/homeassistant/components/bmw_connected_drive/translations/sv.json index 93cd828041e..eb2322abb1f 100644 --- a/homeassistant/components/bmw_connected_drive/translations/sv.json +++ b/homeassistant/components/bmw_connected_drive/translations/sv.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Endast l\u00e4sbar (endast sensorer och meddelanden, ingen k\u00f6rning av tj\u00e4nster, ingen l\u00e5sning)", - "use_location": "Anv\u00e4nd plats f\u00f6r Home Assistant som plats f\u00f6r bilen (kr\u00e4vs f\u00f6r icke i3/i8-fordon tillverkade f\u00f6re 7/2014)" + "read_only": "Endast l\u00e4sbar (endast sensorer och meddelanden, ingen k\u00f6rning av tj\u00e4nster, ingen l\u00e5sning)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/tr.json b/homeassistant/components/bmw_connected_drive/translations/tr.json index d38f950392b..52a0b02b044 100644 --- a/homeassistant/components/bmw_connected_drive/translations/tr.json +++ b/homeassistant/components/bmw_connected_drive/translations/tr.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "Salt okunur (yaln\u0131zca sens\u00f6rler ve bildirim, hizmetlerin y\u00fcr\u00fct\u00fclmesi yok, kilit yok)", - "use_location": "Araba konumu anketleri i\u00e7in Home Assistant konumunu kullan\u0131n (7/2014 tarihinden \u00f6nce \u00fcretilmi\u015f i3/i8 olmayan ara\u00e7lar i\u00e7in gereklidir)" + "read_only": "Salt okunur (yaln\u0131zca sens\u00f6rler ve bildirim, hizmetlerin y\u00fcr\u00fct\u00fclmesi yok, kilit yok)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/uk.json b/homeassistant/components/bmw_connected_drive/translations/uk.json index 68cdee2a66f..4286e0e6063 100644 --- a/homeassistant/components/bmw_connected_drive/translations/uk.json +++ b/homeassistant/components/bmw_connected_drive/translations/uk.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "\u041b\u0438\u0448\u0435 \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u043d\u043d\u044f (\u043b\u0438\u0448\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0442\u0430 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f, \u0431\u0435\u0437 \u0437\u0430\u043f\u0443\u0441\u043a\u0443 \u0441\u0435\u0440\u0432\u0456\u0441\u0456\u0432, \u0431\u0435\u0437 \u0431\u043b\u043e\u043a\u0443\u0432\u0430\u043d\u043d\u044f)", - "use_location": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f Home Assistant \u0434\u043b\u044f \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u044c \u043c\u0456\u0441\u0446\u044f \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0456\u043b\u0456\u0432 (\u043e\u0431\u043e\u0432\u2019\u044f\u0437\u043a\u043e\u0432\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0456\u043b\u0456\u0432, \u0449\u043e \u043d\u0435 \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u0434\u043e i3/i8, \u0432\u0438\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u0438\u0445 \u0434\u043e 7/2014)" + "read_only": "\u041b\u0438\u0448\u0435 \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u043d\u043d\u044f (\u043b\u0438\u0448\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0442\u0430 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f, \u0431\u0435\u0437 \u0437\u0430\u043f\u0443\u0441\u043a\u0443 \u0441\u0435\u0440\u0432\u0456\u0441\u0456\u0432, \u0431\u0435\u0437 \u0431\u043b\u043e\u043a\u0443\u0432\u0430\u043d\u043d\u044f)" } } } diff --git a/homeassistant/components/bmw_connected_drive/translations/zh-Hant.json b/homeassistant/components/bmw_connected_drive/translations/zh-Hant.json index 4f62df586f5..38bf3bbb7cb 100644 --- a/homeassistant/components/bmw_connected_drive/translations/zh-Hant.json +++ b/homeassistant/components/bmw_connected_drive/translations/zh-Hant.json @@ -21,8 +21,7 @@ "step": { "account_options": { "data": { - "read_only": "\u552f\u8b80\uff08\u50c5\u652f\u63f4\u611f\u6e2c\u5668\u8207\u901a\u77e5\uff0c\u4e0d\n\u5305\u542b\u670d\u52d9\u8207\u9396\u5b9a\uff09", - "use_location": "\u4f7f\u7528 Home Assistant \u4f4d\u7f6e\u53d6\u5f97\u6c7d\u8eca\u4f4d\u7f6e\uff08\u9700\u8981\u70ba2014/7 \u524d\u751f\u7522\u7684\u975ei3/i8 \u8eca\u6b3e\uff09" + "read_only": "\u552f\u8b80\uff08\u50c5\u652f\u63f4\u611f\u6e2c\u5668\u8207\u901a\u77e5\uff0c\u4e0d\n\u5305\u542b\u670d\u52d9\u8207\u9396\u5b9a\uff09" } } } diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index 062c1d844c4..557e68272c2 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -5,7 +5,7 @@ import logging from typing import Any from aiohttp import ClientError, ClientResponseError, ClientTimeout -from bond_api import Bond, BPUPSubscriptions, start_bpup +from bond_async import Bond, BPUPSubscriptions, start_bpup from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( diff --git a/homeassistant/components/bond/button.py b/homeassistant/components/bond/button.py index 700c6b5f407..0465e4c51fe 100644 --- a/homeassistant/components/bond/button.py +++ b/homeassistant/components/bond/button.py @@ -5,7 +5,7 @@ from dataclasses import dataclass import logging from typing import Any -from bond_api import Action, BPUPSubscriptions +from bond_async import Action, BPUPSubscriptions from homeassistant.components.button import ButtonEntity, ButtonEntityDescription from homeassistant.config_entries import ConfigEntry @@ -223,6 +223,20 @@ BUTTONS: tuple[BondButtonEntityDescription, ...] = ( mutually_exclusive=Action.OPEN, argument=None, ), + BondButtonEntityDescription( + key=Action.INCREASE_POSITION, + name="Increase Position", + icon="mdi:plus-box", + mutually_exclusive=Action.SET_POSITION, + argument=STEP_SIZE, + ), + BondButtonEntityDescription( + key=Action.DECREASE_POSITION, + name="Decrease Position", + icon="mdi:minus-box", + mutually_exclusive=Action.SET_POSITION, + argument=STEP_SIZE, + ), ) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index d3a7b4adf72..6eba9897468 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -6,7 +6,7 @@ import logging from typing import Any from aiohttp import ClientConnectionError, ClientResponseError -from bond_api import Bond +from bond_async import Bond import voluptuous as vol from homeassistant import config_entries, exceptions diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py index 664431a3145..3938de0d4bd 100644 --- a/homeassistant/components/bond/cover.py +++ b/homeassistant/components/bond/cover.py @@ -3,9 +3,10 @@ from __future__ import annotations from typing import Any -from bond_api import Action, BPUPSubscriptions, DeviceType +from bond_async import Action, BPUPSubscriptions, DeviceType from homeassistant.components.cover import ( + ATTR_POSITION, CoverDeviceClass, CoverEntity, CoverEntityFeature, @@ -20,6 +21,16 @@ from .entity import BondEntity from .utils import BondDevice, BondHub +def _bond_to_hass_position(bond_position: int) -> int: + """Convert bond 0-open 100-closed to hass 0-closed 100-open.""" + return abs(bond_position - 100) + + +def _hass_to_bond_position(hass_position: int) -> int: + """Convert hass 0-closed 100-open to bond 0-open 100-closed.""" + return 100 - hass_position + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -50,6 +61,8 @@ class BondCover(BondEntity, CoverEntity): """Create HA entity representing Bond cover.""" super().__init__(hub, device, bpup_subs) supported_features = 0 + if self._device.supports_set_position(): + supported_features |= CoverEntityFeature.SET_POSITION if self._device.supports_open(): supported_features |= CoverEntityFeature.OPEN if self._device.supports_close(): @@ -67,8 +80,15 @@ class BondCover(BondEntity, CoverEntity): def _apply_state(self, state: dict) -> None: cover_open = state.get("open") - self._attr_is_closed = ( - True if cover_open == 0 else False if cover_open == 1 else None + self._attr_is_closed = None if cover_open is None else cover_open == 0 + if (bond_position := state.get("position")) is not None: + self._attr_current_cover_position = _bond_to_hass_position(bond_position) + + async def async_set_cover_position(self, **kwargs: Any) -> None: + """Set the cover position.""" + await self._hub.bond.action( + self._device.device_id, + Action.set_position(_hass_to_bond_position(kwargs[ATTR_POSITION])), ) async def async_open_cover(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 583f1cd96f7..832e9b5d464 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -8,7 +8,7 @@ import logging from typing import Any from aiohttp import ClientError -from bond_api import BPUPSubscriptions +from bond_async import BPUPSubscriptions from homeassistant.const import ( ATTR_HW_VERSION, @@ -156,9 +156,13 @@ class BondEntity(Entity): self._apply_state(state) @callback - def _async_bpup_callback(self, state: dict) -> None: + def _async_bpup_callback(self, json_msg: dict) -> None: """Process a state change from BPUP.""" - self._async_state_callback(state) + topic = json_msg["t"] + if topic != f"devices/{self._device_id}/state": + return + + self._async_state_callback(json_msg["b"]) self.async_write_ha_state() async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index 9acc7874657..f2f6b15f923 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -6,7 +6,7 @@ import math from typing import Any from aiohttp.client_exceptions import ClientResponseError -from bond_api import Action, BPUPSubscriptions, DeviceType, Direction +from bond_async import Action, BPUPSubscriptions, DeviceType, Direction import voluptuous as vol from homeassistant.components.fan import ( diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index c0c3fc428b8..55084f37b03 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -5,7 +5,7 @@ import logging from typing import Any from aiohttp.client_exceptions import ClientResponseError -from bond_api import Action, BPUPSubscriptions, DeviceType +from bond_async import Action, BPUPSubscriptions, DeviceType import voluptuous as vol from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity diff --git a/homeassistant/components/bond/manifest.json b/homeassistant/components/bond/manifest.json index e5f8b004502..52e9dd1763f 100644 --- a/homeassistant/components/bond/manifest.json +++ b/homeassistant/components/bond/manifest.json @@ -3,10 +3,10 @@ "name": "Bond", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/bond", - "requirements": ["bond-api==0.1.16"], + "requirements": ["bond-async==0.1.20"], "zeroconf": ["_bond._tcp.local."], - "codeowners": ["@bdraco", "@prystupa", "@joshs85"], + "codeowners": ["@bdraco", "@prystupa", "@joshs85", "@marciogranzotto"], "quality_scale": "platinum", "iot_class": "local_push", - "loggers": ["bond_api"] + "loggers": ["bond_async"] } diff --git a/homeassistant/components/bond/switch.py b/homeassistant/components/bond/switch.py index 01c224d8307..da0b19dd9ff 100644 --- a/homeassistant/components/bond/switch.py +++ b/homeassistant/components/bond/switch.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import Any from aiohttp.client_exceptions import ClientResponseError -from bond_api import Action, BPUPSubscriptions, DeviceType +from bond_async import Action, BPUPSubscriptions, DeviceType import voluptuous as vol from homeassistant.components.switch import SwitchEntity diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index fcf519d681d..b2ba43c8275 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "old_firmware": "Niet-ondersteunde oude firmware op het Bond-apparaat - voer een upgrade uit voordat u doorgaat", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 785d7dfbd00..cba213d9450 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -5,7 +5,7 @@ import logging from typing import Any, cast from aiohttp import ClientResponseError -from bond_api import Action, Bond +from bond_async import Action, Bond from homeassistant.util.async_ import gather_with_concurrency @@ -82,6 +82,10 @@ class BondDevice: """Return True if this device supports any of the direction related commands.""" return self._has_any_action({Action.SET_DIRECTION}) + def supports_set_position(self) -> bool: + """Return True if this device supports setting the position.""" + return self._has_any_action({Action.SET_POSITION}) + def supports_open(self) -> bool: """Return True if this device supports opening.""" return self._has_any_action({Action.OPEN}) diff --git a/homeassistant/components/bosch_shc/translations/bg.json b/homeassistant/components/bosch_shc/translations/bg.json index 759dd6b21fb..a0b0548b51b 100644 --- a/homeassistant/components/bosch_shc/translations/bg.json +++ b/homeassistant/components/bosch_shc/translations/bg.json @@ -20,6 +20,5 @@ } } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/ca.json b/homeassistant/components/bosch_shc/translations/ca.json index 6db49cf7103..f6b06e1d884 100644 --- a/homeassistant/components/bosch_shc/translations/ca.json +++ b/homeassistant/components/bosch_shc/translations/ca.json @@ -33,6 +33,5 @@ "title": "Par\u00e0metres d'autenticaci\u00f3 SHC" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/de.json b/homeassistant/components/bosch_shc/translations/de.json index 46b6469afe6..b99c00d6a6f 100644 --- a/homeassistant/components/bosch_shc/translations/de.json +++ b/homeassistant/components/bosch_shc/translations/de.json @@ -33,6 +33,5 @@ "title": "SHC Authentifizierungsparameter" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/el.json b/homeassistant/components/bosch_shc/translations/el.json index f9b5f4ad3d0..3b92111ce1a 100644 --- a/homeassistant/components/bosch_shc/translations/el.json +++ b/homeassistant/components/bosch_shc/translations/el.json @@ -33,6 +33,5 @@ "title": "\u03a0\u03b1\u03c1\u03ac\u03bc\u03b5\u03c4\u03c1\u03bf\u03b9 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 SHC" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/en.json b/homeassistant/components/bosch_shc/translations/en.json index 65f675e2f4d..ab5cde9ef27 100644 --- a/homeassistant/components/bosch_shc/translations/en.json +++ b/homeassistant/components/bosch_shc/translations/en.json @@ -33,6 +33,5 @@ "title": "SHC authentication parameters" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/es-419.json b/homeassistant/components/bosch_shc/translations/es-419.json index fdb8903a318..e868ffb1add 100644 --- a/homeassistant/components/bosch_shc/translations/es-419.json +++ b/homeassistant/components/bosch_shc/translations/es-419.json @@ -17,6 +17,5 @@ "title": "Par\u00e1metros de autenticaci\u00f3n SHC" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/es.json b/homeassistant/components/bosch_shc/translations/es.json index 6d002cfadc2..0e3c536995d 100644 --- a/homeassistant/components/bosch_shc/translations/es.json +++ b/homeassistant/components/bosch_shc/translations/es.json @@ -33,6 +33,5 @@ "title": "Par\u00e1metros de autenticaci\u00f3n SHC" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/et.json b/homeassistant/components/bosch_shc/translations/et.json index cb095bb9de7..7311d1f34e7 100644 --- a/homeassistant/components/bosch_shc/translations/et.json +++ b/homeassistant/components/bosch_shc/translations/et.json @@ -33,6 +33,5 @@ "title": "SHC autentimisparameetrid" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/fr.json b/homeassistant/components/bosch_shc/translations/fr.json index c34f5549663..bc9f62e1d11 100644 --- a/homeassistant/components/bosch_shc/translations/fr.json +++ b/homeassistant/components/bosch_shc/translations/fr.json @@ -33,6 +33,5 @@ "title": "Param\u00e8tres d'authentification SHC" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/hu.json b/homeassistant/components/bosch_shc/translations/hu.json index b3e0ed77815..df5a484cabe 100644 --- a/homeassistant/components/bosch_shc/translations/hu.json +++ b/homeassistant/components/bosch_shc/translations/hu.json @@ -33,6 +33,5 @@ "title": "SHC hiteles\u00edt\u00e9si param\u00e9terek" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/id.json b/homeassistant/components/bosch_shc/translations/id.json index 723c6bdabbe..191c0cd96e8 100644 --- a/homeassistant/components/bosch_shc/translations/id.json +++ b/homeassistant/components/bosch_shc/translations/id.json @@ -33,6 +33,5 @@ "title": "Parameter autentikasi SHC" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/it.json b/homeassistant/components/bosch_shc/translations/it.json index 2c64beee59c..05336a6abea 100644 --- a/homeassistant/components/bosch_shc/translations/it.json +++ b/homeassistant/components/bosch_shc/translations/it.json @@ -33,6 +33,5 @@ "title": "Parametri di autenticazione SHC" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/ja.json b/homeassistant/components/bosch_shc/translations/ja.json index 5146f81301c..da4b471c621 100644 --- a/homeassistant/components/bosch_shc/translations/ja.json +++ b/homeassistant/components/bosch_shc/translations/ja.json @@ -22,7 +22,7 @@ } }, "reauth_confirm": { - "description": "bosch_shc\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", + "description": "Bosch_shc\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", "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" }, "user": { @@ -33,6 +33,5 @@ "title": "SHC\u8a8d\u8a3c\u30d1\u30e9\u30e1\u30fc\u30bf\u30fc" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/ko.json b/homeassistant/components/bosch_shc/translations/ko.json new file mode 100644 index 00000000000..3289cb23ce1 --- /dev/null +++ b/homeassistant/components/bosch_shc/translations/ko.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "pairing_failed": "\ud398\uc5b4\ub9c1\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. Bosch Smart Home Controller\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc(LED \uae5c\ubc15\uc784)\uc774\uace0 \ube44\ubc00\ubc88\ud638\uac00 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624.", + "session_error": "\uc138\uc158 \uc624\ub958: API\uac00 \ube44\uc815\uc0c1 \uacb0\uacfc\ub97c \ubc18\ud658\ud569\ub2c8\ub2e4.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "flow_title": "Bosch SHC: {name}", + "step": { + "confirm_discovery": { + "description": "LED\uac00 \uae5c\ubc15\uc774\uae30 \uc2dc\uc791\ud560 \ub54c\uae4c\uc9c0 Bosch Smart Home Controller\uc758 \uc804\uba74 \ubc84\ud2bc\uc744 \ub204\ub974\uc2ed\uc2dc\uc624.\nHome Assistant\ub85c {model} @ {host} \ub97c \uacc4\uc18d \uc124\uc815\ud560 \uc900\ube44\uac00 \ub418\uc168\uc2b5\ub2c8\uae4c?" + }, + "credentials": { + "data": { + "password": "Smart Home Controller\uc758 \ube44\ubc00\ubc88\ud638" + } + }, + "reauth_confirm": { + "description": "bosch_shc \ud1b5\ud569\uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" + }, + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8" + }, + "description": "Home Assistant\ub85c \ubaa8\ub2c8\ud130\ub9c1\ud558\uace0 \uc81c\uc5b4\ud560 \uc218 \uc788\ub3c4\ub85d Bosch Smart Home Controller\ub97c \uc124\uc815\ud558\uc2ed\uc2dc\uc624.", + "title": "SHC \uc778\uc99d \ub9e4\uac1c\ubcc0\uc218" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/nl.json b/homeassistant/components/bosch_shc/translations/nl.json index 8a6cbd6cfdd..8e966ff9bbe 100644 --- a/homeassistant/components/bosch_shc/translations/nl.json +++ b/homeassistant/components/bosch_shc/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -23,7 +23,7 @@ }, "reauth_confirm": { "description": "De bosch_shc integratie moet uw account herauthenticeren", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { @@ -33,6 +33,5 @@ "title": "SHC-authenticatieparameters" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/no.json b/homeassistant/components/bosch_shc/translations/no.json index 53d64519fd5..1b5b9fb0642 100644 --- a/homeassistant/components/bosch_shc/translations/no.json +++ b/homeassistant/components/bosch_shc/translations/no.json @@ -33,6 +33,5 @@ "title": "SHC-autentiseringsparametere" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/pl.json b/homeassistant/components/bosch_shc/translations/pl.json index 6b21fd87b7c..11ae44dbe48 100644 --- a/homeassistant/components/bosch_shc/translations/pl.json +++ b/homeassistant/components/bosch_shc/translations/pl.json @@ -33,6 +33,5 @@ "title": "Parametry uwierzytelniania SHC" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/pt-BR.json b/homeassistant/components/bosch_shc/translations/pt-BR.json index f8a74157ab3..22f5bbe22bb 100644 --- a/homeassistant/components/bosch_shc/translations/pt-BR.json +++ b/homeassistant/components/bosch_shc/translations/pt-BR.json @@ -33,6 +33,5 @@ "title": "Par\u00e2metros de autentica\u00e7\u00e3o SHC" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/ru.json b/homeassistant/components/bosch_shc/translations/ru.json index 498003b2501..c5a6a53582f 100644 --- a/homeassistant/components/bosch_shc/translations/ru.json +++ b/homeassistant/components/bosch_shc/translations/ru.json @@ -33,6 +33,5 @@ "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 SHC" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/tr.json b/homeassistant/components/bosch_shc/translations/tr.json index f48286cf9cd..173b8e124ca 100644 --- a/homeassistant/components/bosch_shc/translations/tr.json +++ b/homeassistant/components/bosch_shc/translations/tr.json @@ -33,6 +33,5 @@ "title": "SHC kimlik do\u011frulama parametreleri" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/bosch_shc/translations/zh-Hant.json b/homeassistant/components/bosch_shc/translations/zh-Hant.json index fd667210d3e..2e4086051d6 100644 --- a/homeassistant/components/bosch_shc/translations/zh-Hant.json +++ b/homeassistant/components/bosch_shc/translations/zh-Hant.json @@ -33,6 +33,5 @@ "title": "SHC \u8a8d\u8b49\u53c3\u6578" } } - }, - "title": "Bosch SHC" + } } \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/bg.json b/homeassistant/components/braviatv/translations/bg.json index d05511b8d29..ef8edbdab20 100644 --- a/homeassistant/components/braviatv/translations/bg.json +++ b/homeassistant/components/braviatv/translations/bg.json @@ -17,8 +17,7 @@ "user": { "data": { "host": "\u0425\u043e\u0441\u0442" - }, - "title": "Sony Bravia TV" + } } } } diff --git a/homeassistant/components/braviatv/translations/ca.json b/homeassistant/components/braviatv/translations/ca.json index b8616f5e8ba..2f35c77caa1 100644 --- a/homeassistant/components/braviatv/translations/ca.json +++ b/homeassistant/components/braviatv/translations/ca.json @@ -21,8 +21,7 @@ "data": { "host": "Amfitri\u00f3" }, - "description": "Assegura't que el televisor est\u00e0 engegat abans de configurar-lo.", - "title": "Televisor Sony Bravia" + "description": "Assegura't que el televisor est\u00e0 engegat abans de configurar-lo." } } }, diff --git a/homeassistant/components/braviatv/translations/cs.json b/homeassistant/components/braviatv/translations/cs.json index ae6e3e5c851..f08c5d82861 100644 --- a/homeassistant/components/braviatv/translations/cs.json +++ b/homeassistant/components/braviatv/translations/cs.json @@ -20,8 +20,7 @@ "data": { "host": "Hostitel" }, - "description": "Nastavte integraci televize Sony Bravia. Pokud m\u00e1te s nastaven\u00edm probl\u00e9my, pod\u00edvejte se na: https://www.home-assistant.io/integrations/braviatv\n\n Ujist\u011bte se, \u017ee va\u0161e televize je zapnut\u00e1.", - "title": "Televize Sony Bravia" + "description": "Nastavte integraci televize Sony Bravia. Pokud m\u00e1te s nastaven\u00edm probl\u00e9my, pod\u00edvejte se na: https://www.home-assistant.io/integrations/braviatv\n\n Ujist\u011bte se, \u017ee va\u0161e televize je zapnut\u00e1." } } }, diff --git a/homeassistant/components/braviatv/translations/da.json b/homeassistant/components/braviatv/translations/da.json index 006d6b708e5..5b9778575a4 100644 --- a/homeassistant/components/braviatv/translations/da.json +++ b/homeassistant/components/braviatv/translations/da.json @@ -3,9 +3,6 @@ "step": { "authorize": { "title": "Godkend Sony Bravia TV" - }, - "user": { - "title": "Sony Bravia TV" } } } diff --git a/homeassistant/components/braviatv/translations/de.json b/homeassistant/components/braviatv/translations/de.json index 55ddbec464e..ff00828c0f3 100644 --- a/homeassistant/components/braviatv/translations/de.json +++ b/homeassistant/components/braviatv/translations/de.json @@ -21,8 +21,7 @@ "data": { "host": "Host" }, - "description": "Stelle sicher, dass dein Fernseher eingeschaltet ist, bevor du versuchst, ihn einzurichten.", - "title": "Sony Bravia TV" + "description": "Stelle sicher, dass dein Fernseher eingeschaltet ist, bevor du versuchst, ihn einzurichten." } } }, diff --git a/homeassistant/components/braviatv/translations/el.json b/homeassistant/components/braviatv/translations/el.json index 489bf0a7c36..070f546d532 100644 --- a/homeassistant/components/braviatv/translations/el.json +++ b/homeassistant/components/braviatv/translations/el.json @@ -21,8 +21,7 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, - "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7\u03c2 Sony Bravia. \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/braviatv \n\n\u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7.", - "title": "\u03a4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 Sony Bravia" + "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7\u03c2 Sony Bravia. \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/braviatv \n\n\u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7." } } }, diff --git a/homeassistant/components/braviatv/translations/en.json b/homeassistant/components/braviatv/translations/en.json index 9cc2ac23d17..45722d13b9a 100644 --- a/homeassistant/components/braviatv/translations/en.json +++ b/homeassistant/components/braviatv/translations/en.json @@ -21,8 +21,7 @@ "data": { "host": "Host" }, - "description": "Ensure that your TV is turned on before trying to set it up.", - "title": "Sony Bravia TV" + "description": "Ensure that your TV is turned on before trying to set it up." } } }, diff --git a/homeassistant/components/braviatv/translations/es-419.json b/homeassistant/components/braviatv/translations/es-419.json index 319eff13b98..ef12c71d681 100644 --- a/homeassistant/components/braviatv/translations/es-419.json +++ b/homeassistant/components/braviatv/translations/es-419.json @@ -18,8 +18,7 @@ "data": { "host": "Nombre de host de TV o direcci\u00f3n IP" }, - "description": "Configure la integraci\u00f3n de Sony Bravia TV. Si tiene problemas con la configuraci\u00f3n, vaya a: https://www.home-assistant.io/integrations/braviatv \n\n Aseg\u00farese de que su televisi\u00f3n est\u00e9 encendida.", - "title": "Sony Bravia TV" + "description": "Configure la integraci\u00f3n de Sony Bravia TV. Si tiene problemas con la configuraci\u00f3n, vaya a: https://www.home-assistant.io/integrations/braviatv \n\n Aseg\u00farese de que su televisi\u00f3n est\u00e9 encendida." } } }, diff --git a/homeassistant/components/braviatv/translations/es.json b/homeassistant/components/braviatv/translations/es.json index 2cc3b05010c..401a254eeaf 100644 --- a/homeassistant/components/braviatv/translations/es.json +++ b/homeassistant/components/braviatv/translations/es.json @@ -15,14 +15,13 @@ "pin": "C\u00f3digo PIN" }, "description": "Introduce el c\u00f3digo PIN que se muestra en el televisor Sony Bravia.\n\nSi no se muestra ning\u00fan c\u00f3digo PIN, necesitas eliminar el registro de Home Assistant de tu televisor, ve a: Configuraci\u00f3n -> Red -> Configuraci\u00f3n del dispositivo remoto -> Eliminar el dispositivo remoto.", - "title": "Autorizar televisor Sony Bravia" + "title": "Autorizaci\u00f3n del televisor Sony Bravia" }, "user": { "data": { "host": "Host" }, - "description": "Configura la integraci\u00f3n del televisor Sony Bravia. Si tienes problemas con la configuraci\u00f3n, ve a: https://www.home-assistant.io/integrations/braviatv\n\nAseg\u00farate de que tu televisor est\u00e1 encendido.", - "title": "Televisor Sony Bravia" + "description": "Configura la integraci\u00f3n del televisor Sony Bravia. Si tienes problemas con la configuraci\u00f3n, ve a: https://www.home-assistant.io/integrations/braviatv\n\nAseg\u00farate de que tu televisor est\u00e1 encendido." } } }, diff --git a/homeassistant/components/braviatv/translations/et.json b/homeassistant/components/braviatv/translations/et.json index c63e3635185..ecfc89b968d 100644 --- a/homeassistant/components/braviatv/translations/et.json +++ b/homeassistant/components/braviatv/translations/et.json @@ -21,8 +21,7 @@ "data": { "host": "" }, - "description": "Enne teleri seadistamist veendu, et see oleks sisse l\u00fclitatud.", - "title": "" + "description": "Enne teleri seadistamist veendu, et see oleks sisse l\u00fclitatud." } } }, diff --git a/homeassistant/components/braviatv/translations/fr.json b/homeassistant/components/braviatv/translations/fr.json index f85aea705fb..0290dad6857 100644 --- a/homeassistant/components/braviatv/translations/fr.json +++ b/homeassistant/components/braviatv/translations/fr.json @@ -21,8 +21,7 @@ "data": { "host": "H\u00f4te" }, - "description": "Assurez-vous que votre t\u00e9l\u00e9viseur est allum\u00e9 avant d'essayer de le configurer.", - "title": "Sony Bravia TV" + "description": "Assurez-vous que votre t\u00e9l\u00e9viseur est allum\u00e9 avant d'essayer de le configurer." } } }, diff --git a/homeassistant/components/braviatv/translations/hu.json b/homeassistant/components/braviatv/translations/hu.json index 554e74c52b1..d0d372df898 100644 --- a/homeassistant/components/braviatv/translations/hu.json +++ b/homeassistant/components/braviatv/translations/hu.json @@ -21,8 +21,7 @@ "data": { "host": "C\u00edm" }, - "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" + "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." } } }, diff --git a/homeassistant/components/braviatv/translations/id.json b/homeassistant/components/braviatv/translations/id.json index cb7d5396f7a..e387a2113b0 100644 --- a/homeassistant/components/braviatv/translations/id.json +++ b/homeassistant/components/braviatv/translations/id.json @@ -21,8 +21,7 @@ "data": { "host": "Host" }, - "description": "Pastikan TV Anda dinyalakan sebelum menyiapkan.", - "title": "TV Sony Bravia" + "description": "Pastikan TV Anda dinyalakan sebelum menyiapkan." } } }, diff --git a/homeassistant/components/braviatv/translations/it.json b/homeassistant/components/braviatv/translations/it.json index 7ab149fa6c7..3dd57d1359a 100644 --- a/homeassistant/components/braviatv/translations/it.json +++ b/homeassistant/components/braviatv/translations/it.json @@ -21,8 +21,7 @@ "data": { "host": "Host" }, - "description": "Assicurati che la tua TV sia accesa prima di provare a configurarla.", - "title": "Sony Bravia TV" + "description": "Assicurati che la tua TV sia accesa prima di provare a configurarla." } } }, diff --git a/homeassistant/components/braviatv/translations/ja.json b/homeassistant/components/braviatv/translations/ja.json index 87f903f0640..860fd5c89ea 100644 --- a/homeassistant/components/braviatv/translations/ja.json +++ b/homeassistant/components/braviatv/translations/ja.json @@ -21,8 +21,7 @@ "data": { "host": "\u30db\u30b9\u30c8" }, - "description": "\u30bd\u30cb\u30fc Bravia TV\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a\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/braviatv \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044\n\n\u304d\u3061\u3093\u3068\u30c6\u30ec\u30d3\u306e\u96fb\u6e90\u304c\u5165\u3063\u3066\u3044\u308b\u3053\u3068\u3082\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "\u30bd\u30cb\u30fc Bravia TV" + "description": "\u30bd\u30cb\u30fc Bravia TV\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a\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/braviatv \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044\n\n\u304d\u3061\u3093\u3068\u30c6\u30ec\u30d3\u306e\u96fb\u6e90\u304c\u5165\u3063\u3066\u3044\u308b\u3053\u3068\u3082\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002" } } }, diff --git a/homeassistant/components/braviatv/translations/ko.json b/homeassistant/components/braviatv/translations/ko.json index 7bad0f0047c..78c02bceb44 100644 --- a/homeassistant/components/braviatv/translations/ko.json +++ b/homeassistant/components/braviatv/translations/ko.json @@ -21,8 +21,7 @@ "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Sony Bravia TV \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00 \uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/braviatv \ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n\nTV\uac00 \ucf1c\uc838 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", - "title": "Sony Bravia TV" + "description": "Sony Bravia TV \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00 \uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/braviatv \ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n\nTV\uac00 \ucf1c\uc838 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694." } } }, diff --git a/homeassistant/components/braviatv/translations/lb.json b/homeassistant/components/braviatv/translations/lb.json index 3bcf8702c4f..6510666ad55 100644 --- a/homeassistant/components/braviatv/translations/lb.json +++ b/homeassistant/components/braviatv/translations/lb.json @@ -21,8 +21,7 @@ "data": { "host": "Host" }, - "description": "Sony Bravia TV Integratioun ariichten. Falls et Problemer mat der Konfiguratioun g\u00ebtt g\u00e9i op:\nhttps://www.home-assistant.io/integrations/braviatv\nStell s\u00e9cher dass d\u00e4in Fernseh un ass.", - "title": "Sony Bravia TV" + "description": "Sony Bravia TV Integratioun ariichten. Falls et Problemer mat der Konfiguratioun g\u00ebtt g\u00e9i op:\nhttps://www.home-assistant.io/integrations/braviatv\nStell s\u00e9cher dass d\u00e4in Fernseh un ass." } } }, diff --git a/homeassistant/components/braviatv/translations/nl.json b/homeassistant/components/braviatv/translations/nl.json index 133c5277045..d3696024643 100644 --- a/homeassistant/components/braviatv/translations/nl.json +++ b/homeassistant/components/braviatv/translations/nl.json @@ -12,7 +12,7 @@ "step": { "authorize": { "data": { - "pin": "PIN-code" + "pin": "Pincode" }, "description": "Voer de pincode in die wordt weergegeven op de Sony Bravia tv. \n\nAls de pincode niet wordt weergegeven, moet u de Home Assistant op uw tv afmelden, ga naar: Instellingen -> Netwerk -> Instellingen extern apparaat -> Afmelden extern apparaat.", "title": "Autoriseer Sony Bravia tv" @@ -21,8 +21,7 @@ "data": { "host": "Host" }, - "description": "Zorg ervoor dat uw TV aan staat voordat u hem probeert in te stellen.", - "title": "Sony Bravia TV" + "description": "Zorg ervoor dat uw TV aan staat voordat u hem probeert in te stellen." } } }, diff --git a/homeassistant/components/braviatv/translations/no.json b/homeassistant/components/braviatv/translations/no.json index 6465db8d3c0..067a2fcda97 100644 --- a/homeassistant/components/braviatv/translations/no.json +++ b/homeassistant/components/braviatv/translations/no.json @@ -21,8 +21,7 @@ "data": { "host": "Vert" }, - "description": "S\u00f8rg for at TV-en er sl\u00e5tt p\u00e5 f\u00f8r du pr\u00f8ver \u00e5 sette den opp.", - "title": "" + "description": "S\u00f8rg for at TV-en er sl\u00e5tt p\u00e5 f\u00f8r du pr\u00f8ver \u00e5 sette den opp." } } }, diff --git a/homeassistant/components/braviatv/translations/pl.json b/homeassistant/components/braviatv/translations/pl.json index 354962a62d9..83521554e21 100644 --- a/homeassistant/components/braviatv/translations/pl.json +++ b/homeassistant/components/braviatv/translations/pl.json @@ -21,8 +21,7 @@ "data": { "host": "Nazwa hosta lub adres IP" }, - "description": "Upewnij si\u0119, \u017ce telewizor jest w\u0142\u0105czony, zanim spr\u00f3bujesz go skonfigurowa\u0107.", - "title": "Sony Bravia TV" + "description": "Upewnij si\u0119, \u017ce telewizor jest w\u0142\u0105czony, zanim spr\u00f3bujesz go skonfigurowa\u0107." } } }, diff --git a/homeassistant/components/braviatv/translations/pt-BR.json b/homeassistant/components/braviatv/translations/pt-BR.json index a784e30d84c..3931935ff38 100644 --- a/homeassistant/components/braviatv/translations/pt-BR.json +++ b/homeassistant/components/braviatv/translations/pt-BR.json @@ -21,8 +21,7 @@ "data": { "host": "Nome do host" }, - "description": "Certifique-se de que sua TV esteja ligada antes de tentar configur\u00e1-la.", - "title": "Sony Bravia TV" + "description": "Certifique-se de que sua TV esteja ligada antes de tentar configur\u00e1-la." } } }, diff --git a/homeassistant/components/braviatv/translations/pt.json b/homeassistant/components/braviatv/translations/pt.json index 5e5f1367f58..e113d74d6fc 100644 --- a/homeassistant/components/braviatv/translations/pt.json +++ b/homeassistant/components/braviatv/translations/pt.json @@ -19,8 +19,7 @@ "user": { "data": { "host": "Servidor" - }, - "title": "TV Sony Bravia" + } } } }, diff --git a/homeassistant/components/braviatv/translations/ru.json b/homeassistant/components/braviatv/translations/ru.json index 3aeb83f7686..046a46c5ae4 100644 --- a/homeassistant/components/braviatv/translations/ru.json +++ b/homeassistant/components/braviatv/translations/ru.json @@ -21,8 +21,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, - "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" + "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." } } }, diff --git a/homeassistant/components/braviatv/translations/sl.json b/homeassistant/components/braviatv/translations/sl.json index ae98bf15dc3..d412b545579 100644 --- a/homeassistant/components/braviatv/translations/sl.json +++ b/homeassistant/components/braviatv/translations/sl.json @@ -17,8 +17,7 @@ "data": { "host": "TV ime gostitelja ali IP naslov" }, - "description": "Nastavite integracijo Sony Bravia TV. \u010ce imate te\u017eave s konfiguracijo, pojdite na: https://www.home-assistant.io/integrations/braviatv \n\n Prepri\u010dajte se, da je va\u0161 televizor vklopljen.", - "title": "Sony Bravia TV" + "description": "Nastavite integracijo Sony Bravia TV. \u010ce imate te\u017eave s konfiguracijo, pojdite na: https://www.home-assistant.io/integrations/braviatv \n\n Prepri\u010dajte se, da je va\u0161 televizor vklopljen." } } }, diff --git a/homeassistant/components/braviatv/translations/sv.json b/homeassistant/components/braviatv/translations/sv.json index b07494f15cb..e3b98b4e76e 100644 --- a/homeassistant/components/braviatv/translations/sv.json +++ b/homeassistant/components/braviatv/translations/sv.json @@ -14,8 +14,7 @@ "user": { "data": { "host": "V\u00e4rdnamn eller IP-adress f\u00f6r TV" - }, - "title": "Sony Bravia TV" + } } } } diff --git a/homeassistant/components/braviatv/translations/tr.json b/homeassistant/components/braviatv/translations/tr.json index b6df4a7999a..cf5cc45640e 100644 --- a/homeassistant/components/braviatv/translations/tr.json +++ b/homeassistant/components/braviatv/translations/tr.json @@ -21,8 +21,7 @@ "data": { "host": "Ana Bilgisayar" }, - "description": "Kurmaya \u00e7al\u0131\u015fmadan \u00f6nce TV'nizin a\u00e7\u0131k oldu\u011fundan emin olun.", - "title": "Sony Bravia TV" + "description": "Kurmaya \u00e7al\u0131\u015fmadan \u00f6nce TV'nizin a\u00e7\u0131k oldu\u011fundan emin olun." } } }, diff --git a/homeassistant/components/braviatv/translations/uk.json b/homeassistant/components/braviatv/translations/uk.json index 7f66329c57e..5d9e22e59de 100644 --- a/homeassistant/components/braviatv/translations/uk.json +++ b/homeassistant/components/braviatv/translations/uk.json @@ -21,8 +21,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, - "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 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457:\nhttps://www.home-assistant.io/integrations/braviatv", - "title": "\u0422\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Sony Bravia" + "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 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457:\nhttps://www.home-assistant.io/integrations/braviatv" } } }, diff --git a/homeassistant/components/braviatv/translations/zh-Hans.json b/homeassistant/components/braviatv/translations/zh-Hans.json index d02d562d55d..447c136dcf4 100644 --- a/homeassistant/components/braviatv/translations/zh-Hans.json +++ b/homeassistant/components/braviatv/translations/zh-Hans.json @@ -9,8 +9,7 @@ "title": "\u6388\u6743 Sony Bravia \u7535\u89c6" }, "user": { - "description": "\u8bbe\u7f6e Sony Bravia \u7535\u89c6\u96c6\u6210\u3002\u5982\u679c\u60a8\u5728\u914d\u7f6e\u65b9\u9762\u9047\u5230\u95ee\u9898\uff0c\u8bf7\u8bbf\u95ee\uff1ahttps://www.home-assistant.io/integrations/braviatv\n\u786e\u4fdd\u7535\u89c6\u5df2\u6253\u5f00\u3002", - "title": "Sony Bravia TV" + "description": "\u8bbe\u7f6e Sony Bravia \u7535\u89c6\u96c6\u6210\u3002\u5982\u679c\u60a8\u5728\u914d\u7f6e\u65b9\u9762\u9047\u5230\u95ee\u9898\uff0c\u8bf7\u8bbf\u95ee\uff1ahttps://www.home-assistant.io/integrations/braviatv\n\u786e\u4fdd\u7535\u89c6\u5df2\u6253\u5f00\u3002" } } }, diff --git a/homeassistant/components/braviatv/translations/zh-Hant.json b/homeassistant/components/braviatv/translations/zh-Hant.json index 35ef6ef2e4f..1fb0931d4b6 100644 --- a/homeassistant/components/braviatv/translations/zh-Hant.json +++ b/homeassistant/components/braviatv/translations/zh-Hant.json @@ -21,8 +21,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u65bc\u8a2d\u5b9a\u524d\u78ba\u5b9a\u96fb\u8996\u5df2\u7d93\u958b\u555f\u3002", - "title": "Sony Bravia \u96fb\u8996" + "description": "\u65bc\u8a2d\u5b9a\u524d\u78ba\u5b9a\u96fb\u8996\u5df2\u7d93\u958b\u555f\u3002" } } }, diff --git a/homeassistant/components/broadlink/manifest.json b/homeassistant/components/broadlink/manifest.json index f291ba83afa..949f8add20b 100644 --- a/homeassistant/components/broadlink/manifest.json +++ b/homeassistant/components/broadlink/manifest.json @@ -2,7 +2,7 @@ "domain": "broadlink", "name": "Broadlink", "documentation": "https://www.home-assistant.io/integrations/broadlink", - "requirements": ["broadlink==0.18.1"], + "requirements": ["broadlink==0.18.2"], "codeowners": ["@danielhiversen", "@felipediel", "@L-I-Am"], "config_flow": true, "dhcp": [ diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index da75118d5b1..6be9db286e2 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -2,14 +2,14 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", - "cannot_connect": "Kon niet verbinden", + "already_in_progress": "De configuratie is momenteel al bezig", + "cannot_connect": "Kan geen verbinding maken", "invalid_host": "Ongeldige hostnaam of IP-adres", "not_supported": "Apparaat wordt niet ondersteund", "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_host": "Ongeldige hostnaam of IP-adres", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/brother/translations/es.json b/homeassistant/components/brother/translations/es.json index bc6aa749445..9c327a2887a 100644 --- a/homeassistant/components/brother/translations/es.json +++ b/homeassistant/components/brother/translations/es.json @@ -9,7 +9,7 @@ "snmp_error": "El servidor SNMP est\u00e1 apagado o la impresora no es compatible.", "wrong_host": "Nombre del host o direcci\u00f3n IP no v\u00e1lidos." }, - "flow_title": "Impresora Brother: {model} {serial_number}", + "flow_title": "{model} {serial_number}", "step": { "user": { "data": { diff --git a/homeassistant/components/brother/translations/nl.json b/homeassistant/components/brother/translations/nl.json index 97c506299a7..2021acef608 100644 --- a/homeassistant/components/brother/translations/nl.json +++ b/homeassistant/components/brother/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze printer is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "unsupported_model": "Dit printermodel wordt niet ondersteund." }, "error": { diff --git a/homeassistant/components/brunt/translations/es.json b/homeassistant/components/brunt/translations/es.json index 61370b2791f..362ef22b838 100644 --- a/homeassistant/components/brunt/translations/es.json +++ b/homeassistant/components/brunt/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { diff --git a/homeassistant/components/brunt/translations/nl.json b/homeassistant/components/brunt/translations/nl.json index 86a7df6a585..a5aa018a1d0 100644 --- a/homeassistant/components/brunt/translations/nl.json +++ b/homeassistant/components/brunt/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -15,7 +15,7 @@ "password": "Wachtwoord" }, "description": "Voer het wachtwoord opnieuw in voor: {username}", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { diff --git a/homeassistant/components/brunt/translations/sk.json b/homeassistant/components/brunt/translations/sk.json index 71a7aea5018..3f00701d0ba 100644 --- a/homeassistant/components/brunt/translations/sk.json +++ b/homeassistant/components/brunt/translations/sk.json @@ -5,6 +5,13 @@ }, "error": { "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "username": "U\u017e\u00edvate\u013esk\u00e9 meno" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/es.json b/homeassistant/components/bsblan/translations/es.json index 691136f5441..830752dd863 100644 --- a/homeassistant/components/bsblan/translations/es.json +++ b/homeassistant/components/bsblan/translations/es.json @@ -6,7 +6,7 @@ "error": { "cannot_connect": "No se pudo conectar" }, - "flow_title": "BSB-Lan: {name}", + "flow_title": "{name}", "step": { "user": { "data": { diff --git a/homeassistant/components/buienradar/translations/ko.json b/homeassistant/components/buienradar/translations/ko.json new file mode 100644 index 00000000000..8fe5422ea3e --- /dev/null +++ b/homeassistant/components/buienradar/translations/ko.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "country_code": "\uce74\uba54\ub77c \uc774\ubbf8\uc9c0\ub97c \ud45c\uc2dc\ud560 \uad6d\uac00\uc758 \uad6d\uac00 \ucf54\ub4dc\uc785\ub2c8\ub2e4.", + "delta": "\uce74\uba54\ub77c \uc774\ubbf8\uc9c0 \uc5c5\ub370\uc774\ud2b8 \uc0ac\uc774\uc758 \uc2dc\uac04 \uac04\uaca9(\ucd08)", + "timeframe": "\uac15\uc218 \uc608\ubcf4\ub97c \uc704\ud574 \ubbf8\ub9ac \ubcfc \uc2dc\uac04(\ubd84)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/buienradar/translations/nl.json b/homeassistant/components/buienradar/translations/nl.json index 0d022f1c61e..c39ee638e08 100644 --- a/homeassistant/components/buienradar/translations/nl.json +++ b/homeassistant/components/buienradar/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/buienradar/util.py b/homeassistant/components/buienradar/util.py index 3686e2bd3c9..06cd1b32cf5 100644 --- a/homeassistant/components/buienradar/util.py +++ b/homeassistant/components/buienradar/util.py @@ -115,7 +115,7 @@ class BrData: self.load_error_count += 1 threshold_log( self.load_error_count, - "Unable to retrieve json data from Buienradar" "(Msg: %s, status: %s,)", + "Unable to retrieve json data from Buienradar (Msg: %s, status: %s)", content.get(MESSAGE), content.get(STATUS_CODE), ) @@ -135,7 +135,7 @@ class BrData: # unable to get the data threshold_log( self.rain_error_count, - "Unable to retrieve rain data from Buienradar" "(Msg: %s, status: %s)", + "Unable to retrieve rain data from Buienradar (Msg: %s, status: %s)", raincontent.get(MESSAGE), raincontent.get(STATUS_CODE), ) diff --git a/homeassistant/components/button/device_action.py b/homeassistant/components/button/device_action.py index 2dffd9c600f..70033729692 100644 --- a/homeassistant/components/button/device_action.py +++ b/homeassistant/components/button/device_action.py @@ -13,6 +13,7 @@ from homeassistant.const import ( from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from .const import DOMAIN, SERVICE_PRESS @@ -44,7 +45,10 @@ async def async_get_actions( async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" await hass.services.async_call( diff --git a/homeassistant/components/button/device_trigger.py b/homeassistant/components/button/device_trigger.py index ccad50dec05..1418039b2e8 100644 --- a/homeassistant/components/button/device_trigger.py +++ b/homeassistant/components/button/device_trigger.py @@ -1,8 +1,6 @@ """Provides device triggers for Button.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -39,7 +37,7 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for button devices.""" registry = entity_registry.async_get(hass) return [ diff --git a/homeassistant/components/button/translations/sk.json b/homeassistant/components/button/translations/sk.json new file mode 100644 index 00000000000..5dfc88234c6 --- /dev/null +++ b/homeassistant/components/button/translations/sk.json @@ -0,0 +1,7 @@ +{ + "device_automation": { + "action_type": { + "press": "Stla\u010dte tla\u010didlo {entity_name}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/caldav/manifest.json b/homeassistant/components/caldav/manifest.json index 91f563107ed..e6945effca4 100644 --- a/homeassistant/components/caldav/manifest.json +++ b/homeassistant/components/caldav/manifest.json @@ -2,7 +2,7 @@ "domain": "caldav", "name": "CalDAV", "documentation": "https://www.home-assistant.io/integrations/caldav", - "requirements": ["caldav==0.8.2"], + "requirements": ["caldav==0.9.0"], "codeowners": [], "iot_class": "cloud_polling", "loggers": ["caldav", "vobject"] diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 0f122fea55f..432b6943473 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -341,6 +341,8 @@ class CalendarEventView(http.HomeAssistantView): [ { "summary": event.summary, + "description": event.description, + "location": event.location, "start": _get_api_date(event.start), "end": _get_api_date(event.end), } diff --git a/homeassistant/components/calendar/trigger.py b/homeassistant/components/calendar/trigger.py index 3540a9f5148..bb6e874b47f 100644 --- a/homeassistant/components/calendar/trigger.py +++ b/homeassistant/components/calendar/trigger.py @@ -11,7 +11,7 @@ from homeassistant.components.automation import ( AutomationActionType, AutomationTriggerInfo, ) -from homeassistant.const import CONF_ENTITY_ID, CONF_EVENT, CONF_PLATFORM +from homeassistant.const import CONF_ENTITY_ID, CONF_EVENT, CONF_OFFSET, CONF_PLATFORM from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv @@ -36,6 +36,7 @@ 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}), + vol.Optional(CONF_OFFSET, default=datetime.timedelta(0)): cv.time_period, } ) @@ -50,12 +51,14 @@ class CalendarEventListener: trigger_data: dict[str, Any], entity: CalendarEntity, event_type: str, + offset: datetime.timedelta, ) -> None: """Initialize CalendarEventListener.""" self._hass = hass self._job = job self._trigger_data = trigger_data self._entity = entity + self._offset = offset self._unsub_event: CALLBACK_TYPE | None = None self._unsub_refresh: CALLBACK_TYPE | None = None # Upcoming set of events with their trigger time @@ -81,10 +84,18 @@ class CalendarEventListener: async def _fetch_events(self, last_endtime: datetime.datetime) -> None: """Update the set of eligible events.""" + # Use a sliding window for selecting in scope events in the next interval. The event + # search range is offset, then the fire time of the returned events are offset again below. # 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) + start_time = last_endtime - self._offset + end_time = start_time + UPDATE_INTERVAL + datetime.timedelta(seconds=1) + _LOGGER.debug( + "Fetching events between %s, %s (offset=%s)", + start_time, + end_time, + self._offset, + ) + events = await self._entity.async_get_events(self._hass, start_time, 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 @@ -92,13 +103,14 @@ class CalendarEventListener: # trigger time. event_list = [] for event in events: - event_time = ( + event_fire_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_fire_time += self._offset + if event_fire_time > last_endtime: + event_list.append((event_fire_time, event)) event_list.sort(key=lambda x: x[0]) self._events = event_list _LOGGER.debug("Populated event list %s", self._events) @@ -109,12 +121,12 @@ class CalendarEventListener: if not self._events: return - (event_datetime, _event) = self._events[0] - _LOGGER.debug("Scheduling next event trigger @ %s", event_datetime) + (event_fire_time, _event) = self._events[0] + _LOGGER.debug("Scheduled alarm for %s", event_fire_time) self._unsub_event = async_track_point_in_utc_time( self._hass, self._handle_calendar_event, - event_datetime, + event_fire_time, ) def _clear_event_listener(self) -> None: @@ -160,6 +172,7 @@ async def async_attach_trigger( """Attach trigger for the specified calendar.""" entity_id = config[CONF_ENTITY_ID] event_type = config[CONF_EVENT] + offset = config[CONF_OFFSET] component: EntityComponent = hass.data[DOMAIN] if not (entity := component.get_entity(entity_id)) or not isinstance( @@ -173,10 +186,10 @@ async def async_attach_trigger( **automation_info["trigger_data"], "platform": DOMAIN, "event": event_type, + "offset": offset, } - listener = CalendarEventListener( - hass, HassJob(action), trigger_data, entity, event_type + hass, HassJob(action), trigger_data, entity, event_type, offset ) await listener.async_attach() return listener.async_detach diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 2079e962459..4a6e1546f46 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -29,8 +29,12 @@ from homeassistant.components.media_player.const import ( DOMAIN as DOMAIN_MP, SERVICE_PLAY_MEDIA, ) -from homeassistant.components.stream import Stream, create_stream -from homeassistant.components.stream.const import FORMAT_CONTENT_TYPE, OUTPUT_FORMATS +from homeassistant.components.stream import ( + FORMAT_CONTENT_TYPE, + OUTPUT_FORMATS, + Stream, + create_stream, +) from homeassistant.components.websocket_api import ActiveConnection from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -50,6 +54,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 ) from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.network import get_url from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass @@ -103,7 +108,7 @@ class CameraEntityFeature(IntEnum): SUPPORT_ON_OFF: Final = 1 SUPPORT_STREAM: Final = 2 -RTSP_PREFIXES = {"rtsp://", "rtsps://"} +RTSP_PREFIXES = {"rtsp://", "rtsps://", "rtmp://"} DEFAULT_CONTENT_TYPE: Final = "image/jpeg" ENTITY_IMAGE_URL: Final = "/api/camera_proxy/{0}?token={1}" @@ -394,7 +399,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: entity.async_update_token() entity.async_write_ha_state() - hass.helpers.event.async_track_time_interval(update_tokens, TOKEN_CHANGE_INTERVAL) + async_track_time_interval(hass, update_tokens, TOKEN_CHANGE_INTERVAL) component.async_register_entity_service( SERVICE_ENABLE_MOTION, {}, "async_enable_motion_detection" @@ -450,7 +455,7 @@ class Camera(Entity): def __init__(self) -> None: """Initialize a camera.""" self.stream: Stream | None = None - self.stream_options: dict[str, str] = {} + self.stream_options: dict[str, str | bool] = {} self.content_type: str = DEFAULT_CONTENT_TYPE self.access_tokens: collections.deque = collections.deque([], 2) self._warned_old_signature = False diff --git a/homeassistant/components/camera/media_source.py b/homeassistant/components/camera/media_source.py index ffa1962f9ef..733efb3a430 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -15,7 +15,7 @@ from homeassistant.components.media_source.models import ( MediaSourceItem, PlayMedia, ) -from homeassistant.components.stream.const import FORMAT_CONTENT_TYPE, HLS_PROVIDER +from homeassistant.components.stream import FORMAT_CONTENT_TYPE, HLS_PROVIDER from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_component import EntityComponent diff --git a/homeassistant/components/camera/prefs.py b/homeassistant/components/camera/prefs.py index 53a149ff7d8..3d54c10d09a 100644 --- a/homeassistant/components/camera/prefs.py +++ b/homeassistant/components/camera/prefs.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import Final from homeassistant.core import HomeAssistant +from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import UNDEFINED, UndefinedType from .const import DOMAIN, PREF_PRELOAD_STREAM @@ -35,12 +36,14 @@ class CameraPreferences: def __init__(self, hass: HomeAssistant) -> None: """Initialize camera prefs.""" self._hass = hass - self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY) self._prefs: dict[str, dict[str, bool]] | None = None async def async_initialize(self) -> None: """Finish initializing the preferences.""" - if (prefs := await self._store.async_load()) is None: + if (prefs := await self._store.async_load()) is None or not isinstance( + prefs, dict + ): prefs = {} self._prefs = prefs diff --git a/homeassistant/components/camera/translations/nl.json b/homeassistant/components/camera/translations/nl.json index 976d8e651fb..29dd5038b8f 100644 --- a/homeassistant/components/camera/translations/nl.json +++ b/homeassistant/components/camera/translations/nl.json @@ -3,7 +3,7 @@ "_": { "idle": "Inactief", "recording": "Opnemen", - "streaming": "Streamen" + "streaming": "Streaming" } }, "title": "Camera" diff --git a/homeassistant/components/canary/translations/es.json b/homeassistant/components/canary/translations/es.json index 018a1f30b08..76a439da49d 100644 --- a/homeassistant/components/canary/translations/es.json +++ b/homeassistant/components/canary/translations/es.json @@ -7,7 +7,7 @@ "error": { "cannot_connect": "No se pudo conectar" }, - "flow_title": "Canary: {name}", + "flow_title": "{name}", "step": { "user": { "data": { diff --git a/homeassistant/components/canary/translations/nl.json b/homeassistant/components/canary/translations/nl.json index ed64af346ea..18cdee2c7a7 100644 --- a/homeassistant/components/canary/translations/nl.json +++ b/homeassistant/components/canary/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n enkele configuratie mogelijk.", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "flow_title": "{name}", "step": { diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index dfeb9fce25b..d7419f69563 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -266,10 +266,8 @@ async def parse_m3u(hass, url): hls_content_types = ( # https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-10 "application/vnd.apple.mpegurl", - # Some sites serve these as the informal HLS m3u type. - "application/x-mpegurl", - "audio/mpegurl", - "audio/x-mpegurl", + # Additional informal types used by Mozilla gecko not included as they + # don't reliably indicate HLS streams ) m3u_data = await _fetch_playlist(hass, url, hls_content_types) m3u_lines = m3u_data.splitlines() @@ -292,6 +290,9 @@ async def parse_m3u(hass, url): elif line.startswith("#EXT-X-VERSION:"): # HLS stream, supported by cast devices raise PlaylistSupported("HLS") + elif line.startswith("#EXT-X-STREAM-INF:"): + # HLS stream, supported by cast devices + raise PlaylistSupported("HLS") elif line.startswith("#"): # Ignore other extensions continue diff --git a/homeassistant/components/cast/home_assistant_cast.py b/homeassistant/components/cast/home_assistant_cast.py index 2f9583f329c..010e4a046b4 100644 --- a/homeassistant/components/cast/home_assistant_cast.py +++ b/homeassistant/components/cast/home_assistant_cast.py @@ -9,6 +9,7 @@ from homeassistant.const import ATTR_ENTITY_ID from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, dispatcher from homeassistant.helpers.network import NoURLAvailableError, get_url +from homeassistant.helpers.service import async_register_admin_service from .const import DOMAIN, SIGNAL_HASS_CAST_SHOW_VIEW @@ -65,7 +66,8 @@ async def async_setup_ha_cast( call.data.get(ATTR_URL_PATH), ) - hass.helpers.service.async_register_admin_service( + async_register_admin_service( + hass, DOMAIN, SERVICE_SHOW_VIEW, handle_show_view, diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 10edc81e0fc..644a517c666 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==12.1.2"], + "requirements": ["pychromecast==12.1.3"], "after_dependencies": [ "cloud", "http", diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index b64c3372c15..ea21259ccc4 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -605,7 +605,9 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): """Play a piece of media.""" # Handle media_source if media_source.is_media_source_id(media_id): - sourced_media = await media_source.async_resolve_media(self.hass, media_id) + sourced_media = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_type = sourced_media.mime_type media_id = sourced_media.url diff --git a/homeassistant/components/cast/translations/es.json b/homeassistant/components/cast/translations/es.json index d38067588a8..39e34975af8 100644 --- a/homeassistant/components/cast/translations/es.json +++ b/homeassistant/components/cast/translations/es.json @@ -9,7 +9,7 @@ "step": { "config": { "data": { - "known_hosts": "Lista opcional de hosts conocidos si el descubrimiento mDNS no funciona." + "known_hosts": "Anfitriones conocidos" }, "description": "Introduce la configuraci\u00f3n de Google Cast.", "title": "Configuraci\u00f3n de Google Cast" diff --git a/homeassistant/components/cast/translations/ko.json b/homeassistant/components/cast/translations/ko.json index 2f1ee52675f..806a05a01f4 100644 --- a/homeassistant/components/cast/translations/ko.json +++ b/homeassistant/components/cast/translations/ko.json @@ -22,6 +22,11 @@ "options": { "error": { "invalid_known_hosts": "\uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\ub294 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \ud638\uc2a4\ud2b8 \ubaa9\ub85d\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, + "step": { + "basic_options": { + "description": "\uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8 - \uce90\uc2a4\ud2b8 \uc7a5\uce58\uc758 \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uc758 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \ubaa9\ub85d\uc73c\ub85c, mDNS \uac80\uc0c9\uc774 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uc5d0 \uc0ac\uc6a9\ud569\ub2c8\ub2e4" + } } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/nl.json b/homeassistant/components/cast/translations/nl.json index 26dc954ef13..b9dba7ef3e4 100644 --- a/homeassistant/components/cast/translations/nl.json +++ b/homeassistant/components/cast/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "invalid_known_hosts": "Bekende hosts moet een door komma's gescheiden lijst van hosts zijn." @@ -15,7 +15,7 @@ "title": "Google Cast configuratie" }, "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } }, diff --git a/homeassistant/components/cert_expiry/translations/nl.json b/homeassistant/components/cert_expiry/translations/nl.json index e3cd3d7983b..e330a8c01dd 100644 --- a/homeassistant/components/cert_expiry/translations/nl.json +++ b/homeassistant/components/cert_expiry/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", + "already_configured": "Dienst is al geconfigureerd", "import_failed": "Importeren vanuit configuratie is mislukt" }, "error": { diff --git a/homeassistant/components/climacell/translations/af.json b/homeassistant/components/climacell/translations/af.json index b62fc7023a4..d05e07e4eff 100644 --- a/homeassistant/components/climacell/translations/af.json +++ b/homeassistant/components/climacell/translations/af.json @@ -2,9 +2,8 @@ "options": { "step": { "init": { - "title": "Update ClimaCell opties" + "title": "Update [%key:component::climacell::title%] opties" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/bg.json b/homeassistant/components/climacell/translations/bg.json deleted file mode 100644 index af84485310d..00000000000 --- a/homeassistant/components/climacell/translations/bg.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "config": { - "error": { - "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", - "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/climacell/translations/ca.json b/homeassistant/components/climacell/translations/ca.json index 78d43991e90..2b6abb46737 100644 --- a/homeassistant/components/climacell/translations/ca.json +++ b/homeassistant/components/climacell/translations/ca.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "Versi\u00f3 de l'API", - "latitude": "Latitud", - "longitude": "Longitud", - "name": "Nom" - }, - "description": "Si no es proporcionen la Latitud i Longitud, s'utilitzaran els valors per defecte de la configuraci\u00f3 de Home Assistant. Es crear\u00e0 una entitat per a cada tipus de previsi\u00f3, per\u00f2 nom\u00e9s s'habilitaran les que seleccionis." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "Actualitzaci\u00f3 d'opcions de ClimaCell" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/cs.json b/homeassistant/components/climacell/translations/cs.json deleted file mode 100644 index e9a608680d5..00000000000 --- a/homeassistant/components/climacell/translations/cs.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "config": { - "error": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "invalid_api_key": "Neplatn\u00fd kl\u00ed\u010d API", - "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" - }, - "step": { - "user": { - "data": { - "api_key": "Kl\u00ed\u010d API", - "api_version": "Verze API", - "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", - "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka", - "name": "Jm\u00e9no" - } - } - } - }, - "title": "ClimaCell" -} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/de.json b/homeassistant/components/climacell/translations/de.json index 01e9a81647e..7c3e929dde2 100644 --- a/homeassistant/components/climacell/translations/de.json +++ b/homeassistant/components/climacell/translations/de.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "API Version", - "latitude": "Breitengrad", - "longitude": "L\u00e4ngengrad", - "name": "Name" - }, - "description": "Wenn Breitengrad und L\u00e4ngengrad nicht angegeben werden, werden die Standardwerte in der Home Assistant-Konfiguration verwendet. F\u00fcr jeden Vorhersagetyp wird eine Entit\u00e4t erstellt, aber nur die von Ihnen ausgew\u00e4hlten werden standardm\u00e4\u00dfig aktiviert." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "ClimaCell-Optionen aktualisieren" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/el.json b/homeassistant/components/climacell/translations/el.json index 26c6d5e8d40..392573f693c 100644 --- a/homeassistant/components/climacell/translations/el.json +++ b/homeassistant/components/climacell/translations/el.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 API", - "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03a0\u03bb\u03ac\u03c4\u03bf\u03c2", - "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u039c\u03ae\u03ba\u03bf\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" - }, - "description": "\u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2, \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03bf\u03cd\u03bd \u03bf\u03b9 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c4\u03bf\u03c5 Home Assistant. \u0398\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03c4\u03cd\u03c0\u03bf \u03b4\u03b5\u03bb\u03c4\u03af\u03bf\u03c5, \u03b1\u03bb\u03bb\u03ac \u03bc\u03cc\u03bd\u03bf \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03b5\u03c4\u03b5 \u03b8\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03bf\u03cd\u03bd \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd ClimaCell" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/en.json b/homeassistant/components/climacell/translations/en.json index 3e5cd436ba8..a35be85d5b2 100644 --- a/homeassistant/components/climacell/translations/en.json +++ b/homeassistant/components/climacell/translations/en.json @@ -1,24 +1,4 @@ { - "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": { @@ -29,6 +9,5 @@ "title": "Update ClimaCell Options" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/es-419.json b/homeassistant/components/climacell/translations/es-419.json index 17c63089dbf..449ad1ba367 100644 --- a/homeassistant/components/climacell/translations/es-419.json +++ b/homeassistant/components/climacell/translations/es-419.json @@ -1,16 +1,4 @@ { - "config": { - "error": { - "rate_limited": "Actualmente la tarifa est\u00e1 limitada. Vuelve a intentarlo m\u00e1s tarde." - }, - "step": { - "user": { - "data": { - "api_version": "Versi\u00f3n de la API" - } - } - } - }, "options": { "step": { "init": { @@ -21,6 +9,5 @@ "title": "Actualizar opciones de ClimaCell" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/es.json b/homeassistant/components/climacell/translations/es.json index 4e709f03ad1..056d26b077d 100644 --- a/homeassistant/components/climacell/translations/es.json +++ b/homeassistant/components/climacell/translations/es.json @@ -1,24 +1,4 @@ { - "config": { - "error": { - "cannot_connect": "Fallo al conectar", - "invalid_api_key": "Clave API no v\u00e1lida", - "rate_limited": "Actualmente la tarifa est\u00e1 limitada, por favor int\u00e9ntelo m\u00e1s tarde.", - "unknown": "Error inesperado" - }, - "step": { - "user": { - "data": { - "api_key": "Clave API", - "api_version": "Versi\u00f3n del API", - "latitude": "Latitud", - "longitude": "Longitud", - "name": "Nombre" - }, - "description": "Si no se proporcionan Latitud y Longitud , se utilizar\u00e1n los valores predeterminados en la configuraci\u00f3n de Home Assistant. Se crear\u00e1 una entidad para cada tipo de pron\u00f3stico, pero solo las que seleccione estar\u00e1n habilitadas de forma predeterminada." - } - } - }, "options": { "step": { "init": { @@ -26,9 +6,8 @@ "timestep": "Min. Entre pron\u00f3sticos de NowCast" }, "description": "Si elige habilitar la entidad de pron\u00f3stico \"nowcast\", puede configurar el n\u00famero de minutos entre cada pron\u00f3stico. El n\u00famero de pron\u00f3sticos proporcionados depende del n\u00famero de minutos elegidos entre los pron\u00f3sticos.", - "title": "Actualizar las opciones ClimaCell" + "title": "Actualizaci\u00f3n de opciones de ClimaCell" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/et.json b/homeassistant/components/climacell/translations/et.json index 46ac184fa3c..5d915a87d80 100644 --- a/homeassistant/components/climacell/translations/et.json +++ b/homeassistant/components/climacell/translations/et.json @@ -1,24 +1,4 @@ { - "config": { - "error": { - "cannot_connect": "\u00dchendamine nurjus", - "invalid_api_key": "Vale API v\u00f5ti", - "rate_limited": "Hetkel on p\u00e4ringud piiratud, proovi hiljem uuesti.", - "unknown": "Ootamatu t\u00f5rge" - }, - "step": { - "user": { - "data": { - "api_key": "API v\u00f5ti", - "api_version": "API versioon", - "latitude": "Laiuskraad", - "longitude": "Pikkuskraad", - "name": "Nimi" - }, - "description": "Kui [%key:component::climacell::config::step::user::d ata::latitude%] ja [%key:component::climacell::config::step::user::d ata::longitude%] andmed pole sisestatud kasutatakse Home Assistanti vaikev\u00e4\u00e4rtusi. Olem luuakse iga prognoosit\u00fc\u00fcbi jaoks kuid vaikimisi lubatakse ainult need, mille valid." - } - } - }, "options": { "step": { "init": { @@ -26,9 +6,8 @@ "timestep": "Minuteid NowCasti prognooside vahel" }, "description": "Kui otsustad lubada \"nowcast\" prognoosi\u00fcksuse, saad seadistada minutite arvu iga prognoosi vahel. Esitatavate prognooside arv s\u00f5ltub prognooside vahel valitud minutite arvust.", - "title": "V\u00e4rskenda ClimaCell suvandeid" + "title": "V\u00e4rskenda [%key:component::climacell::title%] suvandeid" } } - }, - "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 9194073bb5a..b2c1285ecc9 100644 --- a/homeassistant/components/climacell/translations/fr.json +++ b/homeassistant/components/climacell/translations/fr.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "Version de l'API", - "latitude": "Latitude", - "longitude": "Longitude", - "name": "Nom" - }, - "description": "Si Latitude et Longitude ne sont pas fournis, les valeurs par d\u00e9faut de la configuration de Home Assistant seront utilis\u00e9es. Une entit\u00e9 sera cr\u00e9\u00e9e pour chaque type de pr\u00e9vision, mais seules celles que vous s\u00e9lectionnez seront activ\u00e9es par d\u00e9faut." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "Mettre \u00e0 jour les options ClimaCell" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/hu.json b/homeassistant/components/climacell/translations/hu.json index bdeb913275c..f65fa638ced 100644 --- a/homeassistant/components/climacell/translations/hu.json +++ b/homeassistant/components/climacell/translations/hu.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "API Verzi\u00f3", - "latitude": "Sz\u00e9less\u00e9g", - "longitude": "Hossz\u00fas\u00e1g", - "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." - } - } - }, "options": { "step": { "init": { @@ -26,9 +6,8 @@ "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": "ClimaCell be\u00e1ll\u00edt\u00e1sok friss\u00edt\u00e9se" + "title": "[%key:component::climacell::title%] be\u00e1ll\u00edt\u00e1sok friss\u00edt\u00e9se" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/id.json b/homeassistant/components/climacell/translations/id.json index 88b377261bb..4d020351665 100644 --- a/homeassistant/components/climacell/translations/id.json +++ b/homeassistant/components/climacell/translations/id.json @@ -1,34 +1,13 @@ { - "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", - "api_version": "Versi API", - "latitude": "Lintang", - "longitude": "Bujur", - "name": "Nama" - }, - "description": "Jika Lintang dan Bujur tidak tersedia, nilai default dalam konfigurasi Home Assistant akan digunakan. Entitas akan dibuat untuk setiap jenis prakiraan tetapi hanya yang Anda pilih yang akan diaktifkan secara default." - } - } - }, "options": { "step": { "init": { "data": { "timestep": "Jarak Interval Prakiraan NowCast dalam Menit" }, - "description": "Jika Anda memilih untuk mengaktifkan entitas prakiraan 'nowcast', Anda dapat mengonfigurasi jarak interval prakiraan dalam menit. Jumlah prakiraan yang diberikan tergantung pada nilai interval yang dipilih.", + "description": "Jika Anda memilih untuk mengaktifkan entitas prakiraan `nowcast`, Anda dapat mengonfigurasi jarak interval prakiraan dalam menit. Jumlah prakiraan yang diberikan tergantung pada nilai interval yang dipilih.", "title": "Perbarui Opsi ClimaCell" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/it.json b/homeassistant/components/climacell/translations/it.json index bd1bdd88238..b9667d6bfb1 100644 --- a/homeassistant/components/climacell/translations/it.json +++ b/homeassistant/components/climacell/translations/it.json @@ -1,24 +1,4 @@ { - "config": { - "error": { - "cannot_connect": "Impossibile connettersi", - "invalid_api_key": "Chiave API non valida", - "rate_limited": "Al momento la tariffa \u00e8 limitata, riprova pi\u00f9 tardi.", - "unknown": "Errore imprevisto" - }, - "step": { - "user": { - "data": { - "api_key": "Chiave API", - "api_version": "Versione API", - "latitude": "Latitudine", - "longitude": "Logitudine", - "name": "Nome" - }, - "description": "Se Latitudine e Logitudine non vengono forniti, verranno utilizzati i valori predefiniti nella configurazione di Home Assistant. Verr\u00e0 creata un'entit\u00e0 per ogni tipo di previsione, ma solo quelli selezionati saranno abilitati per impostazione predefinita." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "Aggiorna le opzioni di ClimaCell" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ja.json b/homeassistant/components/climacell/translations/ja.json index 5114f8e9881..5c78820c853 100644 --- a/homeassistant/components/climacell/translations/ja.json +++ b/homeassistant/components/climacell/translations/ja.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "API\u30d0\u30fc\u30b8\u30e7\u30f3", - "latitude": "\u7def\u5ea6", - "longitude": "\u7d4c\u5ea6", - "name": "\u540d\u524d" - }, - "description": "\u7def\u5ea6\u3068\u7d4c\u5ea6\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u3001Home Assistant\u8a2d\u5b9a\u306e\u65e2\u5b9a\u5024\u304c\u4f7f\u7528\u3055\u308c\u307e\u3059\u3002\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u306f\u4e88\u6e2c\u30bf\u30a4\u30d7\u3054\u3068\u306b\u4f5c\u6210\u3055\u308c\u307e\u3059\u304c\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u9078\u629e\u3057\u305f\u3082\u306e\u3060\u3051\u304c\u6709\u52b9\u306b\u306a\u308a\u307e\u3059\u3002" - } - } - }, "options": { "step": { "init": { @@ -26,9 +6,8 @@ "timestep": "\u6700\u5c0f: NowCast Forecasts\u306e\u9593" }, "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": "ClimaCell\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u66f4\u65b0\u3057\u307e\u3059" + "title": "[%key:component::climacell::title%]\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u66f4\u65b0\u3057\u307e\u3059" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ko.json b/homeassistant/components/climacell/translations/ko.json index b5936bbc7d7..8accc07410d 100644 --- a/homeassistant/components/climacell/translations/ko.json +++ b/homeassistant/components/climacell/translations/ko.json @@ -1,24 +1,4 @@ { - "config": { - "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "rate_limited": "\ud604\uc7ac \uc0ac\uc6a9 \ud69f\uc218\ub97c \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" - }, - "step": { - "user": { - "data": { - "api_key": "API \ud0a4", - "api_version": "API \ubc84\uc804", - "latitude": "\uc704\ub3c4", - "longitude": "\uacbd\ub3c4", - "name": "\uc774\ub984" - }, - "description": "\uc704\ub3c4 \ubc0f \uacbd\ub3c4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 Home Assistant \uad6c\uc131\uc758 \uae30\ubcf8\uac12\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4. \uac01 \uc77c\uae30\uc608\ubcf4 \uc720\ud615\uc5d0 \ub300\ud574 \uad6c\uc131\uc694\uc18c\uac00 \uc0dd\uc131\ub418\uc9c0\ub9cc \uae30\ubcf8\uc801\uc73c\ub85c \uc120\ud0dd\ud55c \uad6c\uc131\uc694\uc18c\ub9cc \ud65c\uc131\ud654\ub429\ub2c8\ub2e4." - } - } - }, "options": { "step": { "init": { @@ -26,9 +6,8 @@ "timestep": "\ub2e8\uae30\uc608\uce21 \uc77c\uae30\uc608\ubcf4 \uac04 \ucd5c\uc18c \uc2dc\uac04" }, "description": "`nowcast` \uc77c\uae30\uc608\ubcf4 \uad6c\uc131\uc694\uc18c\ub97c \uc0ac\uc6a9\ud558\ub3c4\ub85d \uc120\ud0dd\ud55c \uacbd\uc6b0 \uac01 \uc77c\uae30\uc608\ubcf4 \uc0ac\uc774\uc758 \uc2dc\uac04(\ubd84)\uc744 \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc81c\uacf5\ub41c \uc77c\uae30\uc608\ubcf4 \ud69f\uc218\ub294 \uc608\uce21 \uac04 \uc120\ud0dd\ud55c \uc2dc\uac04(\ubd84)\uc5d0 \ub530\ub77c \ub2ec\ub77c\uc9d1\ub2c8\ub2e4.", - "title": "ClimaCell \uc635\uc158 \uc5c5\ub370\uc774\ud2b8\ud558\uae30" + "title": "[%key:component::climacell::title%] \uc635\uc158 \uc5c5\ub370\uc774\ud2b8\ud558\uae30" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/nl.json b/homeassistant/components/climacell/translations/nl.json index a8754e81943..a895fa8234d 100644 --- a/homeassistant/components/climacell/translations/nl.json +++ b/homeassistant/components/climacell/translations/nl.json @@ -1,24 +1,4 @@ { - "config": { - "error": { - "cannot_connect": "Kan geen verbinding maken", - "invalid_api_key": "Ongeldige API-sleutel", - "rate_limited": "Momenteel is een beperkt aantal aanvragen mogelijk, probeer later opnieuw.", - "unknown": "Onverwachte fout" - }, - "step": { - "user": { - "data": { - "api_key": "API-sleutel", - "api_version": "API-versie", - "latitude": "Breedtegraad", - "longitude": "Lengtegraad", - "name": "Naam" - }, - "description": "Indien Breedtegraad en Lengtegraad niet worden opgegeven, worden de standaardwaarden in de Home Assistant-configuratie gebruikt. Er wordt een entiteit gemaakt voor elk voorspellingstype, maar alleen degenen die u selecteert worden standaard ingeschakeld." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "Update ClimaCell Opties" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/no.json b/homeassistant/components/climacell/translations/no.json index c20c458c64e..9f050624967 100644 --- a/homeassistant/components/climacell/translations/no.json +++ b/homeassistant/components/climacell/translations/no.json @@ -1,24 +1,4 @@ { - "config": { - "error": { - "cannot_connect": "Tilkobling mislyktes", - "invalid_api_key": "Ugyldig API-n\u00f8kkel", - "rate_limited": "Prisen er for \u00f8yeblikket begrenset. Pr\u00f8v igjen senere.", - "unknown": "Uventet feil" - }, - "step": { - "user": { - "data": { - "api_key": "API-n\u00f8kkel", - "api_version": "API-versjon", - "latitude": "Breddegrad", - "longitude": "Lengdegrad", - "name": "Navn" - }, - "description": "Hvis Breddegrad og Lengdegrad ikke er oppgitt, vil standardverdiene i Home Assistant-konfigurasjonen bli brukt. Det blir opprettet en entitet for hver prognosetype, men bare de du velger blir aktivert som standard." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "Oppdater ClimaCell-alternativer" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/pl.json b/homeassistant/components/climacell/translations/pl.json index e107be1e001..5f69764ffab 100644 --- a/homeassistant/components/climacell/translations/pl.json +++ b/homeassistant/components/climacell/translations/pl.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "Wersja API", - "latitude": "Szeroko\u015b\u0107 geograficzna", - "longitude": "D\u0142ugo\u015b\u0107 geograficzna", - "name": "Nazwa" - }, - "description": "Je\u015bli szeroko\u015b\u0107 i d\u0142ugo\u015b\u0107 geograficzna nie zostan\u0105 podane, zostan\u0105 u\u017cyte domy\u015blne warto\u015bci z konfiguracji Home Assistanta. Zostanie utworzona encja dla ka\u017cdego typu prognozy, ale domy\u015blnie w\u0142\u0105czone bed\u0105 tylko te, kt\u00f3re wybierzesz." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "Opcje aktualizacji ClimaCell" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/pt-BR.json b/homeassistant/components/climacell/translations/pt-BR.json index 54de15d1f7f..b7e71d45971 100644 --- a/homeassistant/components/climacell/translations/pt-BR.json +++ b/homeassistant/components/climacell/translations/pt-BR.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "Vers\u00e3o da API", - "latitude": "Latitude", - "longitude": "Longitude", - "name": "Nome" - }, - "description": "Se Latitude e Longitude n\u00e3o forem fornecidos, os valores padr\u00f5es na configura\u00e7\u00e3o do Home Assistant ser\u00e3o usados. Uma entidade ser\u00e1 criada para cada tipo de previs\u00e3o, mas apenas as selecionadas ser\u00e3o habilitadas por padr\u00e3o." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "Atualizar as op\u00e7\u00f5es do ClimaCell" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/pt.json b/homeassistant/components/climacell/translations/pt.json deleted file mode 100644 index 5c790233b20..00000000000 --- a/homeassistant/components/climacell/translations/pt.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "config": { - "error": { - "cannot_connect": "Falha na liga\u00e7\u00e3o", - "invalid_api_key": "Chave de API inv\u00e1lida", - "unknown": "Erro inesperado" - }, - "step": { - "user": { - "data": { - "api_key": "Chave da API", - "latitude": "Latitude", - "longitude": "Longitude", - "name": "Nome" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ru.json b/homeassistant/components/climacell/translations/ru.json index 0f1a80b5e09..9f3219ce4d6 100644 --- a/homeassistant/components/climacell/translations/ru.json +++ b/homeassistant/components/climacell/translations/ru.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "\u0412\u0435\u0440\u0441\u0438\u044f API", - "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", - "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" - }, - "description": "\u0415\u0441\u043b\u0438 \u0428\u0438\u0440\u043e\u0442\u0430 \u0438 \u0414\u043e\u043b\u0433\u043e\u0442\u0430 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u044b, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 Home Assistant. \u041e\u0431\u044a\u0435\u043a\u0442\u044b \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u044b \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430, \u043d\u043e \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0435 \u0412\u0430\u043c\u0438." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 ClimaCell" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/sensor.ko.json b/homeassistant/components/climacell/translations/sensor.ko.json new file mode 100644 index 00000000000..e5ec616959e --- /dev/null +++ b/homeassistant/components/climacell/translations/sensor.ko.json @@ -0,0 +1,7 @@ +{ + "state": { + "climacell__precipitation_type": { + "snow": "\ub208" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/sensor.nl.json b/homeassistant/components/climacell/translations/sensor.nl.json index c457988681b..710198156d1 100644 --- a/homeassistant/components/climacell/translations/sensor.nl.json +++ b/homeassistant/components/climacell/translations/sensor.nl.json @@ -18,7 +18,7 @@ }, "climacell__precipitation_type": { "freezing_rain": "IJzel", - "ice_pellets": "Hagel", + "ice_pellets": "IJskorrels", "none": "Geen", "rain": "Regen", "snow": "Sneeuw" diff --git a/homeassistant/components/climacell/translations/sensor.sk.json b/homeassistant/components/climacell/translations/sensor.sk.json new file mode 100644 index 00000000000..843169b2f3b --- /dev/null +++ b/homeassistant/components/climacell/translations/sensor.sk.json @@ -0,0 +1,7 @@ +{ + "state": { + "climacell__health_concern": { + "unhealthy": "Nezdrav\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/sk.json b/homeassistant/components/climacell/translations/sk.json deleted file mode 100644 index 8e0bc629a13..00000000000 --- a/homeassistant/components/climacell/translations/sk.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "error": { - "invalid_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d" - }, - "step": { - "user": { - "data": { - "api_key": "API k\u013e\u00fa\u010d", - "latitude": "Zemepisn\u00e1 \u0161\u00edrka", - "longitude": "Zemepisn\u00e1 d\u013a\u017eka", - "name": "N\u00e1zov" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/sv.json b/homeassistant/components/climacell/translations/sv.json deleted file mode 100644 index e6e7a77926f..00000000000 --- a/homeassistant/components/climacell/translations/sv.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "api_version": "API-version", - "latitude": "Latitud", - "longitude": "Longitud", - "name": "Namn" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/tr.json b/homeassistant/components/climacell/translations/tr.json index 369d4c6ae13..54e24f813e4 100644 --- a/homeassistant/components/climacell/translations/tr.json +++ b/homeassistant/components/climacell/translations/tr.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "API S\u00fcr\u00fcm\u00fc", - "latitude": "Enlem", - "longitude": "Boylam", - "name": "Ad" - }, - "description": "Enlem ve Boylam sa\u011flanmazsa, Home Assistant yap\u0131land\u0131rmas\u0131ndaki varsay\u0131lan de\u011ferler kullan\u0131l\u0131r. Her tahmin t\u00fcr\u00fc i\u00e7in bir varl\u0131k olu\u015fturulacak, ancak varsay\u0131lan olarak yaln\u0131zca se\u00e7tikleriniz etkinle\u015ftirilecektir." - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "ClimaCell Se\u00e7eneklerini G\u00fcncelleyin" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/zh-Hant.json b/homeassistant/components/climacell/translations/zh-Hant.json index 68e06219ae7..309b39ab242 100644 --- a/homeassistant/components/climacell/translations/zh-Hant.json +++ b/homeassistant/components/climacell/translations/zh-Hant.json @@ -1,24 +1,4 @@ { - "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", - "api_version": "API \u7248\u672c", - "latitude": "\u7def\u5ea6", - "longitude": "\u7d93\u5ea6", - "name": "\u540d\u7a31" - }, - "description": "\u5047\u5982\u672a\u63d0\u4f9b\u7def\u5ea6\u8207\u7d93\u5ea6\uff0c\u5c07\u6703\u4f7f\u7528 Home Assistant \u8a2d\u5b9a\u4f5c\u70ba\u9810\u8a2d\u503c\u3002\u6bcf\u4e00\u500b\u9810\u5831\u985e\u5225\u90fd\u6703\u7522\u751f\u4e00\u7d44\u5be6\u9ad4\uff0c\u6216\u8005\u9810\u8a2d\u70ba\u6240\u9078\u64c7\u555f\u7528\u7684\u9810\u5831\u3002" - } - } - }, "options": { "step": { "init": { @@ -29,6 +9,5 @@ "title": "\u66f4\u65b0 ClimaCell \u9078\u9805" } } - }, - "title": "ClimaCell" + } } \ No newline at end of file diff --git a/homeassistant/components/climate/device_action.py b/homeassistant/components/climate/device_action.py index 81189417c2b..3c9934d5cbf 100644 --- a/homeassistant/components/climate/device_action.py +++ b/homeassistant/components/climate/device_action.py @@ -15,6 +15,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import get_capability, get_supported_features +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from . import DOMAIN, const @@ -43,7 +44,7 @@ async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Climate devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) actions = [] # Get all the integrations entities for this device @@ -67,7 +68,10 @@ async def async_get_actions( async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} @@ -84,7 +88,9 @@ async def async_call_action_from_config( ) -async def async_get_action_capabilities(hass, config): +async def async_get_action_capabilities( + hass: HomeAssistant, config: ConfigType +) -> dict[str, vol.Schema]: """List action capabilities.""" action_type = config[CONF_TYPE] diff --git a/homeassistant/components/climate/device_condition.py b/homeassistant/components/climate/device_condition.py index dd5842cd2a8..c6179d82215 100644 --- a/homeassistant/components/climate/device_condition.py +++ b/homeassistant/components/climate/device_condition.py @@ -45,7 +45,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Climate devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) conditions = [] # Get all the integrations entities for this device @@ -92,7 +92,9 @@ def async_condition_from_config( return test_is_state -async def async_get_condition_capabilities(hass, config): +async def async_get_condition_capabilities( + hass: HomeAssistant, config: ConfigType +) -> dict[str, vol.Schema]: """List condition capabilities.""" condition_type = config[CONF_TYPE] diff --git a/homeassistant/components/climate/device_trigger.py b/homeassistant/components/climate/device_trigger.py index 6bd6f4c3e02..d8d46342603 100644 --- a/homeassistant/components/climate/device_trigger.py +++ b/homeassistant/components/climate/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for Climate.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -65,9 +63,9 @@ TRIGGER_SCHEMA = vol.Any(HVAC_MODE_TRIGGER_SCHEMA, CURRENT_TRIGGER_SCHEMA) async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Climate devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/climate/translations/es.json b/homeassistant/components/climate/translations/es.json index a7258609d59..09806ddf5ef 100644 --- a/homeassistant/components/climate/translations/es.json +++ b/homeassistant/components/climate/translations/es.json @@ -17,7 +17,7 @@ "state": { "_": { "auto": "Autom\u00e1tico", - "cool": "Fr\u00edo", + "cool": "Enfr\u00eda", "dry": "Seco", "fan_only": "Solo ventilador", "heat": "Calor", diff --git a/homeassistant/components/climate/translations/nl.json b/homeassistant/components/climate/translations/nl.json index 0237d2bbd9a..016131858a4 100644 --- a/homeassistant/components/climate/translations/nl.json +++ b/homeassistant/components/climate/translations/nl.json @@ -18,8 +18,8 @@ "_": { "auto": "Auto", "cool": "Koelen", - "dry": "Droog", - "fan_only": "Alleen ventilatie", + "dry": "Drogen", + "fan_only": "Alleen ventilator", "heat": "Verwarmen", "heat_cool": "Verwarmen/Koelen", "off": "Uit" diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 360e726c89e..aa31f796491 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -22,10 +22,12 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entityfilter from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) +from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from homeassistant.util.aiohttp import MockRequest @@ -250,11 +252,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: elif service.service == SERVICE_REMOTE_DISCONNECT: await prefs.async_update(remote_enabled=False) - hass.helpers.service.async_register_admin_service( - DOMAIN, SERVICE_REMOTE_CONNECT, _service_handler - ) - hass.helpers.service.async_register_admin_service( - DOMAIN, SERVICE_REMOTE_DISCONNECT, _service_handler + async_register_admin_service(hass, DOMAIN, SERVICE_REMOTE_CONNECT, _service_handler) + async_register_admin_service( + hass, DOMAIN, SERVICE_REMOTE_DISCONNECT, _service_handler ) loaded = False @@ -268,15 +268,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return loaded = True - await hass.helpers.discovery.async_load_platform( - Platform.BINARY_SENSOR, DOMAIN, {}, config - ) - await hass.helpers.discovery.async_load_platform( - Platform.STT, DOMAIN, {}, config - ) - await hass.helpers.discovery.async_load_platform( - Platform.TTS, DOMAIN, {}, config - ) + await async_load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config) + await async_load_platform(hass, Platform.STT, DOMAIN, {}, config) + await async_load_platform(hass, Platform.TTS, DOMAIN, {}, config) async_dispatcher_send( hass, SIGNAL_CLOUD_CONNECTION_STATE, CloudConnectionState.CLOUD_CONNECTED diff --git a/homeassistant/components/cloud/account_link.py b/homeassistant/components/cloud/account_link.py index 6dc0da82512..5df16cb1724 100644 --- a/homeassistant/components/cloud/account_link.py +++ b/homeassistant/components/cloud/account_link.py @@ -34,9 +34,9 @@ async def async_provide_implementation(hass: HomeAssistant, domain: str): for service in services: if service["service"] == domain and CURRENT_VERSION >= service["min_version"]: - return CloudOAuth2Implementation(hass, domain) + return [CloudOAuth2Implementation(hass, domain)] - return + return [] async def _get_services(hass): diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 8f190103e87..a0a68aaf84a 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -195,6 +195,8 @@ class CloudGoogleConfig(AbstractConfig): ): await async_setup_component(self.hass, GOOGLE_DOMAIN, {}) + sync_entities = False + if self.should_report_state != self.is_reporting_state: if self.should_report_state: self.async_enable_report_state() @@ -203,7 +205,7 @@ class CloudGoogleConfig(AbstractConfig): # State reporting is reported as a property on entities. # So when we change it, we need to sync all entities. - await self.async_sync_entities_all() + sync_entities = True # If entity prefs are the same or we have filter in config.yaml, # don't sync. @@ -215,12 +217,16 @@ class CloudGoogleConfig(AbstractConfig): if self.enabled and not self.is_local_sdk_active: self.async_enable_local_sdk() + sync_entities = True elif not self.enabled and self.is_local_sdk_active: self.async_disable_local_sdk() self._cur_entity_prefs = prefs.google_entity_configs self._cur_default_expose = prefs.google_default_expose + if sync_entities: + await self.async_sync_entities_all() + @callback def _handle_entity_registry_updated(self, event: Event) -> None: """Handle when entity registry updated.""" diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index e6747c42c45..275c2a56326 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -5,6 +5,7 @@ from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.auth.models import User from homeassistant.components import webhook from homeassistant.core import callback +from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import UNDEFINED from homeassistant.util.logging import async_create_catching_coro @@ -46,7 +47,7 @@ class CloudPreferences: def __init__(self, hass): """Initialize cloud prefs.""" self._hass = hass - self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY) self._prefs = None self._listeners = [] diff --git a/homeassistant/components/cloudflare/config_flow.py b/homeassistant/components/cloudflare/config_flow.py index 27a22dbc5bd..121e0fc9974 100644 --- a/homeassistant/components/cloudflare/config_flow.py +++ b/homeassistant/components/cloudflare/config_flow.py @@ -32,7 +32,7 @@ DATA_SCHEMA = vol.Schema( ) -def _zone_schema(zones: list | None = None): +def _zone_schema(zones: list[str] | None = None) -> vol.Schema: """Zone selection schema.""" zones_list = [] @@ -42,7 +42,7 @@ def _zone_schema(zones: list | None = None): return vol.Schema({vol.Required(CONF_ZONE): vol.In(zones_list)}) -def _records_schema(records: list | None = None): +def _records_schema(records: list[str] | None = None) -> vol.Schema: """Zone records selection schema.""" records_dict = {} @@ -52,13 +52,15 @@ def _records_schema(records: list | None = None): return vol.Schema({vol.Required(CONF_RECORDS): cv.multi_select(records_dict)}) -async def validate_input(hass: HomeAssistant, data: dict): +async def _validate_input( + hass: HomeAssistant, data: dict[str, Any] +) -> dict[str, list[str] | None]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. """ zone = data.get(CONF_ZONE) - records = None + records: list[str] | None = None cfupdate = CloudflareUpdater( async_get_clientsession(hass), @@ -68,7 +70,7 @@ async def validate_input(hass: HomeAssistant, data: dict): ) try: - zones = await cfupdate.get_zones() + zones: list[str] | None = await cfupdate.get_zones() if zone: zone_id = await cfupdate.get_zone_id() records = await cfupdate.get_zone_records(zone_id, "A") @@ -89,11 +91,11 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): entry: ConfigEntry | None = None - def __init__(self): + def __init__(self) -> None: """Initialize the Cloudflare config flow.""" - self.cloudflare_config = {} - self.zones = None - self.records = None + self.cloudflare_config: dict[str, Any] = {} + self.zones: list[str] | None = None + self.records: list[str] | None = None async def async_step_reauth(self, data: dict[str, Any]) -> FlowResult: """Handle initiation of re-authentication with Cloudflare.""" @@ -104,7 +106,7 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle re-authentication with Cloudflare.""" - errors = {} + errors: dict[str, str] = {} if user_input is not None and self.entry: _, errors = await self._async_validate_or_error(user_input) @@ -130,14 +132,16 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_user(self, user_input: dict | None = 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") persistent_notification.async_dismiss(self.hass, "cloudflare_setup") - errors = {} + errors: dict[str, str] = {} if user_input is not None: info, errors = await self._async_validate_or_error(user_input) @@ -151,9 +155,11 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - async def async_step_zone(self, user_input: dict | None = None): + async def async_step_zone( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the picking the zone.""" - errors = {} + errors: dict[str, str] = {} if user_input is not None: self.cloudflare_config.update(user_input) @@ -171,7 +177,9 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_records(self, user_input: dict | None = None): + async def async_step_records( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the picking the zone records.""" if user_input is not None: @@ -184,12 +192,14 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): data_schema=_records_schema(self.records), ) - async def _async_validate_or_error(self, config): - errors = {} + async def _async_validate_or_error( + self, config: dict[str, Any] + ) -> tuple[dict[str, list[str] | None], dict[str, str]]: + errors: dict[str, str] = {} info = {} try: - info = await validate_input(self.hass, config) + info = await _validate_input(self.hass, config) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: diff --git a/homeassistant/components/cloudflare/translations/nl.json b/homeassistant/components/cloudflare/translations/nl.json index 5a1bf188a29..6be4bf1e0a8 100644 --- a/homeassistant/components/cloudflare/translations/nl.json +++ b/homeassistant/components/cloudflare/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "reauth_successful": "Herauthenticatie was succesvol", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", + "reauth_successful": "Herauthenticatie geslaagd", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/coinbase/const.py b/homeassistant/components/coinbase/const.py index 85535613851..08773745f09 100644 --- a/homeassistant/components/coinbase/const.py +++ b/homeassistant/components/coinbase/const.py @@ -278,21 +278,31 @@ WALLETS = { RATES = { "1INCH": "1INCH", "AAVE": "AAVE", + "ACH": "ACH", "ADA": "ADA", "AED": "AED", "AFN": "AFN", + "AGLD": "AGLD", + "ALCX": "ALCX", "ALGO": "ALGO", "ALL": "ALL", "AMD": "AMD", + "AMP": "AMP", "ANG": "ANG", "ANKR": "ANKR", "AOA": "AOA", + "API3": "API3", + "ARPA": "ARPA", "ARS": "ARS", + "ASM": "ASM", "ATOM": "ATOM", "AUCTION": "AUCTION", "AUD": "AUD", + "AVAX": "AVAX", "AWG": "AWG", + "AXS": "AXS", "AZN": "AZN", + "BADGER": "BADGER", "BAL": "BAL", "BAM": "BAM", "BAND": "BAND", @@ -302,16 +312,20 @@ RATES = { "BDT": "BDT", "BGN": "BGN", "BHD": "BHD", + "BICO": "BICO", "BIF": "BIF", + "BLZ": "BLZ", "BMD": "BMD", "BND": "BND", "BNT": "BNT", "BOB": "BOB", + "BOND": "BOND", "BRL": "BRL", "BSD": "BSD", "BSV": "BSV", "BTC": "BTC", "BTN": "BTN", + "BTRST": "BTRST", "BWP": "BWP", "BYN": "BYN", "BYR": "BYR", @@ -320,6 +334,7 @@ RATES = { "CDF": "CDF", "CGLD": "CGLD", "CHF": "CHF", + "CHZ": "CHZ", "CLF": "CLF", "CLP": "CLP", "CLV": "CLV", @@ -327,21 +342,32 @@ RATES = { "CNY": "CNY", "COMP": "COMP", "COP": "COP", + "COTI": "COTI", + "COVAL": "COVAL", "CRC": "CRC", + "CRO": "CRO", "CRV": "CRV", + "CTSI": "CTSI", + "CTX": "CTX", "CUC": "CUC", "CVC": "CVC", "CVE": "CVE", "CZK": "CZK", "DAI": "DAI", "DASH": "DASH", + "DDX": "DDX", + "DESO": "DESO", + "DIA": "DIA", "DJF": "DJF", "DKK": "DKK", "DNT": "DNT", + "DOGE": "DOGE", "DOP": "DOP", + "DOT": "DOT", "DZD": "DZD", "EGP": "EGP", "ENJ": "ENJ", + "ENS": "ENS", "EOS": "EOS", "ERN": "ERN", "ETB": "ETB", @@ -349,50 +375,69 @@ RATES = { "ETH": "ETH", "ETH2": "ETH2", "EUR": "EUR", + "FARM": "FARM", "FET": "FET", "FIL": "FIL", "FJD": "FJD", "FKP": "FKP", "FORTH": "FORTH", + "FOX": "FOX", + "FX": "FX", + "GALA": "GALA", "GBP": "GBP", "GBX": "GBX", "GEL": "GEL", + "GFI": "GFI", "GGP": "GGP", "GHS": "GHS", "GIP": "GIP", "GMD": "GMD", "GNF": "GNF", + "GODS": "GODS", "GRT": "GRT", + "GTC": "GTC", "GTQ": "GTQ", "GYD": "GYD", + "GYEN": "GYEN", "HKD": "HKD", "HNL": "HNL", "HRK": "HRK", "HTG": "HTG", "HUF": "HUF", + "ICP": "ICP", + "IDEX": "IDEX", "IDR": "IDR", "ILS": "ILS", "IMP": "IMP", + "IMX": "IMX", "INR": "INR", + "INV": "INV", + "IOTX": "IOTX", "IQD": "IQD", "ISK": "ISK", + "JASMY": "JASMY", "JEP": "JEP", "JMD": "JMD", "JOD": "JOD", "JPY": "JPY", + "KEEP": "KEEP", "KES": "KES", "KGS": "KGS", "KHR": "KHR", "KMF": "KMF", "KNC": "KNC", + "KRL": "KRL", "KRW": "KRW", "KWD": "KWD", "KYD": "KYD", "KZT": "KZT", "LAK": "LAK", "LBP": "LBP", + "LCX": "LCX", "LINK": "LINK", "LKR": "LKR", + "LPT": "LPT", + "LQTY": "LQTY", "LRC": "LRC", "LRD": "LRD", "LSL": "LSL", @@ -400,23 +445,31 @@ RATES = { "LYD": "LYD", "MAD": "MAD", "MANA": "MANA", + "MASK": "MASK", "MATIC": "MATIC", + "MCO2": "MCO2", "MDL": "MDL", + "MDT": "MDT", "MGA": "MGA", + "MIR": "MIR", "MKD": "MKD", "MKR": "MKR", + "MLN": "MLN", "MMK": "MMK", "MNT": "MNT", "MOP": "MOP", + "MPL": "MPL", "MRO": "MRO", "MTL": "MTL", "MUR": "MUR", + "MUSD": "MUSD", "MVR": "MVR", "MWK": "MWK", "MXN": "MXN", "MYR": "MYR", "MZN": "MZN", "NAD": "NAD", + "NCT": "NCT", "NGN": "NGN", "NIO": "NIO", "NKN": "NKN", @@ -428,19 +481,37 @@ RATES = { "OGN": "OGN", "OMG": "OMG", "OMR": "OMR", + "ORN": "ORN", "OXT": "OXT", "PAB": "PAB", + "PAX": "PAX", "PEN": "PEN", + "PERP": "PERP", "PGK": "PGK", "PHP": "PHP", "PKR": "PKR", + "PLA": "PLA", "PLN": "PLN", + "PLU": "PLU", + "POLS": "POLS", "POLY": "POLY", + "POWR": "POWR", + "PRO": "PRO", "PYG": "PYG", "QAR": "QAR", - "RLY": "RLY", + "QNT": "QNT", + "QUICK": "QUICK", + "RAD": "RAD", + "RAI": "RAI", + "RARI": "RARI", + "RBN": "RBN", "REN": "REN", "REP": "REP", + "REPV2": "REPV2", + "REQ": "REQ", + "RGT": "RGT", + "RLC": "RLC", + "RLY": "RLY", "RON": "RON", "RSD": "RSD", "RUB": "RUB", @@ -452,22 +523,34 @@ RATES = { "SGD": "SGD", "SHIB": "SHIB", "SHP": "SHP", + "SHPING": "SHPING", + "SKK": "SKK", "SKL": "SKL", "SLL": "SLL", "SNX": "SNX", + "SOL": "SOL", "SOS": "SOS", + "SPELL": "SPELL", "SRD": "SRD", "SSP": "SSP", "STD": "STD", "STORJ": "STORJ", + "STX": "STX", + "SUKU": "SUKU", + "SUPER": "SUPER", "SUSHI": "SUSHI", "SVC": "SVC", "SZL": "SZL", "THB": "THB", "TJS": "TJS", + "TMM": "TMM", "TMT": "TMT", "TND": "TND", "TOP": "TOP", + "TRAC": "TRAC", + "TRB": "TRB", + "TRIBE": "TRIBE", + "TRU": "TRU", "TRY": "TRY", "TTD": "TTD", "TWD": "TWD", @@ -475,15 +558,21 @@ RATES = { "UAH": "UAH", "UGX": "UGX", "UMA": "UMA", + "UNFI": "UNFI", "UNI": "UNI", "USD": "USD", "USDC": "USDC", + "USDT": "USDT", + "UST": "UST", "UYU": "UYU", "UZS": "UZS", "VES": "VES", + "VGX": "VGX", "VND": "VND", "VUV": "VUV", "WBTC": "WBTC", + "WCFG": "WCFG", + "WLUNA": "WLUNA", "WST": "WST", "XAF": "XAF", "XAG": "XAG", @@ -495,11 +584,15 @@ RATES = { "XPD": "XPD", "XPF": "XPF", "XPT": "XPT", + "XRP": "XRP", "XTZ": "XTZ", + "XYO": "XYO", "YER": "YER", "YFI": "YFI", + "YFII": "YFII", "ZAR": "ZAR", "ZEC": "ZEC", + "ZEN": "ZEN", "ZMW": "ZMW", "ZRX": "ZRX", "ZWL": "ZWL", diff --git a/homeassistant/components/coinbase/translations/ar.json b/homeassistant/components/coinbase/translations/ar.json index 30655126631..ec83ad6d22b 100644 --- a/homeassistant/components/coinbase/translations/ar.json +++ b/homeassistant/components/coinbase/translations/ar.json @@ -3,8 +3,7 @@ "step": { "user": { "data": { - "api_token": "\u0633\u0631 API", - "exchange_rates": "\u0623\u0633\u0639\u0627\u0631 \u0627\u0644\u0635\u0631\u0641" + "api_token": "\u0633\u0631 API" }, "description": "\u064a\u0631\u062c\u0649 \u0625\u062f\u062e\u0627\u0644 \u062a\u0641\u0627\u0635\u064a\u0644 \u0645\u0641\u062a\u0627\u062d API \u0627\u0644\u062e\u0627\u0635 \u0628\u0643 \u0639\u0644\u0649 \u0627\u0644\u0646\u062d\u0648 \u0627\u0644\u0645\u0646\u0635\u0648\u0635 \u0639\u0644\u064a\u0647 \u0645\u0646 \u0642\u0628\u0644 Coinbase.", "title": "\u062a\u0641\u0627\u0635\u064a\u0644 \u0645\u0641\u062a\u0627\u062d Coinbase API" @@ -13,8 +12,6 @@ }, "options": { "error": { - "currency_unavaliable": "\u0644\u0627 \u064a\u062a\u0645 \u062a\u0648\u0641\u064a\u0631 \u0648\u0627\u062d\u062f \u0623\u0648 \u0623\u0643\u062b\u0631 \u0645\u0646 \u0623\u0631\u0635\u062f\u0629 \u0627\u0644\u0639\u0645\u0644\u0627\u062a \u0627\u0644\u0645\u0637\u0644\u0648\u0628\u0629 \u0628\u0648\u0627\u0633\u0637\u0629 Coinbase API \u0627\u0644\u062e\u0627\u0635 \u0628\u0643.", - "exchange_rate_unavaliable": "\u0644\u0627 \u064a\u062a\u0645 \u062a\u0648\u0641\u064a\u0631 \u0648\u0627\u062d\u062f \u0623\u0648 \u0623\u0643\u062b\u0631 \u0645\u0646 \u0623\u0633\u0639\u0627\u0631 \u0627\u0644\u0635\u0631\u0641 \u0627\u0644\u0645\u0637\u0644\u0648\u0628\u0629 \u0645\u0646 Coinbase.", "unknown": "\u062d\u062f\u062b \u062e\u0637\u0623 \u063a\u064a\u0631 \u0645\u062a\u0648\u0642\u0639" }, "step": { diff --git a/homeassistant/components/coinbase/translations/ca.json b/homeassistant/components/coinbase/translations/ca.json index 0543f97003a..116b611f272 100644 --- a/homeassistant/components/coinbase/translations/ca.json +++ b/homeassistant/components/coinbase/translations/ca.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "Clau API", - "api_token": "Secret API", - "currencies": "Monedes del saldo del compte", - "exchange_rates": "Tipus de canvi" + "api_token": "Secret API" }, "description": "Introdueix els detalls de la teva clau API tal com els proporciona Coinbase.", "title": "Detalls de la clau API de Coinbase" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "L'API de Coinbase no proporciona algun/s dels saldos de moneda que has sol\u00b7licitat.", - "currency_unavaliable": "L'API de Coinbase no proporciona algun/s dels saldos de moneda que has sol\u00b7licitat.", "exchange_rate_unavailable": "L'API de Coinbase no proporciona algun/s dels tipus de canvi que has sol\u00b7licitat.", - "exchange_rate_unavaliable": "L'API de Coinbase no proporciona algun/s dels tipus de canvi que has sol\u00b7licitat.", "unknown": "Error inesperat" }, "step": { diff --git a/homeassistant/components/coinbase/translations/cs.json b/homeassistant/components/coinbase/translations/cs.json index d25e431651d..7ec64b2fa14 100644 --- a/homeassistant/components/coinbase/translations/cs.json +++ b/homeassistant/components/coinbase/translations/cs.json @@ -12,8 +12,7 @@ "user": { "data": { "api_key": "Kl\u00ed\u010d API", - "api_token": "API Secret", - "exchange_rates": "Sm\u011bnn\u00e9 kurzy" + "api_token": "API Secret" }, "title": "Podrobnosti o API kl\u00ed\u010di Coinbase" } diff --git a/homeassistant/components/coinbase/translations/de.json b/homeassistant/components/coinbase/translations/de.json index d4b58ae42bd..f6200633950 100644 --- a/homeassistant/components/coinbase/translations/de.json +++ b/homeassistant/components/coinbase/translations/de.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "API-Schl\u00fcssel", - "api_token": "API-Geheimnis", - "currencies": "Kontostand W\u00e4hrungen", - "exchange_rates": "Wechselkurse" + "api_token": "API-Geheimnis" }, "description": "Bitte gib die Details deines API-Schl\u00fcssels ein, wie von Coinbase bereitgestellt.", "title": "Coinbase API Schl\u00fcssel Details" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "Eine oder mehrere der angeforderten W\u00e4hrungssalden werden von deiner Coinbase-API nicht bereitgestellt.", - "currency_unavaliable": "Eine oder mehrere der angeforderten W\u00e4hrungssalden werden von deiner Coinbase-API nicht bereitgestellt.", "exchange_rate_unavailable": "Einer oder mehrere der angeforderten Wechselkurse werden nicht von Coinbase bereitgestellt.", - "exchange_rate_unavaliable": "Einer oder mehrere der angeforderten Wechselkurse werden nicht von Coinbase bereitgestellt.", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/coinbase/translations/el.json b/homeassistant/components/coinbase/translations/el.json index 05d6a1f7415..6b2141f091f 100644 --- a/homeassistant/components/coinbase/translations/el.json +++ b/homeassistant/components/coinbase/translations/el.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", - "api_token": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc API", - "currencies": "\u039d\u03bf\u03bc\u03af\u03c3\u03bc\u03b1\u03c4\u03b1 \u03c5\u03c0\u03bf\u03bb\u03bf\u03af\u03c0\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", - "exchange_rates": "\u03a3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2" + "api_token": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc API" }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd API \u03c3\u03b1\u03c2, \u03cc\u03c0\u03c9\u03c2 \u03b1\u03c5\u03c4\u03ac \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase.", "title": "\u0392\u03b1\u03c3\u03b9\u03ba\u03ad\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2 API \u03c4\u03bf\u03c5 Coinbase" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "\u0388\u03bd\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03c5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03b1 \u03bd\u03bf\u03bc\u03b9\u03c3\u03bc\u03ac\u03c4\u03c9\u03bd \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf API \u03c4\u03b7\u03c2 Coinbase.", - "currency_unavaliable": "\u0388\u03bd\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03c5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03b1 \u03bd\u03bf\u03bc\u03b9\u03c3\u03bc\u03ac\u03c4\u03c9\u03bd \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf Coinbase API \u03c3\u03b1\u03c2.", "exchange_rate_unavailable": "\u039c\u03af\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\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 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase.", - "exchange_rate_unavaliable": "\u039c\u03af\u03b1 \u03ae \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03b6\u03b7\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\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 \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Coinbase.", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { diff --git a/homeassistant/components/coinbase/translations/en.json b/homeassistant/components/coinbase/translations/en.json index d5d7483e260..019159c8057 100644 --- a/homeassistant/components/coinbase/translations/en.json +++ b/homeassistant/components/coinbase/translations/en.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "API Key", - "api_token": "API Secret", - "currencies": "Account Balance Currencies", - "exchange_rates": "Exchange Rates" + "api_token": "API Secret" }, "description": "Please enter the details of your API key as provided by Coinbase.", "title": "Coinbase API Key Details" @@ -26,9 +24,7 @@ "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/es-419.json b/homeassistant/components/coinbase/translations/es-419.json index c5bc63ee29a..1b72a0c0b2e 100644 --- a/homeassistant/components/coinbase/translations/es-419.json +++ b/homeassistant/components/coinbase/translations/es-419.json @@ -7,9 +7,7 @@ "step": { "user": { "data": { - "api_token": "Secreto de la API", - "currencies": "Monedas del saldo de la cuenta", - "exchange_rates": "Tipos de cambio" + "api_token": "Secreto de la API" }, "description": "Ingrese los detalles de su clave API proporcionada por Coinbase.", "title": "Detalles clave de la API de Coinbase" @@ -19,9 +17,7 @@ "options": { "error": { "currency_unavailable": "Su API de Coinbase no proporciona uno o m\u00e1s de los saldos de divisas solicitados.", - "currency_unavaliable": "Su API de Coinbase no proporciona uno o m\u00e1s de los saldos de divisas solicitados.", - "exchange_rate_unavailable": "Coinbase no proporciona uno o m\u00e1s de los tipos de cambio solicitados.", - "exchange_rate_unavaliable": "Coinbase no proporciona uno o m\u00e1s de los tipos de cambio solicitados." + "exchange_rate_unavailable": "Coinbase no proporciona uno o m\u00e1s de los tipos de cambio solicitados." } } } \ No newline at end of file diff --git a/homeassistant/components/coinbase/translations/es.json b/homeassistant/components/coinbase/translations/es.json index 311fcdb8546..5454aca8ec6 100644 --- a/homeassistant/components/coinbase/translations/es.json +++ b/homeassistant/components/coinbase/translations/es.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "Clave API", - "api_token": "Secreto de la API", - "currencies": "Saldo de la cuenta Monedas", - "exchange_rates": "Tipos de cambio" + "api_token": "Secreto de la API" }, "description": "Por favor, introduce los detalles de tu clave API tal y como te la ha proporcionado Coinbase.", "title": "Detalles de la clave API de Coinbase" @@ -25,8 +23,7 @@ }, "options": { "error": { - "currency_unavaliable": "La API de Coinbase no proporciona uno o m\u00e1s de los saldos de divisas solicitados.", - "exchange_rate_unavaliable": "Coinbase no proporciona uno o m\u00e1s de los tipos de cambio solicitados.", + "exchange_rate_unavailable": "El API de Coinbase no proporciona alguno/s de los tipos de cambio que has solicitado.", "unknown": "Error inesperado" }, "step": { @@ -34,7 +31,8 @@ "data": { "account_balance_currencies": "Saldos de la cartera para informar.", "exchange_base": "Moneda base para sensores de tipo de cambio.", - "exchange_rate_currencies": "Tipos de cambio a informar." + "exchange_rate_currencies": "Tipos de cambio a informar.", + "exchnage_rate_precision": "N\u00famero de posiciones decimales para los tipos de cambio." }, "description": "Ajustar las opciones de Coinbase" } diff --git a/homeassistant/components/coinbase/translations/et.json b/homeassistant/components/coinbase/translations/et.json index 821d17656d2..14bd1eea370 100644 --- a/homeassistant/components/coinbase/translations/et.json +++ b/homeassistant/components/coinbase/translations/et.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "API v\u00f5ti", - "api_token": "API salas\u00f5na", - "currencies": "Konto saldo valuutad", - "exchange_rates": "Vahetuskursid" + "api_token": "API salas\u00f5na" }, "description": "Sisesta Coinbase'i pakutava API-v\u00f5tme \u00fcksikasjad.", "title": "Coinbase'i API v\u00f5tme \u00fcksikasjad" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "Coinbase API ei paku \u00fchte v\u00f5i mitut soovitud valuutasaldot.", - "currency_unavaliable": "Coinbase'i API ei paku \u00fchte v\u00f5i mitut taotletud valuutasaldot.", "exchange_rate_unavailable": "Coinbase ei paku \u00fchte v\u00f5i mitut soovitud vahetuskurssi.", - "exchange_rate_unavaliable": "\u00dchte v\u00f5i mitut taotletud vahetuskurssi Coinbase ei paku.", "unknown": "Ootamatu t\u00f5rge" }, "step": { diff --git a/homeassistant/components/coinbase/translations/fr.json b/homeassistant/components/coinbase/translations/fr.json index 77664cca73d..91f52941a89 100644 --- a/homeassistant/components/coinbase/translations/fr.json +++ b/homeassistant/components/coinbase/translations/fr.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "Cl\u00e9 d'API", - "api_token": "API secr\u00e8te", - "currencies": "Devises du solde du compte", - "exchange_rates": "Taux d'\u00e9change" + "api_token": "API secr\u00e8te" }, "description": "Veuillez saisir les d\u00e9tails de votre cl\u00e9 API tels que fournis par Coinbase.", "title": "D\u00e9tails de la cl\u00e9 de l'API Coinbase" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "Un ou plusieurs des soldes de devises demand\u00e9s ne sont pas fournis par votre API Coinbase.", - "currency_unavaliable": "Un ou plusieurs des soldes de devises demand\u00e9s ne sont pas fournis par votre API Coinbase.", "exchange_rate_unavailable": "Un ou plusieurs des taux de change demand\u00e9s ne sont pas fournis par Coinbase.", - "exchange_rate_unavaliable": "Un ou plusieurs des taux de change demand\u00e9s ne sont pas fournis par Coinbase.", "unknown": "Erreur inattendue" }, "step": { diff --git a/homeassistant/components/coinbase/translations/hu.json b/homeassistant/components/coinbase/translations/hu.json index 44287da0ee6..bcf409d2dda 100644 --- a/homeassistant/components/coinbase/translations/hu.json +++ b/homeassistant/components/coinbase/translations/hu.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "API kulcs", - "api_token": "API jelsz\u00f3", - "currencies": "Sz\u00e1mlaegyenleg-p\u00e9nznemek", - "exchange_rates": "\u00c1rfolyamok" + "api_token": "API jelsz\u00f3" }, "description": "K\u00e9rj\u00fck, adja meg API kulcs\u00e1nak adatait a Coinbase \u00e1ltal megadott m\u00f3don.", "title": "Coinbase API kulcs r\u00e9szletei" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "A k\u00e9rt valutaegyenlegek k\u00f6z\u00fcl egyet vagy t\u00f6bbet nem biztos\u00edt a Coinbase API.", - "currency_unavaliable": "A k\u00e9rt valutaegyenlegek k\u00f6z\u00fcl egyet vagy t\u00f6bbet nem biztos\u00edt a Coinbase API.", "exchange_rate_unavailable": "A k\u00e9rt \u00e1rfolyamok k\u00f6z\u00fcl egyet vagy t\u00f6bbet a Coinbase nem biztos\u00edt.", - "exchange_rate_unavaliable": "A k\u00e9rt \u00e1rfolyamok k\u00f6z\u00fcl egyet vagy t\u00f6bbet a Coinbase nem biztos\u00edt.", "unknown": "Ismeretlen hiba" }, "step": { diff --git a/homeassistant/components/coinbase/translations/id.json b/homeassistant/components/coinbase/translations/id.json index 477aceafa45..114c69acce2 100644 --- a/homeassistant/components/coinbase/translations/id.json +++ b/homeassistant/components/coinbase/translations/id.json @@ -14,11 +14,9 @@ "user": { "data": { "api_key": "Kunci API", - "api_token": "Kode Rahasia API", - "currencies": "Mata Uang Saldo Akun", - "exchange_rates": "Nilai Tukar" + "api_token": "Kode Rahasia API" }, - "description": "Silakan masukkan detail kunci API Anda sesuai yang disediakan oleh Coinbase.", + "description": "Masukkan detail kunci API Anda sesuai yang disediakan oleh Coinbase.", "title": "Detail Kunci API Coinbase" } } @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "Satu atau beberapa saldo mata uang yang diminta tidak disediakan oleh API Coinbase Anda.", - "currency_unavaliable": "Satu atau beberapa saldo mata uang yang diminta tidak disediakan oleh API Coinbase Anda.", "exchange_rate_unavailable": "Satu atau beberapa nilai tukar yang diminta tidak disediakan oleh Coinbase.", - "exchange_rate_unavaliable": "Satu atau beberapa nilai tukar yang diminta tidak disediakan oleh Coinbase.", "unknown": "Kesalahan yang tidak diharapkan" }, "step": { diff --git a/homeassistant/components/coinbase/translations/it.json b/homeassistant/components/coinbase/translations/it.json index 64b5b0cdca7..f26e08a727c 100644 --- a/homeassistant/components/coinbase/translations/it.json +++ b/homeassistant/components/coinbase/translations/it.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "Chiave API", - "api_token": "API segreta", - "currencies": "Valute del saldo del conto", - "exchange_rates": "Tassi di cambio" + "api_token": "API segreta" }, "description": "Inserisci i dettagli della tua chiave API come forniti da Coinbase.", "title": "Dettagli della chiave API di Coinbase" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "Uno o pi\u00f9 dei saldi in valuta richiesti non sono forniti dalla tua API Coinbase.", - "currency_unavaliable": "Uno o pi\u00f9 saldi in valuta richiesti non sono forniti dalla tua API Coinbase.", "exchange_rate_unavailable": "Uno o pi\u00f9 dei tassi di cambio richiesti non sono forniti da Coinbase.", - "exchange_rate_unavaliable": "Uno o pi\u00f9 dei tassi di cambio richiesti non sono forniti da Coinbase.", "unknown": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/coinbase/translations/ja.json b/homeassistant/components/coinbase/translations/ja.json index 321e0c05d9d..011ff093747 100644 --- a/homeassistant/components/coinbase/translations/ja.json +++ b/homeassistant/components/coinbase/translations/ja.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "API\u30ad\u30fc", - "api_token": "API\u30b7\u30fc\u30af\u30ec\u30c3\u30c8", - "currencies": "\u53e3\u5ea7\u6b8b\u9ad8 \u901a\u8ca8", - "exchange_rates": "\u70ba\u66ff\u30ec\u30fc\u30c8" + "api_token": "API\u30b7\u30fc\u30af\u30ec\u30c3\u30c8" }, "description": "Coinbase\u304b\u3089\u63d0\u4f9b\u3055\u308c\u305fAPI\u30ad\u30fc\u306e\u8a73\u7d30\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "title": "Coinbase API\u30ad\u30fc\u306e\u8a73\u7d30" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "\u30ea\u30af\u30a8\u30b9\u30c8\u3057\u305f\u901a\u8ca8\u6b8b\u9ad8\u306e1\u3064\u4ee5\u4e0a\u304c\u3001Coinbase API\u306b\u3088\u3063\u3066\u63d0\u4f9b\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", - "currency_unavaliable": "\u8981\u6c42\u3055\u308c\u305f\u901a\u8ca8\u6b8b\u9ad8\u306e1\u3064\u4ee5\u4e0a\u304c\u3001Coinbase API\u306b\u3088\u3063\u3066\u63d0\u4f9b\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "exchange_rate_unavailable": "\u30ea\u30af\u30a8\u30b9\u30c8\u3057\u305f\u70ba\u66ff\u30ec\u30fc\u30c8\u306e1\u3064\u4ee5\u4e0a\u304c\u3001Coinbase\u306b\u3088\u3063\u3066\u63d0\u4f9b\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", - "exchange_rate_unavaliable": "\u8981\u6c42\u3055\u308c\u305f\u70ba\u66ff\u30ec\u30fc\u30c8\u306e1\u3064\u4ee5\u4e0a\u304cCoinbase\u306b\u3088\u3063\u3066\u63d0\u4f9b\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { diff --git a/homeassistant/components/coinbase/translations/nl.json b/homeassistant/components/coinbase/translations/nl.json index 98763deb9a7..472a15659c0 100644 --- a/homeassistant/components/coinbase/translations/nl.json +++ b/homeassistant/components/coinbase/translations/nl.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "API-sleutel", - "api_token": "API-geheim", - "currencies": "Valuta's van rekeningsaldo", - "exchange_rates": "Wisselkoersen" + "api_token": "API-geheim" }, "description": "Voer de gegevens van uw API-sleutel in zoals verstrekt door Coinbase.", "title": "Coinbase API Sleutel Details" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "Een of meer van de gevraagde valutabalansen wordt niet geleverd door uw Coinbase API.", - "currency_unavaliable": "Een of meer van de gevraagde valutasaldi worden niet geleverd door uw Coinbase API.", "exchange_rate_unavailable": "Een of meer van de gevraagde wisselkoersen worden niet door Coinbase geleverd.", - "exchange_rate_unavaliable": "Een of meer van de gevraagde wisselkoersen worden niet door Coinbase verstrekt.", "unknown": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/coinbase/translations/no.json b/homeassistant/components/coinbase/translations/no.json index 5171814cf9d..c3f2b34cf92 100644 --- a/homeassistant/components/coinbase/translations/no.json +++ b/homeassistant/components/coinbase/translations/no.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "API-n\u00f8kkel", - "api_token": "API-hemmelighet", - "currencies": "Valutaer for kontosaldo", - "exchange_rates": "Valutakurser" + "api_token": "API-hemmelighet" }, "description": "Vennligst skriv inn detaljene for API-n\u00f8kkelen din som gitt av Coinbase.", "title": "Detaljer for Coinbase API-n\u00f8kkel" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "En eller flere av de forespurte valutasaldoene leveres ikke av Coinbase API.", - "currency_unavaliable": "En eller flere av de forespurte valutasaldoene leveres ikke av Coinbase API.", "exchange_rate_unavailable": "En eller flere av de forespurte valutakursene er ikke levert av Coinbase.", - "exchange_rate_unavaliable": "En eller flere av de forespurte valutakursene leveres ikke av Coinbase.", "unknown": "Uventet feil" }, "step": { diff --git a/homeassistant/components/coinbase/translations/pl.json b/homeassistant/components/coinbase/translations/pl.json index 7465ae24486..70a1a021cdf 100644 --- a/homeassistant/components/coinbase/translations/pl.json +++ b/homeassistant/components/coinbase/translations/pl.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "Klucz API", - "api_token": "Sekretne API", - "currencies": "Waluty salda konta", - "exchange_rates": "Kursy wymiany" + "api_token": "Sekretne API" }, "description": "Wprowad\u017a dane swojego klucza API podane przez Coinbase.", "title": "Szczeg\u00f3\u0142y klucza API Coinbase" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "Jeden lub wi\u0119cej \u017c\u0105danych sald walutowych nie jest dostarczanych przez interfejs API Coinbase.", - "currency_unavaliable": "Jeden lub wi\u0119cej \u017c\u0105danych sald walutowych nie jest dostarczanych przez interfejs API Coinbase.", "exchange_rate_unavailable": "Jeden lub wi\u0119cej z \u017c\u0105danych kurs\u00f3w wymiany nie jest dostarczany przez Coinbase.", - "exchange_rate_unavaliable": "Jeden lub wi\u0119cej z \u017c\u0105danych kurs\u00f3w wymiany nie jest dostarczany przez Coinbase.", "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { diff --git a/homeassistant/components/coinbase/translations/pt-BR.json b/homeassistant/components/coinbase/translations/pt-BR.json index 5ed52fa7afc..5f2bb7d96e3 100644 --- a/homeassistant/components/coinbase/translations/pt-BR.json +++ b/homeassistant/components/coinbase/translations/pt-BR.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "Chave da API", - "api_token": "Segredo da API", - "currencies": "Moedas do saldo da conta", - "exchange_rates": "Taxas de c\u00e2mbio" + "api_token": "Segredo da API" }, "description": "Por favor, insira os detalhes da sua chave de API conforme fornecido pela Coinbase.", "title": "Detalhes da chave da API Coinbase" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "Um ou mais dos saldos de moeda solicitados n\u00e3o s\u00e3o fornecidos pela sua API Coinbase.", - "currency_unavaliable": "Um ou mais dos saldos de moeda solicitados n\u00e3o s\u00e3o fornecidos pela sua API Coinbase.", "exchange_rate_unavailable": "Uma ou mais taxas de c\u00e2mbio solicitadas n\u00e3o s\u00e3o fornecidas pela Coinbase.", - "exchange_rate_unavaliable": "Uma ou mais taxas de c\u00e2mbio solicitadas n\u00e3o s\u00e3o fornecidas pela Coinbase.", "unknown": "Erro inesperado" }, "step": { diff --git a/homeassistant/components/coinbase/translations/ru.json b/homeassistant/components/coinbase/translations/ru.json index 951c0182320..cbdf39e61a6 100644 --- a/homeassistant/components/coinbase/translations/ru.json +++ b/homeassistant/components/coinbase/translations/ru.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API", - "api_token": "\u0421\u0435\u043a\u0440\u0435\u0442 API", - "currencies": "\u041e\u0441\u0442\u0430\u0442\u043e\u043a \u0432\u0430\u043b\u044e\u0442\u044b \u043d\u0430 \u0441\u0447\u0435\u0442\u0435", - "exchange_rates": "\u041e\u0431\u043c\u0435\u043d\u043d\u044b\u0435 \u043a\u0443\u0440\u0441\u044b" + "api_token": "\u0421\u0435\u043a\u0440\u0435\u0442 API" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0412\u0430\u0448\u0435\u0433\u043e \u043a\u043b\u044e\u0447\u0430 API Coinbase.", "title": "\u041a\u043b\u044e\u0447 API Coinbase" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "\u041e\u0434\u0438\u043d \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u0445 \u043e\u0441\u0442\u0430\u0442\u043a\u043e\u0432 \u0432\u0430\u043b\u044e\u0442\u044b \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0412\u0430\u0448\u0438\u043c API Coinbase.", - "currency_unavaliable": "\u041e\u0434\u0438\u043d \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u0445 \u043e\u0441\u0442\u0430\u0442\u043a\u043e\u0432 \u0432\u0430\u043b\u044e\u0442\u044b \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0412\u0430\u0448\u0438\u043c API Coinbase.", "exchange_rate_unavailable": "Coinbase \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u0434\u0438\u043d \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u0445 \u043e\u0431\u043c\u0435\u043d\u043d\u044b\u0445 \u043a\u0443\u0440\u0441\u043e\u0432.", - "exchange_rate_unavaliable": "Coinbase \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u0434\u0438\u043d \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u0445 \u043e\u0431\u043c\u0435\u043d\u043d\u044b\u0445 \u043a\u0443\u0440\u0441\u043e\u0432.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { diff --git a/homeassistant/components/coinbase/translations/sk.json b/homeassistant/components/coinbase/translations/sk.json index ff853127803..f161003be52 100644 --- a/homeassistant/components/coinbase/translations/sk.json +++ b/homeassistant/components/coinbase/translations/sk.json @@ -10,5 +10,10 @@ } } } + }, + "options": { + "error": { + "currency_unavailable": "Jeden alebo viacero po\u017eadovan\u00fdch zostatkov mien nie s\u00fa poskytovan\u00e9 Va\u0161\u00edm Coinbase API." + } } } \ No newline at end of file diff --git a/homeassistant/components/coinbase/translations/tr.json b/homeassistant/components/coinbase/translations/tr.json index e21cab489e4..b84e2bf740e 100644 --- a/homeassistant/components/coinbase/translations/tr.json +++ b/homeassistant/components/coinbase/translations/tr.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "API Anahtar\u0131", - "api_token": "API Gizli Anahtar\u0131", - "currencies": "Hesap Bakiyesi Para Birimleri", - "exchange_rates": "D\u00f6viz Kurlar\u0131" + "api_token": "API Gizli Anahtar\u0131" }, "description": "L\u00fctfen API anahtar\u0131n\u0131z\u0131n ayr\u0131nt\u0131lar\u0131n\u0131 Coinbase taraf\u0131ndan sa\u011flanan \u015fekilde girin.", "title": "Coinbase API Anahtar Ayr\u0131nt\u0131lar\u0131" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "\u0130stenen para birimi bakiyelerinden biri veya daha fazlas\u0131 Coinbase API'niz taraf\u0131ndan sa\u011flanm\u0131yor.", - "currency_unavaliable": "\u0130stenen para birimi bakiyelerinden biri veya daha fazlas\u0131 Coinbase API'niz taraf\u0131ndan sa\u011flanm\u0131yor.", "exchange_rate_unavailable": "\u0130stenen d\u00f6viz kurlar\u0131ndan biri veya daha fazlas\u0131 Coinbase taraf\u0131ndan sa\u011flanm\u0131yor.", - "exchange_rate_unavaliable": "\u0130stenen d\u00f6viz kurlar\u0131ndan biri veya daha fazlas\u0131 Coinbase taraf\u0131ndan sa\u011flanm\u0131yor.", "unknown": "Beklenmeyen hata" }, "step": { diff --git a/homeassistant/components/coinbase/translations/zh-Hans.json b/homeassistant/components/coinbase/translations/zh-Hans.json index 1a5eaa19dec..954a10c70a6 100644 --- a/homeassistant/components/coinbase/translations/zh-Hans.json +++ b/homeassistant/components/coinbase/translations/zh-Hans.json @@ -12,9 +12,7 @@ "user": { "data": { "api_key": "API \u5bc6\u94a5", - "api_token": "API Token", - "currencies": "\u8d26\u6237\u4f59\u989d", - "exchange_rates": "\u6c47\u7387" + "api_token": "API Token" }, "description": "\u8bf7\u8f93\u5165\u7531 Coinbase \u63d0\u4f9b\u7684 API \u5bc6\u94a5\u4fe1\u606f", "title": "Coinbase API \u5bc6\u94a5\u8be6\u60c5" @@ -23,8 +21,6 @@ }, "options": { "error": { - "currency_unavaliable": "Coinbase \u65e0\u6cd5\u63d0\u4f9b\u5176\u8bbe\u5b9a\u7684\u6c47\u7387\u4fe1\u606f", - "exchange_rate_unavaliable": "Coinbase \u65e0\u6cd5\u63d0\u4f9b\u5176\u8bbe\u5b9a\u7684\u6c47\u7387\u4fe1\u606f", "unknown": "\u672a\u77e5\u9519\u8bef" }, "step": { diff --git a/homeassistant/components/coinbase/translations/zh-Hant.json b/homeassistant/components/coinbase/translations/zh-Hant.json index 315fe90254f..ea48d90fc7e 100644 --- a/homeassistant/components/coinbase/translations/zh-Hant.json +++ b/homeassistant/components/coinbase/translations/zh-Hant.json @@ -14,9 +14,7 @@ "user": { "data": { "api_key": "API \u91d1\u9470", - "api_token": "API \u79c1\u9470", - "currencies": "\u5e33\u6236\u9918\u984d\u8ca8\u5e63", - "exchange_rates": "\u532f\u7387" + "api_token": "API \u79c1\u9470" }, "description": "\u8acb\u8f38\u5165\u7531 Coinbase \u63d0\u4f9b\u7684 API \u91d1\u9470\u8cc7\u8a0a\u3002", "title": "Coinbase API \u91d1\u9470\u8cc7\u6599" @@ -26,9 +24,7 @@ "options": { "error": { "currency_unavailable": "Coinbase API \u672a\u63d0\u4f9b\u4e00\u500b\u6216\u591a\u500b\u6240\u8981\u6c42\u7684\u8ca8\u5e63\u9918\u984d\u3002", - "currency_unavaliable": "Coinbase API \u672a\u63d0\u4f9b\u4e00\u500b\u6216\u591a\u500b\u6240\u8981\u6c42\u7684\u8ca8\u5e63\u9918\u984d\u3002", "exchange_rate_unavailable": "Coinbase \u672a\u63d0\u4f9b\u4e00\u500b\u6216\u591a\u500b\u6240\u8981\u6c42\u7684\u532f\u7387\u3002", - "exchange_rate_unavaliable": "Coinbase \u672a\u63d0\u4f9b\u4e00\u500b\u6216\u591a\u500b\u6240\u8981\u6c42\u7684\u532f\u7387\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/configurator/translations/es.json b/homeassistant/components/configurator/translations/es.json index dffb90e6d49..50ea2f68a7c 100644 --- a/homeassistant/components/configurator/translations/es.json +++ b/homeassistant/components/configurator/translations/es.json @@ -1,7 +1,7 @@ { "state": { "_": { - "configure": "Configurar", + "configure": "Configura", "configured": "Configurado" } }, diff --git a/homeassistant/components/configurator/translations/nl.json b/homeassistant/components/configurator/translations/nl.json index d8ad5061e0f..ea1bd002c5d 100644 --- a/homeassistant/components/configurator/translations/nl.json +++ b/homeassistant/components/configurator/translations/nl.json @@ -1,7 +1,7 @@ { "state": { "_": { - "configure": "Configureer", + "configure": "Configureren", "configured": "Geconfigureerd" } }, diff --git a/homeassistant/components/coronavirus/translations/nl.json b/homeassistant/components/coronavirus/translations/nl.json index fec0b6462eb..9b0872dafa2 100644 --- a/homeassistant/components/coronavirus/translations/nl.json +++ b/homeassistant/components/coronavirus/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", + "already_configured": "Dienst is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken" }, "step": { diff --git a/homeassistant/components/counter/manifest.json b/homeassistant/components/counter/manifest.json index ab1a4bf0438..6db4a0a7a97 100644 --- a/homeassistant/components/counter/manifest.json +++ b/homeassistant/components/counter/manifest.json @@ -1,6 +1,7 @@ { "domain": "counter", "name": "Counter", + "integration_type": "helper", "documentation": "https://www.home-assistant.io/integrations/counter", "codeowners": ["@fabaff"], "quality_scale": "internal" diff --git a/homeassistant/components/cover/device_action.py b/homeassistant/components/cover/device_action.py index debb2368cf2..c3c0e928f0f 100644 --- a/homeassistant/components/cover/device_action.py +++ b/homeassistant/components/cover/device_action.py @@ -21,7 +21,7 @@ from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import get_supported_features -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from . import ( ATTR_POSITION, @@ -63,7 +63,7 @@ async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Cover devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) actions = [] # Get all the integrations entities for this device @@ -120,7 +120,10 @@ async def async_get_action_capabilities( async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} diff --git a/homeassistant/components/cover/device_condition.py b/homeassistant/components/cover/device_condition.py index cca608187a2..bb66d54b79b 100644 --- a/homeassistant/components/cover/device_condition.py +++ b/homeassistant/components/cover/device_condition.py @@ -66,7 +66,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Cover devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) conditions: list[dict[str, str]] = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/cover/device_trigger.py b/homeassistant/components/cover/device_trigger.py index f960fcdcce6..a6ed7785486 100644 --- a/homeassistant/components/cover/device_trigger.py +++ b/homeassistant/components/cover/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for Cover.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -74,9 +72,9 @@ TRIGGER_SCHEMA = vol.Any(POSITION_TRIGGER_SCHEMA, STATE_TRIGGER_SCHEMA) async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Cover devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/cover/intent.py b/homeassistant/components/cover/intent.py index 1fa353151ba..9174e8399f3 100644 --- a/homeassistant/components/cover/intent.py +++ b/homeassistant/components/cover/intent.py @@ -11,13 +11,15 @@ INTENT_CLOSE_COVER = "HassCloseCover" async def async_setup_intents(hass: HomeAssistant) -> None: """Set up the cover intents.""" - hass.helpers.intent.async_register( + intent.async_register( + hass, intent.ServiceIntentHandler( INTENT_OPEN_COVER, DOMAIN, SERVICE_OPEN_COVER, "Opened {}" - ) + ), ) - hass.helpers.intent.async_register( + intent.async_register( + hass, intent.ServiceIntentHandler( INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, "Closed {}" - ) + ), ) diff --git a/homeassistant/components/cover/translations/es.json b/homeassistant/components/cover/translations/es.json index 181cda45fb5..550c9d368e9 100644 --- a/homeassistant/components/cover/translations/es.json +++ b/homeassistant/components/cover/translations/es.json @@ -35,5 +35,5 @@ "stopped": "Detenido" } }, - "title": "Persiana" + "title": "Cubierta" } \ No newline at end of file diff --git a/homeassistant/components/cover/translations/nl.json b/homeassistant/components/cover/translations/nl.json index c3998187c4f..31c8c90d2b7 100644 --- a/homeassistant/components/cover/translations/nl.json +++ b/homeassistant/components/cover/translations/nl.json @@ -29,11 +29,11 @@ "state": { "_": { "closed": "Gesloten", - "closing": "Sluiten", + "closing": "Sluitend", "open": "Open", "opening": "Opent", "stopped": "Gestopt" } }, - "title": "Rolluik" + "title": "Afdekkingen" } \ No newline at end of file diff --git a/homeassistant/components/cpuspeed/translations/ko.json b/homeassistant/components/cpuspeed/translations/ko.json new file mode 100644 index 00000000000..758f3336cd4 --- /dev/null +++ b/homeassistant/components/cpuspeed/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cpuspeed/translations/nl.json b/homeassistant/components/cpuspeed/translations/nl.json index c3d09b0b8a6..fab4a86c645 100644 --- a/homeassistant/components/cpuspeed/translations/nl.json +++ b/homeassistant/components/cpuspeed/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", + "already_configured": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "not_compatible": "Kan geen CPU-informatie ophalen, deze integratie is niet compatibel met uw systeem" }, "step": { "user": { - "description": "Wilt u beginnen met instellen?", + "description": "Wil je beginnen met instellen?", "title": "CPU-snelheid" } } diff --git a/homeassistant/components/crownstone/translations/es.json b/homeassistant/components/crownstone/translations/es.json index f52b0074322..b71c69db33c 100644 --- a/homeassistant/components/crownstone/translations/es.json +++ b/homeassistant/components/crownstone/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "usb_setup_complete": "Configuraci\u00f3n USB de Crownstone completa.", "usb_setup_unsuccessful": "La configuraci\u00f3n del USB de Crownstone no tuvo \u00e9xito." }, diff --git a/homeassistant/components/crownstone/translations/ko.json b/homeassistant/components/crownstone/translations/ko.json new file mode 100644 index 00000000000..22a5729e256 --- /dev/null +++ b/homeassistant/components/crownstone/translations/ko.json @@ -0,0 +1,9 @@ +{ + "options": { + "step": { + "usb_sphere_config": { + "title": "\ud06c\ub77c\uc6b4\uc2a4\ud1a4 USB \uc2a4\ud53c\uc5b4" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/nl.json b/homeassistant/components/daikin/translations/nl.json index 33659797e7a..47b65415437 100644 --- a/homeassistant/components/daikin/translations/nl.json +++ b/homeassistant/components/daikin/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "error": { "api_password": "Ongeldige authenticatie, gebruik API-sleutel of wachtwoord.", diff --git a/homeassistant/components/deconz/alarm_control_panel.py b/homeassistant/components/deconz/alarm_control_panel.py index a29c439123e..90c34da0f12 100644 --- a/homeassistant/components/deconz/alarm_control_panel.py +++ b/homeassistant/components/deconz/alarm_control_panel.py @@ -1,7 +1,7 @@ """Support for deCONZ alarm control panel devices.""" from __future__ import annotations -from pydeconz.models.alarm_system import AlarmSystem +from pydeconz.interfaces.alarm_systems import ArmAction from pydeconz.models.event import EventType from pydeconz.models.sensor.ancillary_control import ( ANCILLARY_CONTROL_ARMED_AWAY, @@ -53,13 +53,13 @@ DECONZ_TO_ALARM_STATE = { } -def get_alarm_system_for_unique_id( +def get_alarm_system_id_for_unique_id( gateway: DeconzGateway, unique_id: str -) -> AlarmSystem | None: - """Retrieve alarm system unique ID is registered to.""" +) -> str | None: + """Retrieve alarm system ID the unique ID is registered to.""" for alarm_system in gateway.api.alarmsystems.values(): if unique_id in alarm_system.devices: - return alarm_system + return alarm_system.resource_id return None @@ -76,19 +76,18 @@ async def async_setup_entry( def async_add_sensor(_: EventType, sensor_id: str) -> None: """Add alarm control panel devices from deCONZ.""" 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)]) + if alarm_system_id := get_alarm_system_id_for_unique_id( + gateway, sensor.unique_id + ): + async_add_entities( + [DeconzAlarmControlPanel(sensor, gateway, alarm_system_id)] + ) - config_entry.async_on_unload( - gateway.api.sensors.ancillary_control.subscribe( - async_add_sensor, - EventType.ADDED, - ) + gateway.register_platform_add_device_callback( + async_add_sensor, + gateway.api.sensors.ancillary_control, ) - for sensor_id in gateway.api.sensors.ancillary_control: - async_add_sensor(EventType.ADDED, sensor_id) - class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): """Representation of a deCONZ alarm control panel.""" @@ -107,11 +106,11 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): self, device: AncillaryControl, gateway: DeconzGateway, - alarm_system: AlarmSystem, + alarm_system_id: str, ) -> None: """Set up alarm control panel device.""" super().__init__(device, gateway) - self.alarm_system = alarm_system + self.alarm_system_id = alarm_system_id @callback def async_update_callback(self) -> None: @@ -133,19 +132,27 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" if code: - await self.alarm_system.arm_away(code) + await self.gateway.api.alarmsystems.arm( + self.alarm_system_id, ArmAction.AWAY, code + ) async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" if code: - await self.alarm_system.arm_stay(code) + await self.gateway.api.alarmsystems.arm( + self.alarm_system_id, ArmAction.STAY, code + ) async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send arm night command.""" if code: - await self.alarm_system.arm_night(code) + await self.gateway.api.alarmsystems.arm( + self.alarm_system_id, ArmAction.NIGHT, code + ) async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" if code: - await self.alarm_system.disarm(code) + await self.gateway.api.alarmsystems.arm( + self.alarm_system_id, ArmAction.DISARM, code + ) diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index c3b16e509cb..d109fb8b34d 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -5,6 +5,7 @@ from collections.abc import Callable from dataclasses import dataclass from pydeconz.interfaces.sensors import SensorResources +from pydeconz.models.event import EventType from pydeconz.models.sensor.alarm import Alarm from pydeconz.models.sensor.carbon_monoxide import CarbonMonoxide from pydeconz.models.sensor.fire import Fire @@ -188,48 +189,44 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_sensor(sensors: list[SensorResources] | None = None) -> None: - """Add binary sensor from deCONZ.""" - entities: list[DeconzBinarySensor] = [] + def async_add_sensor(_: EventType, sensor_id: str) -> None: + """Add sensor from deCONZ.""" + sensor = gateway.api.sensors[sensor_id] - if sensors is None: - sensors = gateway.api.sensors.values() + if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): + return - for sensor in sensors: - - if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): + for description in ( + ENTITY_DESCRIPTIONS.get(type(sensor), []) + BINARY_SENSOR_DESCRIPTIONS + ): + if ( + not hasattr(sensor, description.key) + or description.value_fn(sensor) is None + ): continue - known_entities = set(gateway.entities[DOMAIN]) - for description in ( - ENTITY_DESCRIPTIONS.get(type(sensor), []) + BINARY_SENSOR_DESCRIPTIONS - ): + async_add_entities([DeconzBinarySensor(sensor, gateway, description)]) - if ( - not hasattr(sensor, description.key) - or description.value_fn(sensor) is None - ): - continue + gateway.register_platform_add_device_callback( + async_add_sensor, + gateway.api.sensors, + ) - new_sensor = DeconzBinarySensor(sensor, gateway, description) - if new_sensor.unique_id not in known_entities: - entities.append(new_sensor) - - if entities: - async_add_entities(entities) + @callback + def async_reload_clip_sensors() -> None: + """Load clip sensor sensors from deCONZ.""" + for sensor_id, sensor in gateway.api.sensors.items(): + if sensor.type.startswith("CLIP"): + async_add_sensor(EventType.ADDED, sensor_id) config_entry.async_on_unload( async_dispatcher_connect( hass, - gateway.signal_new_sensor, - async_add_sensor, + gateway.signal_reload_clip_sensors, + async_reload_clip_sensors, ) ) - async_add_sensor( - [gateway.api.sensors[key] for key in sorted(gateway.api.sensors, key=int)] - ) - class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): """Representation of a deCONZ binary sensor.""" diff --git a/homeassistant/components/deconz/button.py b/homeassistant/components/deconz/button.py index ed61750af6f..498c88a2351 100644 --- a/homeassistant/components/deconz/button.py +++ b/homeassistant/components/deconz/button.py @@ -65,16 +65,11 @@ async def async_setup_entry( for description in ENTITY_DESCRIPTIONS.get(PydeconzScene, []) ) - config_entry.async_on_unload( - gateway.api.scenes.subscribe( - async_add_scene, - EventType.ADDED, - ) + gateway.register_platform_add_device_callback( + async_add_scene, + gateway.api.scenes, ) - for scene_id in gateway.api.scenes: - async_add_scene(EventType.ADDED, scene_id) - class DeconzButton(DeconzSceneMixin, ButtonEntity): """Representation of a deCONZ button entity.""" diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index aaedc7bae90..6887b4238d2 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Any +from pydeconz.models.event import EventType from pydeconz.models.sensor.thermostat import ( THERMOSTAT_FAN_MODE_AUTO, THERMOSTAT_FAN_MODE_HIGH, @@ -91,45 +92,38 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the deCONZ climate devices. - - Thermostats are based on the same device class as sensors in deCONZ. - """ + """Set up the deCONZ climate devices.""" gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() @callback - def async_add_climate(sensors: list[Thermostat] | None = None) -> None: - """Add climate devices from deCONZ.""" - entities: list[DeconzThermostat] = [] + def async_add_climate(_: EventType, climate_id: str) -> None: + """Add climate from deCONZ.""" + climate = gateway.api.sensors.thermostat[climate_id] + if not gateway.option_allow_clip_sensor and climate.type.startswith("CLIP"): + return + async_add_entities([DeconzThermostat(climate, gateway)]) - if sensors is None: - sensors = list(gateway.api.sensors.thermostat.values()) + gateway.register_platform_add_device_callback( + async_add_climate, + gateway.api.sensors.thermostat, + ) - for sensor in sensors: - - if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): - continue - - if ( - isinstance(sensor, Thermostat) - and sensor.unique_id not in gateway.entities[DOMAIN] - ): - entities.append(DeconzThermostat(sensor, gateway)) - - if entities: - async_add_entities(entities) + @callback + def async_reload_clip_sensors() -> None: + """Load clip sensors from deCONZ.""" + for climate_id, climate in gateway.api.sensors.thermostat.items(): + if climate.type.startswith("CLIP"): + async_add_climate(EventType.ADDED, climate_id) config_entry.async_on_unload( async_dispatcher_connect( hass, - gateway.signal_new_sensor, - async_add_climate, + gateway.signal_reload_clip_sensors, + async_reload_clip_sensors, ) ) - async_add_climate() - class DeconzThermostat(DeconzDevice, ClimateEntity): """Representation of a deCONZ thermostat.""" diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index d5656d31bee..9efbeac366f 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Any, cast +from pydeconz.models.event import EventType from pydeconz.models.light.cover import Cover from homeassistant.components.cover import ( @@ -15,7 +16,6 @@ from homeassistant.components.cover 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_platform import AddEntitiesCallback from .deconz_device import DeconzDevice @@ -38,33 +38,16 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_cover(lights: list[Cover] | None = None) -> None: + def async_add_cover(_: EventType, cover_id: str) -> None: """Add cover from deCONZ.""" - entities = [] + cover = gateway.api.lights.covers[cover_id] + async_add_entities([DeconzCover(cover, gateway)]) - if lights is None: - lights = list(gateway.api.lights.covers.values()) - - for light in lights: - if ( - isinstance(light, Cover) - and light.unique_id not in gateway.entities[DOMAIN] - ): - entities.append(DeconzCover(light, gateway)) - - if entities: - async_add_entities(entities) - - config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_light, - async_add_cover, - ) + gateway.register_platform_add_device_callback( + async_add_cover, + gateway.api.lights.covers, ) - async_add_cover() - class DeconzCover(DeconzDevice, CoverEntity): """Representation of a deCONZ cover.""" diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index 4f04aa34fe2..fa53ef1b5bc 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -58,29 +58,18 @@ async def async_setup_events(gateway: DeconzGateway) -> None: elif isinstance(sensor, AncillaryControl): new_event = DeconzAlarmEvent(sensor, gateway) - else: - return None - gateway.hass.async_create_task(new_event.async_update_device_registry()) gateway.events.append(new_event) - gateway.config_entry.async_on_unload( - gateway.api.sensors.ancillary_control.subscribe( - async_add_sensor, - EventType.ADDED, - ) + gateway.register_platform_add_device_callback( + async_add_sensor, + gateway.api.sensors.switch, ) - - gateway.config_entry.async_on_unload( - gateway.api.sensors.switch.subscribe( - async_add_sensor, - EventType.ADDED, - ) + gateway.register_platform_add_device_callback( + async_add_sensor, + gateway.api.sensors.ancillary_control, ) - for sensor_id in gateway.api.sensors: - async_add_sensor(EventType.ADDED, sensor_id) - @callback def async_unload_events(gateway: DeconzGateway) -> None: diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index c92ad7f46dc..e15513bddbf 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -1,9 +1,6 @@ """Provides device automations for deconz events.""" - from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -643,8 +640,8 @@ def _get_deconz_event_from_device( async def async_validate_trigger_config( hass: HomeAssistant, - config: dict[str, Any], -) -> vol.Schema: + config: ConfigType, +) -> ConfigType: """Validate config.""" config = TRIGGER_SCHEMA(config) @@ -703,7 +700,7 @@ async def async_attach_trigger( async def async_get_triggers( hass: HomeAssistant, device_id: str, -) -> list | None: +) -> list[dict[str, str]]: """List device triggers. Make sure device is a supported remote model. @@ -714,7 +711,7 @@ async def async_get_triggers( device = device_registry.devices[device_id] if device.model not in REMOTES: - return None + return [] triggers = [] for trigger, subtype in REMOTES[device.model].keys(): diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 8fcc7c14f9d..ab9a1ba6f4a 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Any, Literal +from pydeconz.models.event import EventType from pydeconz.models.light.fan import ( FAN_SPEED_25_PERCENT, FAN_SPEED_50_PERCENT, @@ -15,7 +16,6 @@ from pydeconz.models.light.fan import ( 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 from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.percentage import ( ordered_list_item_to_percentage, @@ -43,34 +43,16 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_fan(lights: list[Fan] | None = None) -> None: + def async_add_fan(_: EventType, fan_id: str) -> None: """Add fan from deCONZ.""" - entities = [] + fan = gateway.api.lights.fans[fan_id] + async_add_entities([DeconzFan(fan, gateway)]) - if lights is None: - lights = list(gateway.api.lights.fans.values()) - - for light in lights: - - if ( - isinstance(light, Fan) - and light.unique_id not in gateway.entities[DOMAIN] - ): - entities.append(DeconzFan(light, gateway)) - - if entities: - async_add_entities(entities) - - config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_light, - async_add_fan, - ) + gateway.register_platform_add_device_callback( + async_add_fan, + gateway.api.lights.fans, ) - async_add_fan() - class DeconzFan(DeconzDevice, FanEntity): """Representation of a deCONZ fan.""" diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index f54cd4076d3..5890e372e66 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -3,16 +3,14 @@ from __future__ import annotations import asyncio +from collections.abc import Callable from types import MappingProxyType from typing import TYPE_CHECKING, Any, cast import async_timeout 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 pydeconz.interfaces.api import APIItems, GroupedAPIItems +from pydeconz.models.event import EventType from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT @@ -55,7 +53,6 @@ class DeconzGateway: self.config_entry = config_entry self.api = api - api.add_device_callback = self.async_add_device_callback api.connection_status_callback = self.async_connection_status_callback self.available = True @@ -63,22 +60,19 @@ class DeconzGateway: 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_light = f"deconz_new_light_{config_entry.entry_id}" - self.signal_new_sensor = f"deconz_new_sensor_{config_entry.entry_id}" - - self.deconz_resource_type_to_signal_new_device = { - ResourceGroup.LIGHT.value: self.signal_new_light, - ResourceGroup.SENSOR.value: self.signal_new_sensor, - } + self.signal_reload_clip_sensors = f"deconz_reload_clip_{config_entry.entry_id}" self.deconz_ids: dict[str, str] = {} self.entities: dict[str, set[str]] = {} self.events: list[DeconzAlarmEvent | DeconzEvent] = [] + self.ignored_devices: set[tuple[Callable[[EventType, str], None], str]] = set() self._option_allow_deconz_groups = self.config_entry.options.get( CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS ) + self.option_allow_new_devices = self.config_entry.options.get( + CONF_ALLOW_NEW_DEVICES, DEFAULT_ALLOW_NEW_DEVICES + ) @property def bridgeid(self) -> str: @@ -111,13 +105,42 @@ class DeconzGateway: CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS ) - @property - def option_allow_new_devices(self) -> bool: - """Allow automatic adding of new devices.""" - return self.config_entry.options.get( - CONF_ALLOW_NEW_DEVICES, DEFAULT_ALLOW_NEW_DEVICES + @callback + def register_platform_add_device_callback( + self, + add_device_callback: Callable[[EventType, str], None], + deconz_device_interface: APIItems | GroupedAPIItems, + ) -> None: + """Wrap add_device_callback to check allow_new_devices option.""" + + def async_add_device(event: EventType, device_id: str) -> None: + """Add device or add it to ignored_devices set. + + If ignore_state_updates is True means device_refresh service is used. + Device_refresh is expected to load new devices. + """ + if not self.option_allow_new_devices and not self.ignore_state_updates: + self.ignored_devices.add((async_add_device, device_id)) + return + add_device_callback(event, device_id) + + self.config_entry.async_on_unload( + deconz_device_interface.subscribe( + async_add_device, + EventType.ADDED, + ) ) + for device_id in deconz_device_interface: + add_device_callback(EventType.ADDED, device_id) + + @callback + def load_ignored_devices(self) -> None: + """Load previously ignored devices.""" + for add_entities, device_id in self.ignored_devices: + add_entities(EventType.ADDED, device_id) + self.ignored_devices.clear() + # Callbacks @callback @@ -127,37 +150,6 @@ class DeconzGateway: self.ignore_state_updates = False async_dispatcher_send(self.hass, self.signal_reachable) - @callback - def async_add_device_callback( - self, - resource_type: str, - device: DeconzAlarmSystem - | DeconzGroup - | DeconzLight - | DeconzSensor - | list[DeconzAlarmSystem | DeconzGroup | DeconzLight | DeconzSensor] - | None = None, - force: bool = False, - ) -> None: - """Handle event of new device creation in deCONZ.""" - if ( - not force - and not self.option_allow_new_devices - or resource_type not in self.deconz_resource_type_to_signal_new_device - ): - return - - args = [] - - if device is not None and not isinstance(device, list): - args.append([device]) - - async_dispatcher_send( - self.hass, - self.deconz_resource_type_to_signal_new_device[resource_type], - *args, # Don't send device if None, it would override default value in listeners - ) - async def async_update_device_registry(self) -> None: """Update device registry.""" if self.api.config.mac is None: @@ -210,8 +202,10 @@ class DeconzGateway: """Manage entities affected by config entry options.""" deconz_ids = [] + # Allow CLIP sensors + if self.option_allow_clip_sensor: - self.async_add_device_callback(ResourceGroup.SENSOR.value) + async_dispatcher_send(self.hass, self.signal_reload_clip_sensors) else: deconz_ids += [ @@ -220,6 +214,8 @@ class DeconzGateway: if sensor.type.startswith("CLIP") ] + # Allow Groups + if self.option_allow_deconz_groups: if not self._option_allow_deconz_groups: async_dispatcher_send(self.hass, self.signal_reload_groups) @@ -228,6 +224,18 @@ class DeconzGateway: self._option_allow_deconz_groups = self.option_allow_deconz_groups + # Allow adding new devices + + option_allow_new_devices = self.config_entry.options.get( + CONF_ALLOW_NEW_DEVICES, DEFAULT_ALLOW_NEW_DEVICES + ) + if option_allow_new_devices != self.option_allow_new_devices: + self.option_allow_new_devices = option_allow_new_devices + if option_allow_new_devices: + self.load_ignored_devices() + + # Remove entities based on above categories + entity_registry = er.async_get(self.hass) for entity_id, deconz_id in self.deconz_ids.items(): @@ -279,6 +287,7 @@ async def get_deconz_session( config[CONF_HOST], config[CONF_PORT], config[CONF_API_KEY], + legacy_add_device=False, ) try: async with async_timeout.timeout(10): diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 2b03bb0ddb4..53773369176 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -93,23 +93,15 @@ async def async_setup_entry( async_add_entities([DeconzLight(light, gateway)]) - config_entry.async_on_unload( - gateway.api.lights.lights.subscribe( - async_add_light, - EventType.ADDED, - ) + gateway.register_platform_add_device_callback( + async_add_light, + gateway.api.lights.lights, ) - for light_id in gateway.api.lights.lights: - async_add_light(EventType.ADDED, light_id) - config_entry.async_on_unload( - gateway.api.lights.fans.subscribe( - async_add_light, - EventType.ADDED, - ) + gateway.register_platform_add_device_callback( + async_add_light, + gateway.api.lights.fans, ) - for light_id in gateway.api.lights.fans: - async_add_light(EventType.ADDED, light_id) @callback def async_add_group(_: EventType, group_id: str) -> None: diff --git a/homeassistant/components/deconz/lock.py b/homeassistant/components/deconz/lock.py index 51c2d4fc0fc..78ccae30441 100644 --- a/homeassistant/components/deconz/lock.py +++ b/homeassistant/components/deconz/lock.py @@ -4,13 +4,13 @@ from __future__ import annotations from typing import Any +from pydeconz.models.event import EventType 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 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 @@ -27,62 +27,27 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_lock_from_light(lights: list[Lock] | None = None) -> None: + def async_add_lock_from_light(_: EventType, lock_id: str) -> None: """Add lock from deCONZ.""" - entities = [] + lock = gateway.api.lights.locks[lock_id] + async_add_entities([DeconzLock(lock, gateway)]) - if lights is None: - lights = list(gateway.api.lights.locks.values()) - - for light in lights: - - if ( - isinstance(light, Lock) - and light.unique_id not in gateway.entities[DOMAIN] - ): - entities.append(DeconzLock(light, gateway)) - - if entities: - async_add_entities(entities) - - config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_light, - async_add_lock_from_light, - ) + gateway.register_platform_add_device_callback( + async_add_lock_from_light, + gateway.api.lights.locks, ) @callback - def async_add_lock_from_sensor(sensors: list[DoorLock] | None = None) -> None: + def async_add_lock_from_sensor(_: EventType, lock_id: str) -> None: """Add lock from deCONZ.""" - entities = [] + lock = gateway.api.sensors.door_lock[lock_id] + async_add_entities([DeconzLock(lock, gateway)]) - if sensors is None: - sensors = list(gateway.api.sensors.door_lock.values()) - - for sensor in sensors: - - if ( - isinstance(sensor, DoorLock) - and sensor.unique_id not in gateway.entities[DOMAIN] - ): - entities.append(DeconzLock(sensor, gateway)) - - if entities: - async_add_entities(entities) - - config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_sensor, - async_add_lock_from_sensor, - ) + gateway.register_platform_add_device_callback( + async_add_lock_from_sensor, + gateway.api.sensors.door_lock, ) - async_add_lock_from_light() - async_add_lock_from_sensor() - class DeconzLock(DeconzDevice, LockEntity): """Representation of a deCONZ lock.""" diff --git a/homeassistant/components/deconz/logbook.py b/homeassistant/components/deconz/logbook.py index 3dedeb4bfac..07dc7cb0124 100644 --- a/homeassistant/components/deconz/logbook.py +++ b/homeassistant/components/deconz/logbook.py @@ -3,6 +3,10 @@ from __future__ import annotations from collections.abc import Callable +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) from homeassistant.const import ATTR_DEVICE_ID, CONF_EVENT from homeassistant.core import Event, HomeAssistant, callback import homeassistant.helpers.device_registry as dr @@ -135,8 +139,8 @@ def async_describe_events( data = event.data[CONF_EVENT] return { - "name": f"{deconz_alarm_event.device.name}", - "message": f"fired event '{data}'.", + LOGBOOK_ENTRY_NAME: f"{deconz_alarm_event.device.name}", + LOGBOOK_ENTRY_MESSAGE: f"fired event '{data}'", } @callback @@ -157,27 +161,27 @@ def async_describe_events( # Unknown event if not data: return { - "name": f"{deconz_event.device.name}", - "message": "fired an unknown event.", + LOGBOOK_ENTRY_NAME: f"{deconz_event.device.name}", + LOGBOOK_ENTRY_MESSAGE: "fired an unknown event", } # No device event match if not action: return { - "name": f"{deconz_event.device.name}", - "message": f"fired event '{data}'.", + LOGBOOK_ENTRY_NAME: f"{deconz_event.device.name}", + LOGBOOK_ENTRY_MESSAGE: f"fired event '{data}'", } # Gesture event if not interface: return { - "name": f"{deconz_event.device.name}", - "message": f"fired event '{ACTIONS[action]}'.", + LOGBOOK_ENTRY_NAME: f"{deconz_event.device.name}", + LOGBOOK_ENTRY_MESSAGE: f"fired event '{ACTIONS[action]}'", } return { - "name": f"{deconz_event.device.name}", - "message": f"'{ACTIONS[action]}' event for '{INTERFACES[interface]}' was fired.", + LOGBOOK_ENTRY_NAME: f"{deconz_event.device.name}", + LOGBOOK_ENTRY_MESSAGE: f"'{ACTIONS[action]}' event for '{INTERFACES[interface]}' was fired", } async_describe_event( diff --git a/homeassistant/components/deconz/number.py b/homeassistant/components/deconz/number.py index 12ff768cad2..a7bb014d76a 100644 --- a/homeassistant/components/deconz/number.py +++ b/homeassistant/components/deconz/number.py @@ -5,6 +5,7 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass +from pydeconz.models.event import EventType from pydeconz.models.sensor.presence import PRESENCE_DELAY, Presence from homeassistant.components.number import ( @@ -14,7 +15,6 @@ from homeassistant.components.number 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 @@ -62,47 +62,22 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - 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"): + def async_add_sensor(_: EventType, sensor_id: str) -> None: + """Add sensor from deCONZ.""" + sensor = gateway.api.sensors.presence[sensor_id] + if sensor.type.startswith("CLIP"): + return + for description in ENTITY_DESCRIPTIONS.get(type(sensor), []): + if ( + not hasattr(sensor, description.key) + or description.value_fn(sensor) is None + ): continue + async_add_entities([DeconzNumber(sensor, gateway, description)]) - known_entities = set(gateway.entities[DOMAIN]) - for description in ENTITY_DESCRIPTIONS.get(type(sensor), []): - - if ( - not hasattr(sensor, description.key) - or description.value_fn(sensor) is None - ): - continue - - new_entity = DeconzNumber(sensor, gateway, description) - if new_entity.unique_id not in known_entities: - entities.append(new_entity) - - if entities: - async_add_entities(entities) - - config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_sensor, - async_add_sensor, - ) - ) - - async_add_sensor( - [ - gateway.api.sensors.presence[key] - for key in sorted(gateway.api.sensors.presence, key=int) - ] + gateway.register_platform_add_device_callback( + async_add_sensor, + gateway.api.sensors.presence, ) diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py index 28448da4f75..dfbb6ae828b 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -30,16 +30,11 @@ async def async_setup_entry( scene = gateway.api.scenes[scene_id] async_add_entities([DeconzScene(scene, gateway)]) - config_entry.async_on_unload( - gateway.api.scenes.subscribe( - async_add_scene, - EventType.ADDED, - ) + gateway.register_platform_add_device_callback( + async_add_scene, + gateway.api.scenes, ) - for scene_id in gateway.api.scenes: - async_add_scene(EventType.ADDED, scene_id) - class DeconzScene(DeconzSceneMixin, Scene): """Representation of a deCONZ scene.""" diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index d3a20fad522..2aff2b12448 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from datetime import datetime from pydeconz.interfaces.sensors import SensorResources +from pydeconz.models.event import EventType from pydeconz.models.sensor.air_quality import AirQuality from pydeconz.models.sensor.consumption import Consumption from pydeconz.models.sensor.daylight import Daylight @@ -38,10 +39,7 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType @@ -160,6 +158,7 @@ ENTITY_DESCRIPTIONS = { else None, update_key="lightlevel", device_class=SensorDeviceClass.ILLUMINANCE, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=LIGHT_LUX, ) ], @@ -243,61 +242,55 @@ async def async_setup_entry( gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() - battery_handler = DeconzBatteryHandler(gateway) - @callback - def async_add_sensor(sensors: list[SensorResources] | None = None) -> None: - """Add sensors from deCONZ. - - Create DeconzBattery if sensor has a battery attribute. - Create DeconzSensor if not a battery, switch or thermostat and not a binary sensor. - """ + def async_add_sensor(_: EventType, sensor_id: str) -> None: + """Add sensor from deCONZ.""" + sensor = gateway.api.sensors[sensor_id] entities: list[DeconzSensor] = [] - if sensors is None: - sensors = gateway.api.sensors.values() + if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): + return - for sensor in sensors: + if sensor.battery is None and not sensor.type.startswith("CLIP"): + DeconzBatteryTracker(sensor_id, gateway, async_add_entities) - if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): + known_entities = set(gateway.entities[DOMAIN]) + + for description in ( + ENTITY_DESCRIPTIONS.get(type(sensor), []) + SENSOR_DESCRIPTIONS + ): + if ( + not hasattr(sensor, description.key) + or description.value_fn(sensor) is None + ): continue - if sensor.battery is None: - battery_handler.create_tracker(sensor) + entity = DeconzSensor(sensor, gateway, description) + if entity.unique_id not in known_entities: + entities.append(entity) - known_entities = set(gateway.entities[DOMAIN]) - for description in ( - ENTITY_DESCRIPTIONS.get(type(sensor), []) + SENSOR_DESCRIPTIONS - ): + async_add_entities(entities) - if ( - not hasattr(sensor, description.key) - or description.value_fn(sensor) is None - ): - continue + gateway.register_platform_add_device_callback( + async_add_sensor, + gateway.api.sensors, + ) - new_entity = DeconzSensor(sensor, gateway, description) - if new_entity.unique_id not in known_entities: - entities.append(new_entity) - - if description.key == "battery": - battery_handler.remove_tracker(sensor) - - if entities: - async_add_entities(entities) + @callback + def async_reload_clip_sensors() -> None: + """Load clip sensor sensors from deCONZ.""" + for sensor_id, sensor in gateway.api.sensors.items(): + if sensor.type.startswith("CLIP"): + async_add_sensor(EventType.ADDED, sensor_id) config_entry.async_on_unload( async_dispatcher_connect( hass, - gateway.signal_new_sensor, - async_add_sensor, + gateway.signal_reload_clip_sensors, + async_reload_clip_sensors, ) ) - async_add_sensor( - [gateway.api.sensors[key] for key in sorted(gateway.api.sensors, key=int)] - ) - class DeconzSensor(DeconzDevice, SensorEntity): """Representation of a deCONZ sensor.""" @@ -397,52 +390,27 @@ class DeconzSensor(DeconzDevice, SensorEntity): return attr -class DeconzSensorStateTracker: - """Track sensors without a battery state and signal when battery state exist.""" +class DeconzBatteryTracker: + """Track sensors without a battery state and add entity when battery state exist.""" - def __init__(self, sensor: SensorResources, gateway: DeconzGateway) -> None: + def __init__( + self, + sensor_id: str, + gateway: DeconzGateway, + async_add_entities: AddEntitiesCallback, + ) -> None: """Set up tracker.""" - self.sensor = sensor + self.sensor = gateway.api.sensors[sensor_id] self.gateway = gateway - sensor.register_callback(self.async_update_callback) - - @callback - def close(self) -> None: - """Clean up tracker.""" - self.sensor.remove_callback(self.async_update_callback) + self.async_add_entities = async_add_entities + self.unsub = self.sensor.subscribe(self.async_update_callback) @callback def async_update_callback(self) -> None: - """Sensor state updated.""" + """Update the device's state.""" if "battery" in self.sensor.changed_keys: - async_dispatcher_send( - self.gateway.hass, - self.gateway.signal_new_sensor, - [self.sensor], - ) - - -class DeconzBatteryHandler: - """Creates and stores trackers for sensors without a battery state.""" - - def __init__(self, gateway: DeconzGateway) -> None: - """Set up battery handler.""" - self.gateway = gateway - self._trackers: set[DeconzSensorStateTracker] = set() - - @callback - def create_tracker(self, sensor: SensorResources) -> None: - """Create new tracker for battery state.""" - for tracker in self._trackers: - if sensor == tracker.sensor: - return - self._trackers.add(DeconzSensorStateTracker(sensor, self.gateway)) - - @callback - def remove_tracker(self, sensor: SensorResources) -> None: - """Remove tracker of battery state.""" - for tracker in self._trackers: - if sensor == tracker.sensor: - tracker.close() - self._trackers.remove(tracker) - break + self.unsub() + known_entities = set(self.gateway.entities[DOMAIN]) + entity = DeconzSensor(self.sensor, self.gateway, SENSOR_DESCRIPTIONS[0]) + if entity.unique_id not in known_entities: + self.async_add_entities([entity]) diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index 3d12a293c39..e4399e53524 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -144,11 +144,9 @@ async def async_refresh_devices_service(gateway: DeconzGateway) -> None: """Refresh available devices from deCONZ.""" gateway.ignore_state_updates = True await gateway.api.refresh_state() + gateway.load_ignored_devices() gateway.ignore_state_updates = False - for resource_type in gateway.deconz_resource_type_to_signal_new_device: - gateway.async_add_device_callback(resource_type, force=True) - async def async_remove_orphaned_entries_service(gateway: DeconzGateway) -> None: """Remove orphaned deCONZ entries from device and entity registries.""" diff --git a/homeassistant/components/deconz/siren.py b/homeassistant/components/deconz/siren.py index c279afae696..8427b6ce75d 100644 --- a/homeassistant/components/deconz/siren.py +++ b/homeassistant/components/deconz/siren.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Any +from pydeconz.models.event import EventType from pydeconz.models.light.siren import Siren from homeassistant.components.siren import ( @@ -13,7 +14,6 @@ from homeassistant.components.siren 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_platform import AddEntitiesCallback from .deconz_device import DeconzDevice @@ -30,34 +30,16 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_siren(lights: list[Siren] | None = None) -> None: + def async_add_siren(_: EventType, siren_id: str) -> None: """Add siren from deCONZ.""" - entities = [] + siren = gateway.api.lights.sirens[siren_id] + async_add_entities([DeconzSiren(siren, gateway)]) - if lights is None: - lights = list(gateway.api.lights.sirens.values()) - - for light in lights: - - if ( - isinstance(light, Siren) - and light.unique_id not in gateway.entities[DOMAIN] - ): - entities.append(DeconzSiren(light, gateway)) - - if entities: - async_add_entities(entities) - - config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_light, - async_add_siren, - ) + gateway.register_platform_add_device_callback( + async_add_siren, + gateway.api.lights.sirens, ) - async_add_siren() - class DeconzSiren(DeconzDevice, SirenEntity): """Representation of a deCONZ siren.""" diff --git a/homeassistant/components/deconz/switch.py b/homeassistant/components/deconz/switch.py index f1ff95183dd..d54ff1f36ba 100644 --- a/homeassistant/components/deconz/switch.py +++ b/homeassistant/components/deconz/switch.py @@ -4,12 +4,12 @@ from __future__ import annotations from typing import Any +from pydeconz.models.event import EventType 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.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import POWER_PLUGS @@ -30,34 +30,18 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_switch(lights: list[Light] | None = None) -> None: + def async_add_switch(_: EventType, switch_id: str) -> None: """Add switch from deCONZ.""" - entities = [] + switch = gateway.api.lights.lights[switch_id] + if switch.type not in POWER_PLUGS: + return + async_add_entities([DeconzPowerPlug(switch, gateway)]) - if lights is None: - lights = list(gateway.api.lights.lights.values()) - - for light in lights: - - if ( - light.type in POWER_PLUGS - and light.unique_id not in gateway.entities[DOMAIN] - ): - entities.append(DeconzPowerPlug(light, gateway)) - - if entities: - async_add_entities(entities) - - config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_light, - async_add_switch, - ) + gateway.register_platform_add_device_callback( + async_add_switch, + gateway.api.lights.lights, ) - async_add_switch() - class DeconzPowerPlug(DeconzDevice, SwitchEntity): """Representation of a deCONZ power plug.""" diff --git a/homeassistant/components/deconz/translations/bg.json b/homeassistant/components/deconz/translations/bg.json index 3fe700efd3e..f8b1e351fb0 100644 --- a/homeassistant/components/deconz/translations/bg.json +++ b/homeassistant/components/deconz/translations/bg.json @@ -4,10 +4,10 @@ "already_configured": "\u041c\u043e\u0441\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d", "already_in_progress": "\u0412 \u043c\u043e\u043c\u0435\u043d\u0442\u0430 \u0442\u0435\u0447\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f.", "no_bridges": "\u041d\u0435 \u0441\u0430 \u043e\u0442\u043a\u0440\u0438\u0442\u0438 \u043c\u043e\u0441\u0442\u043e\u0432\u0435 deCONZ", - "not_deconz_bridge": "\u041d\u0435 \u0435 deCONZ \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f", "updated_instance": "\u041e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 deCONZ \u0441 \u043d\u043e\u0432 \u0430\u0434\u0440\u0435\u0441" }, "error": { + "linking_not_possible": "\u041d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0436\u0435 \u0441 \u0448\u043b\u044e\u0437\u0430", "no_key": "\u041d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0441\u0435 \u043f\u043e\u043b\u0443\u0447\u0438 API \u043a\u043b\u044e\u0447" }, "flow_title": "deCONZ Zigbee \u0448\u043b\u044e\u0437 ({host})", diff --git a/homeassistant/components/deconz/translations/ca.json b/homeassistant/components/deconz/translations/ca.json index 2f839b209eb..f4659557503 100644 --- a/homeassistant/components/deconz/translations/ca.json +++ b/homeassistant/components/deconz/translations/ca.json @@ -5,10 +5,10 @@ "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "no_bridges": "No s'han descobert enlla\u00e7os amb deCONZ", "no_hardware_available": "No hi ha cap maquinari r\u00e0dio connectat a deCONZ", - "not_deconz_bridge": "No \u00e9s un enlla\u00e7 deCONZ", "updated_instance": "S'ha actualitzat la inst\u00e0ncia de deCONZ amb una nova adre\u00e7a" }, "error": { + "linking_not_possible": "No s'ha pogut enlla\u00e7ar amb la passarel\u00b7la", "no_key": "No s'ha pogut obtenir una clau API" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/cs.json b/homeassistant/components/deconz/translations/cs.json index 323b29ddac8..8e6c6a71a44 100644 --- a/homeassistant/components/deconz/translations/cs.json +++ b/homeassistant/components/deconz/translations/cs.json @@ -5,7 +5,6 @@ "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", "no_bridges": "\u017d\u00e1dn\u00e9 deCONZ p\u0159emost\u011bn\u00ed nebyly nalezeny", "no_hardware_available": "K deCONZ nen\u00ed p\u0159ipojeno \u017e\u00e1dn\u00e9 r\u00e1diov\u00e9 za\u0159\u00edzen\u00ed", - "not_deconz_bridge": "Nejedn\u00e1 se o deCONZ p\u0159emost\u011bn\u00ed", "updated_instance": "Instance deCONZ aktualizov\u00e1na s nov\u00fdm hostitelem" }, "error": { diff --git a/homeassistant/components/deconz/translations/da.json b/homeassistant/components/deconz/translations/da.json index 6f63540c924..0091e9d0349 100644 --- a/homeassistant/components/deconz/translations/da.json +++ b/homeassistant/components/deconz/translations/da.json @@ -4,7 +4,6 @@ "already_configured": "Bridge er allerede konfigureret", "already_in_progress": "Konfigurationsflow for bro er allerede i gang.", "no_bridges": "Ingen deConz-bro fundet", - "not_deconz_bridge": "Ikke en deCONZ-bro", "updated_instance": "Opdaterede deCONZ-instans med ny v\u00e6rtadresse" }, "error": { diff --git a/homeassistant/components/deconz/translations/de.json b/homeassistant/components/deconz/translations/de.json index a24dbb44ad4..29b322466d5 100644 --- a/homeassistant/components/deconz/translations/de.json +++ b/homeassistant/components/deconz/translations/de.json @@ -5,10 +5,10 @@ "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "no_bridges": "Keine deCONZ-Bridges entdeckt", "no_hardware_available": "Keine Funkhardware an deCONZ angeschlossen", - "not_deconz_bridge": "Keine deCONZ Bridge entdeckt", "updated_instance": "deCONZ-Instanz mit neuer Host-Adresse aktualisiert" }, "error": { + "linking_not_possible": "Es konnte keine Verbindung mit dem Gateway hergestellt werden", "no_key": "Es konnte kein API-Schl\u00fcssel abgerufen werden" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/el.json b/homeassistant/components/deconz/translations/el.json index 273d939cc34..3e7d3b166c1 100644 --- a/homeassistant/components/deconz/translations/el.json +++ b/homeassistant/components/deconz/translations/el.json @@ -5,10 +5,10 @@ "already_in_progress": "\u03a4\u03bf \u03b4\u03b9\u03ac\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03b3\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", "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 deCONZ", "no_hardware_available": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03b1\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03bf \u03c5\u03bb\u03b9\u03ba\u03cc \u03c3\u03c4\u03bf deCONZ", - "not_deconz_bridge": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 deCONZ", "updated_instance": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03c9\u03bc\u03ad\u03bd\u03b7 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 deCONZ \u03bc\u03b5 \u03bd\u03ad\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae" }, "error": { + "linking_not_possible": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7", "no_key": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03bb\u03ae\u03c8\u03b7 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd API" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/en.json b/homeassistant/components/deconz/translations/en.json index 16034e12414..af73a3b43be 100644 --- a/homeassistant/components/deconz/translations/en.json +++ b/homeassistant/components/deconz/translations/en.json @@ -5,7 +5,6 @@ "already_in_progress": "Configuration flow is 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" }, "error": { diff --git a/homeassistant/components/deconz/translations/es-419.json b/homeassistant/components/deconz/translations/es-419.json index ceb0ca39d2c..a8a8965daf5 100644 --- a/homeassistant/components/deconz/translations/es-419.json +++ b/homeassistant/components/deconz/translations/es-419.json @@ -5,7 +5,6 @@ "already_in_progress": "El flujo de configuraci\u00f3n para el puente ya est\u00e1 en progreso.", "no_bridges": "No se descubrieron puentes deCONZ", "no_hardware_available": "No hay hardware de radio conectado a deCONZ", - "not_deconz_bridge": "No es un puente deCONZ", "updated_instance": "Instancia deCONZ actualizada con nueva direcci\u00f3n de host" }, "error": { diff --git a/homeassistant/components/deconz/translations/es.json b/homeassistant/components/deconz/translations/es.json index 6822b71d564..2921dc2f2bf 100644 --- a/homeassistant/components/deconz/translations/es.json +++ b/homeassistant/components/deconz/translations/es.json @@ -2,20 +2,20 @@ "config": { "abort": { "already_configured": "La pasarela ya est\u00e1 configurada", - "already_in_progress": "El flujo de configuraci\u00f3n para la pasarela ya est\u00e1 en marcha.", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "no_bridges": "No se han descubierto pasarelas deCONZ", "no_hardware_available": "No hay hardware de radio conectado a deCONZ", - "not_deconz_bridge": "No es una pasarela deCONZ", "updated_instance": "Instancia deCONZ actualizada con nueva direcci\u00f3n de host" }, "error": { + "linking_not_possible": "No se ha podido enlazar con la pasarela", "no_key": "No se pudo obtener una clave API" }, "flow_title": "{host}", "step": { "hassio_confirm": { "description": "\u00bfQuieres configurar Home Assistant para que se conecte al gateway de deCONZ proporcionado por el add-on {addon} de Supervisor?", - "title": "Add-on deCONZ Zigbee v\u00eda Supervisor" + "title": "Pasarela de enlace de CONZ Zigbee v\u00eda complemento de Home Assistant" }, "link": { "description": "Desbloquea tu gateway de deCONZ para registrarte con Home Assistant.\n\n1. Dir\u00edgete a deCONZ Settings -> Gateway -> Advanced\n2. Pulsa el bot\u00f3n \"Authenticate app\"", diff --git a/homeassistant/components/deconz/translations/et.json b/homeassistant/components/deconz/translations/et.json index 6be6a7fbdc6..0fd28a0889d 100644 --- a/homeassistant/components/deconz/translations/et.json +++ b/homeassistant/components/deconz/translations/et.json @@ -5,10 +5,10 @@ "already_in_progress": "Seadistamine on juba k\u00e4imas", "no_bridges": "DeCONZ l\u00fc\u00fcse ei leitud", "no_hardware_available": "DeCONZi raadio riistvara puudub", - "not_deconz_bridge": "See pole deCONZ-i sild", "updated_instance": "DeCONZ-i eksemplarile omistati uus hostiaadress" }, "error": { + "linking_not_possible": "\u00dchendus l\u00fc\u00fcsiga nurjus", "no_key": "API v\u00f5tit ei leitud" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/fr.json b/homeassistant/components/deconz/translations/fr.json index 39a32fc2434..711322df9dd 100644 --- a/homeassistant/components/deconz/translations/fr.json +++ b/homeassistant/components/deconz/translations/fr.json @@ -5,10 +5,10 @@ "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "no_bridges": "Aucun pont deCONZ n'a \u00e9t\u00e9 d\u00e9couvert", "no_hardware_available": "Aucun mat\u00e9riel radio connect\u00e9 \u00e0 deCONZ", - "not_deconz_bridge": "Pas un pont deCONZ", "updated_instance": "Instance deCONZ mise \u00e0 jour avec la nouvelle adresse d'h\u00f4te" }, "error": { + "linking_not_possible": "Impossible d'\u00e9tablir une liaison avec la passerelle", "no_key": "Impossible d'obtenir une cl\u00e9 d'API" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/hu.json b/homeassistant/components/deconz/translations/hu.json index 47bb343247c..7bf6d021634 100644 --- a/homeassistant/components/deconz/translations/hu.json +++ b/homeassistant/components/deconz/translations/hu.json @@ -5,10 +5,10 @@ "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", "updated_instance": "A deCONZ-p\u00e9ld\u00e1ny \u00faj \u00e1llom\u00e1sc\u00edmmel friss\u00edtve" }, "error": { + "linking_not_possible": "Nem tudott kapcsol\u00f3dni az \u00e1tj\u00e1r\u00f3hoz", "no_key": "API kulcs lek\u00e9r\u00e9se nem siker\u00fclt" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/id.json b/homeassistant/components/deconz/translations/id.json index f63261e6e87..b6d7329758b 100644 --- a/homeassistant/components/deconz/translations/id.json +++ b/homeassistant/components/deconz/translations/id.json @@ -5,10 +5,10 @@ "already_in_progress": "Alur konfigurasi sedang berlangsung", "no_bridges": "deCONZ bridge tidak ditemukan", "no_hardware_available": "Tidak ada perangkat keras radio yang terhubung ke deCONZ", - "not_deconz_bridge": "Bukan bridge deCONZ", "updated_instance": "Instans deCONZ yang diperbarui dengan alamat host baru" }, "error": { + "linking_not_possible": "Tidak dapat terhubung dengan gateway", "no_key": "Tidak bisa mendapatkan kunci API" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/it.json b/homeassistant/components/deconz/translations/it.json index 2c3e42adcc8..638b4db4222 100644 --- a/homeassistant/components/deconz/translations/it.json +++ b/homeassistant/components/deconz/translations/it.json @@ -5,10 +5,10 @@ "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "no_bridges": "Nessun bridge deCONZ rilevato", "no_hardware_available": "Nessun hardware radio collegato a deCONZ", - "not_deconz_bridge": "Non \u00e8 un bridge deCONZ", "updated_instance": "Istanza deCONZ aggiornata con nuovo indirizzo host" }, "error": { + "linking_not_possible": "Impossibile collegarsi al gateway", "no_key": "Impossibile ottenere una chiave API" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/ja.json b/homeassistant/components/deconz/translations/ja.json index a074be8d8cd..2962f3d4531 100644 --- a/homeassistant/components/deconz/translations/ja.json +++ b/homeassistant/components/deconz/translations/ja.json @@ -5,10 +5,10 @@ "already_in_progress": "\u69cb\u6210\u30d5\u30ed\u30fc\u306f\u3059\u3067\u306b\u9032\u884c\u4e2d\u3067\u3059", "no_bridges": "deCONZ\u30d6\u30ea\u30c3\u30b8\u306f\u691c\u51fa\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f", "no_hardware_available": "deCONZ\u306b\u63a5\u7d9a\u3055\u308c\u3066\u3044\u308b\u7121\u7dda\u30cf\u30fc\u30c9\u30a6\u30a7\u30a2\u304c\u3042\u308a\u307e\u305b\u3093", - "not_deconz_bridge": "deCONZ bridge\u3067\u306f\u3042\u308a\u307e\u305b\u3093", "updated_instance": "\u65b0\u3057\u3044\u30db\u30b9\u30c8\u30a2\u30c9\u30ec\u30b9\u3067deCONZ\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f" }, "error": { + "linking_not_possible": "\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4\u3068\u30ea\u30f3\u30af\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f", "no_key": "API\u30ad\u30fc\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/ko.json b/homeassistant/components/deconz/translations/ko.json index 5158d557106..e12bab3b64a 100644 --- a/homeassistant/components/deconz/translations/ko.json +++ b/homeassistant/components/deconz/translations/ko.json @@ -5,10 +5,10 @@ "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "no_bridges": "\ubc1c\uacac\ub41c deCONZ \ube0c\ub9ac\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", "no_hardware_available": "deCONZ\uc5d0 \uc5f0\uacb0\ub41c \ubb34\uc120 \ud558\ub4dc\uc6e8\uc5b4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", - "not_deconz_bridge": "deCONZ \ube0c\ub9ac\uc9c0\uac00 \uc544\ub2d9\ub2c8\ub2e4", "updated_instance": "deCONZ \uc778\uc2a4\ud134\uc2a4\ub97c \uc0c8\ub85c\uc6b4 \ud638\uc2a4\ud2b8 \uc8fc\uc18c\ub85c \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4" }, "error": { + "linking_not_possible": "\uac8c\uc774\ud2b8\uc6e8\uc774\uc640 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "no_key": "API \ud0a4\ub97c \uac00\uc838\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "flow_title": "deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774 ({host})", diff --git a/homeassistant/components/deconz/translations/lb.json b/homeassistant/components/deconz/translations/lb.json index 84a535c8ebc..b5bb383d29a 100644 --- a/homeassistant/components/deconz/translations/lb.json +++ b/homeassistant/components/deconz/translations/lb.json @@ -5,7 +5,6 @@ "already_in_progress": "Konfiguratioun's Oflaf ass schonn am gaang.", "no_bridges": "Keng dECONZ bridges fonnt", "no_hardware_available": "Keng Radio Hardware verbonne mat deCONZ", - "not_deconz_bridge": "Keng deCONZ Bridge", "updated_instance": "deCONZ Instanz gouf mat der neier Adress vum Apparat ge\u00e4nnert" }, "error": { diff --git a/homeassistant/components/deconz/translations/nl.json b/homeassistant/components/deconz/translations/nl.json index 9c83fb806e8..474973b9594 100644 --- a/homeassistant/components/deconz/translations/nl.json +++ b/homeassistant/components/deconz/translations/nl.json @@ -2,13 +2,13 @@ "config": { "abort": { "already_configured": "Bridge is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "no_bridges": "Geen deCONZ bridges ontdekt", "no_hardware_available": "Geen radiohardware aangesloten op deCONZ", - "not_deconz_bridge": "Geen deCONZ bridge", "updated_instance": "DeCONZ-instantie bijgewerkt met nieuw host-adres" }, "error": { + "linking_not_possible": "Kan geen koppeling maken met de gateway", "no_key": "Kon geen API-sleutel ophalen" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/no.json b/homeassistant/components/deconz/translations/no.json index 06c03b8b585..22a294d3242 100644 --- a/homeassistant/components/deconz/translations/no.json +++ b/homeassistant/components/deconz/translations/no.json @@ -5,10 +5,10 @@ "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "no_bridges": "Ingen deCONZ broer oppdaget", "no_hardware_available": "Ingen radiomaskinvare koblet til deCONZ", - "not_deconz_bridge": "Ikke en deCONZ bro", "updated_instance": "Oppdatert deCONZ forekomst med ny vertsadresse" }, "error": { + "linking_not_possible": "Kunne ikke koble til gatewayen", "no_key": "Kunne ikke f\u00e5 en API-n\u00f8kkel" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/pl.json b/homeassistant/components/deconz/translations/pl.json index 945830a5734..7894494336e 100644 --- a/homeassistant/components/deconz/translations/pl.json +++ b/homeassistant/components/deconz/translations/pl.json @@ -5,10 +5,10 @@ "already_in_progress": "Konfiguracja jest ju\u017c w toku", "no_bridges": "Nie odkryto mostk\u00f3w deCONZ", "no_hardware_available": "Nie wykryto komponentu radiowego pod\u0142\u0105czonego do deCONZ", - "not_deconz_bridge": "To nie jest mostek deCONZ", "updated_instance": "Zaktualizowano instancj\u0119 deCONZ o nowy adres hosta" }, "error": { + "linking_not_possible": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z bramk\u0105", "no_key": "Nie mo\u017cna uzyska\u0107 klucza API" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/pt-BR.json b/homeassistant/components/deconz/translations/pt-BR.json index 03004cae304..785ada6b4c3 100644 --- a/homeassistant/components/deconz/translations/pt-BR.json +++ b/homeassistant/components/deconz/translations/pt-BR.json @@ -5,10 +5,10 @@ "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "no_bridges": "N\u00e3o h\u00e1 pontes de deCONZ descobertas", "no_hardware_available": "Nenhum hardware de r\u00e1dio conectado ao deCONZ", - "not_deconz_bridge": "N\u00e3o \u00e9 uma ponte deCONZ", "updated_instance": "Atualiza\u00e7\u00e3o da inst\u00e2ncia deCONZ com novo endere\u00e7o de host" }, "error": { + "linking_not_possible": "N\u00e3o foi poss\u00edvel se conectar com o gateway", "no_key": "N\u00e3o foi poss\u00edvel obter uma chave de API" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/pt.json b/homeassistant/components/deconz/translations/pt.json index a64e37ed15b..94efb5c68ca 100644 --- a/homeassistant/components/deconz/translations/pt.json +++ b/homeassistant/components/deconz/translations/pt.json @@ -3,8 +3,7 @@ "abort": { "already_configured": "Bridge j\u00e1 est\u00e1 configurada", "already_in_progress": "O processo de configura\u00e7\u00e3o j\u00e1 est\u00e1 a decorrer", - "no_bridges": "Nenhum hub deCONZ descoberto", - "not_deconz_bridge": "N\u00e3o \u00e9 uma bridge deCONZ" + "no_bridges": "Nenhum hub deCONZ descoberto" }, "error": { "no_key": "N\u00e3o foi poss\u00edvel obter uma Chave da API" diff --git a/homeassistant/components/deconz/translations/ru.json b/homeassistant/components/deconz/translations/ru.json index 412c12198f3..91c44036331 100644 --- a/homeassistant/components/deconz/translations/ru.json +++ b/homeassistant/components/deconz/translations/ru.json @@ -5,10 +5,10 @@ "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.", "no_bridges": "\u0428\u043b\u044e\u0437\u044b deCONZ \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.", "no_hardware_available": "\u041a deCONZ \u043d\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0440\u0430\u0434\u0438\u043e\u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u0435.", - "not_deconz_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 deCONZ.", "updated_instance": "\u0410\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d." }, "error": { + "linking_not_possible": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0448\u043b\u044e\u0437\u0443.", "no_key": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043a\u043b\u044e\u0447 API." }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/sl.json b/homeassistant/components/deconz/translations/sl.json index 9e8ed42c07e..cfb52ca7af5 100644 --- a/homeassistant/components/deconz/translations/sl.json +++ b/homeassistant/components/deconz/translations/sl.json @@ -4,7 +4,6 @@ "already_configured": "Most je \u017ee nastavljen", "already_in_progress": "Konfiguracijski tok za most je \u017ee v teku.", "no_bridges": "Ni odkritih mostov deCONZ", - "not_deconz_bridge": "Ni deCONZ most", "updated_instance": "Posodobljen deCONZ z novim naslovom gostitelja" }, "error": { diff --git a/homeassistant/components/deconz/translations/sv.json b/homeassistant/components/deconz/translations/sv.json index d7ec321ff36..fd0968a941a 100644 --- a/homeassistant/components/deconz/translations/sv.json +++ b/homeassistant/components/deconz/translations/sv.json @@ -4,10 +4,10 @@ "already_configured": "Bryggan \u00e4r redan konfigurerad", "already_in_progress": "Konfigurations fl\u00f6det f\u00f6r bryggan p\u00e5g\u00e5r redan.", "no_bridges": "Inga deCONZ-bryggor uppt\u00e4cktes", - "not_deconz_bridge": "Inte en deCONZ-brygga", "updated_instance": "Uppdaterad deCONZ-instans med ny v\u00e4rdadress" }, "error": { + "linking_not_possible": "Det gick inte att l\u00e4nka till gatewayen", "no_key": "Det gick inte att ta emot en API-nyckel" }, "flow_title": "deCONZ Zigbee gateway ({host})", diff --git a/homeassistant/components/deconz/translations/tr.json b/homeassistant/components/deconz/translations/tr.json index 021c4845989..8b57d284864 100644 --- a/homeassistant/components/deconz/translations/tr.json +++ b/homeassistant/components/deconz/translations/tr.json @@ -5,10 +5,10 @@ "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "no_bridges": "DeCONZ k\u00f6pr\u00fcs\u00fc bulunamad\u0131", "no_hardware_available": "deCONZ'a ba\u011fl\u0131 radyo donan\u0131m\u0131 yok", - "not_deconz_bridge": "deCONZ k\u00f6pr\u00fcs\u00fc de\u011fil", "updated_instance": "DeCONZ yeni ana bilgisayar adresiyle g\u00fcncelle\u015ftirildi" }, "error": { + "linking_not_possible": "A\u011f ge\u00e7idi ile ba\u011flant\u0131 kurulamad\u0131", "no_key": "API anahtar\u0131 al\u0131namad\u0131" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/uk.json b/homeassistant/components/deconz/translations/uk.json index 3b09a517385..5df5e6a6078 100644 --- a/homeassistant/components/deconz/translations/uk.json +++ b/homeassistant/components/deconz/translations/uk.json @@ -5,7 +5,6 @@ "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0442\u0440\u0438\u0432\u0430\u0454.", "no_bridges": "\u0428\u043b\u044e\u0437\u0438 deCONZ \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456.", "no_hardware_available": "\u0420\u0430\u0434\u0456\u043e\u043e\u0431\u043b\u0430\u0434\u043d\u0430\u043d\u043d\u044f \u043d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e deCONZ.", - "not_deconz_bridge": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0454 \u0448\u043b\u044e\u0437\u043e\u043c deCONZ.", "updated_instance": "\u0410\u0434\u0440\u0435\u0441\u0443 \u0445\u043e\u0441\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043e." }, "error": { diff --git a/homeassistant/components/deconz/translations/zh-Hant.json b/homeassistant/components/deconz/translations/zh-Hant.json index d6d4dfeba45..bd945ecc360 100644 --- a/homeassistant/components/deconz/translations/zh-Hant.json +++ b/homeassistant/components/deconz/translations/zh-Hant.json @@ -5,10 +5,10 @@ "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "no_bridges": "\u672a\u767c\u73fe\u5230 deCONZ Bridfe", "no_hardware_available": "deCONZ \u6c92\u6709\u4efb\u4f55\u7121\u7dda\u96fb\u88dd\u7f6e\u9023\u7dda", - "not_deconz_bridge": "\u975e deCONZ Bridge \u88dd\u7f6e", "updated_instance": "\u4f7f\u7528\u65b0\u4e3b\u6a5f\u7aef\u4f4d\u5740\u66f4\u65b0 deCONZ \u88dd\u7f6e" }, "error": { + "linking_not_possible": "\u7121\u6cd5\u8207\u8def\u7531\u5668\u9023\u7dda", "no_key": "\u7121\u6cd5\u53d6\u5f97 API key" }, "flow_title": "{host}", diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index b43881c39b9..9dbc031d476 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -1,13 +1,16 @@ """Support for Decora dimmers.""" from __future__ import annotations +from collections.abc import Callable import copy from functools import wraps import logging import time +from typing import TypeVar from bluepy.btle import BTLEException # pylint: disable=import-error import decora # pylint: disable=import-error +from typing_extensions import Concatenate, ParamSpec import voluptuous as vol from homeassistant import util @@ -23,6 +26,10 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +_DecoraLightT = TypeVar("_DecoraLightT", bound="DecoraLight") +_R = TypeVar("_R") +_P = ParamSpec("_P") + _LOGGER = logging.getLogger(__name__) @@ -50,11 +57,15 @@ PLATFORM_SCHEMA = vol.Schema( ) -def retry(method): +def retry( + method: Callable[Concatenate[_DecoraLightT, _P], _R] +) -> Callable[Concatenate[_DecoraLightT, _P], _R | None]: """Retry bluetooth commands.""" @wraps(method) - def wrapper_retry(device, *args, **kwargs): + def wrapper_retry( + device: _DecoraLightT, *args: _P.args, **kwargs: _P.kwargs + ) -> _R | None: """Try send command and retry on error.""" initial = time.monotonic() diff --git a/homeassistant/components/default_config/manifest.json b/homeassistant/components/default_config/manifest.json index 1ab827529c6..1742092cc70 100644 --- a/homeassistant/components/default_config/manifest.json +++ b/homeassistant/components/default_config/manifest.json @@ -3,6 +3,7 @@ "name": "Default Config", "documentation": "https://www.home-assistant.io/integrations/default_config", "dependencies": [ + "application_credentials", "automation", "cloud", "counter", diff --git a/homeassistant/components/deluge/config_flow.py b/homeassistant/components/deluge/config_flow.py index fc0dd5ff300..2f38d4d447d 100644 --- a/homeassistant/components/deluge/config_flow.py +++ b/homeassistant/components/deluge/config_flow.py @@ -1,7 +1,6 @@ """Config flow for the Deluge integration.""" from __future__ import annotations -import logging import socket from ssl import SSLError from typing import Any @@ -12,8 +11,6 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow from homeassistant.const import ( CONF_HOST, - CONF_MONITORED_VARIABLES, - CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SOURCE, @@ -30,8 +27,6 @@ from .const import ( DOMAIN, ) -_LOGGER = logging.getLogger(__name__) - class DelugeFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Deluge.""" @@ -41,11 +36,8 @@ class DelugeFlowHandler(ConfigFlow, domain=DOMAIN): ) -> FlowResult: """Handle a flow initiated by the user.""" errors = {} - title = None if user_input is not None: - if CONF_NAME in user_input: - title = user_input.pop(CONF_NAME) if (error := await self.validate_input(user_input)) is None: for entry in self._async_current_entries(): if ( @@ -60,7 +52,7 @@ class DelugeFlowHandler(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="reauth_successful") return self.async_abort(reason="already_configured") return self.async_create_entry( - title=title or DEFAULT_NAME, + title=DEFAULT_NAME, data=user_input, ) errors["base"] = error @@ -87,20 +79,6 @@ class DelugeFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a reauthorization flow request.""" return await self.async_step_user() - async def async_step_import(self, config: dict[str, Any]) -> FlowResult: - """Import a config entry from configuration.yaml.""" - if CONF_MONITORED_VARIABLES in config: - config.pop(CONF_MONITORED_VARIABLES) - config[CONF_WEB_PORT] = DEFAULT_WEB_PORT - - for entry in self._async_current_entries(): - if entry.data[CONF_HOST] == config[CONF_HOST]: - _LOGGER.warning( - "Deluge yaml config has been imported. Please remove it" - ) - return self.async_abort(reason="already_configured") - return await self.async_step_user(config) - async def validate_input(self, user_input: dict[str, Any]) -> str | None: """Handle common flow input validation.""" host = user_input[CONF_HOST] diff --git a/homeassistant/components/deluge/const.py b/homeassistant/components/deluge/const.py index 505c20e860f..704c024c41b 100644 --- a/homeassistant/components/deluge/const.py +++ b/homeassistant/components/deluge/const.py @@ -1,12 +1,16 @@ """Constants for the Deluge integration.""" import logging +from typing import Final CONF_WEB_PORT = "web_port" +CURRENT_STATUS = "current_status" +DATA_KEYS = ["upload_rate", "download_rate", "dht_upload_rate", "dht_download_rate"] DEFAULT_NAME = "Deluge" DEFAULT_RPC_PORT = 58846 DEFAULT_WEB_PORT = 8112 -DHT_UPLOAD = 1000 -DHT_DOWNLOAD = 1000 -DOMAIN = "deluge" +DOMAIN: Final = "deluge" +DOWNLOAD_SPEED = "download_speed" LOGGER = logging.getLogger(__package__) + +UPLOAD_SPEED = "upload_speed" diff --git a/homeassistant/components/deluge/coordinator.py b/homeassistant/components/deluge/coordinator.py index 0ac97e77674..89f9afc31ad 100644 --- a/homeassistant/components/deluge/coordinator.py +++ b/homeassistant/components/deluge/coordinator.py @@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import LOGGER +from .const import DATA_KEYS, LOGGER class DelugeDataUpdateCoordinator(DataUpdateCoordinator): @@ -38,16 +38,12 @@ class DelugeDataUpdateCoordinator(DataUpdateCoordinator): """Get the latest data from Deluge and updates the state.""" data = {} try: - data[Platform.SENSOR] = await self.hass.async_add_executor_job( + _data = await self.hass.async_add_executor_job( self.api.call, "core.get_session_status", - [ - "upload_rate", - "download_rate", - "dht_upload_rate", - "dht_download_rate", - ], + DATA_KEYS, ) + data[Platform.SENSOR] = {k.decode(): v for k, v in _data.items()} data[Platform.SWITCH] = await self.hass.async_add_executor_job( self.api.call, "core.get_torrents_status", {}, ["paused"] ) diff --git a/homeassistant/components/deluge/sensor.py b/homeassistant/components/deluge/sensor.py index 99e63e6ef17..8a8e8f64657 100644 --- a/homeassistant/components/deluge/sensor.py +++ b/homeassistant/components/deluge/sensor.py @@ -1,84 +1,71 @@ """Support for monitoring the Deluge BitTorrent client API.""" from __future__ import annotations -import voluptuous as vol +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any from homeassistant.components.sensor import ( - PLATFORM_SCHEMA, SensorEntity, SensorEntityDescription, SensorStateClass, ) -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - CONF_HOST, - CONF_MONITORED_VARIABLES, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, - CONF_USERNAME, - DATA_RATE_KILOBYTES_PER_SECOND, - STATE_IDLE, - Platform, -) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import DATA_RATE_KILOBYTES_PER_SECOND, STATE_IDLE, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType +from homeassistant.helpers.typing import StateType from . import DelugeEntity -from .const import DEFAULT_NAME, DEFAULT_RPC_PORT, DOMAIN +from .const import CURRENT_STATUS, DATA_KEYS, DOMAIN, DOWNLOAD_SPEED, UPLOAD_SPEED from .coordinator import DelugeDataUpdateCoordinator -SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( - SensorEntityDescription( - key="current_status", + +def get_state(data: dict[str, float], key: str) -> str | float: + """Get current download/upload state.""" + upload = data[DATA_KEYS[0]] - data[DATA_KEYS[2]] + download = data[DATA_KEYS[1]] - data[DATA_KEYS[3]] + if key == CURRENT_STATUS: + if upload > 0 and download > 0: + return "Up/Down" + if upload > 0 and download == 0: + return "Seeding" + if upload == 0 and download > 0: + return "Downloading" + return STATE_IDLE + kb_spd = float(upload if key == UPLOAD_SPEED else download) / 1024 + return round(kb_spd, 2 if kb_spd < 0.1 else 1) + + +@dataclass +class DelugeSensorEntityDescription(SensorEntityDescription): + """Class to describe a Deluge sensor.""" + + value: Callable[[dict[str, float]], Any] = lambda val: val + + +SENSOR_TYPES: tuple[DelugeSensorEntityDescription, ...] = ( + DelugeSensorEntityDescription( + key=CURRENT_STATUS, name="Status", + value=lambda data: get_state(data, CURRENT_STATUS), ), - SensorEntityDescription( - key="download_speed", + DelugeSensorEntityDescription( + key=DOWNLOAD_SPEED, name="Down Speed", native_unit_of_measurement=DATA_RATE_KILOBYTES_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, + value=lambda data: get_state(data, DOWNLOAD_SPEED), ), - SensorEntityDescription( - key="upload_speed", + DelugeSensorEntityDescription( + key=UPLOAD_SPEED, name="Up Speed", native_unit_of_measurement=DATA_RATE_KILOBYTES_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, + value=lambda data: get_state(data, UPLOAD_SPEED), ), ) -SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES] - -# Deprecated in Home Assistant 2022.3 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_RPC_PORT): cv.port, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MONITORED_VARIABLES, default=[]): vol.All( - cv.ensure_list, [vol.In(SENSOR_KEYS)] - ), - } -) - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: entity_platform.AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the Deluge sensor component.""" - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=config - ) - ) - async def async_setup_entry( hass: HomeAssistant, @@ -95,10 +82,12 @@ async def async_setup_entry( class DelugeSensor(DelugeEntity, SensorEntity): """Representation of a Deluge sensor.""" + entity_description: DelugeSensorEntityDescription + def __init__( self, coordinator: DelugeDataUpdateCoordinator, - description: SensorEntityDescription, + description: DelugeSensorEntityDescription, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) @@ -109,27 +98,4 @@ class DelugeSensor(DelugeEntity, SensorEntity): @property def native_value(self) -> StateType: """Return the state of the sensor.""" - if self.coordinator.data: - data = self.coordinator.data[Platform.SENSOR] - upload = data[b"upload_rate"] - data[b"dht_upload_rate"] - download = data[b"download_rate"] - data[b"dht_download_rate"] - if self.entity_description.key == "current_status": - if data: - if upload > 0 and download > 0: - return "Up/Down" - if upload > 0 and download == 0: - return "Seeding" - if upload == 0 and download > 0: - return "Downloading" - return STATE_IDLE - - if data: - if self.entity_description.key == "download_speed": - kb_spd = float(download) - kb_spd = kb_spd / 1024 - return round(kb_spd, 2 if kb_spd < 0.1 else 1) - if self.entity_description.key == "upload_speed": - kb_spd = float(upload) - kb_spd = kb_spd / 1024 - return round(kb_spd, 2 if kb_spd < 0.1 else 1) - return None + return self.entity_description.value(self.coordinator.data[Platform.SENSOR]) diff --git a/homeassistant/components/deluge/switch.py b/homeassistant/components/deluge/switch.py index d438d236e5c..cc11fcaf86c 100644 --- a/homeassistant/components/deluge/switch.py +++ b/homeassistant/components/deluge/switch.py @@ -3,52 +3,16 @@ from __future__ import annotations from typing import Any -import voluptuous as vol - -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - CONF_HOST, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, - CONF_USERNAME, - Platform, -) +from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import DelugeEntity -from .const import DEFAULT_RPC_PORT, DOMAIN +from .const import DOMAIN from .coordinator import DelugeDataUpdateCoordinator -# Deprecated in Home Assistant 2022.3 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_RPC_PORT): cv.port, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_NAME, default="Deluge Switch"): cv.string, - } -) - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: entity_platform.AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the Deluge sensor component.""" - 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/deluge/translations/es.json b/homeassistant/components/deluge/translations/es.json new file mode 100644 index 00000000000..7ba1b8c3bf9 --- /dev/null +++ b/homeassistant/components/deluge/translations/es.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Contrase\u00f1a", + "port": "Puerto", + "username": "Usuario", + "web_port": "Puerto web (para el servicio de visita)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/it.json b/homeassistant/components/deluge/translations/it.json index d9407f0c29f..a7b03fc75ed 100644 --- a/homeassistant/components/deluge/translations/it.json +++ b/homeassistant/components/deluge/translations/it.json @@ -16,7 +16,7 @@ "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 " + "description": "Per poter utilizzare questa integrazione, devi abilitare la seguente opzione nelle impostazioni di diluvio: Daemon > Consenti controlli remoti" } } } diff --git a/homeassistant/components/deluge/translations/nl.json b/homeassistant/components/deluge/translations/nl.json index 6c824130708..d96824b10de 100644 --- a/homeassistant/components/deluge/translations/nl.json +++ b/homeassistant/components/deluge/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/deluge/translations/sk.json b/homeassistant/components/deluge/translations/sk.json new file mode 100644 index 00000000000..0fbba9ccd6b --- /dev/null +++ b/homeassistant/components/deluge/translations/sk.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port", + "username": "U\u017e\u00edvate\u013esk\u00e9 meno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index 9c399c67f35..d6c5a5d3afc 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -18,6 +18,7 @@ from homeassistant.const import ( ) import homeassistant.core as ha from homeassistant.core import HomeAssistant +from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.typing import ConfigType import homeassistant.util.dt as dt_util @@ -71,9 +72,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # Set up demo platforms for platform in COMPONENTS_WITH_DEMO_PLATFORM: - hass.async_create_task( - hass.helpers.discovery.async_load_platform(platform, DOMAIN, {}, config) - ) + hass.async_create_task(async_load_platform(hass, platform, DOMAIN, {}, config)) config.setdefault(ha.DOMAIN, {}) config.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/demo/calendar.py b/homeassistant/components/demo/calendar.py index 42ec04e42f2..3a8b909bd0c 100644 --- a/homeassistant/components/demo/calendar.py +++ b/homeassistant/components/demo/calendar.py @@ -39,6 +39,8 @@ def calendar_data_future() -> CalendarEvent: start=one_hour_from_now, end=one_hour_from_now + datetime.timedelta(minutes=60), summary="Future Event", + description="Future Description", + location="Future Location", ) @@ -90,6 +92,8 @@ class LegacyDemoCalendar(CalendarEventDevice): ).isoformat() }, "summary": "Future Event", + "description": "Future Description", + "location": "Future Location", } @property diff --git a/homeassistant/components/demo/translations/select.nl.json b/homeassistant/components/demo/translations/select.nl.json index 4312d8c4d34..1d7e7f9be13 100644 --- a/homeassistant/components/demo/translations/select.nl.json +++ b/homeassistant/components/demo/translations/select.nl.json @@ -2,7 +2,7 @@ "state": { "demo__speed": { "light_speed": "Lichtsnelheid", - "ludicrous_speed": "Lachwekkende snelheid", + "ludicrous_speed": "Belachelijke snelheid", "ridiculous_speed": "Belachelijke snelheid" } } diff --git a/homeassistant/components/denonavr/manifest.json b/homeassistant/components/denonavr/manifest.json index bb6e59053fb..b43dbe3acb7 100644 --- a/homeassistant/components/denonavr/manifest.json +++ b/homeassistant/components/denonavr/manifest.json @@ -56,5 +56,8 @@ } ], "iot_class": "local_polling", - "loggers": ["denonavr"] + "loggers": ["denonavr"], + "supported_brands": { + "marantz": "Marantz" + } } diff --git a/homeassistant/components/denonavr/translations/ca.json b/homeassistant/components/denonavr/translations/ca.json index 11065514ce4..ba857fceab1 100644 --- a/homeassistant/components/denonavr/translations/ca.json +++ b/homeassistant/components/denonavr/translations/ca.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Confirma l'addici\u00f3 del receptor", - "title": "Receptors de xarxa AVR de Denon" + "description": "Confirma l'addici\u00f3 del receptor" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Configura la Zona 2", "zone3": "Configura la Zona 3" }, - "description": "Especifica par\u00e0metres opcionals", - "title": "Receptors de xarxa AVR de Denon" + "description": "Especifica par\u00e0metres opcionals" } } } diff --git a/homeassistant/components/denonavr/translations/cs.json b/homeassistant/components/denonavr/translations/cs.json index 1c66dae10f0..0f385ce1675 100644 --- a/homeassistant/components/denonavr/translations/cs.json +++ b/homeassistant/components/denonavr/translations/cs.json @@ -13,8 +13,7 @@ "flow_title": "S\u00ed\u0165ov\u00fd p\u0159ij\u00edma\u010d Denon AVR: {name}", "step": { "confirm": { - "description": "Potvr\u010fte pros\u00edm p\u0159id\u00e1n\u00ed p\u0159ij\u00edma\u010de", - "title": "S\u00ed\u0165ov\u00e9 p\u0159ij\u00edma\u010de Denon AVR" + "description": "Potvr\u010fte pros\u00edm p\u0159id\u00e1n\u00ed p\u0159ij\u00edma\u010de" }, "user": { "data": { diff --git a/homeassistant/components/denonavr/translations/de.json b/homeassistant/components/denonavr/translations/de.json index 1c9a1a8ec95..e4df128612b 100644 --- a/homeassistant/components/denonavr/translations/de.json +++ b/homeassistant/components/denonavr/translations/de.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Bitte best\u00e4tige das Hinzuf\u00fcgen des Receivers", - "title": "Denon AVR-Netzwerk-Receiver" + "description": "Bitte best\u00e4tige das Hinzuf\u00fcgen des Receivers" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Zone 2 einrichten", "zone3": "Zone 3 einrichten" }, - "description": "Optionale Einstellungen festlegen", - "title": "Denon AVR-Netzwerk-Receiver" + "description": "Optionale Einstellungen festlegen" } } } diff --git a/homeassistant/components/denonavr/translations/el.json b/homeassistant/components/denonavr/translations/el.json index d176fd944d6..77cc89731f5 100644 --- a/homeassistant/components/denonavr/translations/el.json +++ b/homeassistant/components/denonavr/translations/el.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "\u0395\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03b9\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03ad\u03ba\u03c4\u03b7", - "title": "\u0394\u03ad\u03ba\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR" + "description": "\u0395\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03b9\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03ad\u03ba\u03c4\u03b7" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b6\u03ce\u03bd\u03b7\u03c2 2", "zone3": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b6\u03ce\u03bd\u03b7\u03c2 3" }, - "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ad\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2", - "title": "\u0394\u03ad\u03ba\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR" + "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ad\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2" } } } diff --git a/homeassistant/components/denonavr/translations/en.json b/homeassistant/components/denonavr/translations/en.json index 2d937856b1c..668e18aae6e 100644 --- a/homeassistant/components/denonavr/translations/en.json +++ b/homeassistant/components/denonavr/translations/en.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Please confirm adding the receiver", - "title": "Denon AVR Network Receivers" + "description": "Please confirm adding the receiver" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Set up Zone 2", "zone3": "Set up Zone 3" }, - "description": "Specify optional settings", - "title": "Denon AVR Network Receivers" + "description": "Specify optional settings" } } } diff --git a/homeassistant/components/denonavr/translations/es-419.json b/homeassistant/components/denonavr/translations/es-419.json index e22b8feebd1..eb8e5698c09 100644 --- a/homeassistant/components/denonavr/translations/es-419.json +++ b/homeassistant/components/denonavr/translations/es-419.json @@ -11,8 +11,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Por favor, confirme la adici\u00f3n del receptor", - "title": "Receptores de red Denon AVR" + "description": "Por favor, confirme la adici\u00f3n del receptor" }, "select": { "data": { @@ -20,10 +19,6 @@ }, "description": "Vuelva a ejecutar la configuraci\u00f3n si desea conectar receptores adicionales", "title": "Seleccione el receptor que desea conectar" - }, - "user": { - "description": "Con\u00e9ctese a su receptor, si la direcci\u00f3n IP no est\u00e1 configurada, se usa el descubrimiento autom\u00e1tico", - "title": "Receptores de red Denon AVR" } } }, @@ -36,8 +31,7 @@ "zone2": "Configurar Zona 2", "zone3": "Configurar Zona 3" }, - "description": "Especificar configuraciones opcionales", - "title": "Receptores de red Denon AVR" + "description": "Especificar configuraciones opcionales" } } } diff --git a/homeassistant/components/denonavr/translations/es.json b/homeassistant/components/denonavr/translations/es.json index 04bcb55ace5..3cfe69aeac4 100644 --- a/homeassistant/components/denonavr/translations/es.json +++ b/homeassistant/components/denonavr/translations/es.json @@ -10,11 +10,10 @@ "error": { "discovery_error": "Error detectando un Receptor AVR Denon en Red" }, - "flow_title": "Receptor AVR Denon en Red: {name}", + "flow_title": "{name}", "step": { "confirm": { - "description": "Por favor confirma la adici\u00f3n del receptor", - "title": "Receptores AVR Denon en Red" + "description": "Por favor confirma la adici\u00f3n del receptor" }, "select": { "data": { @@ -26,9 +25,7 @@ "user": { "data": { "host": "Direcci\u00f3n IP" - }, - "description": "Con\u00e9ctar con tu receptor, si la direcci\u00f3n IP no est\u00e1 configurada, se utilizar\u00e1 la detecci\u00f3n autom\u00e1tica", - "title": "Receptores AVR Denon en Red" + } } } }, @@ -41,8 +38,7 @@ "zone2": "Configurar la Zona 2", "zone3": "Configurar la Zona 3" }, - "description": "Especificar configuraciones opcionales", - "title": "Receptores AVR Denon en Red" + "description": "Especificar configuraciones opcionales" } } } diff --git a/homeassistant/components/denonavr/translations/et.json b/homeassistant/components/denonavr/translations/et.json index f133aaf9dd7..490624212b5 100644 --- a/homeassistant/components/denonavr/translations/et.json +++ b/homeassistant/components/denonavr/translations/et.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Palun kinnita vastuv\u00f5tja lisamine", - "title": "" + "description": "Palun kinnita vastuv\u00f5tja lisamine" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "data_description": { "host": "Automaatse avastamise kasutamiseks j\u00e4ta v\u00e4li t\u00fchjaks." - }, - "description": "Kui IP-aadressi pole m\u00e4\u00e4ratud, kasutatakse automaatset avastamist", - "title": "" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Seadista tsoon 2", "zone3": "Seadista tsoon 3" }, - "description": "Valikuliste s\u00e4tete m\u00e4\u00e4ramine", - "title": "" + "description": "Valikuliste s\u00e4tete m\u00e4\u00e4ramine" } } } diff --git a/homeassistant/components/denonavr/translations/fr.json b/homeassistant/components/denonavr/translations/fr.json index 27a72477164..3b3af580f1c 100644 --- a/homeassistant/components/denonavr/translations/fr.json +++ b/homeassistant/components/denonavr/translations/fr.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Veuillez confirmer l'ajout du r\u00e9cepteur", - "title": "R\u00e9cepteurs r\u00e9seaux Denon AVR" + "description": "Veuillez confirmer l'ajout du r\u00e9cepteur" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Configurer Zone 2", "zone3": "Configurer Zone 3" }, - "description": "Sp\u00e9cifiez les param\u00e8tres optionnels", - "title": "R\u00e9cepteurs r\u00e9seaux Denon AVR" + "description": "Sp\u00e9cifiez les param\u00e8tres optionnels" } } } diff --git a/homeassistant/components/denonavr/translations/hu.json b/homeassistant/components/denonavr/translations/hu.json index 6891d18a9c4..8c51c7e990f 100644 --- a/homeassistant/components/denonavr/translations/hu.json +++ b/homeassistant/components/denonavr/translations/hu.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "K\u00e9rj\u00fck, er\u0151s\u00edtse meg a vev\u0151 hozz\u00e1ad\u00e1s\u00e1t", - "title": "Denon AVR h\u00e1l\u00f3zati vev\u0151k\u00e9sz\u00fcl\u00e9kek" + "description": "K\u00e9rj\u00fck, er\u0151s\u00edtse meg a vev\u0151 hozz\u00e1ad\u00e1s\u00e1t" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "\u00c1ll\u00edtsa be a 2. z\u00f3n\u00e1t", "zone3": "\u00c1ll\u00edtsa be a 3. z\u00f3n\u00e1t" }, - "description": "Adja meg az opcion\u00e1lis be\u00e1ll\u00edt\u00e1sokat", - "title": "Denon AVR h\u00e1l\u00f3zati vev\u0151k\u00e9sz\u00fcl\u00e9kek" + "description": "Adja meg az opcion\u00e1lis be\u00e1ll\u00edt\u00e1sokat" } } } diff --git a/homeassistant/components/denonavr/translations/id.json b/homeassistant/components/denonavr/translations/id.json index 50a28ef5447..5c20f9b377d 100644 --- a/homeassistant/components/denonavr/translations/id.json +++ b/homeassistant/components/denonavr/translations/id.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Konfirmasikan penambahan Receiver", - "title": "Network Receiver Denon AVR" + "description": "Konfirmasikan penambahan Receiver" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Siapkan Zona 2", "zone3": "Siapkan Zona 3" }, - "description": "Tentukan pengaturan opsional", - "title": "Network Receiver Denon AVR" + "description": "Tentukan pengaturan opsional" } } } diff --git a/homeassistant/components/denonavr/translations/it.json b/homeassistant/components/denonavr/translations/it.json index 23fffd3ab44..142ff573880 100644 --- a/homeassistant/components/denonavr/translations/it.json +++ b/homeassistant/components/denonavr/translations/it.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Conferma l'aggiunta del ricevitore", - "title": "Ricevitori di rete Denon AVR" + "description": "Conferma l'aggiunta del ricevitore" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Configura la zona 2", "zone3": "Configura la zona 3" }, - "description": "Specificare le impostazioni opzionali", - "title": "Ricevitori di rete Denon AVR" + "description": "Specificare le impostazioni opzionali" } } } diff --git a/homeassistant/components/denonavr/translations/ja.json b/homeassistant/components/denonavr/translations/ja.json index cd3198b7e10..d81d5fab462 100644 --- a/homeassistant/components/denonavr/translations/ja.json +++ b/homeassistant/components/denonavr/translations/ja.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "\u53d7\u4fe1\u6a5f\u306e\u8ffd\u52a0\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "\u30c7\u30ce\u30f3(Denon)AVR\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30ec\u30b7\u30fc\u30d0\u30fc" + "description": "\u53d7\u4fe1\u6a5f\u306e\u8ffd\u52a0\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "\u30be\u30fc\u30f32\u306e\u8a2d\u5b9a", "zone3": "\u30be\u30fc\u30f33\u306e\u8a2d\u5b9a" }, - "description": "\u30aa\u30d7\u30b7\u30e7\u30f3\u8a2d\u5b9a\u306e\u6307\u5b9a", - "title": "\u30c7\u30ce\u30f3(Denon)AVR\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30ec\u30b7\u30fc\u30d0\u30fc" + "description": "\u30aa\u30d7\u30b7\u30e7\u30f3\u8a2d\u5b9a\u306e\u6307\u5b9a" } } } diff --git a/homeassistant/components/denonavr/translations/ko.json b/homeassistant/components/denonavr/translations/ko.json index c0121a1e2ca..4a880f6757f 100644 --- a/homeassistant/components/denonavr/translations/ko.json +++ b/homeassistant/components/denonavr/translations/ko.json @@ -13,8 +13,7 @@ "flow_title": "Denon AVR \ub124\ud2b8\uc6cc\ud06c \ub9ac\uc2dc\ubc84: {name}", "step": { "confirm": { - "description": "\ub9ac\uc2dc\ubc84 \ucd94\uac00\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694", - "title": "Denon AVR \ub124\ud2b8\uc6cc\ud06c \ub9ac\uc2dc\ubc84" + "description": "\ub9ac\uc2dc\ubc84 \ucd94\uac00\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694" }, "select": { "data": { @@ -26,9 +25,7 @@ "user": { "data": { "host": "IP \uc8fc\uc18c" - }, - "description": "\ub9ac\uc2dc\ubc84\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \uc8fc\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", - "title": "Denon AVR \ub124\ud2b8\uc6cc\ud06c \ub9ac\uc2dc\ubc84" + } } } }, @@ -37,11 +34,11 @@ "init": { "data": { "show_all_sources": "\ubaa8\ub4e0 \uc785\ub825\uc18c\uc2a4 \ud45c\uc2dc", + "update_audyssey": "Audyssey \uc124\uc815 \uc5c5\ub370\uc774\ud2b8", "zone2": "Zone 2 \uc124\uc815", "zone3": "Zone 3 \uc124\uc815" }, - "description": "\uc635\uc158 \uc124\uc815 \uc9c0\uc815", - "title": "Denon AVR \ub124\ud2b8\uc6cc\ud06c \ub9ac\uc2dc\ubc84" + "description": "\uc635\uc158 \uc124\uc815 \uc9c0\uc815" } } } diff --git a/homeassistant/components/denonavr/translations/lb.json b/homeassistant/components/denonavr/translations/lb.json index fd8ece3c0bc..021d1de1a8d 100644 --- a/homeassistant/components/denonavr/translations/lb.json +++ b/homeassistant/components/denonavr/translations/lb.json @@ -13,8 +13,7 @@ "flow_title": "Denon AVR Netzwierk Empf\u00e4nger: {name}", "step": { "confirm": { - "description": "Best\u00e4teg dob\u00e4isetzen vum Receiver", - "title": "Denon AVR Netzwierk Empf\u00e4nger" + "description": "Best\u00e4teg dob\u00e4isetzen vum Receiver" }, "select": { "data": { @@ -26,9 +25,7 @@ "user": { "data": { "host": "IP Adress" - }, - "description": "Mam Receiver verbannen, falls keng IP Adress uginn ass g\u00ebtt auto-discovery benotzt", - "title": "Denon AVR Netzwierk Empf\u00e4nger" + } } } }, @@ -40,8 +37,7 @@ "zone2": "Zone 2 ariichten", "zone3": "Zone 3 ariichten" }, - "description": "Optionell Astellungen uginn", - "title": "Denon AVR Netzwierk Empf\u00e4nger" + "description": "Optionell Astellungen uginn" } } } diff --git a/homeassistant/components/denonavr/translations/nl.json b/homeassistant/components/denonavr/translations/nl.json index f03895452df..6ebb0d845d7 100644 --- a/homeassistant/components/denonavr/translations/nl.json +++ b/homeassistant/components/denonavr/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Verbinding mislukt, probeer het opnieuw, de stroom- en ethernetkabels loskoppelen en opnieuw aansluiten kan helpen", "not_denonavr_manufacturer": "Geen Denon AVR Netwerk Receiver, ontdekte fabrikant komt niet overeen", "not_denonavr_missing": "Geen Denon AVR netwerkontvanger, zoekinformatie niet compleet" @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Bevestig het toevoegen van de ontvanger", - "title": "Denon AVR Network Receivers" + "description": "Bevestig het toevoegen van de ontvanger" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Stel Zone 2 in", "zone3": "Stel Zone 3 in" }, - "description": "Optionele instellingen opgeven", - "title": "Denon AVR Network Receivers" + "description": "Optionele instellingen opgeven" } } } diff --git a/homeassistant/components/denonavr/translations/no.json b/homeassistant/components/denonavr/translations/no.json index 333c55a44f7..46f23680812 100644 --- a/homeassistant/components/denonavr/translations/no.json +++ b/homeassistant/components/denonavr/translations/no.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Bekreft at du legger til mottakeren", - "title": "Denon AVR nettverksmottakere" + "description": "Bekreft at du legger til mottakeren" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Sett opp sone 2", "zone3": "Sett opp sone 3" }, - "description": "Spesifiser valgfrie innstillinger", - "title": "Denon AVR Network Receivers" + "description": "Spesifiser valgfrie innstillinger" } } } diff --git a/homeassistant/components/denonavr/translations/pl.json b/homeassistant/components/denonavr/translations/pl.json index 054371c6ba1..b82a6867dd8 100644 --- a/homeassistant/components/denonavr/translations/pl.json +++ b/homeassistant/components/denonavr/translations/pl.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Prosz\u0119 potwierdzi\u0107 dodanie urz\u0105dzenia", - "title": "Denon AVR" + "description": "Prosz\u0119 potwierdzi\u0107 dodanie urz\u0105dzenia" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Konfiguracja Strefy 2", "zone3": "Konfiguracja Strefy 3" }, - "description": "Ustawienia opcjonalne", - "title": "Denon AVR" + "description": "Ustawienia opcjonalne" } } } diff --git a/homeassistant/components/denonavr/translations/pt-BR.json b/homeassistant/components/denonavr/translations/pt-BR.json index 2a716dacdca..558accaf777 100644 --- a/homeassistant/components/denonavr/translations/pt-BR.json +++ b/homeassistant/components/denonavr/translations/pt-BR.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Confirme a adi\u00e7\u00e3o do receptor", - "title": "Receptores de rede Denon AVR" + "description": "Confirme a adi\u00e7\u00e3o do receptor" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "Configure a Zona 2", "zone3": "Configurar a Zona 3" }, - "description": "Especificar configura\u00e7\u00f5es opcionais", - "title": "Receptores de rede Denon AVR" + "description": "Especificar configura\u00e7\u00f5es opcionais" } } } diff --git a/homeassistant/components/denonavr/translations/ru.json b/homeassistant/components/denonavr/translations/ru.json index 1db49decaad..01f0d868897 100644 --- a/homeassistant/components/denonavr/translations/ru.json +++ b/homeassistant/components/denonavr/translations/ru.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0440\u0435\u0441\u0438\u0432\u0435\u0440\u0430", - "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + "description": "\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0440\u0435\u0441\u0438\u0432\u0435\u0440\u0430" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0437\u043e\u043d\u044b 2", "zone3": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0437\u043e\u043d\u044b 3" }, - "description": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", - "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + "description": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438" } } } diff --git a/homeassistant/components/denonavr/translations/tr.json b/homeassistant/components/denonavr/translations/tr.json index 046e535c621..fde01548e14 100644 --- a/homeassistant/components/denonavr/translations/tr.json +++ b/homeassistant/components/denonavr/translations/tr.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "L\u00fctfen al\u0131c\u0131y\u0131 eklemeyi onaylay\u0131n", - "title": "Denon AVR A\u011f Al\u0131c\u0131lar\u0131" + "description": "L\u00fctfen al\u0131c\u0131y\u0131 eklemeyi onaylay\u0131n" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "B\u00f6lge 2'yi kurun", "zone3": "B\u00f6lge 3'\u00fc kurun" }, - "description": "\u0130ste\u011fe ba\u011fl\u0131 ayarlar\u0131 belirtin", - "title": "Denon AVR A\u011f Al\u0131c\u0131lar\u0131" + "description": "\u0130ste\u011fe ba\u011fl\u0131 ayarlar\u0131 belirtin" } } } diff --git a/homeassistant/components/denonavr/translations/uk.json b/homeassistant/components/denonavr/translations/uk.json index efb4cb41777..dcc68648fcc 100644 --- a/homeassistant/components/denonavr/translations/uk.json +++ b/homeassistant/components/denonavr/translations/uk.json @@ -13,8 +13,7 @@ "flow_title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon: {name}", "step": { "confirm": { - "description": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0456\u0442\u044c \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f \u0440\u0435\u0441\u0438\u0432\u0435\u0440\u0430", - "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + "description": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0456\u0442\u044c \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f \u0440\u0435\u0441\u0438\u0432\u0435\u0440\u0430" }, "select": { "data": { @@ -26,9 +25,7 @@ "user": { "data": { "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" - }, - "description": "\u042f\u043a\u0449\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u0430, \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f", - "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + } } } }, @@ -40,8 +37,7 @@ "zone2": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043e\u043d\u0438 2", "zone3": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043e\u043d\u0438 3" }, - "description": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f", - "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + "description": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" } } } diff --git a/homeassistant/components/denonavr/translations/zh-Hant.json b/homeassistant/components/denonavr/translations/zh-Hant.json index 1217a6d7b87..7983a4f7972 100644 --- a/homeassistant/components/denonavr/translations/zh-Hant.json +++ b/homeassistant/components/denonavr/translations/zh-Hant.json @@ -13,8 +13,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "\u8acb\u78ba\u8a8d\u65b0\u589e\u63a5\u6536\u5668", - "title": "Denon AVR \u7db2\u8def\u63a5\u6536\u5668" + "description": "\u8acb\u78ba\u8a8d\u65b0\u589e\u63a5\u6536\u5668" }, "select": { "data": { @@ -29,9 +28,7 @@ }, "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" + } } } }, @@ -44,8 +41,7 @@ "zone2": "\u8a2d\u5b9a\u5340\u57df 2", "zone3": "\u8a2d\u5b9a\u5340\u57df 3" }, - "description": "\u6307\u5b9a\u9078\u9805\u8a2d\u5b9a", - "title": "Denon AVR \u7db2\u8def\u63a5\u6536\u5668" + "description": "\u6307\u5b9a\u9078\u9805\u8a2d\u5b9a" } } } diff --git a/homeassistant/components/derivative/translations/bg.json b/homeassistant/components/derivative/translations/bg.json index 14946d95fe0..b6c3577d1d0 100644 --- a/homeassistant/components/derivative/translations/bg.json +++ b/homeassistant/components/derivative/translations/bg.json @@ -14,11 +14,6 @@ "data": { "name": "\u0418\u043c\u0435" } - }, - "options": { - "data": { - "name": "\u0418\u043c\u0435" - } } } } diff --git a/homeassistant/components/derivative/translations/ca.json b/homeassistant/components/derivative/translations/ca.json index 3003b9349fd..9dff83bb746 100644 --- a/homeassistant/components/derivative/translations/ca.json +++ b/homeassistant/components/derivative/translations/ca.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/de.json b/homeassistant/components/derivative/translations/de.json index 4e1dbc1929c..1a23de37c25 100644 --- a/homeassistant/components/derivative/translations/de.json +++ b/homeassistant/components/derivative/translations/de.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/el.json b/homeassistant/components/derivative/translations/el.json index a5a19efc1e5..a0c55e93a5e 100644 --- a/homeassistant/components/derivative/translations/el.json +++ b/homeassistant/components/derivative/translations/el.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/en.json b/homeassistant/components/derivative/translations/en.json index 884f1fa5244..b91318b5237 100644 --- a/homeassistant/components/derivative/translations/en.json +++ b/homeassistant/components/derivative/translations/en.json @@ -36,22 +36,6 @@ "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/es.json b/homeassistant/components/derivative/translations/es.json new file mode 100644 index 00000000000..e6df75ba4d6 --- /dev/null +++ b/homeassistant/components/derivative/translations/es.json @@ -0,0 +1,34 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nombre", + "source": "Sensor de entrada", + "time_window": "Ventana de tiempo", + "unit_prefix": "Prefijo m\u00e9trico", + "unit_time": "Unidad de tiempo" + }, + "description": "Crea un sensor que ama la derivada de otro sensor.", + "title": "A\u00f1ade sensor derivativo" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nombre", + "round": "Precisi\u00f3n", + "unit_prefix": "Prefijo m\u00e9trico", + "unit_time": "Unidad de tiempo" + }, + "data_description": { + "round": "Controla el n\u00famero de d\u00edgitos decimales en la salida.", + "unit_prefix": "a salida se escalar\u00e1 seg\u00fan el prefijo m\u00e9trico y la unidad de tiempo de la derivada seleccionados." + } + } + } + }, + "title": "Sensor derivatiu" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/et.json b/homeassistant/components/derivative/translations/et.json index 45c566fac9b..4c3af55a294 100644 --- a/homeassistant/components/derivative/translations/et.json +++ b/homeassistant/components/derivative/translations/et.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/fr.json b/homeassistant/components/derivative/translations/fr.json index d967a3abc89..3716c6907b9 100644 --- a/homeassistant/components/derivative/translations/fr.json +++ b/homeassistant/components/derivative/translations/fr.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/he.json b/homeassistant/components/derivative/translations/he.json deleted file mode 100644 index 317b836da6d..00000000000 --- a/homeassistant/components/derivative/translations/he.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "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 index e71175d2a32..65c9a38a473 100644 --- a/homeassistant/components/derivative/translations/hu.json +++ b/homeassistant/components/derivative/translations/hu.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/id.json b/homeassistant/components/derivative/translations/id.json index 67d6752a182..0461824523a 100644 --- a/homeassistant/components/derivative/translations/id.json +++ b/homeassistant/components/derivative/translations/id.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/it.json b/homeassistant/components/derivative/translations/it.json index ad888e13745..713f8334037 100644 --- a/homeassistant/components/derivative/translations/it.json +++ b/homeassistant/components/derivative/translations/it.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/ja.json b/homeassistant/components/derivative/translations/ja.json index 10232f996d6..c5939e95deb 100644 --- a/homeassistant/components/derivative/translations/ja.json +++ b/homeassistant/components/derivative/translations/ja.json @@ -36,22 +36,6 @@ "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" } } }, diff --git a/homeassistant/components/derivative/translations/ko.json b/homeassistant/components/derivative/translations/ko.json new file mode 100644 index 00000000000..a45cb7d22e5 --- /dev/null +++ b/homeassistant/components/derivative/translations/ko.json @@ -0,0 +1,43 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\uc774\ub984", + "round": "\uc18c\uc218\uc810", + "source": "\uc785\ub825 \uc13c\uc11c", + "time_window": "\uc2dc\uac04\ub300", + "unit_prefix": "\ubbf8\ud130\ubc95", + "unit_time": "\uc2dc\uac04 \ub2e8\uc704" + }, + "data_description": { + "round": "\uc18c\uc218\uc810 \uc790\ub9bf\uc218\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4.", + "time_window": "\uc124\uc815\ub41c \uacbd\uc6b0 \uc13c\uc11c\uc758 \uac12\uc740 \uc2dc\uac04\ub300 \ub0b4 \ub3c4\ud568\uc218\uc758 \uc2dc\uac04 \uac00\uc911 \uc774\ub3d9 \ud3c9\uade0\uc785\ub2c8\ub2e4.", + "unit_prefix": "\uc120\ud0dd\ud55c \ubbf8\ud130\ubc95 \ubc0f \ub3c4\ud568\uc218\uc758 \ub2e8\uc704\uc2dc\uac04\uc73c\ub85c \ud45c\uc2dc\ub429\ub2c8\ub2e4." + }, + "description": "\ub3c4\ud568\uc218\ub97c \uad6c\ud558\ub294 \uc13c\uc11c\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.", + "title": "\ub3c4\ud568\uc218 \uc13c\uc11c \ucd94\uac00" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "\uc774\ub984", + "round": "\uc18c\uc218\uc810", + "source": "\uc785\ub825 \uc13c\uc11c", + "time_window": "\uc2dc\uac04\ub300", + "unit_prefix": "\ubbf8\ud130\ubc95", + "unit_time": "\uc2dc\uac04 \ub2e8\uc704" + }, + "data_description": { + "round": "\uc18c\uc218\uc810 \uc790\ub9bf\uc218\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4.", + "time_window": "\uc124\uc815\ub41c \uacbd\uc6b0 \uc13c\uc11c\uc758 \uac12\uc740 \uc2dc\uac04\ub300 \ub0b4 \ub3c4\ud568\uc218\uc758 \uc2dc\uac04 \uac00\uc911 \uc774\ub3d9 \ud3c9\uade0\uc785\ub2c8\ub2e4.", + "unit_prefix": "\uc120\ud0dd\ud55c \ubbf8\ud130\ubc95 \ubc0f \ub3c4\ud568\uc218\uc758 \ub2e8\uc704\uc2dc\uac04\uc73c\ub85c \ud45c\uc2dc\ub429\ub2c8\ub2e4.." + } + } + } + }, + "title": "\ub3c4\ud568\uc218 \uc13c\uc11c" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/nl.json b/homeassistant/components/derivative/translations/nl.json index 8b7cf5a9402..c6d485e7548 100644 --- a/homeassistant/components/derivative/translations/nl.json +++ b/homeassistant/components/derivative/translations/nl.json @@ -4,19 +4,19 @@ "user": { "data": { "name": "Naam", - "round": "Precisie", - "source": "Invoer sensor", + "round": "Nauwkeurigheid", + "source": "Bronsensor", "time_window": "Tijdsvenster", "unit_prefix": "Metrisch voorvoegsel", "unit_time": "Tijdseenheid" }, "data_description": { - "round": "Regelt het aantal decimale cijfers in de uitvoer.", + "round": "Regelt het aantal decimalen 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" + "title": "Voeg afgeleidesensor toe" } } }, @@ -25,35 +25,19 @@ "init": { "data": { "name": "Naam", - "round": "Precisie", - "source": "Invoer sensor", + "round": "Nauwkeurigheid", + "source": "Bronsensor", "time_window": "Tijdsvenster", "unit_prefix": "Metrisch voorvoegsel", "unit_time": "Tijdseenheid" }, "data_description": { - "round": "Regelt het aantal decimale cijfers in de uitvoer.", + "round": "Regelt het aantal decimalen 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" + "title": "Afgeleide" } \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/no.json b/homeassistant/components/derivative/translations/no.json index 735f1fae6ae..ca04f9d0d28 100644 --- a/homeassistant/components/derivative/translations/no.json +++ b/homeassistant/components/derivative/translations/no.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/pl.json b/homeassistant/components/derivative/translations/pl.json index 041d52ffeff..3f97ff29c2c 100644 --- a/homeassistant/components/derivative/translations/pl.json +++ b/homeassistant/components/derivative/translations/pl.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/pt-BR.json b/homeassistant/components/derivative/translations/pt-BR.json index 4d29a3970ee..54acbb3a5e5 100644 --- a/homeassistant/components/derivative/translations/pt-BR.json +++ b/homeassistant/components/derivative/translations/pt-BR.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/ru.json b/homeassistant/components/derivative/translations/ru.json index d7c62f070e1..6155d64301a 100644 --- a/homeassistant/components/derivative/translations/ru.json +++ b/homeassistant/components/derivative/translations/ru.json @@ -11,8 +11,11 @@ "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." + "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.", + "time_window": "\u0415\u0441\u043b\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0432\u043d\u043e \u0432\u0437\u0432\u0435\u0448\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0441\u043a\u043e\u043b\u044c\u0437\u044f\u0449\u0435\u043c\u0443 \u0441\u0440\u0435\u0434\u043d\u0435\u043c\u0443 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u044b\u0445 \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u044d\u0442\u043e\u0433\u043e \u043e\u043a\u043d\u0430.", + "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 \u0438 \u0435\u0434\u0438\u043d\u0438\u0446\u0435\u0439 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u043e\u0439." }, + "description": "\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0441\u0435\u043d\u0441\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u0443\u044e \u0441\u0435\u043d\u0441\u043e\u0440\u0430.", "title": "\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u0430\u044f" } } @@ -30,21 +33,8 @@ }, "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": "." + "time_window": "\u0415\u0441\u043b\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0432\u043d\u043e \u0432\u0437\u0432\u0435\u0448\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0441\u043a\u043e\u043b\u044c\u0437\u044f\u0449\u0435\u043c\u0443 \u0441\u0440\u0435\u0434\u043d\u0435\u043c\u0443 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u044b\u0445 \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u044d\u0442\u043e\u0433\u043e \u043e\u043a\u043d\u0430.", + "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 \u0438 \u0435\u0434\u0438\u043d\u0438\u0446\u0435\u0439 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u043e\u0439.." } } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/lt.json b/homeassistant/components/derivative/translations/sk.json similarity index 72% rename from homeassistant/components/trafikverket_weatherstation/translations/lt.json rename to homeassistant/components/derivative/translations/sk.json index 87076aabdf1..cc4a1cc7cb9 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/lt.json +++ b/homeassistant/components/derivative/translations/sk.json @@ -3,7 +3,7 @@ "step": { "user": { "data": { - "name": "Prisijungimo vardas" + "name": "Meno" } } } diff --git a/homeassistant/components/derivative/translations/tr.json b/homeassistant/components/derivative/translations/tr.json index 68016c74372..500a0007494 100644 --- a/homeassistant/components/derivative/translations/tr.json +++ b/homeassistant/components/derivative/translations/tr.json @@ -36,22 +36,6 @@ "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." } } }, diff --git a/homeassistant/components/derivative/translations/zh-Hans.json b/homeassistant/components/derivative/translations/zh-Hans.json index 689f057dec0..130e7292282 100644 --- a/homeassistant/components/derivative/translations/zh-Hans.json +++ b/homeassistant/components/derivative/translations/zh-Hans.json @@ -36,22 +36,6 @@ "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" } } }, diff --git a/homeassistant/components/derivative/translations/zh-Hant.json b/homeassistant/components/derivative/translations/zh-Hant.json index 3c100df8034..11236b0ad63 100644 --- a/homeassistant/components/derivative/translations/zh-Hant.json +++ b/homeassistant/components/derivative/translations/zh-Hant.json @@ -36,22 +36,6 @@ "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" } } }, diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 73f28c54657..61fd93354fe 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -26,7 +26,7 @@ from homeassistant.helpers import ( entity_registry as er, ) from homeassistant.helpers.typing import ConfigType -from homeassistant.loader import IntegrationNotFound, bind_hass +from homeassistant.loader import IntegrationNotFound from homeassistant.requirements import async_get_integration_with_requirements from .exceptions import DeviceNotFound, InvalidDeviceAutomationConfig @@ -44,7 +44,6 @@ if TYPE_CHECKING: ] # mypy: allow-untyped-calls, allow-untyped-defs - DOMAIN = "device_automation" DEVICE_TRIGGER_BASE_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( @@ -212,7 +211,6 @@ async def _async_get_device_automations_from_domain( ) -@bind_hass async def async_get_device_automations( hass: HomeAssistant, automation_type: DeviceAutomationType, diff --git a/homeassistant/components/device_automation/action.py b/homeassistant/components/device_automation/action.py index 5261757c645..5737fbc5bf3 100644 --- a/homeassistant/components/device_automation/action.py +++ b/homeassistant/components/device_automation/action.py @@ -1,6 +1,7 @@ """Device action validator.""" from __future__ import annotations +from collections.abc import Awaitable from typing import Any, Protocol, cast import voluptuous as vol @@ -25,7 +26,6 @@ class DeviceAutomationActionProtocol(Protocol): self, hass: HomeAssistant, config: ConfigType ) -> ConfigType: """Validate config.""" - raise NotImplementedError async def async_call_action_from_config( self, @@ -35,7 +35,16 @@ class DeviceAutomationActionProtocol(Protocol): context: Context | None, ) -> None: """Execute a device action.""" - raise NotImplementedError + + def async_get_action_capabilities( + self, hass: HomeAssistant, config: ConfigType + ) -> dict[str, vol.Schema] | Awaitable[dict[str, vol.Schema]]: + """List action capabilities.""" + + def async_get_actions( + self, hass: HomeAssistant, device_id: str + ) -> list[dict[str, Any]] | Awaitable[list[dict[str, Any]]]: + """List actions.""" async def async_validate_action_config( diff --git a/homeassistant/components/device_automation/condition.py b/homeassistant/components/device_automation/condition.py index 1c226ee8c29..1f1f8e94832 100644 --- a/homeassistant/components/device_automation/condition.py +++ b/homeassistant/components/device_automation/condition.py @@ -1,7 +1,8 @@ """Validate device conditions.""" from __future__ import annotations -from typing import TYPE_CHECKING, Protocol, cast +from collections.abc import Awaitable +from typing import TYPE_CHECKING, Any, Protocol, cast import voluptuous as vol @@ -29,13 +30,21 @@ class DeviceAutomationConditionProtocol(Protocol): self, hass: HomeAssistant, config: ConfigType ) -> ConfigType: """Validate config.""" - raise NotImplementedError def async_condition_from_config( self, hass: HomeAssistant, config: ConfigType ) -> condition.ConditionCheckerType: """Evaluate state based on configuration.""" - raise NotImplementedError + + def async_get_condition_capabilities( + self, hass: HomeAssistant, config: ConfigType + ) -> dict[str, vol.Schema] | Awaitable[dict[str, vol.Schema]]: + """List condition capabilities.""" + + def async_get_conditions( + self, hass: HomeAssistant, device_id: str + ) -> list[dict[str, Any]] | Awaitable[list[dict[str, Any]]]: + """List conditions.""" async def async_validate_condition_config( diff --git a/homeassistant/components/device_automation/entity.py b/homeassistant/components/device_automation/entity.py index b27a8c67561..4bc77370150 100644 --- a/homeassistant/components/device_automation/entity.py +++ b/homeassistant/components/device_automation/entity.py @@ -1,8 +1,6 @@ """Device automation helpers for entity.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -12,8 +10,7 @@ from homeassistant.components.automation import ( from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.const import CONF_ENTITY_ID, CONF_FOR, CONF_PLATFORM, CONF_TYPE from homeassistant.core import CALLBACK_TYPE, HomeAssistant -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity_registry import async_entries_for_device +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.typing import ConfigType from . import DEVICE_TRIGGER_BASE_SCHEMA @@ -68,11 +65,11 @@ async def _async_get_automations( ) -> list[dict[str, str]]: """List device automations.""" automations: list[dict[str, str]] = [] - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entries = [ entry - for entry in async_entries_for_device(entity_registry, device_id) + for entry in er.async_entries_for_device(entity_registry, device_id) if entry.domain == domain ] @@ -92,7 +89,7 @@ async def _async_get_automations( async def async_get_triggers( hass: HomeAssistant, device_id: str, domain: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers.""" return await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain) diff --git a/homeassistant/components/device_automation/toggle_entity.py b/homeassistant/components/device_automation/toggle_entity.py index 4559e88f9d3..af97de85f70 100644 --- a/homeassistant/components/device_automation/toggle_entity.py +++ b/homeassistant/components/device_automation/toggle_entity.py @@ -1,8 +1,6 @@ """Device automation helpers for toggle entity.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -20,8 +18,11 @@ from homeassistant.const import ( CONF_TYPE, ) from homeassistant.core import CALLBACK_TYPE, Context, HomeAssistant, callback -from homeassistant.helpers import condition, config_validation as cv -from homeassistant.helpers.entity_registry import async_entries_for_device +from homeassistant.helpers import ( + condition, + config_validation as cv, + entity_registry as er, +) from homeassistant.helpers.typing import ConfigType, TemplateVarsType from . import DEVICE_TRIGGER_BASE_SCHEMA, entity @@ -187,11 +188,11 @@ async def _async_get_automations( ) -> list[dict[str, str]]: """List device automations.""" automations: list[dict[str, str]] = [] - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entries = [ entry - for entry in async_entries_for_device(entity_registry, device_id) + for entry in er.async_entries_for_device(entity_registry, device_id) if entry.domain == domain ] @@ -225,7 +226,7 @@ async def async_get_conditions( async def async_get_triggers( hass: HomeAssistant, device_id: str, domain: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers.""" triggers = await entity.async_get_triggers(hass, device_id, domain) triggers.extend( diff --git a/homeassistant/components/device_automation/trigger.py b/homeassistant/components/device_automation/trigger.py index 933c5c4c60a..c5f42b3e813 100644 --- a/homeassistant/components/device_automation/trigger.py +++ b/homeassistant/components/device_automation/trigger.py @@ -1,5 +1,8 @@ """Offer device oriented automation.""" -from typing import Protocol, cast +from __future__ import annotations + +from collections.abc import Awaitable +from typing import Any, Protocol, cast import voluptuous as vol @@ -33,7 +36,6 @@ class DeviceAutomationTriggerProtocol(Protocol): self, hass: HomeAssistant, config: ConfigType ) -> ConfigType: """Validate config.""" - raise NotImplementedError async def async_attach_trigger( self, @@ -43,7 +45,16 @@ class DeviceAutomationTriggerProtocol(Protocol): automation_info: AutomationTriggerInfo, ) -> CALLBACK_TYPE: """Attach a trigger.""" - raise NotImplementedError + + def async_get_trigger_capabilities( + self, hass: HomeAssistant, config: ConfigType + ) -> dict[str, vol.Schema] | Awaitable[dict[str, vol.Schema]]: + """List trigger capabilities.""" + + def async_get_triggers( + self, hass: HomeAssistant, device_id: str + ) -> list[dict[str, Any]] | Awaitable[list[dict[str, Any]]]: + """List triggers.""" async def async_validate_trigger_config( diff --git a/homeassistant/components/device_tracker/device_condition.py b/homeassistant/components/device_tracker/device_condition.py index 0703d3c7646..1a6adabda63 100644 --- a/homeassistant/components/device_tracker/device_condition.py +++ b/homeassistant/components/device_tracker/device_condition.py @@ -33,7 +33,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Device tracker devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) conditions = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/device_tracker/device_trigger.py b/homeassistant/components/device_tracker/device_trigger.py index 4350f03cefc..1d9dc4548b3 100644 --- a/homeassistant/components/device_tracker/device_trigger.py +++ b/homeassistant/components/device_tracker/device_trigger.py @@ -1,7 +1,7 @@ """Provides device automations for Device Tracker.""" from __future__ import annotations -from typing import Any, Final +from typing import Final import voluptuous as vol @@ -39,9 +39,9 @@ TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Device Tracker devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 01fc8a444e1..87026fc32ff 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -30,9 +30,12 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_per_platform, discovery -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers import ( + config_per_platform, + config_validation as cv, + discovery, + entity_registry as er, +) from homeassistant.helpers.event import ( async_track_time_interval, async_track_utc_time_change, @@ -487,7 +490,7 @@ class DeviceTracker: This method is a coroutine. """ - registry = await async_get_registry(self.hass) + registry = er.async_get(self.hass) if mac is None and dev_id is None: raise HomeAssistantError("Neither mac or device id passed in") if mac is not None: diff --git a/homeassistant/components/device_tracker/translations/es.json b/homeassistant/components/device_tracker/translations/es.json index eccef30e765..47948da66ec 100644 --- a/homeassistant/components/device_tracker/translations/es.json +++ b/homeassistant/components/device_tracker/translations/es.json @@ -12,8 +12,8 @@ "state": { "_": { "home": "En casa", - "not_home": "Fuera de casa" + "not_home": "Fuera" } }, - "title": "Rastreador de dispositivo" + "title": "Seguimiento de dispositivos" } \ No newline at end of file diff --git a/homeassistant/components/devolo_home_control/__init__.py b/homeassistant/components/devolo_home_control/__init__.py index 46b8f9dcaea..b79d0a5c8fe 100644 --- a/homeassistant/components/devolo_home_control/__init__.py +++ b/homeassistant/components/devolo_home_control/__init__.py @@ -15,6 +15,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers.device_registry import DeviceEntry from .const import ( CONF_MYDEVOLO, @@ -92,6 +93,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry +) -> bool: + """Remove a config entry from a device.""" + return True + + def configure_mydevolo(conf: dict[str, Any] | MappingProxyType[str, Any]) -> Mydevolo: """Configure mydevolo.""" mydevolo = Mydevolo() diff --git a/homeassistant/components/devolo_home_control/manifest.json b/homeassistant/components/devolo_home_control/manifest.json index 75cca12d99e..0de3cd7f07d 100644 --- a/homeassistant/components/devolo_home_control/manifest.json +++ b/homeassistant/components/devolo_home_control/manifest.json @@ -2,7 +2,7 @@ "domain": "devolo_home_control", "name": "devolo Home Control", "documentation": "https://www.home-assistant.io/integrations/devolo_home_control", - "requirements": ["devolo-home-control-api==0.18.1"], + "requirements": ["devolo-home-control-api==0.18.2"], "after_dependencies": ["zeroconf"], "config_flow": true, "codeowners": ["@2Fake", "@Shutgun"], diff --git a/homeassistant/components/devolo_home_control/translations/es.json b/homeassistant/components/devolo_home_control/translations/es.json index b4a7a873aaa..91a06530273 100644 --- a/homeassistant/components/devolo_home_control/translations/es.json +++ b/homeassistant/components/devolo_home_control/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { diff --git a/homeassistant/components/devolo_home_control/translations/nl.json b/homeassistant/components/devolo_home_control/translations/nl.json index c85c685597d..18ad384342b 100644 --- a/homeassistant/components/devolo_home_control/translations/nl.json +++ b/homeassistant/components/devolo_home_control/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/devolo_home_network/binary_sensor.py b/homeassistant/components/devolo_home_network/binary_sensor.py new file mode 100644 index 00000000000..6bc02d802f5 --- /dev/null +++ b/homeassistant/components/devolo_home_network/binary_sensor.py @@ -0,0 +1,99 @@ +"""Platform for binary sensor integration.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from devolo_plc_api.device import Device + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_PLUG, + BinarySensorEntity, + BinarySensorEntityDescription, +) +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 homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import CONNECTED_PLC_DEVICES, CONNECTED_TO_ROUTER, DOMAIN +from .entity import DevoloEntity + + +def _is_connected_to_router(entity: DevoloBinarySensorEntity) -> bool: + """Check, if device is attached to the router.""" + return all( + device["attached_to_router"] + for device in entity.coordinator.data["network"]["devices"] + if device["mac_address"] == entity.device.mac + ) + + +@dataclass +class DevoloBinarySensorRequiredKeysMixin: + """Mixin for required keys.""" + + value_func: Callable[[DevoloBinarySensorEntity], bool] + + +@dataclass +class DevoloBinarySensorEntityDescription( + BinarySensorEntityDescription, DevoloBinarySensorRequiredKeysMixin +): + """Describes devolo sensor entity.""" + + +SENSOR_TYPES: dict[str, DevoloBinarySensorEntityDescription] = { + CONNECTED_TO_ROUTER: DevoloBinarySensorEntityDescription( + key=CONNECTED_TO_ROUTER, + device_class=DEVICE_CLASS_PLUG, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + icon="mdi:router-network", + name="Connected to router", + value_func=_is_connected_to_router, + ), +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Get all devices and sensors and setup them via config entry.""" + device: Device = hass.data[DOMAIN][entry.entry_id]["device"] + coordinators: dict[str, DataUpdateCoordinator] = hass.data[DOMAIN][entry.entry_id][ + "coordinators" + ] + + entities: list[BinarySensorEntity] = [] + if device.plcnet: + entities.append( + DevoloBinarySensorEntity( + coordinators[CONNECTED_PLC_DEVICES], + SENSOR_TYPES[CONNECTED_TO_ROUTER], + device, + entry.title, + ) + ) + async_add_entities(entities) + + +class DevoloBinarySensorEntity(DevoloEntity, BinarySensorEntity): + """Representation of a devolo binary sensor.""" + + def __init__( + self, + coordinator: DataUpdateCoordinator, + description: DevoloBinarySensorEntityDescription, + device: Device, + device_name: str, + ) -> None: + """Initialize entity.""" + self.entity_description: DevoloBinarySensorEntityDescription = description + super().__init__(coordinator, device, device_name) + + @property + def is_on(self) -> bool: + """State of the binary sensor.""" + return self.entity_description.value_func(self) diff --git a/homeassistant/components/devolo_home_network/const.py b/homeassistant/components/devolo_home_network/const.py index bd7170bfde5..2dfdd3c1d9a 100644 --- a/homeassistant/components/devolo_home_network/const.py +++ b/homeassistant/components/devolo_home_network/const.py @@ -5,7 +5,7 @@ from datetime import timedelta from homeassistant.const import Platform DOMAIN = "devolo_home_network" -PLATFORMS = [Platform.SENSOR] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] PRODUCT = "product" SERIAL_NUMBER = "serial_number" @@ -15,5 +15,6 @@ LONG_UPDATE_INTERVAL = timedelta(minutes=5) SHORT_UPDATE_INTERVAL = timedelta(seconds=15) CONNECTED_PLC_DEVICES = "connected_plc_devices" +CONNECTED_TO_ROUTER = "connected_to_router" CONNECTED_WIFI_CLIENTS = "connected_wifi_clients" NEIGHBORING_WIFI_NETWORKS = "neighboring_wifi_networks" diff --git a/homeassistant/components/devolo_home_network/entity.py b/homeassistant/components/devolo_home_network/entity.py index dbfe0e4035a..dd26324bc2c 100644 --- a/homeassistant/components/devolo_home_network/entity.py +++ b/homeassistant/components/devolo_home_network/entity.py @@ -21,17 +21,14 @@ class DevoloEntity(CoordinatorEntity): """Initialize a devolo home network device.""" super().__init__(coordinator) - self._device = device - self._device_name = device_name + self.device = device self._attr_device_info = DeviceInfo( - configuration_url=f"http://{self._device.ip}", - identifiers={(DOMAIN, str(self._device.serial_number))}, + configuration_url=f"http://{device.ip}", + identifiers={(DOMAIN, str(device.serial_number))}, manufacturer="devolo", - model=self._device.product, - name=self._device_name, - sw_version=self._device.firmware_version, - ) - self._attr_unique_id = ( - f"{self._device.serial_number}_{self.entity_description.key}" + model=device.product, + name=device_name, + sw_version=device.firmware_version, ) + self._attr_unique_id = f"{device.serial_number}_{self.entity_description.key}" diff --git a/homeassistant/components/devolo_home_network/manifest.json b/homeassistant/components/devolo_home_network/manifest.json index 445a383ea14..94f26c8a615 100644 --- a/homeassistant/components/devolo_home_network/manifest.json +++ b/homeassistant/components/devolo_home_network/manifest.json @@ -3,7 +3,7 @@ "name": "devolo Home Network", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/devolo_home_network", - "requirements": ["devolo-plc-api==0.7.1"], + "requirements": ["devolo-plc-api==0.8.0"], "zeroconf": [ { "type": "_dvl-deviceapi._tcp.local.", "properties": { "MT": "*" } } ], diff --git a/homeassistant/components/devolo_home_network/translations/ko.json b/homeassistant/components/devolo_home_network/translations/ko.json new file mode 100644 index 00000000000..0e7d5b287f2 --- /dev/null +++ b/homeassistant/components/devolo_home_network/translations/ko.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + }, + "zeroconf_confirm": { + "title": "devolo \ud648 \ub124\ud2b8\uc6cc\ud06c \uc7a5\uce58 \ubc1c\uacac" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/devolo_home_network/translations/nl.json b/homeassistant/components/devolo_home_network/translations/nl.json index e8730f44b5e..af3d6e55365 100644 --- a/homeassistant/components/devolo_home_network/translations/nl.json +++ b/homeassistant/components/devolo_home_network/translations/nl.json @@ -14,7 +14,7 @@ "data": { "ip_address": "IP-adres" }, - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" }, "zeroconf_confirm": { "description": "Wilt u het devolo-thuisnetwerkapparaat met de hostnaam ` {host_name} ` aan Home Assistant toevoegen?", diff --git a/homeassistant/components/dexcom/translations/es.json b/homeassistant/components/dexcom/translations/es.json index 934c40be05e..24d06db3ee6 100644 --- a/homeassistant/components/dexcom/translations/es.json +++ b/homeassistant/components/dexcom/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada" + "already_configured": "La cuenta ya est\u00e1 configurada" }, "error": { "cannot_connect": "No se pudo conectar", diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index 1756f620f46..8581a5f2241 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -52,14 +52,11 @@ from homeassistant.helpers.event import ( async_track_state_added_domain, async_track_time_interval, ) -from homeassistant.helpers.frame import report from homeassistant.helpers.typing import ConfigType from homeassistant.loader import DHCPMatcher, async_get_dhcp from homeassistant.util.async_ import run_callback_threadsafe from homeassistant.util.network import is_invalid, is_link_local, is_loopback -from .const import DOMAIN - if TYPE_CHECKING: from scapy.packet import Packet from scapy.sendrecv import AsyncSniffer @@ -86,36 +83,6 @@ class DhcpServiceInfo(BaseServiceInfo): hostname: str macaddress: str - def __getitem__(self, name: str) -> Any: - """ - Enable method for compatibility reason. - - Deprecated, and will be removed in version 2022.6. - """ - report( - f"accessed discovery_info['{name}'] instead of discovery_info.{name}; " - "this will fail in version 2022.6", - exclude_integrations={DOMAIN}, - error_if_core=False, - ) - return getattr(self, name) - - def get(self, name: str, default: Any = None) -> Any: - """ - Enable method for compatibility reason. - - Deprecated, and will be removed in version 2022.6. - """ - report( - f"accessed discovery_info.get('{name}') instead of discovery_info.{name}; " - "this will fail in version 2022.6", - exclude_integrations={DOMAIN}, - error_if_core=False, - ) - if hasattr(self, name): - return getattr(self, name) - return default - async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the dhcp component.""" diff --git a/homeassistant/components/dialogflow/translations/es.json b/homeassistant/components/dialogflow/translations/es.json index ca2370f9086..8c2c5b4993e 100644 --- a/homeassistant/components/dialogflow/translations/es.json +++ b/homeassistant/components/dialogflow/translations/es.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No conectado a Home Assistant Cloud.", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n.", "webhook_not_internet_accessible": "Tu instancia de Home Assistant debe estar accesible desde Internet para recibir mensajes webhook." }, diff --git a/homeassistant/components/dialogflow/translations/fr.json b/homeassistant/components/dialogflow/translations/fr.json index 661d86566b0..56d754d7f0a 100644 --- a/homeassistant/components/dialogflow/translations/fr.json +++ b/homeassistant/components/dialogflow/translations/fr.json @@ -10,7 +10,7 @@ }, "step": { "user": { - "description": "\u00cates-vous s\u00fbr de vouloir configurer Dialogflow?", + "description": "Voulez-vous vraiment configurer Dialogflow\u00a0?", "title": "Configurer le Webhook Dialogflow" } } diff --git a/homeassistant/components/dialogflow/translations/nl.json b/homeassistant/components/dialogflow/translations/nl.json index 3d2617d25e5..7403e2c36dd 100644 --- a/homeassistant/components/dialogflow/translations/nl.json +++ b/homeassistant/components/dialogflow/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." + "webhook_not_internet_accessible": "Je Home Assistant-instantie moet toegankelijk zijn vanaf internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Om evenementen naar de Home Assistant te verzenden, moet u [webhookintegratie van Dialogflow]({dialogflow_url}) instellen. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nZie [de documentatie]({docs_url}) voor verdere informatie." diff --git a/homeassistant/components/directv/translations/es.json b/homeassistant/components/directv/translations/es.json index f1d896e698b..92cf160462c 100644 --- a/homeassistant/components/directv/translations/es.json +++ b/homeassistant/components/directv/translations/es.json @@ -7,7 +7,7 @@ "error": { "cannot_connect": "Error al conectar" }, - "flow_title": "DirecTV: {name}", + "flow_title": "{name}", "step": { "ssdp_confirm": { "description": "\u00bfQuieres configurar {name}?" diff --git a/homeassistant/components/discord/__init__.py b/homeassistant/components/discord/__init__.py index ec41ac9d073..ae06447f741 100644 --- a/homeassistant/components/discord/__init__.py +++ b/homeassistant/components/discord/__init__.py @@ -2,33 +2,17 @@ from aiohttp.client_exceptions import ClientConnectorError import nextcord -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_API_TOKEN, CONF_PLATFORM, Platform +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_TOKEN, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import discovery -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN PLATFORMS = [Platform.NOTIFY] -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the Discord component.""" - # Iterate all entries for notify to only get Discord - if Platform.NOTIFY in config: - for entry in config[Platform.NOTIFY]: - if entry[CONF_PLATFORM] == DOMAIN: - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=entry - ) - ) - - return True - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Discord from a config entry.""" nextcord.VoiceClient.warn_nacl = False diff --git a/homeassistant/components/discord/config_flow.py b/homeassistant/components/discord/config_flow.py index 8abd2a6be37..bce27feced3 100644 --- a/homeassistant/components/discord/config_flow.py +++ b/homeassistant/components/discord/config_flow.py @@ -8,7 +8,7 @@ import nextcord import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_API_TOKEN, CONF_NAME, CONF_TOKEN +from homeassistant.const import CONF_API_TOKEN, CONF_NAME from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, URL_PLACEHOLDER @@ -69,7 +69,7 @@ class DiscordFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._abort_if_unique_id_configured() return self.async_create_entry( title=info.name, - data=user_input | {CONF_NAME: user_input.get(CONF_NAME, info.name)}, + data=user_input | {CONF_NAME: info.name}, ) user_input = user_input or {} @@ -80,20 +80,6 @@ class DiscordFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, import_config: dict[str, str]) -> FlowResult: - """Import a config entry from configuration.yaml.""" - _LOGGER.warning( - "Configuration of the Discord integration 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" - ) - for entry in self._async_current_entries(): - if entry.data[CONF_API_TOKEN] == import_config[CONF_TOKEN]: - return self.async_abort(reason="already_configured") - import_config[CONF_API_TOKEN] = import_config.pop(CONF_TOKEN) - return await self.async_step_user(import_config) - async def _async_try_connect(token: str) -> tuple[str | None, nextcord.AppInfo | None]: """Try connecting to Discord.""" diff --git a/homeassistant/components/discord/notify.py b/homeassistant/components/discord/notify.py index 098857876a1..299919472cf 100644 --- a/homeassistant/components/discord/notify.py +++ b/homeassistant/components/discord/notify.py @@ -7,17 +7,14 @@ from typing import Any, cast import nextcord from nextcord.abc import Messageable -import voluptuous as vol from homeassistant.components.notify import ( ATTR_DATA, ATTR_TARGET, - PLATFORM_SCHEMA, BaseNotificationService, ) -from homeassistant.const import CONF_API_TOKEN, CONF_TOKEN +from homeassistant.const import CONF_API_TOKEN from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) @@ -33,9 +30,6 @@ ATTR_EMBED_THUMBNAIL = "thumbnail" ATTR_EMBED_URL = "url" ATTR_IMAGES = "images" -# Deprecated in Home Assistant 2022.4 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_TOKEN): cv.string}) - async def async_get_service( hass: HomeAssistant, diff --git a/homeassistant/components/discord/translations/es.json b/homeassistant/components/discord/translations/es.json new file mode 100644 index 00000000000..768afb877f3 --- /dev/null +++ b/homeassistant/components/discord/translations/es.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "reauth_successful": "Re-autenticaci\u00f3n realizada correctamente" + }, + "error": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "Token API" + }, + "description": "Consulta la documentaci\u00f3n para obtener tu clave de bote de Discord.\n\n{url}" + }, + "user": { + "data": { + "api_token": "Token API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/nl.json b/homeassistant/components/discord/translations/nl.json index 55fb894031d..b84af57af18 100644 --- a/homeassistant/components/discord/translations/nl.json +++ b/homeassistant/components/discord/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "already_configured": "Dienst is al geconfigureerd", + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/dlna_dmr/config_flow.py b/homeassistant/components/dlna_dmr/config_flow.py index ac7e3c83253..c7b37537e2b 100644 --- a/homeassistant/components/dlna_dmr/config_flow.py +++ b/homeassistant/components/dlna_dmr/config_flow.py @@ -21,6 +21,7 @@ from homeassistant.exceptions import IntegrationError import homeassistant.helpers.config_validation as cv from .const import ( + CONF_BROWSE_UNFILTERED, CONF_CALLBACK_URL_OVERRIDE, CONF_LISTEN_PORT, CONF_POLL_AVAILABILITY, @@ -328,6 +329,7 @@ class DlnaDmrOptionsFlowHandler(config_entries.OptionsFlow): options[CONF_LISTEN_PORT] = listen_port options[CONF_CALLBACK_URL_OVERRIDE] = callback_url_override options[CONF_POLL_AVAILABILITY] = user_input[CONF_POLL_AVAILABILITY] + options[CONF_BROWSE_UNFILTERED] = user_input[CONF_BROWSE_UNFILTERED] # Save if there's no errors, else fall through and show the form again if not errors: @@ -335,9 +337,14 @@ class DlnaDmrOptionsFlowHandler(config_entries.OptionsFlow): fields = {} - def _add_with_suggestion(key: str, validator: Callable) -> None: - """Add a field to with a suggested, not default, value.""" - if (suggested_value := options.get(key)) is None: + def _add_with_suggestion(key: str, validator: Callable | type[bool]) -> None: + """Add a field to with a suggested value. + + For bools, use the existing value as default, or fallback to False. + """ + if validator is bool: + fields[vol.Required(key, default=options.get(key, False))] = validator + elif (suggested_value := options.get(key)) is None: fields[vol.Optional(key)] = validator else: fields[ @@ -347,12 +354,8 @@ class DlnaDmrOptionsFlowHandler(config_entries.OptionsFlow): # listen_port can be blank or 0 for "bind any free port" _add_with_suggestion(CONF_LISTEN_PORT, cv.port) _add_with_suggestion(CONF_CALLBACK_URL_OVERRIDE, str) - fields[ - vol.Required( - CONF_POLL_AVAILABILITY, - default=options.get(CONF_POLL_AVAILABILITY, False), - ) - ] = bool + _add_with_suggestion(CONF_POLL_AVAILABILITY, bool) + _add_with_suggestion(CONF_BROWSE_UNFILTERED, bool) return self.async_show_form( step_id="init", diff --git a/homeassistant/components/dlna_dmr/const.py b/homeassistant/components/dlna_dmr/const.py index a4118a0ce78..4f9982061fb 100644 --- a/homeassistant/components/dlna_dmr/const.py +++ b/homeassistant/components/dlna_dmr/const.py @@ -16,6 +16,7 @@ DOMAIN: Final = "dlna_dmr" CONF_LISTEN_PORT: Final = "listen_port" CONF_CALLBACK_URL_OVERRIDE: Final = "callback_url_override" CONF_POLL_AVAILABILITY: Final = "poll_availability" +CONF_BROWSE_UNFILTERED: Final = "browse_unfiltered" DEFAULT_NAME: Final = "DLNA Digital Media Renderer" diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index e8cdb282118..15beea714da 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.29.0"], + "requirements": ["async-upnp-client==0.30.1"], "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 101b59c7125..9ecf9f8ad40 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -45,6 +45,7 @@ from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( + CONF_BROWSE_UNFILTERED, CONF_CALLBACK_URL_OVERRIDE, CONF_LISTEN_PORT, CONF_POLL_AVAILABILITY, @@ -61,18 +62,20 @@ from .data import EventListenAddr, get_domain_data PARALLEL_UPDATES = 0 -_T = TypeVar("_T", bound="DlnaDmrEntity") +_DlnaDmrEntityT = TypeVar("_DlnaDmrEntityT", bound="DlnaDmrEntity") _R = TypeVar("_R") _P = ParamSpec("_P") def catch_request_errors( - func: Callable[Concatenate[_T, _P], Awaitable[_R]] # type: ignore[misc] -) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, _R | None]]: # type: ignore[misc] + func: Callable[Concatenate[_DlnaDmrEntityT, _P], Awaitable[_R]] +) -> Callable[Concatenate[_DlnaDmrEntityT, _P], Coroutine[Any, Any, _R | None]]: """Catch UpnpError errors.""" @functools.wraps(func) - async def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R | None: + async def wrapper( + self: _DlnaDmrEntityT, *args: _P.args, **kwargs: _P.kwargs + ) -> _R | None: """Catch UpnpError errors and check availability before and after request.""" if not self.available: _LOGGER.warning( @@ -80,7 +83,7 @@ def catch_request_errors( ) return None try: - return await func(self, *args, **kwargs) # type: ignore[no-any-return] # mypy can't yet infer 'func' + return await func(self, *args, **kwargs) except UpnpError as err: self.check_available = True _LOGGER.error("Error during call %s: %r", func.__name__, err) @@ -106,6 +109,7 @@ async def async_setup_entry( event_callback_url=entry.options.get(CONF_CALLBACK_URL_OVERRIDE), poll_availability=entry.options.get(CONF_POLL_AVAILABILITY, False), location=entry.data[CONF_URL], + browse_unfiltered=entry.options.get(CONF_BROWSE_UNFILTERED, False), ) async_add_entities([entity]) @@ -122,6 +126,8 @@ class DlnaDmrEntity(MediaPlayerEntity): # Last known URL for the device, used when adding this entity to hass to try # to connect before SSDP has rediscovered it, or when SSDP discovery fails. location: str + # Should the async_browse_media function *not* filter out incompatible media? + browse_unfiltered: bool _device_lock: asyncio.Lock # Held when connecting or disconnecting the device _device: DmrDevice | None = None @@ -144,6 +150,7 @@ class DlnaDmrEntity(MediaPlayerEntity): event_callback_url: str | None, poll_availability: bool, location: str, + browse_unfiltered: bool, ) -> None: """Initialize DLNA DMR entity.""" self.udn = udn @@ -152,6 +159,7 @@ class DlnaDmrEntity(MediaPlayerEntity): self._event_addr = EventListenAddr(None, event_port, event_callback_url) self.poll_availability = poll_availability self.location = location + self.browse_unfiltered = browse_unfiltered self._device_lock = asyncio.Lock() async def async_added_to_hass(self) -> None: @@ -273,6 +281,7 @@ class DlnaDmrEntity(MediaPlayerEntity): ) self.location = entry.data[CONF_URL] self.poll_availability = entry.options.get(CONF_POLL_AVAILABILITY, False) + self.browse_unfiltered = entry.options.get(CONF_BROWSE_UNFILTERED, False) new_port = entry.options.get(CONF_LISTEN_PORT) or 0 new_callback_url = entry.options.get(CONF_CALLBACK_URL_OVERRIDE) @@ -588,7 +597,9 @@ class DlnaDmrEntity(MediaPlayerEntity): # If media is media_source, resolve it to url and MIME type, and maybe metadata if media_source.is_media_source_id(media_id): - sourced_media = await media_source.async_resolve_media(self.hass, media_id) + sourced_media = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_type = sourced_media.mime_type media_id = sourced_media.url _LOGGER.debug("sourced_media is %s", sourced_media) @@ -760,14 +771,21 @@ class DlnaDmrEntity(MediaPlayerEntity): # media_content_type is ignored; it's the content_type of the current # media_content_id, not the desired content_type of whomever is calling. - content_filter = self._get_content_filter() + if self.browse_unfiltered: + content_filter = None + else: + content_filter = self._get_content_filter() return await media_source.async_browse_media( self.hass, media_content_id, content_filter=content_filter ) def _get_content_filter(self) -> Callable[[BrowseMedia], bool]: - """Return a function that filters media based on what the renderer can play.""" + """Return a function that filters media based on what the renderer can play. + + The filtering is pretty loose; it's better to show something that can't + be played than hide something that can. + """ if not self._device or not self._device.sink_protocol_info: # Nothing is specified by the renderer, so show everything _LOGGER.debug("Get content filter with no device or sink protocol info") @@ -778,18 +796,25 @@ class DlnaDmrEntity(MediaPlayerEntity): # Renderer claims it can handle everything, so show everything return lambda _: True - # Convert list of things like "http-get:*:audio/mpeg:*" to just "audio/mpeg" - content_types: list[str] = [] + # Convert list of things like "http-get:*:audio/mpeg;codecs=mp3:*" + # to just "audio/mpeg" + content_types = set[str]() for protocol_info in self._device.sink_protocol_info: protocol, _, content_format, _ = protocol_info.split(":", 3) + # Transform content_format for better generic matching + content_format = content_format.lower().replace("/x-", "/", 1) + content_format = content_format.partition(";")[0] + if protocol in STREAMABLE_PROTOCOLS: - content_types.append(content_format) + content_types.add(content_format) - def _content_type_filter(item: BrowseMedia) -> bool: - """Filter media items by their content_type.""" - return item.media_content_type in content_types + def _content_filter(item: BrowseMedia) -> bool: + """Filter media items by their media_content_type.""" + content_type = item.media_content_type + content_type = content_type.lower().replace("/x-", "/", 1).partition(";")[0] + return content_type in content_types - return _content_type_filter + return _content_filter @property def media_title(self) -> str | None: diff --git a/homeassistant/components/dlna_dmr/strings.json b/homeassistant/components/dlna_dmr/strings.json index ac77009e0cb..d646f20f7a1 100644 --- a/homeassistant/components/dlna_dmr/strings.json +++ b/homeassistant/components/dlna_dmr/strings.json @@ -44,7 +44,8 @@ "data": { "listen_port": "Event listener port (random if not set)", "callback_url_override": "Event listener callback URL", - "poll_availability": "Poll for device availability" + "poll_availability": "Poll for device availability", + "browse_unfiltered": "Show incompatible media when browsing" } } }, diff --git a/homeassistant/components/dlna_dmr/translations/bg.json b/homeassistant/components/dlna_dmr/translations/bg.json index 00e64e1568d..4227d0b68f6 100644 --- a/homeassistant/components/dlna_dmr/translations/bg.json +++ b/homeassistant/components/dlna_dmr/translations/bg.json @@ -3,13 +3,11 @@ "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", "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", - "could_not_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 DLNA \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e", "incomplete_config": "\u0412 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u043b\u0438\u043f\u0441\u0432\u0430 \u0437\u0430\u0434\u044a\u043b\u0436\u0438\u0442\u0435\u043b\u043d\u0430 \u043f\u0440\u043e\u043c\u0435\u043d\u043b\u0438\u0432\u0430", "non_unique_id": "\u041d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0441\u0430 \u043d\u044f\u043a\u043e\u043b\u043a\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u0435\u0434\u0438\u043d \u0438 \u0441\u044a\u0449 \u0443\u043d\u0438\u043a\u0430\u043b\u0435\u043d \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440" }, "error": { - "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", - "could_not_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 DLNA \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "flow_title": "{name}", "step": { @@ -23,8 +21,7 @@ }, "user": { "data": { - "host": "\u0425\u043e\u0441\u0442", - "url": "URL" + "host": "\u0425\u043e\u0441\u0442" }, "title": "\u041e\u0442\u043a\u0440\u0438\u0442\u0438 DLNA DMR \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" } diff --git a/homeassistant/components/dlna_dmr/translations/ca.json b/homeassistant/components/dlna_dmr/translations/ca.json index 944af3bfebc..0bbdc0b3de8 100644 --- a/homeassistant/components/dlna_dmr/translations/ca.json +++ b/homeassistant/components/dlna_dmr/translations/ca.json @@ -4,7 +4,6 @@ "already_configured": "El dispositiu ja est\u00e0 configurat", "alternative_integration": "El dispositiu t\u00e9 millor compatibilitat amb una altra integraci\u00f3", "cannot_connect": "Ha fallat la connexi\u00f3", - "could_not_connect": "No s'ha pogut connectar amb el dispositiu DLNA", "discovery_error": "No s'ha pogut descobrir cap dispositiu DLNA coincident", "incomplete_config": "Falta una variable obligat\u00f2ria a la configuraci\u00f3", "non_unique_id": "S'han trobat diversos dispositius amb el mateix identificador \u00fanic", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", - "could_not_connect": "No s'ha pogut connectar amb el dispositiu DLNA", "not_dmr": "El dispositiu no \u00e9s un renderitzador de mitjans digitals compatible" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Amfitri\u00f3", - "url": "URL" + "host": "Amfitri\u00f3" }, "description": "Tria un dispositiu a configurar o deixeu-ho en blanc per introduir un URL", "title": "Dispositius descoberts DLNA DMR" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Mostra fitxers multim\u00e8dia incompatibles mentre navegui", "callback_url_override": "URL de crida de l'oient d'esdeveniments", "listen_port": "Port de l'oient d'esdeveniments (aleatori si no es defineix)", "poll_availability": "Sondeja per saber la disponibilitat del dispositiu" diff --git a/homeassistant/components/dlna_dmr/translations/cs.json b/homeassistant/components/dlna_dmr/translations/cs.json index 85c9a831dda..c9087b82ab7 100644 --- a/homeassistant/components/dlna_dmr/translations/cs.json +++ b/homeassistant/components/dlna_dmr/translations/cs.json @@ -7,11 +7,6 @@ "step": { "confirm": { "description": "Chcete za\u010d\u00edt nastavovat?" - }, - "user": { - "data": { - "url": "URL" - } } } } diff --git a/homeassistant/components/dlna_dmr/translations/de.json b/homeassistant/components/dlna_dmr/translations/de.json index e37786c401c..64cae60c13e 100644 --- a/homeassistant/components/dlna_dmr/translations/de.json +++ b/homeassistant/components/dlna_dmr/translations/de.json @@ -4,7 +4,6 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "alternative_integration": "Das Ger\u00e4t wird besser durch eine andere Integration unterst\u00fctzt", "cannot_connect": "Verbindung fehlgeschlagen", - "could_not_connect": "Verbindung zum DLNA-Ger\u00e4t fehlgeschlagen", "discovery_error": "Ein passendes DLNA-Ger\u00e4t konnte nicht gefunden werden", "incomplete_config": "In der Konfiguration fehlt eine erforderliche Variable", "non_unique_id": "Mehrere Ger\u00e4te mit derselben eindeutigen ID gefunden", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", - "could_not_connect": "Verbindung zum DLNA-Ger\u00e4t fehlgeschlagen", "not_dmr": "Ger\u00e4t ist kein unterst\u00fctzter Digital Media Renderer" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Host", - "url": "URL" + "host": "Host" }, "description": "W\u00e4hle ein zu konfigurierendes Ger\u00e4t oder lasse es leer, um eine URL einzugeben.", "title": "Erkannte DLNA-DMR-Ger\u00e4te" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Inkompatible Medien beim Durchsuchen anzeigen", "callback_url_override": "R\u00fcckruf-URL des Ereignis-Listeners", "listen_port": "Port des Ereignis-Listeners (zuf\u00e4llig, wenn nicht festgelegt)", "poll_availability": "Abfrage der Ger\u00e4teverf\u00fcgbarkeit" diff --git a/homeassistant/components/dlna_dmr/translations/el.json b/homeassistant/components/dlna_dmr/translations/el.json index 91a1d353bcc..884e1c51cb2 100644 --- a/homeassistant/components/dlna_dmr/translations/el.json +++ b/homeassistant/components/dlna_dmr/translations/el.json @@ -4,7 +4,6 @@ "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", "alternative_integration": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03ba\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc \u03ac\u03bb\u03bb\u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "could_not_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae DLNA", "discovery_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b1\u03bd\u03c4\u03af\u03c3\u03c4\u03bf\u03b9\u03c7\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 DLNA", "incomplete_config": "\u0391\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bb\u03b5\u03af\u03c0\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03bc\u03b5\u03c4\u03b1\u03b2\u03bb\u03b7\u03c4\u03ae", "non_unique_id": "\u0392\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ad\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03bc\u03b5 \u03c4\u03bf \u03af\u03b4\u03b9\u03bf \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "could_not_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae DLNA", "not_dmr": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03c8\u03b7\u03c6\u03b9\u03b1\u03ba\u03cc\u03c2 \u03b1\u03bd\u03b1\u03bc\u03b5\u03c4\u03b1\u03b4\u03cc\u03c4\u03b7\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ae \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 DLNA DMR" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03bc\u03b7 \u03c3\u03c5\u03bc\u03b2\u03b1\u03c4\u03ce\u03bd \u03bc\u03ad\u03c3\u03c9\u03bd \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03b9\u03ae\u03b3\u03b7\u03c3\u03b7", "callback_url_override": "URL \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2 \u03b1\u03ba\u03c1\u03bf\u03b1\u03c4\u03ae \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03bf\u03c2", "listen_port": "\u0398\u03cd\u03c1\u03b1 \u03b1\u03ba\u03c1\u03cc\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03c9\u03bd (\u03c4\u03c5\u03c7\u03b1\u03af\u03b1 \u03b1\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af)", "poll_availability": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b8\u03b5\u03c3\u03b9\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" diff --git a/homeassistant/components/dlna_dmr/translations/en.json b/homeassistant/components/dlna_dmr/translations/en.json index 512dfe7f11c..153f3f892d0 100644 --- a/homeassistant/components/dlna_dmr/translations/en.json +++ b/homeassistant/components/dlna_dmr/translations/en.json @@ -4,7 +4,6 @@ "already_configured": "Device is already configured", "alternative_integration": "Device is better supported by another integration", "cannot_connect": "Failed to connect", - "could_not_connect": "Failed to connect to DLNA device", "discovery_error": "Failed to discover a matching DLNA device", "incomplete_config": "Configuration is missing a required variable", "non_unique_id": "Multiple devices found with the same unique ID", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "Failed to connect", - "could_not_connect": "Failed to connect to DLNA device", "not_dmr": "Device is not a supported Digital Media Renderer" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Host", - "url": "URL" + "host": "Host" }, "description": "Choose a device to configure or leave blank to enter a URL", "title": "Discovered DLNA DMR devices" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Show incompatible media when browsing", "callback_url_override": "Event listener callback URL", "listen_port": "Event listener port (random if not set)", "poll_availability": "Poll for device availability" diff --git a/homeassistant/components/dlna_dmr/translations/es-419.json b/homeassistant/components/dlna_dmr/translations/es-419.json index 3dff7685122..14f0a98c68f 100644 --- a/homeassistant/components/dlna_dmr/translations/es-419.json +++ b/homeassistant/components/dlna_dmr/translations/es-419.json @@ -2,14 +2,12 @@ "config": { "abort": { "alternative_integration": "El dispositivo es mejor compatible con otra integraci\u00f3n", - "could_not_connect": "No se pudo conectar al dispositivo DLNA", "discovery_error": "Error al descubrir un dispositivo DLNA coincidente", "incomplete_config": "A la configuraci\u00f3n le falta una variable requerida", "non_unique_id": "Varios dispositivos encontrados con la misma ID \u00fanica", "not_dmr": "El dispositivo no es un renderizador de medios digitales compatible" }, "error": { - "could_not_connect": "No se pudo conectar al dispositivo DLNA", "not_dmr": "El dispositivo no es un renderizador de medios digitales compatible" }, "flow_title": "{name}", diff --git a/homeassistant/components/dlna_dmr/translations/es.json b/homeassistant/components/dlna_dmr/translations/es.json index 6c7bd0ae4e3..6bb9dd83dbd 100644 --- a/homeassistant/components/dlna_dmr/translations/es.json +++ b/homeassistant/components/dlna_dmr/translations/es.json @@ -4,7 +4,6 @@ "already_configured": "El dispositivo ya est\u00e1 configurado", "alternative_integration": "Dispositivo compatible con otra integraci\u00f3n", "cannot_connect": "No se pudo conectar", - "could_not_connect": "No se pudo conectar al dispositivo DLNA", "discovery_error": "No se ha podido descubrir un dispositivo DLNA coincidente", "incomplete_config": "A la configuraci\u00f3n le falta una variable necesaria", "non_unique_id": "Se han encontrado varios dispositivos con el mismo ID \u00fanico", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "No se pudo conectar", - "could_not_connect": "No se pudo conectar al dispositivo DLNA", "not_dmr": "El dispositivo no es un procesador de medios digitales compatible" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Host", - "url": "URL" + "host": "Host" }, "description": "Elija un dispositivo para configurar o d\u00e9jelo en blanco para introducir una URL", "title": "Dispositivos DLNA DMR descubiertos" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Muestra archivos multimedia incompatibles mientras navega", "callback_url_override": "URL de devoluci\u00f3n de llamada del detector de eventos", "listen_port": "Puerto de escucha de eventos (aleatorio si no se establece)", "poll_availability": "Sondeo para la disponibilidad del dispositivo" diff --git a/homeassistant/components/dlna_dmr/translations/et.json b/homeassistant/components/dlna_dmr/translations/et.json index b5019f5f9a1..adf665e4d2e 100644 --- a/homeassistant/components/dlna_dmr/translations/et.json +++ b/homeassistant/components/dlna_dmr/translations/et.json @@ -4,7 +4,6 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "alternative_integration": "Seadet toetab paremini teine sidumine", "cannot_connect": "\u00dchendamine nurjus", - "could_not_connect": "DLNA seadmega \u00fchenduse loomine nurjus", "discovery_error": "Sobiva DLNA -seadme leidmine nurjus", "incomplete_config": "Seadetes puudub n\u00f5utav muutuja", "non_unique_id": "Leiti mitu sama unikaalse ID-ga seadet", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "\u00dchendamine nurjus", - "could_not_connect": "DLNA seadmega \u00fchenduse loomine nurjus", "not_dmr": "Seade ei ole toetatud digitaalne meediumiedastusseade" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Host", - "url": "URL" + "host": "Host" }, "description": "Vali h\u00e4\u00e4lestatav seade v\u00f5i j\u00e4ta URL -i sisestamiseks t\u00fchjaks", "title": "Avastatud DLNA DMR-seadmed" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Kuva sirvimisel \u00fchildumatu meedia", "callback_url_override": "S\u00fcndmuse kuulaja URL", "listen_port": "S\u00fcndmuste kuulaja port (juhuslik kui pole m\u00e4\u00e4ratud)", "poll_availability": "K\u00fcsitle seadme saadavuse kohta" diff --git a/homeassistant/components/dlna_dmr/translations/fr.json b/homeassistant/components/dlna_dmr/translations/fr.json index 6554a3994d6..9d3ed1e9f0a 100644 --- a/homeassistant/components/dlna_dmr/translations/fr.json +++ b/homeassistant/components/dlna_dmr/translations/fr.json @@ -4,7 +4,6 @@ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "alternative_integration": "L'appareil est mieux pris en charge par une autre int\u00e9gration", "cannot_connect": "\u00c9chec de connexion", - "could_not_connect": "\u00c9chec de la connexion \u00e0 l'appareil DLNA", "discovery_error": "\u00c9chec de la d\u00e9couverte d'un appareil DLNA correspondant", "incomplete_config": "Il manque une variable requise dans la configuration", "non_unique_id": "Plusieurs appareils trouv\u00e9s avec le m\u00eame identifiant unique", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion", - "could_not_connect": "\u00c9chec de la connexion \u00e0 l'appareil DLNA", "not_dmr": "L'appareil n'est pas un moteur de rendu multim\u00e9dia num\u00e9rique pris en charge" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "H\u00f4te", - "url": "URL" + "host": "H\u00f4te" }, "description": "Choisissez un appareil \u00e0 configurer ou laissez vide pour saisir une URL", "title": "Appareils DLNA DMR d\u00e9couverts" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Afficher les fichiers multim\u00e9dias incompatibles lors de la navigation", "callback_url_override": "URL de rappel de l'\u00e9couteur d'\u00e9v\u00e9nement", "listen_port": "Port d'\u00e9coute d'\u00e9v\u00e9nement (al\u00e9atoire s'il n'est pas d\u00e9fini)", "poll_availability": "Sondage pour la disponibilit\u00e9 de l'appareil" diff --git a/homeassistant/components/dlna_dmr/translations/he.json b/homeassistant/components/dlna_dmr/translations/he.json index 7025721fa43..145b91f2646 100644 --- a/homeassistant/components/dlna_dmr/translations/he.json +++ b/homeassistant/components/dlna_dmr/translations/he.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "host": "\u05de\u05d0\u05e8\u05d7", - "url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8" + "host": "\u05de\u05d0\u05e8\u05d7" } } } diff --git a/homeassistant/components/dlna_dmr/translations/hu.json b/homeassistant/components/dlna_dmr/translations/hu.json index 5e2ba148602..e030d89530f 100644 --- a/homeassistant/components/dlna_dmr/translations/hu.json +++ b/homeassistant/components/dlna_dmr/translations/hu.json @@ -4,7 +4,6 @@ "already_configured": "Az eszk\u00f6z m\u00e1r be van konfigur\u00e1lva", "alternative_integration": "Az eszk\u00f6zt jobban t\u00e1mogatja egy m\u00e1sik integr\u00e1ci\u00f3", "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "could_not_connect": "Nem siker\u00fclt csatlakozni a DLNA-eszk\u00f6zh\u00f6z", "discovery_error": "Nem siker\u00fclt megfelel\u0151 DLNA-eszk\u00f6zt tal\u00e1lni", "incomplete_config": "A konfigur\u00e1ci\u00f3b\u00f3l hi\u00e1nyzik egy sz\u00fcks\u00e9ges \u00e9rt\u00e9k", "non_unique_id": "T\u00f6bb eszk\u00f6z tal\u00e1lhat\u00f3 ugyanazzal az egyedi azonos\u00edt\u00f3val", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "could_not_connect": "Nem siker\u00fclt csatlakozni a DLNA-eszk\u00f6zh\u00f6z", "not_dmr": "Az eszk\u00f6z nem digit\u00e1lis m\u00e9dia renderel\u0151" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "C\u00edm", - "url": "URL" + "host": "C\u00edm" }, "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" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Inkompatibilis m\u00e9dia megjelen\u00edt\u00e9se b\u00f6ng\u00e9sz\u00e9s k\u00f6zben", "callback_url_override": "Esem\u00e9nyfigyel\u0151 visszah\u00edv\u00e1si URL (callback)", "listen_port": "Esem\u00e9nyfigyel\u0151 port (v\u00e9letlenszer\u0171, ha nincs be\u00e1ll\u00edtva)", "poll_availability": "Eszk\u00f6z el\u00e9r\u00e9s\u00e9nek tesztel\u00e9se lek\u00e9rdez\u00e9ssel" diff --git a/homeassistant/components/dlna_dmr/translations/id.json b/homeassistant/components/dlna_dmr/translations/id.json index 152c4f73a56..ee35acf0d5a 100644 --- a/homeassistant/components/dlna_dmr/translations/id.json +++ b/homeassistant/components/dlna_dmr/translations/id.json @@ -4,7 +4,6 @@ "already_configured": "Perangkat sudah dikonfigurasi", "alternative_integration": "Perangkat dapat didukung lebih baik lewat integrasi lainnya", "cannot_connect": "Gagal terhubung", - "could_not_connect": "Gagal terhubung ke perangkat DLNA", "discovery_error": "Gagal menemukan perangkat DLNA yang cocok", "incomplete_config": "Konfigurasi tidak memiliki variabel yang diperlukan", "non_unique_id": "Beberapa perangkat ditemukan dengan ID unik yang sama", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "Gagal terhubung", - "could_not_connect": "Gagal terhubung ke perangkat DLNA", "not_dmr": "Perangkat bukan Digital Media Renderer yang didukung" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Host", - "url": "URL" + "host": "Host" }, "description": "Pilih perangkat untuk dikonfigurasi atau biarkan kosong untuk memasukkan URL", "title": "Perangkat DLNA DMR yang ditemukan" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Tampilkan media yang tidak kompatibel saat menjelajah", "callback_url_override": "URL panggilan balik pendengar peristiwa", "listen_port": "Port pendengar peristiwa (acak jika tidak diatur)", "poll_availability": "Polling untuk ketersediaan perangkat" diff --git a/homeassistant/components/dlna_dmr/translations/it.json b/homeassistant/components/dlna_dmr/translations/it.json index 545d3cadbcb..e9b725de2d3 100644 --- a/homeassistant/components/dlna_dmr/translations/it.json +++ b/homeassistant/components/dlna_dmr/translations/it.json @@ -4,7 +4,6 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "alternative_integration": "Il dispositivo \u00e8 meglio supportato da un'altra integrazione", "cannot_connect": "Impossibile connettersi", - "could_not_connect": "Impossibile connettersi al dispositivo DLNA", "discovery_error": "Impossibile individuare un dispositivo DLNA corrispondente", "incomplete_config": "Nella configurazione manca una variabile richiesta", "non_unique_id": "Pi\u00f9 dispositivi trovati con lo stesso ID univoco", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "Impossibile connettersi", - "could_not_connect": "Impossibile connettersi al dispositivo DLNA", "not_dmr": "Il dispositivo non \u00e8 un Digital Media Renderer supportato" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Host", - "url": "URL" + "host": "Host" }, "description": "Scegli un dispositivo da configurare o lascia vuoto per inserire un URL", "title": "Rilevati dispositivi DLNA DMR" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Mostra file multimediali incompatibili durante la navigazione", "callback_url_override": "URL di richiamata dell'ascoltatore di eventi", "listen_port": "Porta dell'ascoltatore di eventi (casuale se non impostata)", "poll_availability": "Interrogazione per la disponibilit\u00e0 del dispositivo" diff --git a/homeassistant/components/dlna_dmr/translations/ja.json b/homeassistant/components/dlna_dmr/translations/ja.json index 9edb9156534..73e242dbb36 100644 --- a/homeassistant/components/dlna_dmr/translations/ja.json +++ b/homeassistant/components/dlna_dmr/translations/ja.json @@ -4,7 +4,6 @@ "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", "alternative_integration": "\u30c7\u30d0\u30a4\u30b9\u306f\u5225\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u3001\u3088\u308a\u9069\u5207\u306b\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u3059", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", - "could_not_connect": "DLNA\u30c7\u30d0\u30a4\u30b9\u3078\u306e\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "discovery_error": "\u4e00\u81f4\u3059\u308bDLNA \u30c7\u30d0\u30a4\u30b9\u3092\u691c\u51fa\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f", "incomplete_config": "\u8a2d\u5b9a\u306b\u5fc5\u8981\u306a\u5909\u6570\u304c\u3042\u308a\u307e\u305b\u3093", "non_unique_id": "\u540c\u4e00\u306eID\u3067\u8907\u6570\u306e\u30c7\u30d0\u30a4\u30b9\u304c\u691c\u51fa\u3055\u308c\u307e\u3057\u305f", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", - "could_not_connect": "DLNA\u30c7\u30d0\u30a4\u30b9\u3078\u306e\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "not_dmr": "\u30c7\u30d0\u30a4\u30b9\u304c\u3001\u672a\u30b5\u30dd\u30fc\u30c8\u306aDigital Media Renderer\u3067\u3059" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "\u30db\u30b9\u30c8", - "url": "URL" + "host": "\u30db\u30b9\u30c8" }, "description": "\u8a2d\u5b9a\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3059\u308b\u304b\u3001\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u3066URL\u3092\u5165\u529b\u3057\u307e\u3059\u3002", "title": "\u767a\u898b\u3055\u308c\u305fDLNA DMR\u6a5f\u5668" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "\u30d6\u30e9\u30a6\u30ba\u6642\u306b\u4e92\u63db\u6027\u306e\u306a\u3044\u30e1\u30c7\u30a3\u30a2\u3092\u8868\u793a\u3059\u308b", "callback_url_override": "\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30ca\u30fc\u306e\u30b3\u30fc\u30eb\u30d0\u30c3\u30afURL", "listen_port": "\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30ca\u30fc\u30dd\u30fc\u30c8(\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u30e9\u30f3\u30c0\u30e0)", "poll_availability": "\u30c7\u30d0\u30a4\u30b9\u306e\u53ef\u7528\u6027\u3092\u30dd\u30fc\u30ea\u30f3\u30b0" diff --git a/homeassistant/components/dlna_dmr/translations/ko.json b/homeassistant/components/dlna_dmr/translations/ko.json new file mode 100644 index 00000000000..c25d0fcabfc --- /dev/null +++ b/homeassistant/components/dlna_dmr/translations/ko.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "alternative_integration": "\uae30\uae30\uac00 \ub2e4\ub978 \ud1b5\ud569\uad6c\uc131\uc694\uc18c\uc5d0\uc11c \ub354 \uc798 \uc9c0\uc6d0\ub429\ub2c8\ub2e4." + }, + "step": { + "confirm": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + }, + "manual": { + "description": "\uc7a5\uce58 \uc124\uba85 XML \ud30c\uc77c\uc758 URL" + }, + "user": { + "title": "DLNA DMR \uc7a5\uce58 \ubc1c\uacac" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "browse_unfiltered": "\ud638\ud658\ub418\uc9c0 \uc54a\ub294 \ubbf8\ub514\uc5b4 \ud45c\uc2dc" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dmr/translations/nl.json b/homeassistant/components/dlna_dmr/translations/nl.json index 5331f3340dd..adc554b0fbc 100644 --- a/homeassistant/components/dlna_dmr/translations/nl.json +++ b/homeassistant/components/dlna_dmr/translations/nl.json @@ -4,7 +4,6 @@ "already_configured": "Apparaat is al geconfigureerd", "alternative_integration": "Apparaat wordt beter ondersteund door een andere integratie", "cannot_connect": "Kan geen verbinding maken", - "could_not_connect": "Mislukt om te verbinden met DNLA apparaat", "discovery_error": "Kan geen overeenkomend DLNA-apparaat vinden", "incomplete_config": "Configuratie mist een vereiste variabele", "non_unique_id": "Meerdere apparaten gevonden met hetzelfde unieke ID", @@ -12,13 +11,12 @@ }, "error": { "cannot_connect": "Kan geen verbinding maken", - "could_not_connect": "Mislukt om te verbinden met DNLA apparaat", "not_dmr": "Apparaat is een niet-ondersteund Digital Media Renderer" }, "flow_title": "{name}", "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" }, "import_turn_on": { "description": "Zet het apparaat aan en klik op verzenden om door te gaan met de migratie" @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Host", - "url": "URL" + "host": "Host" }, "description": "Kies een apparaat om te configureren of laat leeg om een URL in te voeren", "title": "Ontdekt DLNA Digital Media Renderer" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Incompatibele media weergeven tijdens browsen", "callback_url_override": "Event listener callback URL", "listen_port": "Poort om naar gebeurtenissen te luisteren (willekeurige poort indien niet ingesteld)", "poll_availability": "Pollen voor apparaat beschikbaarheid" diff --git a/homeassistant/components/dlna_dmr/translations/no.json b/homeassistant/components/dlna_dmr/translations/no.json index a1ce1fdce32..a1f4fb9b7cc 100644 --- a/homeassistant/components/dlna_dmr/translations/no.json +++ b/homeassistant/components/dlna_dmr/translations/no.json @@ -4,7 +4,6 @@ "already_configured": "Enheten er allerede konfigurert", "alternative_integration": "Enheten st\u00f8ttes bedre av en annen integrasjon", "cannot_connect": "Tilkobling mislyktes", - "could_not_connect": "Kunne ikke koble til DLNA -enhet", "discovery_error": "Kunne ikke finne en matchende DLNA -enhet", "incomplete_config": "Konfigurasjonen mangler en n\u00f8dvendig variabel", "non_unique_id": "Flere enheter ble funnet med samme unike ID", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "Tilkobling mislyktes", - "could_not_connect": "Kunne ikke koble til DLNA -enhet", "not_dmr": "Enheten er ikke en st\u00f8ttet Digital Media Renderer" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Vert", - "url": "URL" + "host": "Vert" }, "description": "Velg en enhet du vil konfigurere, eller la den st\u00e5 tom for \u00e5 angi en URL", "title": "Oppdaget DLNA DMR -enheter" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Vis inkompatible medier n\u00e5r du surfer", "callback_url_override": "URL for tilbakeringing av hendelseslytter", "listen_port": "Hendelseslytterport (tilfeldig hvis den ikke er angitt)", "poll_availability": "Avstemning for tilgjengelighet av enheter" diff --git a/homeassistant/components/dlna_dmr/translations/pl.json b/homeassistant/components/dlna_dmr/translations/pl.json index 7f831c92f99..dae2b58e1c9 100644 --- a/homeassistant/components/dlna_dmr/translations/pl.json +++ b/homeassistant/components/dlna_dmr/translations/pl.json @@ -4,7 +4,6 @@ "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "alternative_integration": "Urz\u0105dzenie jest lepiej obs\u0142ugiwane przez inn\u0105 integracj\u0119", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "could_not_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem DLNA", "discovery_error": "Nie uda\u0142o si\u0119 wykry\u0107 pasuj\u0105cego urz\u0105dzenia DLNA", "incomplete_config": "W konfiguracji brakuje wymaganej zmiennej", "non_unique_id": "Znaleziono wiele urz\u0105dze\u0144 z tym samym unikalnym identyfikatorem", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "could_not_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem DLNA", "not_dmr": "Urz\u0105dzenie nie jest obs\u0142ugiwanym rendererem multimedi\u00f3w cyfrowych (DMR)" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Nazwa hosta lub adres IP", - "url": "URL" + "host": "Nazwa hosta lub adres IP" }, "description": "Wybierz urz\u0105dzenie do skonfigurowania lub pozostaw puste, aby wprowadzi\u0107 adres URL", "title": "Wykryto urz\u0105dzenia DLNA DMR" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Poka\u017c niezgodne multimedia podczas przegl\u0105dania", "callback_url_override": "Adres Callback URL dla detektora zdarze\u0144", "listen_port": "Port detektora zdarze\u0144 (losowy, je\u015bli nie jest ustawiony)", "poll_availability": "Sondowanie na dost\u0119pno\u015b\u0107 urz\u0105dze\u0144" diff --git a/homeassistant/components/dlna_dmr/translations/pt-BR.json b/homeassistant/components/dlna_dmr/translations/pt-BR.json index 0df5a60e565..f5f8370e52c 100644 --- a/homeassistant/components/dlna_dmr/translations/pt-BR.json +++ b/homeassistant/components/dlna_dmr/translations/pt-BR.json @@ -4,7 +4,6 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "alternative_integration": "O dispositivo \u00e9 melhor suportado por outra integra\u00e7\u00e3o", "cannot_connect": "Falha ao conectar", - "could_not_connect": "Falha ao conectar ao dispositivo DLNA", "discovery_error": "Falha ao descobrir um dispositivo DLNA correspondente", "incomplete_config": "A configura\u00e7\u00e3o n\u00e3o tem uma vari\u00e1vel obrigat\u00f3ria", "non_unique_id": "V\u00e1rios dispositivos encontrados com o mesmo ID exclusivo", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "Falha ao conectar", - "could_not_connect": "Falha ao conectar-se ao dispositivo DLNA", "not_dmr": "O dispositivo n\u00e3o \u00e9 um renderizador de m\u00eddia digital compat\u00edvel" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Nome do host", - "url": "URL" + "host": "Nome do host" }, "description": "Escolha um dispositivo para configurar ou deixe em branco para inserir um URL", "title": "Dispositivos DMR DLNA descobertos" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Mostrar m\u00eddia incompat\u00edvel ao navegar", "callback_url_override": "URL de retorno do ouvinte de eventos", "listen_port": "Porta do ouvinte de eventos (aleat\u00f3rio se n\u00e3o estiver definido)", "poll_availability": "Pesquisa de disponibilidade do dispositivo" diff --git a/homeassistant/components/dlna_dmr/translations/ru.json b/homeassistant/components/dlna_dmr/translations/ru.json index d8931e268a0..7028b754d5d 100644 --- a/homeassistant/components/dlna_dmr/translations/ru.json +++ b/homeassistant/components/dlna_dmr/translations/ru.json @@ -4,7 +4,6 @@ "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.", "alternative_integration": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043b\u0443\u0447\u0448\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u0440\u0443\u0433\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0435\u0439.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", - "could_not_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443.", "discovery_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0435\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e DLNA.", "incomplete_config": "\u0412 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f.", "non_unique_id": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0441 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c.", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", - "could_not_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443.", "not_dmr": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 Digital Media Renderer." }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "\u0425\u043e\u0441\u0442", - "url": "URL-\u0430\u0434\u0440\u0435\u0441" + "host": "\u0425\u043e\u0441\u0442" }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u043b\u0438 \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0432\u0432\u0435\u0441\u0442\u0438 URL.", "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 DLNA DMR" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u044b\u0435 \u043c\u0435\u0434\u0438\u0430", "callback_url_override": "Callback URL \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439", "listen_port": "\u041f\u043e\u0440\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 (\u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d)", "poll_availability": "\u041e\u043f\u0440\u043e\u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" diff --git a/homeassistant/components/dlna_dmr/translations/sv.json b/homeassistant/components/dlna_dmr/translations/sv.json new file mode 100644 index 00000000000..8f95dda75db --- /dev/null +++ b/homeassistant/components/dlna_dmr/translations/sv.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "browse_unfiltered": "Visa inkompatibla media n\u00e4r du surfar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dmr/translations/tr.json b/homeassistant/components/dlna_dmr/translations/tr.json index 59c5221b870..26d911056b7 100644 --- a/homeassistant/components/dlna_dmr/translations/tr.json +++ b/homeassistant/components/dlna_dmr/translations/tr.json @@ -4,7 +4,6 @@ "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", "alternative_integration": "Cihaz ba\u015fka bir entegrasyon taraf\u0131ndan daha iyi destekleniyor", "cannot_connect": "Ba\u011flanma hatas\u0131", - "could_not_connect": "DLNA cihaz\u0131na ba\u011flan\u0131lamad\u0131", "discovery_error": "E\u015fle\u015fen bir DLNA cihaz\u0131 bulunamad\u0131", "incomplete_config": "Yap\u0131land\u0131rmada gerekli bir de\u011fi\u015fken eksik", "non_unique_id": "Ayn\u0131 benzersiz kimli\u011fe sahip birden fazla cihaz bulundu", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", - "could_not_connect": "DLNA cihaz\u0131na ba\u011flan\u0131lamad\u0131", "not_dmr": "Cihaz, desteklenen bir Dijital Medya Olu\u015fturucu de\u011fil" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "Sunucu", - "url": "URL" + "host": "Sunucu" }, "description": "Yap\u0131land\u0131rmak i\u00e7in bir cihaz se\u00e7in veya bir URL girmek i\u00e7in bo\u015f b\u0131rak\u0131n", "title": "Ke\u015ffedilen DLNA DMR cihazlar\u0131" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "Tarama s\u0131ras\u0131nda uyumsuz medyay\u0131 g\u00f6ster", "callback_url_override": "Olay dinleyici geri \u00e7a\u011f\u0131rma URL'si", "listen_port": "Olay dinleyici ba\u011flant\u0131 noktas\u0131 (ayarlanmam\u0131\u015fsa rastgele)", "poll_availability": "Cihaz kullan\u0131labilirli\u011fi i\u00e7in anket" diff --git a/homeassistant/components/dlna_dmr/translations/zh-Hans.json b/homeassistant/components/dlna_dmr/translations/zh-Hans.json index 2046f1c2a47..16bd75d28bf 100644 --- a/homeassistant/components/dlna_dmr/translations/zh-Hans.json +++ b/homeassistant/components/dlna_dmr/translations/zh-Hans.json @@ -4,7 +4,6 @@ "already_configured": "\u8bbe\u5907\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86", "alternative_integration": "\u8be5\u8bbe\u5907\u5728\u53e6\u4e00\u96c6\u6210\u80fd\u63d0\u4f9b\u66f4\u597d\u7684\u652f\u6301", "cannot_connect": "\u8fde\u63a5\u5931\u8d25", - "could_not_connect": "\u8fde\u63a5 DLNA \u8bbe\u5907\u5931\u8d25", "discovery_error": "\u672a\u53d1\u73b0\u53ef\u7528\u7684 DLNA \u8bbe\u5907", "incomplete_config": "\u914d\u7f6e\u7f3a\u5c11\u5fc5\u8981\u7684\u53d8\u91cf\u4fe1\u606f", "non_unique_id": "\u53d1\u73b0\u591a\u53f0\u8bbe\u5907\u5177\u6709\u76f8\u540c\u7684 unique ID", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "\u8fde\u63a5\u5931\u8d25", - "could_not_connect": "\u65e0\u6cd5\u8fde\u63a5\u5230 DLNA \u8bbe\u5907", "not_dmr": "\u8be5\u8bbe\u5907\u4e0d\u662f\u53d7\u652f\u6301\u7684\u6570\u5b57\u5a92\u4f53\u6e32\u67d3\u5668\uff08DMR\uff09" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "\u4e3b\u673a", - "url": "\u7f51\u5740" + "host": "\u4e3b\u673a" }, "title": "\u53d1\u73b0 DLNA DMR \u8bbe\u5907" } diff --git a/homeassistant/components/dlna_dmr/translations/zh-Hant.json b/homeassistant/components/dlna_dmr/translations/zh-Hant.json index f085767565d..07293607685 100644 --- a/homeassistant/components/dlna_dmr/translations/zh-Hant.json +++ b/homeassistant/components/dlna_dmr/translations/zh-Hant.json @@ -4,7 +4,6 @@ "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "alternative_integration": "\u4f7f\u7528\u5176\u4ed6\u6574\u5408\u4ee5\u53d6\u5f97\u66f4\u4f73\u7684\u88dd\u7f6e\u652f\u63f4", "cannot_connect": "\u9023\u7dda\u5931\u6557", - "could_not_connect": "DLNA \u88dd\u7f6e\u9023\u7dda\u5931\u6557\u3002", "discovery_error": "DLNA \u88dd\u7f6e\u641c\u7d22\u5931\u6557", "incomplete_config": "\u6240\u7f3a\u5c11\u7684\u8a2d\u5b9a\u70ba\u5fc5\u9808\u8b8a\u6578", "non_unique_id": "\u627e\u5230\u591a\u7d44\u88dd\u7f6e\u4f7f\u7528\u4e86\u76f8\u540c\u552f\u4e00 ID", @@ -12,7 +11,6 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "could_not_connect": "DLNA \u88dd\u7f6e\u9023\u7dda\u5931\u6557\u3002", "not_dmr": "\u88dd\u7f6e\u70ba\u975e\u652f\u63f4 Digital Media Renderer" }, "flow_title": "{name}", @@ -32,8 +30,7 @@ }, "user": { "data": { - "host": "\u4e3b\u6a5f\u7aef", - "url": "\u7db2\u5740" + "host": "\u4e3b\u6a5f\u7aef" }, "description": "\u9078\u64c7\u88dd\u7f6e\u9032\u884c\u8a2d\u5b9a\u6216\u4fdd\u7559\u7a7a\u767d\u4ee5\u8f38\u5165 URL", "title": "\u5df2\u767c\u73fe\u7684 DLNA DMR \u88dd\u7f6e" @@ -47,6 +44,7 @@ "step": { "init": { "data": { + "browse_unfiltered": "\u7576\u700f\u89bd\u6642\u986f\u793a\u4e0d\u76f8\u5bb9\u5a92\u9ad4", "callback_url_override": "\u4e8b\u4ef6\u76e3\u807d\u56de\u547c URL", "listen_port": "\u4e8b\u4ef6\u76e3\u807d\u901a\u8a0a\u57e0\uff08\u672a\u8a2d\u7f6e\u5247\u70ba\u96a8\u6a5f\uff09", "poll_availability": "\u67e5\u8a62\u88dd\u7f6e\u53ef\u7528\u6027" diff --git a/homeassistant/components/dlna_dms/manifest.json b/homeassistant/components/dlna_dms/manifest.json index 20c38a5be3f..21329440788 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.29.0"], + "requirements": ["async-upnp-client==0.30.1"], "dependencies": ["ssdp"], "after_dependencies": ["media_source"], "ssdp": [ diff --git a/homeassistant/components/dlna_dms/translations/es.json b/homeassistant/components/dlna_dms/translations/es.json index b4bc90a666a..1ce13967ea5 100644 --- a/homeassistant/components/dlna_dms/translations/es.json +++ b/homeassistant/components/dlna_dms/translations/es.json @@ -1,10 +1,15 @@ { "config": { "abort": { + "bad_ssdp": "Falta un valor necesario en los datos SSDP", "no_devices_found": "No se han encontrado dispositivos en la red" }, + "flow_title": "{name}", "step": { "user": { + "data": { + "host": "Host" + }, "description": "Escoge un dispositivo a configurar" } } diff --git a/homeassistant/components/dlna_dms/translations/ko.json b/homeassistant/components/dlna_dms/translations/ko.json new file mode 100644 index 00000000000..853a3eb2b1a --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/ko.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + }, + "user": { + "title": "DLNA DMA \uc7a5\uce58 \ubc1c\uacac" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/nl.json b/homeassistant/components/dlna_dms/translations/nl.json index d480118b76a..2edd4c5d2a6 100644 --- a/homeassistant/components/dlna_dms/translations/nl.json +++ b/homeassistant/components/dlna_dms/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "bad_ssdp": "SSDP-gegevens missen een vereiste waarde", "no_devices_found": "Geen apparaten gevonden op het netwerk", "not_dms": "Apparaat is geen ondersteunde mediaserver" @@ -10,7 +10,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" }, "user": { "data": { diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json index 2ea550c3792..ec170733e44 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.1.0"], + "requirements": ["pydoods==1.0.2", "pillow==9.1.1"], "codeowners": [], "iot_class": "local_polling", "loggers": ["pydoods"] diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 8a2c06c7157..133c7612a89 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -287,7 +287,7 @@ class ConfiguredDoorBird: self.device.change_favorite("http", f"Home Assistant ({event})", url) if not self.webhook_is_registered(url): _LOGGER.warning( - 'Unable to set favorite URL "%s". ' 'Event "%s" will not fire', + 'Unable to set favorite URL "%s". Event "%s" will not fire', url, event, ) diff --git a/homeassistant/components/doorbird/logbook.py b/homeassistant/components/doorbird/logbook.py index a4889360d81..110d3f22fbf 100644 --- a/homeassistant/components/doorbird/logbook.py +++ b/homeassistant/components/doorbird/logbook.py @@ -1,5 +1,10 @@ """Describe logbook events.""" +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_ENTITY_ID, + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import callback @@ -16,11 +21,11 @@ def async_describe_events(hass, async_describe_event): doorbird_event = event.event_type.split("_", 1)[1] return { - "name": "Doorbird", - "message": f"Event {event.event_type} was fired.", - "entity_id": hass.data[DOMAIN][DOOR_STATION_EVENT_ENTITY_IDS].get( - doorbird_event, event.data.get(ATTR_ENTITY_ID) - ), + LOGBOOK_ENTRY_NAME: "Doorbird", + LOGBOOK_ENTRY_MESSAGE: f"Event {event.event_type} was fired", + LOGBOOK_ENTRY_ENTITY_ID: hass.data[DOMAIN][ + DOOR_STATION_EVENT_ENTITY_IDS + ].get(doorbird_event, event.data.get(ATTR_ENTITY_ID)), } domain_data = hass.data[DOMAIN] diff --git a/homeassistant/components/doorbird/translations/ca.json b/homeassistant/components/doorbird/translations/ca.json index 0049709caa3..14c5f5f452d 100644 --- a/homeassistant/components/doorbird/translations/ca.json +++ b/homeassistant/components/doorbird/translations/ca.json @@ -18,8 +18,7 @@ "name": "Nom del dispositiu", "password": "[%key::common::config_flow::data::password%]", "username": "[%key::common::config_flow::data::username%]" - }, - "title": "Connexi\u00f3 amb DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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/cs.json b/homeassistant/components/doorbird/translations/cs.json index fea0647ec85..2b09c0648d0 100644 --- a/homeassistant/components/doorbird/translations/cs.json +++ b/homeassistant/components/doorbird/translations/cs.json @@ -18,8 +18,7 @@ "name": "Jm\u00e9no za\u0159\u00edzen\u00ed", "password": "Heslo", "username": "U\u017eivatelsk\u00e9 jm\u00e9no" - }, - "title": "P\u0159ipojen\u00ed k DoorBird" + } } } }, @@ -28,8 +27,7 @@ "init": { "data": { "events": "Seznam ud\u00e1lost\u00ed odd\u011blen\u00fdch \u010d\u00e1rkami." - }, - "description": "Zadejte n\u00e1zvy ud\u00e1lost\u00ed odd\u011blen\u00e9 \u010d\u00e1rkou, kter\u00e9 chcete sledovat. Po jejich zad\u00e1n\u00ed je pomoc\u00ed aplikace DoorBird p\u0159i\u0159a\u010fte ke konkr\u00e9tn\u00ed ud\u00e1losti. Viz dokumentace na https://www.home-assistant.io/integrations/doorbird/#events. P\u0159\u00edklad: nekdo_stiskl_tlacitko, pohyb" + } } } } diff --git a/homeassistant/components/doorbird/translations/de.json b/homeassistant/components/doorbird/translations/de.json index 7a60a3bf4ae..9689b0bc728 100644 --- a/homeassistant/components/doorbird/translations/de.json +++ b/homeassistant/components/doorbird/translations/de.json @@ -18,8 +18,7 @@ "name": "Ger\u00e4tename", "password": "Passwort", "username": "Benutzername" - }, - "title": "Stelle eine Verbindung zu DoorBird her" + } } } }, @@ -31,8 +30,7 @@ }, "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 da06dc3e0be..1805e57e70a 100644 --- a/homeassistant/components/doorbird/translations/el.json +++ b/homeassistant/components/doorbird/translations/el.json @@ -18,8 +18,7 @@ "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", "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" - }, - "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03bf DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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 ee005dfbfed..b5df9f89257 100644 --- a/homeassistant/components/doorbird/translations/en.json +++ b/homeassistant/components/doorbird/translations/en.json @@ -18,8 +18,7 @@ "name": "Device Name", "password": "Password", "username": "Username" - }, - "title": "Connect to the DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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/es-419.json b/homeassistant/components/doorbird/translations/es-419.json index 6b893b93014..9eed0664e00 100644 --- a/homeassistant/components/doorbird/translations/es-419.json +++ b/homeassistant/components/doorbird/translations/es-419.json @@ -14,8 +14,7 @@ "data": { "host": "Host (direcci\u00f3n IP)", "name": "Nombre del dispositivo" - }, - "title": "Conectar con DoorBird" + } } } }, @@ -24,8 +23,7 @@ "init": { "data": { "events": "Lista de eventos separados por comas." - }, - "description": "Agregue un nombre de evento separado por comas para cada evento que desee rastrear. Despu\u00e9s de ingresarlos aqu\u00ed, use la aplicaci\u00f3n DoorBird para asignarlos a un evento espec\u00edfico. Consulte la documentaci\u00f3n en https://www.home-assistant.io/integrations/doorbird/#events. Ejemplo: somebody_pressed_the_button, motion" + } } } } diff --git a/homeassistant/components/doorbird/translations/es.json b/homeassistant/components/doorbird/translations/es.json index f7e2ba8d92c..68de2419d2a 100644 --- a/homeassistant/components/doorbird/translations/es.json +++ b/homeassistant/components/doorbird/translations/es.json @@ -18,8 +18,7 @@ "name": "Nombre del dispositivo", "password": "Contrase\u00f1a", "username": "Usuario" - }, - "title": "Conectar con DoorBird" + } } } }, @@ -28,8 +27,7 @@ "init": { "data": { "events": "Lista de eventos separados por comas." - }, - "description": "A\u00f1ade un nombre de evento separado por comas para cada evento del que deseas realizar un seguimiento. Despu\u00e9s de introducirlos aqu\u00ed, utiliza la aplicaci\u00f3n DoorBird para asignarlos a un evento espec\u00edfico. Consulta la documentaci\u00f3n en https://www.home-assistant.io/integrations/doorbird/#events. Ejemplo: somebody_pressed_the_button, motion" + } } } } diff --git a/homeassistant/components/doorbird/translations/et.json b/homeassistant/components/doorbird/translations/et.json index f37d5d0244b..01601861ffc 100644 --- a/homeassistant/components/doorbird/translations/et.json +++ b/homeassistant/components/doorbird/translations/et.json @@ -18,8 +18,7 @@ "name": "Seadme nimi", "password": "Salas\u00f5na", "username": "Kasutajanimi" - }, - "title": "Loo \u00fchendus DoorBird-ga" + } } } }, @@ -31,8 +30,7 @@ }, "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 b4819897366..873204b0ef0 100644 --- a/homeassistant/components/doorbird/translations/fr.json +++ b/homeassistant/components/doorbird/translations/fr.json @@ -18,8 +18,7 @@ "name": "Nom de l'appareil", "password": "Mot de passe", "username": "Nom d'utilisateur" - }, - "title": "Connectez-vous au DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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 51bbb0864dd..0062ffd46ef 100644 --- a/homeassistant/components/doorbird/translations/hu.json +++ b/homeassistant/components/doorbird/translations/hu.json @@ -18,8 +18,7 @@ "name": "Eszk\u00f6z neve", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - }, - "title": "Csatlakozzon a DoorBird-hez" + } } } }, @@ -31,8 +30,7 @@ }, "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 c5d5733457d..e9155a8315f 100644 --- a/homeassistant/components/doorbird/translations/id.json +++ b/homeassistant/components/doorbird/translations/id.json @@ -18,8 +18,7 @@ "name": "Nama Perangkat", "password": "Kata Sandi", "username": "Nama Pengguna" - }, - "title": "Hubungkan ke DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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 9864f100954..4922ed6bc48 100644 --- a/homeassistant/components/doorbird/translations/it.json +++ b/homeassistant/components/doorbird/translations/it.json @@ -18,8 +18,7 @@ "name": "Nome del dispositivo", "password": "Password", "username": "Nome utente" - }, - "title": "Connettiti a DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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 55363310ec5..e47c1b21e5e 100644 --- a/homeassistant/components/doorbird/translations/ja.json +++ b/homeassistant/components/doorbird/translations/ja.json @@ -18,8 +18,7 @@ "name": "\u30c7\u30d0\u30a4\u30b9\u540d", "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", "username": "\u30e6\u30fc\u30b6\u30fc\u540d" - }, - "title": "DoorBird\u306b\u63a5\u7d9a" + } } } }, @@ -31,8 +30,7 @@ }, "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/ko.json b/homeassistant/components/doorbird/translations/ko.json index 85d00317c2d..7406536982e 100644 --- a/homeassistant/components/doorbird/translations/ko.json +++ b/homeassistant/components/doorbird/translations/ko.json @@ -18,8 +18,7 @@ "name": "\uae30\uae30 \uc774\ub984", "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - }, - "title": "DoorBird\uc5d0 \uc5f0\uacb0\ud558\uae30" + } } } }, @@ -28,8 +27,7 @@ "init": { "data": { "events": "\uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \uc774\ubca4\ud2b8 \ubaa9\ub85d." - }, - "description": "\ucd94\uc801\ud558\ub824\ub294 \uac01 \uc774\ubca4\ud2b8\uc5d0 \ub300\ud574 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \uc774\ubca4\ud2b8 \uc774\ub984\uc744 \ucd94\uac00\ud574\uc8fc\uc138\uc694. \uc5ec\uae30\uc5d0 \uc785\ub825\ud55c \ud6c4 DoorBird \uc571\uc744 \uc0ac\uc6a9\ud558\uc5ec \ud2b9\uc815 \uc774\ubca4\ud2b8\uc5d0 \ud560\ub2f9\ud574\uc8fc\uc138\uc694. \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 https://www.home-assistant.io/integrations/doorbird/#event \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694. \uc608: somebody_pressed_the_button, motion" + } } } } diff --git a/homeassistant/components/doorbird/translations/lb.json b/homeassistant/components/doorbird/translations/lb.json index c7cb7234fea..1e10d04ba19 100644 --- a/homeassistant/components/doorbird/translations/lb.json +++ b/homeassistant/components/doorbird/translations/lb.json @@ -18,8 +18,7 @@ "name": "Numm vum Apparat", "password": "Passwuert", "username": "Benotzernumm" - }, - "title": "Mat DoorBird verbannen" + } } } }, @@ -28,8 +27,7 @@ "init": { "data": { "events": "Komma getrennte L\u00ebscht vun Evenementer" - }, - "description": "Setzt ee mat Komma getrennten Evenement Numm fir all Evenement dob\u00e4i d\u00e9i sollt suiv\u00e9iert ginn. Wann's du se hei aginn hues, benotz d'DoorBird App fir se zu engem spezifeschen Evenement dob\u00e4i ze setzen. Kuckt d'Dokumentatioun op https://www.home-assistant.io/integrations/doorbird/#events. Beispill: somebody_pressed_the_button, motion" + } } } } diff --git a/homeassistant/components/doorbird/translations/nl.json b/homeassistant/components/doorbird/translations/nl.json index 83e47ca4c5e..cc59cd156b4 100644 --- a/homeassistant/components/doorbird/translations/nl.json +++ b/homeassistant/components/doorbird/translations/nl.json @@ -18,8 +18,7 @@ "name": "Apparaatnaam", "password": "Wachtwoord", "username": "Gebruikersnaam" - }, - "title": "Maak verbinding met de DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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 a30fc93013a..0c1732b64c6 100644 --- a/homeassistant/components/doorbird/translations/no.json +++ b/homeassistant/components/doorbird/translations/no.json @@ -18,8 +18,7 @@ "name": "Enhetsnavn", "password": "Passord", "username": "Brukernavn" - }, - "title": "Koble til DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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 85a7a8765a7..f8731e1bb04 100644 --- a/homeassistant/components/doorbird/translations/pl.json +++ b/homeassistant/components/doorbird/translations/pl.json @@ -18,8 +18,7 @@ "name": "Nazwa urz\u0105dzenia", "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" - }, - "title": "Po\u0142\u0105czenie z DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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 07170c086b7..7265638b43b 100644 --- a/homeassistant/components/doorbird/translations/pt-BR.json +++ b/homeassistant/components/doorbird/translations/pt-BR.json @@ -18,8 +18,7 @@ "name": "Nome do dispositivo", "password": "Senha", "username": "Usu\u00e1rio" - }, - "title": "Conecte-se ao DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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 045092a2b97..be790392f50 100644 --- a/homeassistant/components/doorbird/translations/ru.json +++ b/homeassistant/components/doorbird/translations/ru.json @@ -18,8 +18,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" - }, - "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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/sl.json b/homeassistant/components/doorbird/translations/sl.json index 336a40904d2..446445a2b31 100644 --- a/homeassistant/components/doorbird/translations/sl.json +++ b/homeassistant/components/doorbird/translations/sl.json @@ -18,8 +18,7 @@ "name": "Ime naprave", "password": "Geslo", "username": "Uporabni\u0161ko ime" - }, - "title": "Pove\u017eite se z DoorBird" + } } } }, @@ -28,8 +27,7 @@ "init": { "data": { "events": "Seznam dogodkov, lo\u010denih z vejico." - }, - "description": "Za vsak dogodek, ki ga \u017eelite spremljati, dodajte ime, lo\u010deno z vejico. Ko jih tukaj vnesete, uporabite aplikacijo DoorBird, da jih dodelite dolo\u010denemu dogodku. Glej dokumentacijo na strani https://www.home-assistant.io/integrations/doorbird/#events. Primer: nekdo_pritisnil_gumb, gibanje" + } } } } diff --git a/homeassistant/components/doorbird/translations/sv.json b/homeassistant/components/doorbird/translations/sv.json index 546535fb937..b2a809a576e 100644 --- a/homeassistant/components/doorbird/translations/sv.json +++ b/homeassistant/components/doorbird/translations/sv.json @@ -9,8 +9,7 @@ "host": "V\u00e4rd (IP-adress)", "name": "Enhetsnamn", "username": "Anv\u00e4ndarnamn" - }, - "title": "Anslut till DoorBird" + } } } } diff --git a/homeassistant/components/doorbird/translations/tr.json b/homeassistant/components/doorbird/translations/tr.json index 1e472a43535..bffb5aacf82 100644 --- a/homeassistant/components/doorbird/translations/tr.json +++ b/homeassistant/components/doorbird/translations/tr.json @@ -18,8 +18,7 @@ "name": "Cihaz ad\u0131", "password": "Parola", "username": "Kullan\u0131c\u0131 Ad\u0131" - }, - "title": "DoorBird'e ba\u011flan\u0131n" + } } } }, @@ -31,8 +30,7 @@ }, "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/uk.json b/homeassistant/components/doorbird/translations/uk.json index 07bbdfacafe..85a7959abdf 100644 --- a/homeassistant/components/doorbird/translations/uk.json +++ b/homeassistant/components/doorbird/translations/uk.json @@ -18,8 +18,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" - }, - "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e DoorBird" + } } } }, @@ -28,8 +27,7 @@ "init": { "data": { "events": "\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0434\u0456\u0439 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043c\u0443." - }, - "description": "\u0414\u043e\u0434\u0430\u0439\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043c\u0443 \u043d\u0430\u0437\u0432\u0438 \u043f\u043e\u0434\u0456\u0439, \u044f\u043a\u0435 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0456\u0434\u0441\u043b\u0456\u0434\u043a\u043e\u0432\u0443\u0432\u0430\u0442\u0438. \u041f\u0456\u0441\u043b\u044f \u0446\u044c\u043e\u0433\u043e, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a DoorBird, \u0449\u043e\u0431 \u043f\u0440\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0457\u0445 \u0434\u043e \u043f\u0435\u0432\u043d\u043e\u0457 \u043f\u043e\u0434\u0456\u0457. \u041f\u0440\u0438\u043a\u043b\u0430\u0434: somebody_pressed_the_button, motion. \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0454\u044e \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0431\u0456\u043b\u044c\u0448 \u0434\u043e\u043a\u043b\u0430\u0434\u043d\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457: https://www.home-assistant.io/integrations/doorbird/#events." + } } } } diff --git a/homeassistant/components/doorbird/translations/zh-Hant.json b/homeassistant/components/doorbird/translations/zh-Hant.json index 616e346b0dc..22bdf8b0c91 100644 --- a/homeassistant/components/doorbird/translations/zh-Hant.json +++ b/homeassistant/components/doorbird/translations/zh-Hant.json @@ -18,8 +18,7 @@ "name": "\u88dd\u7f6e\u540d\u7a31", "password": "\u5bc6\u78bc", "username": "\u4f7f\u7528\u8005\u540d\u7a31" - }, - "title": "\u9023\u7dda\u81f3 DoorBird" + } } } }, @@ -31,8 +30,7 @@ }, "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/const.py b/homeassistant/components/dsmr/const.py index 2533aa8d025..43c0e66e945 100644 --- a/homeassistant/components/dsmr/const.py +++ b/homeassistant/components/dsmr/const.py @@ -271,7 +271,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( state_class=SensorStateClass.TOTAL_INCREASING, ), DSMRSensorEntityDescription( - key=obis_references.BELGIUM_HOURLY_GAS_METER_READING, + key=obis_references.BELGIUM_5MIN_GAS_METER_READING, name="Gas Consumption", dsmr_versions={"5B"}, is_gas=True, diff --git a/homeassistant/components/dsmr/manifest.json b/homeassistant/components/dsmr/manifest.json index e15a7c3b80a..b086944a4d2 100644 --- a/homeassistant/components/dsmr/manifest.json +++ b/homeassistant/components/dsmr/manifest.json @@ -2,7 +2,7 @@ "domain": "dsmr", "name": "DSMR Slimme Meter", "documentation": "https://www.home-assistant.io/integrations/dsmr", - "requirements": ["dsmr_parser==0.32"], + "requirements": ["dsmr_parser==0.33"], "codeowners": ["@Robbie1221", "@frenck"], "config_flow": true, "iot_class": "local_push", diff --git a/homeassistant/components/dunehd/config_flow.py b/homeassistant/components/dunehd/config_flow.py index 434bbc7bd84..b5a656716b0 100644 --- a/homeassistant/components/dunehd/config_flow.py +++ b/homeassistant/components/dunehd/config_flow.py @@ -2,9 +2,8 @@ from __future__ import annotations import ipaddress -import logging import re -from typing import Any, Final +from typing import Any from pdunehd import DuneHDPlayer import voluptuous as vol @@ -15,8 +14,6 @@ from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN -_LOGGER: Final = logging.getLogger(__name__) - def host_valid(host: str) -> bool: """Return True if hostname or IP address is valid.""" @@ -72,29 +69,6 @@ class DuneHDConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import( - self, user_input: dict[str, str] | None = None - ) -> FlowResult: - """Handle configuration by yaml file.""" - _LOGGER.warning( - "Configuration of the Dune HD integration 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" - ) - assert user_input is not None - host: str = user_input[CONF_HOST] - - self._async_abort_entries_match({CONF_HOST: host}) - - try: - await self.init_device(host) - except CannotConnect: - _LOGGER.error("Import aborted, cannot connect to %s", host) - return self.async_abort(reason="cannot_connect") - else: - return self.async_create_entry(title=host, data=user_input) - def host_already_configured(self, host: str) -> bool: """See if we already have a dunehd entry matching user input configured.""" existing_hosts = { diff --git a/homeassistant/components/dunehd/media_player.py b/homeassistant/components/dunehd/media_player.py index d39d148e5a5..f6d90b7b1d9 100644 --- a/homeassistant/components/dunehd/media_player.py +++ b/homeassistant/components/dunehd/media_player.py @@ -4,40 +4,21 @@ from __future__ import annotations from typing import Any, Final from pdunehd import DuneHDPlayer -import voluptuous as vol from homeassistant.components.media_player import ( - PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, MediaPlayerEntity, MediaPlayerEntityFeature, ) -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - CONF_HOST, - CONF_NAME, - STATE_OFF, - STATE_ON, - STATE_PAUSED, - STATE_PLAYING, -) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import ATTR_MANUFACTURER, DEFAULT_NAME, DOMAIN CONF_SOURCES: Final = "sources" -PLATFORM_SCHEMA: Final = PARENT_PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_SOURCES): vol.Schema({cv.string: cv.string}), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - } -) - DUNEHD_PLAYER_SUPPORT: Final[int] = ( MediaPlayerEntityFeature.PAUSE | MediaPlayerEntityFeature.TURN_ON @@ -48,22 +29,6 @@ DUNEHD_PLAYER_SUPPORT: Final[int] = ( ) -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the Dune HD media player platform.""" - host: str = config[CONF_HOST] - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_HOST: host} - ) - ) - - async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: diff --git a/homeassistant/components/dunehd/translations/ca.json b/homeassistant/components/dunehd/translations/ca.json index 08dd841bf4f..e1da2dc867c 100644 --- a/homeassistant/components/dunehd/translations/ca.json +++ b/homeassistant/components/dunehd/translations/ca.json @@ -13,8 +13,7 @@ "data": { "host": "Amfitri\u00f3" }, - "description": "Assegura't que el reproductor est\u00e0 engegat.", - "title": "Dune HD" + "description": "Assegura't que el reproductor est\u00e0 engegat." } } } diff --git a/homeassistant/components/dunehd/translations/cs.json b/homeassistant/components/dunehd/translations/cs.json index b52eb3d2cdf..e0e097b925d 100644 --- a/homeassistant/components/dunehd/translations/cs.json +++ b/homeassistant/components/dunehd/translations/cs.json @@ -13,8 +13,7 @@ "data": { "host": "Hostitel" }, - "description": "Nastaven\u00ed integrace Dune HD. Pokud m\u00e1te probl\u00e9my s nastaven\u00edm, p\u0159ejd\u011bte na: https://www.home-assistant.io/integrations/dunehd \n\nUjist\u011bte se, \u017ee je v\u00e1\u0161 p\u0159ehr\u00e1va\u010d zapnut\u00fd.", - "title": "Dune HD" + "description": "Nastaven\u00ed integrace Dune HD. Pokud m\u00e1te probl\u00e9my s nastaven\u00edm, p\u0159ejd\u011bte na: https://www.home-assistant.io/integrations/dunehd \n\nUjist\u011bte se, \u017ee je v\u00e1\u0161 p\u0159ehr\u00e1va\u010d zapnut\u00fd." } } } diff --git a/homeassistant/components/dunehd/translations/de.json b/homeassistant/components/dunehd/translations/de.json index bcab02b2a09..58171e2fa23 100644 --- a/homeassistant/components/dunehd/translations/de.json +++ b/homeassistant/components/dunehd/translations/de.json @@ -13,8 +13,7 @@ "data": { "host": "Host" }, - "description": "Stelle sicher, dass dein Player eingeschaltet ist.", - "title": "Dune HD" + "description": "Stelle sicher, dass dein Player eingeschaltet ist." } } } diff --git a/homeassistant/components/dunehd/translations/el.json b/homeassistant/components/dunehd/translations/el.json index 96ad16ac67f..e14fb5c7308 100644 --- a/homeassistant/components/dunehd/translations/el.json +++ b/homeassistant/components/dunehd/translations/el.json @@ -13,8 +13,7 @@ "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\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Dune HD. \u0391\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 \u03c0\u03b7\u03b3\u03b1\u03af\u03bd\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: https://www.home-assistant.io/integrations/dunehd \n\n\u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7.", - "title": "Dune HD" + "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 Dune HD. \u0391\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 \u03c0\u03b7\u03b3\u03b1\u03af\u03bd\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: https://www.home-assistant.io/integrations/dunehd \n\n\u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7." } } } diff --git a/homeassistant/components/dunehd/translations/en.json b/homeassistant/components/dunehd/translations/en.json index d6392509fcf..6139d5b5251 100644 --- a/homeassistant/components/dunehd/translations/en.json +++ b/homeassistant/components/dunehd/translations/en.json @@ -13,8 +13,7 @@ "data": { "host": "Host" }, - "description": "Ensure that your player is turned on.", - "title": "Dune HD" + "description": "Ensure that your player is turned on." } } } diff --git a/homeassistant/components/dunehd/translations/es-419.json b/homeassistant/components/dunehd/translations/es-419.json index 5ad7a6640b4..d74cafdedde 100644 --- a/homeassistant/components/dunehd/translations/es-419.json +++ b/homeassistant/components/dunehd/translations/es-419.json @@ -2,8 +2,7 @@ "config": { "step": { "user": { - "description": "Configure la integraci\u00f3n de Dune HD. Si tiene problemas con la configuraci\u00f3n, vaya a: https://www.home-assistant.io/integrations/dunehd \n\n Aseg\u00farese de que su reproductor est\u00e9 encendido.", - "title": "Dune HD" + "description": "Configure la integraci\u00f3n de Dune HD. Si tiene problemas con la configuraci\u00f3n, vaya a: https://www.home-assistant.io/integrations/dunehd \n\n Aseg\u00farese de que su reproductor est\u00e9 encendido." } } } diff --git a/homeassistant/components/dunehd/translations/es.json b/homeassistant/components/dunehd/translations/es.json index 33f2e026359..7e2c1282ca6 100644 --- a/homeassistant/components/dunehd/translations/es.json +++ b/homeassistant/components/dunehd/translations/es.json @@ -13,8 +13,7 @@ "data": { "host": "Host" }, - "description": "Configura la integraci\u00f3n de Dune HD. Si tienes problemas con la configuraci\u00f3n, ve a: https://www.home-assistant.io/integrations/dunehd \n\n Aseg\u00farate de que tu reproductor est\u00e1 encendido.", - "title": "Dune HD" + "description": "Configura la integraci\u00f3n de Dune HD. Si tienes problemas con la configuraci\u00f3n, ve a: https://www.home-assistant.io/integrations/dunehd \n\n Aseg\u00farate de que tu reproductor est\u00e1 encendido." } } } diff --git a/homeassistant/components/dunehd/translations/et.json b/homeassistant/components/dunehd/translations/et.json index 3f4a9b4b085..9cd7d30bc2c 100644 --- a/homeassistant/components/dunehd/translations/et.json +++ b/homeassistant/components/dunehd/translations/et.json @@ -13,8 +13,7 @@ "data": { "host": "" }, - "description": "Veendu, et m\u00e4ngija on sisse l\u00fclitatud.", - "title": "" + "description": "Veendu, et m\u00e4ngija on sisse l\u00fclitatud." } } } diff --git a/homeassistant/components/dunehd/translations/fr.json b/homeassistant/components/dunehd/translations/fr.json index 8c3bc89a0e6..a8831b94f49 100644 --- a/homeassistant/components/dunehd/translations/fr.json +++ b/homeassistant/components/dunehd/translations/fr.json @@ -13,8 +13,7 @@ "data": { "host": "H\u00f4te" }, - "description": "Assurez-vous que votre lecteur est allum\u00e9.", - "title": "Dune HD" + "description": "Assurez-vous que votre lecteur est allum\u00e9." } } } diff --git a/homeassistant/components/dunehd/translations/hu.json b/homeassistant/components/dunehd/translations/hu.json index 1dc7c8ec6cc..66245b690fd 100644 --- a/homeassistant/components/dunehd/translations/hu.json +++ b/homeassistant/components/dunehd/translations/hu.json @@ -13,8 +13,7 @@ "data": { "host": "C\u00edm" }, - "description": "Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l, hogy a lej\u00e1tsz\u00f3 be van kapcsolva.", - "title": "Dune HD" + "description": "Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l, hogy a lej\u00e1tsz\u00f3 be van kapcsolva." } } } diff --git a/homeassistant/components/dunehd/translations/id.json b/homeassistant/components/dunehd/translations/id.json index 5a86947a07a..0a0f90a5a0f 100644 --- a/homeassistant/components/dunehd/translations/id.json +++ b/homeassistant/components/dunehd/translations/id.json @@ -13,8 +13,7 @@ "data": { "host": "Host" }, - "description": "Pastikan pemutar Anda dinyalakan.", - "title": "Dune HD" + "description": "Pastikan pemutar Anda dinyalakan." } } } diff --git a/homeassistant/components/dunehd/translations/it.json b/homeassistant/components/dunehd/translations/it.json index 0e33feb2640..9ef4be284b6 100644 --- a/homeassistant/components/dunehd/translations/it.json +++ b/homeassistant/components/dunehd/translations/it.json @@ -13,8 +13,7 @@ "data": { "host": "Host" }, - "description": "Assicurati che il tuo lettore sia acceso.", - "title": "Dune HD" + "description": "Assicurati che il tuo lettore sia acceso." } } } diff --git a/homeassistant/components/dunehd/translations/ja.json b/homeassistant/components/dunehd/translations/ja.json index 79bb6b0746d..c82d7ce8f94 100644 --- a/homeassistant/components/dunehd/translations/ja.json +++ b/homeassistant/components/dunehd/translations/ja.json @@ -13,8 +13,7 @@ "data": { "host": "\u30db\u30b9\u30c8" }, - "description": "Dune HD\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/dunehd \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044\n\n\u304d\u3061\u3093\u3068\u30d7\u30ec\u30fc\u30e4\u30fc\u306e\u96fb\u6e90\u304c\u5165\u3063\u3066\u3044\u308b\u3053\u3068\u3082\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "Dune HD" + "description": "Dune HD\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/dunehd \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044\n\n\u304d\u3061\u3093\u3068\u30d7\u30ec\u30fc\u30e4\u30fc\u306e\u96fb\u6e90\u304c\u5165\u3063\u3066\u3044\u308b\u3053\u3068\u3082\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002" } } } diff --git a/homeassistant/components/dunehd/translations/ko.json b/homeassistant/components/dunehd/translations/ko.json index 5c7feb27f7e..8bb3de70327 100644 --- a/homeassistant/components/dunehd/translations/ko.json +++ b/homeassistant/components/dunehd/translations/ko.json @@ -13,8 +13,7 @@ "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Dune HD \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00 \uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/dunehd \ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n\n\uae30\uae30\uac00 \ucf1c\uc838 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", - "title": "Dune HD" + "description": "Dune HD \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00 \uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/dunehd \ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n\n\uae30\uae30\uac00 \ucf1c\uc838 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/dunehd/translations/lb.json b/homeassistant/components/dunehd/translations/lb.json index 035bd24f6b3..9dc5f1f2c5d 100644 --- a/homeassistant/components/dunehd/translations/lb.json +++ b/homeassistant/components/dunehd/translations/lb.json @@ -13,8 +13,7 @@ "data": { "host": "Host" }, - "description": "Dune HD Integratioun ariichten. Falls et Problemer mat der Konfiguratioun g\u00ebtt g\u00e9i op:\nhttps://www.home-assistant.io/integrations/dunehd \nStell s\u00e9cher dass d\u00e4in Ofspiller un ass.", - "title": "Dune HD" + "description": "Dune HD Integratioun ariichten. Falls et Problemer mat der Konfiguratioun g\u00ebtt g\u00e9i op:\nhttps://www.home-assistant.io/integrations/dunehd \nStell s\u00e9cher dass d\u00e4in Ofspiller un ass." } } } diff --git a/homeassistant/components/dunehd/translations/nl.json b/homeassistant/components/dunehd/translations/nl.json index f4c54a5dba5..9f75cd5a91a 100644 --- a/homeassistant/components/dunehd/translations/nl.json +++ b/homeassistant/components/dunehd/translations/nl.json @@ -5,16 +5,15 @@ }, "error": { "already_configured": "Apparaat is al geconfigureerd", - "cannot_connect": "Kon niet verbinden", - "invalid_host": "ongeldige host of IP adres" + "cannot_connect": "Kan geen verbinding maken", + "invalid_host": "Ongeldige hostnaam of IP-adres" }, "step": { "user": { "data": { "host": "Host" }, - "description": "Zorg ervoor dat uw speler is ingeschakeld.", - "title": "Dune HD" + "description": "Zorg ervoor dat uw speler is ingeschakeld." } } } diff --git a/homeassistant/components/dunehd/translations/no.json b/homeassistant/components/dunehd/translations/no.json index 1b35f5581b1..ac38bbbdbf9 100644 --- a/homeassistant/components/dunehd/translations/no.json +++ b/homeassistant/components/dunehd/translations/no.json @@ -13,8 +13,7 @@ "data": { "host": "Vert" }, - "description": "S\u00f8rg for at spilleren er sl\u00e5tt p\u00e5.", - "title": "" + "description": "S\u00f8rg for at spilleren er sl\u00e5tt p\u00e5." } } } diff --git a/homeassistant/components/dunehd/translations/pl.json b/homeassistant/components/dunehd/translations/pl.json index 376b84e05d9..4af098c58f9 100644 --- a/homeassistant/components/dunehd/translations/pl.json +++ b/homeassistant/components/dunehd/translations/pl.json @@ -13,8 +13,7 @@ "data": { "host": "Nazwa hosta lub adres IP" }, - "description": "Upewnij si\u0119, \u017ce odtwarzacz jest w\u0142\u0105czony.", - "title": "Dune HD" + "description": "Upewnij si\u0119, \u017ce odtwarzacz jest w\u0142\u0105czony." } } } diff --git a/homeassistant/components/dunehd/translations/pt-BR.json b/homeassistant/components/dunehd/translations/pt-BR.json index 4775f1af379..a9c2f20a225 100644 --- a/homeassistant/components/dunehd/translations/pt-BR.json +++ b/homeassistant/components/dunehd/translations/pt-BR.json @@ -13,8 +13,7 @@ "data": { "host": "Nome do host" }, - "description": "Certifique-se de que seu player est\u00e1 ligado.", - "title": "Dune HD" + "description": "Certifique-se de que seu player est\u00e1 ligado." } } } diff --git a/homeassistant/components/dunehd/translations/ru.json b/homeassistant/components/dunehd/translations/ru.json index 134511e66f2..8c32af72af7 100644 --- a/homeassistant/components/dunehd/translations/ru.json +++ b/homeassistant/components/dunehd/translations/ru.json @@ -13,8 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, - "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" + "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." } } } diff --git a/homeassistant/components/dunehd/translations/tr.json b/homeassistant/components/dunehd/translations/tr.json index 79c2f033cfc..f4c7c0db43a 100644 --- a/homeassistant/components/dunehd/translations/tr.json +++ b/homeassistant/components/dunehd/translations/tr.json @@ -13,8 +13,7 @@ "data": { "host": "Sunucu" }, - "description": "Oynat\u0131c\u0131n\u0131z\u0131n a\u00e7\u0131k oldu\u011fundan emin olun.", - "title": "Dune HD" + "description": "Oynat\u0131c\u0131n\u0131z\u0131n a\u00e7\u0131k oldu\u011fundan emin olun." } } } diff --git a/homeassistant/components/dunehd/translations/uk.json b/homeassistant/components/dunehd/translations/uk.json index d2f4eadbdcb..bec5590ecb5 100644 --- a/homeassistant/components/dunehd/translations/uk.json +++ b/homeassistant/components/dunehd/translations/uk.json @@ -13,8 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Dune HD. \u042f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0432\u0438\u043d\u0438\u043a\u043b\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u0437 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438 \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e: https://www.home-assistant.io/integrations/dunehd \n\n \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0412\u0430\u0448 \u043f\u043b\u0435\u0454\u0440 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439.", - "title": "Dune HD" + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Dune HD. \u042f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0432\u0438\u043d\u0438\u043a\u043b\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u0437 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438 \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e: https://www.home-assistant.io/integrations/dunehd \n\n \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0412\u0430\u0448 \u043f\u043b\u0435\u0454\u0440 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439." } } } diff --git a/homeassistant/components/dunehd/translations/zh-Hant.json b/homeassistant/components/dunehd/translations/zh-Hant.json index 993a6d9a210..138b3801adf 100644 --- a/homeassistant/components/dunehd/translations/zh-Hant.json +++ b/homeassistant/components/dunehd/translations/zh-Hant.json @@ -13,8 +13,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u78ba\u5b9a\u64ad\u653e\u5668\u5df2\u7d93\u958b\u555f\u3002", - "title": "Dune HD" + "description": "\u78ba\u5b9a\u64ad\u653e\u5668\u5df2\u7d93\u958b\u555f\u3002" } } } diff --git a/homeassistant/components/dynalite/config_flow.py b/homeassistant/components/dynalite/config_flow.py index d148d09354f..d723825319a 100644 --- a/homeassistant/components/dynalite/config_flow.py +++ b/homeassistant/components/dynalite/config_flow.py @@ -5,6 +5,7 @@ from typing import Any from homeassistant import config_entries from homeassistant.const import CONF_HOST +from homeassistant.data_entry_flow import FlowResult from .bridge import DynaliteBridge from .const import DOMAIN, LOGGER @@ -20,7 +21,7 @@ class DynaliteFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Initialize the Dynalite flow.""" self.host = None - async def async_step_import(self, import_info: dict[str, Any]) -> Any: + async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult: """Import a new bridge as a config entry.""" LOGGER.debug("Starting async_step_import - %s", import_info) host = import_info[CONF_HOST] diff --git a/homeassistant/components/ecobee/translations/nl.json b/homeassistant/components/ecobee/translations/nl.json index 957d2f8244d..03ba56253bf 100644 --- a/homeassistant/components/ecobee/translations/nl.json +++ b/homeassistant/components/ecobee/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "pin_request_failed": "Fout bij het aanvragen van pincode bij ecobee; Controleer of de API-sleutel correct is.", diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index 278ac004121..f7a79d727a0 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -24,13 +24,12 @@ from homeassistant.const import ( POWER_WATT, ) from homeassistant.core import HomeAssistant, callback -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util.dt import utcnow @@ -329,9 +328,9 @@ class EDL21: self._registered_obis.add((electricity_id, obis)) elif obis not in self._OBIS_BLACKLIST: _LOGGER.warning( - "Unhandled sensor %s detected. Please report at " - 'https://github.com/home-assistant/core/issues?q=is%%3Aissue+label%%3A"integration%%3A+edl21"+', + "Unhandled sensor %s detected. Please report at %s", obis, + "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+edl21%22", ) self._OBIS_BLACKLIST.add(obis) @@ -340,7 +339,7 @@ class EDL21: async def add_entities(self, new_entities) -> None: """Migrate old unique IDs, then add entities to hass.""" - registry = await async_get_registry(self._hass) + registry = er.async_get(self._hass) for entity in new_entities: old_entity_id = registry.async_get_entity_id( diff --git a/homeassistant/components/efergy/translations/ca.json b/homeassistant/components/efergy/translations/ca.json index 298826e75e5..5b8a723019a 100644 --- a/homeassistant/components/efergy/translations/ca.json +++ b/homeassistant/components/efergy/translations/ca.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "Clau API" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/cs.json b/homeassistant/components/efergy/translations/cs.json index b2fe2ea015f..058abebbf28 100644 --- a/homeassistant/components/efergy/translations/cs.json +++ b/homeassistant/components/efergy/translations/cs.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "API kl\u00ed\u010d" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/de.json b/homeassistant/components/efergy/translations/de.json index 3945d3da7d4..659657731f4 100644 --- a/homeassistant/components/efergy/translations/de.json +++ b/homeassistant/components/efergy/translations/de.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "API-Schl\u00fcssel" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/el.json b/homeassistant/components/efergy/translations/el.json index f1206afd71b..8ce9b0ddd8c 100644 --- a/homeassistant/components/efergy/translations/el.json +++ b/homeassistant/components/efergy/translations/el.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/en.json b/homeassistant/components/efergy/translations/en.json index aa76f9c0636..0909241c423 100644 --- a/homeassistant/components/efergy/translations/en.json +++ b/homeassistant/components/efergy/translations/en.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "API Key" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/es.json b/homeassistant/components/efergy/translations/es.json index a72347abef6..ce25d3f3184 100644 --- a/homeassistant/components/efergy/translations/es.json +++ b/homeassistant/components/efergy/translations/es.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "Clave API" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/et.json b/homeassistant/components/efergy/translations/et.json index 73c2f1a3547..c3f599934dc 100644 --- a/homeassistant/components/efergy/translations/et.json +++ b/homeassistant/components/efergy/translations/et.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "API v\u00f5ti" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/fr.json b/homeassistant/components/efergy/translations/fr.json index a506454bc14..9bcf81e0cab 100644 --- a/homeassistant/components/efergy/translations/fr.json +++ b/homeassistant/components/efergy/translations/fr.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "Cl\u00e9 d'API" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/hu.json b/homeassistant/components/efergy/translations/hu.json index 032ef05d527..9d1f47f803c 100644 --- a/homeassistant/components/efergy/translations/hu.json +++ b/homeassistant/components/efergy/translations/hu.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "API kulcs" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/id.json b/homeassistant/components/efergy/translations/id.json index 234e5122db2..037d72a0a15 100644 --- a/homeassistant/components/efergy/translations/id.json +++ b/homeassistant/components/efergy/translations/id.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "Kunci API" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/it.json b/homeassistant/components/efergy/translations/it.json index d5677424d42..df4b96223e5 100644 --- a/homeassistant/components/efergy/translations/it.json +++ b/homeassistant/components/efergy/translations/it.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "Chiave API" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/ja.json b/homeassistant/components/efergy/translations/ja.json index 98dd06ab4f0..319fbd744cf 100644 --- a/homeassistant/components/efergy/translations/ja.json +++ b/homeassistant/components/efergy/translations/ja.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "API\u30ad\u30fc" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/nl.json b/homeassistant/components/efergy/translations/nl.json index 4f97bad11a0..ea0ec0c62a9 100644 --- a/homeassistant/components/efergy/translations/nl.json +++ b/homeassistant/components/efergy/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "API-sleutel" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/no.json b/homeassistant/components/efergy/translations/no.json index 388cbb36f64..4a109ab8fa9 100644 --- a/homeassistant/components/efergy/translations/no.json +++ b/homeassistant/components/efergy/translations/no.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "API-n\u00f8kkel" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/pl.json b/homeassistant/components/efergy/translations/pl.json index b96038d24b3..9ef0e4a5a43 100644 --- a/homeassistant/components/efergy/translations/pl.json +++ b/homeassistant/components/efergy/translations/pl.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "Klucz API" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/pt-BR.json b/homeassistant/components/efergy/translations/pt-BR.json index 8197121b5d5..065c29ab9ab 100644 --- a/homeassistant/components/efergy/translations/pt-BR.json +++ b/homeassistant/components/efergy/translations/pt-BR.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "Chave da API" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/ru.json b/homeassistant/components/efergy/translations/ru.json index 6a659c9b7c6..3c0e7797c7b 100644 --- a/homeassistant/components/efergy/translations/ru.json +++ b/homeassistant/components/efergy/translations/ru.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/tr.json b/homeassistant/components/efergy/translations/tr.json index e13f215b5fb..cbb81598338 100644 --- a/homeassistant/components/efergy/translations/tr.json +++ b/homeassistant/components/efergy/translations/tr.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "API Anahtar\u0131" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/efergy/translations/zh-Hant.json b/homeassistant/components/efergy/translations/zh-Hant.json index c8f2d660c2f..dda28849714 100644 --- a/homeassistant/components/efergy/translations/zh-Hant.json +++ b/homeassistant/components/efergy/translations/zh-Hant.json @@ -13,8 +13,7 @@ "user": { "data": { "api_key": "API \u91d1\u9470" - }, - "title": "Efergy" + } } } } diff --git a/homeassistant/components/eight_sleep/__init__.py b/homeassistant/components/eight_sleep/__init__.py index 3495d1da28b..e986a7f6d60 100644 --- a/homeassistant/components/eight_sleep/__init__.py +++ b/homeassistant/components/eight_sleep/__init__.py @@ -3,20 +3,12 @@ from __future__ import annotations from datetime import timedelta import logging -from typing import Union from pyeight.eight import EightSleep from pyeight.user import EightUser import voluptuous as vol -from homeassistant.const import ( - ATTR_ENTITY_ID, - CONF_BINARY_SENSORS, - CONF_PASSWORD, - CONF_SENSORS, - CONF_USERNAME, - Platform, -) +from homeassistant.const import ATTR_ENTITY_ID, CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers import discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -27,44 +19,24 @@ from homeassistant.helpers.update_coordinator import ( DataUpdateCoordinator, ) +from .const import ( + ATTR_HEAT_DURATION, + ATTR_TARGET_HEAT, + DATA_API, + DATA_HEAT, + DATA_USER, + DOMAIN, + NAME_MAP, + SERVICE_HEAT_SET, +) + _LOGGER = logging.getLogger(__name__) -DATA_EIGHT = "eight_sleep" -DATA_HEAT = "heat" -DATA_USER = "user" -DATA_API = "api" -DOMAIN = "eight_sleep" - -HEAT_ENTITY = "heat" -USER_ENTITY = "user" - +PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] HEAT_SCAN_INTERVAL = timedelta(seconds=60) USER_SCAN_INTERVAL = timedelta(seconds=300) -NAME_MAP = { - "left_current_sleep": "Left Sleep Session", - "left_current_sleep_fitness": "Left Sleep Fitness", - "left_last_sleep": "Left Previous Sleep Session", - "right_current_sleep": "Right Sleep Session", - "right_current_sleep_fitness": "Right Sleep Fitness", - "right_last_sleep": "Right Previous Sleep Session", -} - -SENSORS = [ - "current_sleep", - "current_sleep_fitness", - "last_sleep", - "bed_state", - "bed_temperature", - "sleep_stage", -] - -SERVICE_HEAT_SET = "heat_set" - -ATTR_TARGET_HEAT = "target" -ATTR_HEAT_DURATION = "duration" - VALID_TARGET_HEAT = vol.All(vol.Coerce(int), vol.Clamp(min=-100, max=100)) VALID_DURATION = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=28800)) @@ -107,15 +79,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: user = conf[CONF_USERNAME] password = conf[CONF_PASSWORD] - if hass.config.time_zone is None: - _LOGGER.error("Timezone is not set in Home Assistant") - return False + eight = EightSleep( + user, password, hass.config.time_zone, async_get_clientsession(hass) + ) - timezone = str(hass.config.time_zone) - - eight = EightSleep(user, password, timezone, async_get_clientsession(hass)) - - hass.data.setdefault(DATA_EIGHT, {})[DATA_API] = eight + hass.data.setdefault(DOMAIN, {}) # Authenticate, build sensors success = await eight.start() @@ -123,43 +91,37 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # Authentication failed, cannot continue return False - heat_coordinator = hass.data[DOMAIN][DATA_HEAT] = EightSleepHeatDataCoordinator( - hass, eight + heat_coordinator: DataUpdateCoordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=f"{DOMAIN}_heat", + update_interval=HEAT_SCAN_INTERVAL, + update_method=eight.update_device_data, ) - user_coordinator = hass.data[DOMAIN][DATA_USER] = EightSleepUserDataCoordinator( - hass, eight + user_coordinator: DataUpdateCoordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=f"{DOMAIN}_user", + update_interval=USER_SCAN_INTERVAL, + update_method=eight.update_user_data, ) await heat_coordinator.async_config_entry_first_refresh() await user_coordinator.async_config_entry_first_refresh() - # Load sub components - sensors = [] - binary_sensors = [] - if eight.users: - for user, obj in eight.users.items(): - for sensor in SENSORS: - sensors.append((obj.side, sensor)) - binary_sensors.append((obj.side, "bed_presence")) - sensors.append((None, "room_temperature")) - else: + if not eight.users: # No users, cannot continue return False - hass.async_create_task( - discovery.async_load_platform( - hass, Platform.SENSOR, DOMAIN, {CONF_SENSORS: sensors}, config - ) - ) + hass.data[DOMAIN] = { + DATA_API: eight, + DATA_HEAT: heat_coordinator, + DATA_USER: user_coordinator, + } - hass.async_create_task( - discovery.async_load_platform( - hass, - Platform.BINARY_SENSOR, - DOMAIN, - {CONF_BINARY_SENSORS: binary_sensors}, - config, + for platform in PLATFORMS: + hass.async_create_task( + discovery.async_load_platform(hass, platform, DOMAIN, {}, config) ) - ) async def async_service_handler(service: ServiceCall) -> None: """Handle eight sleep service calls.""" @@ -185,88 +147,30 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -class EightSleepHeatDataCoordinator(DataUpdateCoordinator): - """Class to retrieve heat data from Eight Sleep.""" - - def __init__(self, hass: HomeAssistant, api: EightSleep) -> None: - """Initialize coordinator.""" - self.api = api - super().__init__( - hass, - _LOGGER, - name=f"{DOMAIN}_heat", - update_interval=HEAT_SCAN_INTERVAL, - ) - - async def _async_update_data(self) -> None: - await self.api.update_device_data() - - -class EightSleepUserDataCoordinator(DataUpdateCoordinator): - """Class to retrieve user data from Eight Sleep.""" - - def __init__(self, hass: HomeAssistant, api: EightSleep) -> None: - """Initialize coordinator.""" - self.api = api - super().__init__( - hass, - _LOGGER, - name=f"{DOMAIN}_user", - update_interval=USER_SCAN_INTERVAL, - ) - - async def _async_update_data(self) -> None: - await self.api.update_user_data() - - -class EightSleepBaseEntity( - CoordinatorEntity[ - Union[EightSleepUserDataCoordinator, EightSleepHeatDataCoordinator] - ] -): +class EightSleepBaseEntity(CoordinatorEntity[DataUpdateCoordinator]): """The base Eight Sleep entity class.""" def __init__( self, - name: str, - coordinator: EightSleepUserDataCoordinator | EightSleepHeatDataCoordinator, + coordinator: DataUpdateCoordinator, eight: EightSleep, - side: str | None, + user_id: str | None, sensor: str, ) -> None: """Initialize the data object.""" super().__init__(coordinator) self._eight = eight - self._side = side + self._user_id = user_id self._sensor = sensor - self._usrobj: EightUser | None = None - if self._side: - self._usrobj = self._eight.users[self._eight.fetch_userid(self._side)] - full_sensor_name = self._sensor - if self._side is not None: - full_sensor_name = f"{self._side}_{full_sensor_name}" - mapped_name = NAME_MAP.get( - full_sensor_name, full_sensor_name.replace("_", " ").title() - ) + self._user_obj: EightUser | None = None + if self._user_id: + self._user_obj = self._eight.users[user_id] - self._attr_name = f"{name} {mapped_name}" + mapped_name = NAME_MAP.get(sensor, sensor.replace("_", " ").title()) + if self._user_obj is not None: + mapped_name = f"{self._user_obj.side.title()} {mapped_name}" + + self._attr_name = f"Eight {mapped_name}" self._attr_unique_id = ( - f"{_get_device_unique_id(eight, self._usrobj)}.{self._sensor}" + f"{_get_device_unique_id(eight, self._user_obj)}.{sensor}" ) - - -class EightSleepUserEntity(EightSleepBaseEntity): - """The Eight Sleep user entity.""" - - def __init__( - self, - name: str, - coordinator: EightSleepUserDataCoordinator, - eight: EightSleep, - side: str | None, - sensor: str, - units: str, - ) -> None: - """Initialize the data object.""" - super().__init__(name, coordinator, eight, side, sensor) - self._units = units diff --git a/homeassistant/components/eight_sleep/binary_sensor.py b/homeassistant/components/eight_sleep/binary_sensor.py index a13a0fd840d..868a5177cfe 100644 --- a/homeassistant/components/eight_sleep/binary_sensor.py +++ b/homeassistant/components/eight_sleep/binary_sensor.py @@ -12,15 +12,10 @@ from homeassistant.components.binary_sensor import ( 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 DataUpdateCoordinator -from . import ( - CONF_BINARY_SENSORS, - DATA_API, - DATA_EIGHT, - DATA_HEAT, - EightSleepBaseEntity, - EightSleepHeatDataCoordinator, -) +from . import EightSleepBaseEntity +from .const import DATA_API, DATA_HEAT, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -35,43 +30,42 @@ async def async_setup_platform( if discovery_info is None: return - name = "Eight" - sensors = discovery_info[CONF_BINARY_SENSORS] - eight: EightSleep = hass.data[DATA_EIGHT][DATA_API] - heat_coordinator: EightSleepHeatDataCoordinator = hass.data[DATA_EIGHT][DATA_HEAT] + eight: EightSleep = hass.data[DOMAIN][DATA_API] + heat_coordinator: DataUpdateCoordinator = hass.data[DOMAIN][DATA_HEAT] - all_sensors = [ - EightHeatSensor(name, heat_coordinator, eight, side, sensor) - for side, sensor in sensors - ] + entities = [] + for user in eight.users.values(): + entities.append( + EightHeatSensor(heat_coordinator, eight, user.userid, "bed_presence") + ) - async_add_entities(all_sensors) + async_add_entities(entities) class EightHeatSensor(EightSleepBaseEntity, BinarySensorEntity): """Representation of a Eight Sleep heat-based sensor.""" + _attr_device_class = BinarySensorDeviceClass.OCCUPANCY + def __init__( self, - name: str, - coordinator: EightSleepHeatDataCoordinator, + coordinator: DataUpdateCoordinator, eight: EightSleep, - side: str | None, + user_id: str | None, sensor: str, ) -> None: """Initialize the sensor.""" - super().__init__(name, coordinator, eight, side, sensor) - self._attr_device_class = BinarySensorDeviceClass.OCCUPANCY - assert self._usrobj + super().__init__(coordinator, eight, user_id, sensor) + assert self._user_obj _LOGGER.debug( "Presence Sensor: %s, Side: %s, User: %s", - self._sensor, - self._side, - self._usrobj.userid, + sensor, + self._user_obj.side, + user_id, ) @property def is_on(self) -> bool: """Return true if the binary sensor is on.""" - assert self._usrobj - return bool(self._usrobj.bed_presence) + assert self._user_obj + return bool(self._user_obj.bed_presence) diff --git a/homeassistant/components/eight_sleep/const.py b/homeassistant/components/eight_sleep/const.py new file mode 100644 index 00000000000..42a9eea590e --- /dev/null +++ b/homeassistant/components/eight_sleep/const.py @@ -0,0 +1,19 @@ +"""Eight Sleep constants.""" +DATA_HEAT = "heat" +DATA_USER = "user" +DATA_API = "api" +DOMAIN = "eight_sleep" + +HEAT_ENTITY = "heat" +USER_ENTITY = "user" + +NAME_MAP = { + "current_sleep": "Sleep Session", + "current_sleep_fitness": "Sleep Fitness", + "last_sleep": "Previous Sleep Session", +} + +SERVICE_HEAT_SET = "heat_set" + +ATTR_TARGET_HEAT = "target" +ATTR_HEAT_DURATION = "duration" diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index 5be36c0024e..b405617e276 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -6,23 +6,15 @@ from typing import Any from pyeight.eight import EightSleep -from homeassistant.components.sensor import SensorEntity -from homeassistant.const import PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity +from homeassistant.const import PERCENTAGE, TEMP_CELSIUS 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 DataUpdateCoordinator -from . import ( - CONF_SENSORS, - DATA_API, - DATA_EIGHT, - DATA_HEAT, - DATA_USER, - EightSleepBaseEntity, - EightSleepHeatDataCoordinator, - EightSleepUserDataCoordinator, - EightSleepUserEntity, -) +from . import EightSleepBaseEntity +from .const import DATA_API, DATA_HEAT, DATA_USER, DOMAIN ATTR_ROOM_TEMP = "Room Temperature" ATTR_AVG_ROOM_TEMP = "Average Room Temperature" @@ -51,6 +43,16 @@ ATTR_FIT_WAKEUP_SCORE = "Fitness Wakeup Score" _LOGGER = logging.getLogger(__name__) +EIGHT_USER_SENSORS = [ + "current_sleep", + "current_sleep_fitness", + "last_sleep", + "bed_temperature", + "sleep_stage", +] +EIGHT_HEAT_SENSORS = ["bed_state"] +EIGHT_ROOM_SENSORS = ["room_temperature"] + async def async_setup_platform( hass: HomeAssistant, @@ -62,32 +64,23 @@ async def async_setup_platform( if discovery_info is None: return - name = "Eight" - sensors = discovery_info[CONF_SENSORS] - eight: EightSleep = hass.data[DATA_EIGHT][DATA_API] - heat_coordinator: EightSleepHeatDataCoordinator = hass.data[DATA_EIGHT][DATA_HEAT] - user_coordinator: EightSleepUserDataCoordinator = hass.data[DATA_EIGHT][DATA_USER] - - if hass.config.units.is_metric: - units = "si" - else: - units = "us" + eight: EightSleep = hass.data[DOMAIN][DATA_API] + heat_coordinator: DataUpdateCoordinator = hass.data[DOMAIN][DATA_HEAT] + user_coordinator: DataUpdateCoordinator = hass.data[DOMAIN][DATA_USER] all_sensors: list[SensorEntity] = [] - for side, sensor in sensors: - if sensor == "bed_state": + for obj in eight.users.values(): + for sensor in EIGHT_USER_SENSORS: all_sensors.append( - EightHeatSensor(name, heat_coordinator, eight, side, sensor) + EightUserSensor(user_coordinator, eight, obj.userid, sensor) ) - elif sensor == "room_temperature": + for sensor in EIGHT_HEAT_SENSORS: all_sensors.append( - EightRoomSensor(name, user_coordinator, eight, side, sensor, units) - ) - else: - all_sensors.append( - EightUserSensor(name, user_coordinator, eight, side, sensor, units) + EightHeatSensor(heat_coordinator, eight, obj.userid, sensor) ) + for sensor in EIGHT_ROOM_SENSORS: + all_sensors.append(EightRoomSensor(user_coordinator, eight, sensor)) async_add_entities(all_sensors) @@ -95,40 +88,40 @@ async def async_setup_platform( class EightHeatSensor(EightSleepBaseEntity, SensorEntity): """Representation of an eight sleep heat-based sensor.""" + _attr_native_unit_of_measurement = PERCENTAGE + def __init__( self, - name: str, - coordinator: EightSleepHeatDataCoordinator, + coordinator: DataUpdateCoordinator, eight: EightSleep, - side: str | None, + user_id: str, sensor: str, ) -> None: """Initialize the sensor.""" - super().__init__(name, coordinator, eight, side, sensor) - self._attr_native_unit_of_measurement = PERCENTAGE - assert self._usrobj + super().__init__(coordinator, eight, user_id, sensor) + assert self._user_obj _LOGGER.debug( "Heat Sensor: %s, Side: %s, User: %s", self._sensor, - self._side, - self._usrobj.userid, + self._user_obj.side, + self._user_id, ) @property def native_value(self) -> int: """Return the state of the sensor.""" - assert self._usrobj - return self._usrobj.heating_level + assert self._user_obj + return self._user_obj.heating_level @property def extra_state_attributes(self) -> dict[str, Any]: """Return device state attributes.""" - assert self._usrobj + assert self._user_obj return { - ATTR_TARGET_HEAT: self._usrobj.target_heating_level, - ATTR_ACTIVE_HEAT: self._usrobj.now_heating, - ATTR_DURATION_HEAT: self._usrobj.heating_remaining, + ATTR_TARGET_HEAT: self._user_obj.target_heating_level, + ATTR_ACTIVE_HEAT: self._user_obj.now_heating, + ATTR_DURATION_HEAT: self._user_obj.heating_remaining, } @@ -142,92 +135,74 @@ def _get_breakdown_percent( return 0 -class EightUserSensor(EightSleepUserEntity, SensorEntity): +def _get_rounded_value(attr: dict[str, Any], key: str) -> int | float | None: + """Get rounded value for given key.""" + if (val := attr.get(key)) is None: + return None + return round(val, 2) + + +class EightUserSensor(EightSleepBaseEntity, SensorEntity): """Representation of an eight sleep user-based sensor.""" def __init__( self, - name: str, - coordinator: EightSleepUserDataCoordinator, + coordinator: DataUpdateCoordinator, eight: EightSleep, - side: str | None, + user_id: str, sensor: str, - units: str, ) -> None: """Initialize the sensor.""" - super().__init__(name, coordinator, eight, side, sensor, units) + super().__init__(coordinator, eight, user_id, sensor) + assert self._user_obj if self._sensor == "bed_temperature": self._attr_icon = "mdi:thermometer" + self._attr_device_class = SensorDeviceClass.TEMPERATURE + self._attr_native_unit_of_measurement = TEMP_CELSIUS + elif self._sensor in ("current_sleep", "last_sleep", "current_sleep_fitness"): + self._attr_native_unit_of_measurement = "Score" _LOGGER.debug( "User Sensor: %s, Side: %s, User: %s", self._sensor, - self._side, - self._usrobj.userid if self._usrobj else None, + self._user_obj.side, + self._user_id, ) @property def native_value(self) -> str | int | float | None: """Return the state of the sensor.""" - if not self._usrobj: + if not self._user_obj: return None if "current" in self._sensor: if "fitness" in self._sensor: - return self._usrobj.current_sleep_fitness_score - return self._usrobj.current_sleep_score + return self._user_obj.current_sleep_fitness_score + return self._user_obj.current_sleep_score if "last" in self._sensor: - return self._usrobj.last_sleep_score + return self._user_obj.last_sleep_score if self._sensor == "bed_temperature": - temp = self._usrobj.current_values["bed_temp"] - try: - if self._units == "si": - return round(temp, 2) - return round((temp * 1.8) + 32, 2) - except TypeError: - return None + return self._user_obj.current_values["bed_temp"] if self._sensor == "sleep_stage": - return self._usrobj.current_values["stage"] + return self._user_obj.current_values["stage"] return None - @property - def native_unit_of_measurement(self) -> str | None: - """Return the unit the value is expressed in.""" - if self._sensor in ("current_sleep", "last_sleep", "current_sleep_fitness"): - return "Score" - if self._sensor == "bed_temperature": - if self._units == "si": - return TEMP_CELSIUS - return TEMP_FAHRENHEIT - return None - - def _get_rounded_value( - self, attr: dict[str, Any], key: str, use_units: bool = True - ) -> int | float | None: - """Get rounded value based on units for given key.""" - try: - if self._units == "si" or not use_units: - return round(attr["room_temp"], 2) - return round((attr["room_temp"] * 1.8) + 32, 2) - except TypeError: - return None - @property def extra_state_attributes(self) -> dict[str, Any] | None: """Return device state attributes.""" attr = None - if "current" in self._sensor and self._usrobj: + if "current" in self._sensor and self._user_obj: if "fitness" in self._sensor: - attr = self._usrobj.current_fitness_values + attr = self._user_obj.current_fitness_values else: - attr = self._usrobj.current_values - elif "last" in self._sensor and self._usrobj: - attr = self._usrobj.last_values + attr = self._user_obj.current_values + elif "last" in self._sensor and self._user_obj: + attr = self._user_obj.last_values if attr is None: # Skip attributes if sensor type doesn't support @@ -258,59 +233,41 @@ class EightUserSensor(EightSleepUserEntity, SensorEntity): ) state_attr[ATTR_REM_PERC] = _get_breakdown_percent(attr, "rem", sleep_time) - room_temp = self._get_rounded_value(attr, "room_temp") - bed_temp = self._get_rounded_value(attr, "bed_temp") + room_temp = _get_rounded_value(attr, "room_temp") + bed_temp = _get_rounded_value(attr, "bed_temp") if "current" in self._sensor: - state_attr[ATTR_RESP_RATE] = self._get_rounded_value( - attr, "resp_rate", False - ) - state_attr[ATTR_HEART_RATE] = self._get_rounded_value( - attr, "heart_rate", False - ) + state_attr[ATTR_RESP_RATE] = _get_rounded_value(attr, "resp_rate") + state_attr[ATTR_HEART_RATE] = _get_rounded_value(attr, "heart_rate") state_attr[ATTR_SLEEP_STAGE] = attr["stage"] state_attr[ATTR_ROOM_TEMP] = room_temp state_attr[ATTR_BED_TEMP] = bed_temp elif "last" in self._sensor: - state_attr[ATTR_AVG_RESP_RATE] = self._get_rounded_value( - attr, "resp_rate", False - ) - state_attr[ATTR_AVG_HEART_RATE] = self._get_rounded_value( - attr, "heart_rate", False - ) + state_attr[ATTR_AVG_RESP_RATE] = _get_rounded_value(attr, "resp_rate") + state_attr[ATTR_AVG_HEART_RATE] = _get_rounded_value(attr, "heart_rate") state_attr[ATTR_AVG_ROOM_TEMP] = room_temp state_attr[ATTR_AVG_BED_TEMP] = bed_temp return state_attr -class EightRoomSensor(EightSleepUserEntity, SensorEntity): +class EightRoomSensor(EightSleepBaseEntity, SensorEntity): """Representation of an eight sleep room sensor.""" + _attr_icon = "mdi:thermometer" + _attr_device_class = SensorDeviceClass.TEMPERATURE + _attr_native_unit_of_measurement = TEMP_CELSIUS + def __init__( self, - name: str, - coordinator: EightSleepUserDataCoordinator, + coordinator: DataUpdateCoordinator, eight: EightSleep, - side: str | None, sensor: str, - units: str, ) -> None: """Initialize the sensor.""" - super().__init__(name, coordinator, eight, side, sensor, units) - - self._attr_icon = "mdi:thermometer" - self._attr_native_unit_of_measurement: str = ( - TEMP_CELSIUS if self._units == "si" else TEMP_FAHRENHEIT - ) + super().__init__(coordinator, eight, None, sensor) @property def native_value(self) -> int | float | None: """Return the state of the sensor.""" - temp = self._eight.room_temperature() - try: - if self._units == "si": - return round(temp, 2) - return round((temp * 1.8) + 32, 2) - except TypeError: - return None + return self._eight.room_temperature() diff --git a/homeassistant/components/elgato/translations/es.json b/homeassistant/components/elgato/translations/es.json index 940272f5562..2cdbd7e6ae1 100644 --- a/homeassistant/components/elgato/translations/es.json +++ b/homeassistant/components/elgato/translations/es.json @@ -1,20 +1,20 @@ { "config": { "abort": { - "already_configured": "Este dispositivo Elgato Key Light ya est\u00e1 configurado.", + "already_configured": "El dispositivo ya est\u00e1 configurado", "cannot_connect": "No se pudo conectar" }, "error": { "cannot_connect": "No se pudo conectar" }, - "flow_title": "Elgato Key Light: {serial_number}", + "flow_title": "{serial_number}", "step": { "user": { "data": { - "host": "Host o direcci\u00f3n IP", + "host": "Host", "port": "Puerto" }, - "description": "Configura tu Elgato Key Light para integrarlo con Home Assistant." + "description": "Configura la integraci\u00f3n de Elgato Light con Home Assistant." }, "zeroconf_confirm": { "description": "\u00bfDesea a\u00f1adir Elgato Key Light con el n\u00famero de serie `{serial_number}` a Home Assistant?", diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 22dd50499d6..9a7c7dbc43a 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -42,6 +42,7 @@ from .const import ( ATTR_KEY, ATTR_KEY_NAME, ATTR_KEYPAD_ID, + ATTR_KEYPAD_NAME, CONF_AREA, CONF_AUTO_CONFIGURE, CONF_COUNTER, @@ -266,21 +267,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) elk.connect() - def _element_changed(element: Element, changeset: dict[str, Any]) -> None: + def _keypad_changed(keypad: Element, changeset: dict[str, Any]) -> None: if (keypress := changeset.get("last_keypress")) is None: return hass.bus.async_fire( EVENT_ELKM1_KEYPAD_KEY_PRESSED, { - ATTR_KEYPAD_ID: element.index + 1, + ATTR_KEYPAD_NAME: keypad.name, + ATTR_KEYPAD_ID: keypad.index + 1, ATTR_KEY_NAME: keypress[0], ATTR_KEY: keypress[1], }, ) for keypad in elk.keypads: - keypad.add_callback(_element_changed) + keypad.add_callback(_keypad_changed) try: if not await async_wait_for_elk_to_sync(elk, LOGIN_TIMEOUT, SYNC_TIMEOUT): diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index 49aa6f68a09..6b6a5b44d55 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -205,18 +205,18 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity): 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, - ArmedStatus.ARMED_STAY.value: STATE_ALARM_ARMED_HOME, - ArmedStatus.ARMED_STAY_INSTANT.value: STATE_ALARM_ARMED_HOME, - ArmedStatus.ARMED_TO_NIGHT.value: STATE_ALARM_ARMED_NIGHT, - ArmedStatus.ARMED_TO_NIGHT_INSTANT.value: STATE_ALARM_ARMED_NIGHT, - ArmedStatus.ARMED_TO_VACATION.value: STATE_ALARM_ARMED_AWAY, + ArmedStatus.DISARMED: STATE_ALARM_DISARMED, + ArmedStatus.ARMED_AWAY: STATE_ALARM_ARMED_AWAY, + ArmedStatus.ARMED_STAY: STATE_ALARM_ARMED_HOME, + ArmedStatus.ARMED_STAY_INSTANT: STATE_ALARM_ARMED_HOME, + ArmedStatus.ARMED_TO_NIGHT: STATE_ALARM_ARMED_NIGHT, + ArmedStatus.ARMED_TO_NIGHT_INSTANT: STATE_ALARM_ARMED_NIGHT, + ArmedStatus.ARMED_TO_VACATION: STATE_ALARM_ARMED_AWAY, } if self._element.alarm_state is None: self._state = None - elif self._element.alarm_state >= AlarmState.FIRE_ALARM.value: + elif self._element.in_alarm_state(): # Area is in alarm state self._state = STATE_ALARM_TRIGGERED elif self._entry_exit_timer_is_running(): @@ -239,32 +239,32 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity): async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" if code is not None: - self._element.arm(ArmLevel.ARMED_STAY.value, int(code)) + self._element.arm(ArmLevel.ARMED_STAY, int(code)) async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" if code is not None: - self._element.arm(ArmLevel.ARMED_AWAY.value, int(code)) + self._element.arm(ArmLevel.ARMED_AWAY, int(code)) async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send arm night command.""" if code is not None: - self._element.arm(ArmLevel.ARMED_NIGHT.value, int(code)) + self._element.arm(ArmLevel.ARMED_NIGHT, int(code)) async def async_alarm_arm_home_instant(self, code: str | None = None) -> None: """Send arm stay instant command.""" if code is not None: - self._element.arm(ArmLevel.ARMED_STAY_INSTANT.value, int(code)) + self._element.arm(ArmLevel.ARMED_STAY_INSTANT, int(code)) async def async_alarm_arm_night_instant(self, code: str | None = None) -> None: """Send arm night instant command.""" if code is not None: - self._element.arm(ArmLevel.ARMED_NIGHT_INSTANT.value, int(code)) + self._element.arm(ArmLevel.ARMED_NIGHT_INSTANT, int(code)) async def async_alarm_arm_vacation(self, code: str | None = None) -> None: """Send arm vacation command.""" if code is not None: - self._element.arm(ArmLevel.ARMED_VACATION.value, int(code)) + self._element.arm(ArmLevel.ARMED_VACATION, int(code)) async def async_display_message( self, clear: int, beep: bool, timeout: int, line1: str, line2: str diff --git a/homeassistant/components/elkm1/climate.py b/homeassistant/components/elkm1/climate.py index 7b6de739d4f..9f6dc359f6f 100644 --- a/homeassistant/components/elkm1/climate.py +++ b/homeassistant/components/elkm1/climate.py @@ -33,26 +33,26 @@ SUPPORT_HVAC = [ 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), + HVACMode.OFF: (ThermostatMode.OFF, ThermostatFan.AUTO), + HVACMode.HEAT: (ThermostatMode.HEAT, None), + HVACMode.COOL: (ThermostatMode.COOL, None), + HVACMode.HEAT_COOL: (ThermostatMode.AUTO, None), + HVACMode.FAN_ONLY: (ThermostatMode.OFF, ThermostatFan.ON), } 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, + ThermostatMode.OFF: HVACMode.OFF, + ThermostatMode.COOL: HVACMode.COOL, + ThermostatMode.HEAT: HVACMode.HEAT, + ThermostatMode.EMERGENCY_HEAT: HVACMode.HEAT, + ThermostatMode.AUTO: HVACMode.HEAT_COOL, } HASS_TO_ELK_FAN_MODES = { - FAN_AUTO: (None, ThermostatFan.AUTO.value), - FAN_ON: (None, ThermostatFan.ON.value), + FAN_AUTO: (None, ThermostatFan.AUTO), + FAN_ON: (None, ThermostatFan.ON), } ELK_TO_HASS_FAN_MODES = { - ThermostatFan.AUTO.value: FAN_AUTO, - ThermostatFan.ON.value: FAN_ON, + ThermostatFan.AUTO: FAN_AUTO, + ThermostatFan.ON: FAN_ON, } @@ -84,7 +84,7 @@ class ElkThermostat(ElkEntity, ClimateEntity): def __init__(self, element: Element, elk: Elk, elk_data: dict[str, Any]) -> None: """Initialize climate entity.""" super().__init__(element, elk, elk_data) - self._state: str = HVACMode.OFF + self._state: str | None = None @property def temperature_unit(self) -> str: @@ -100,11 +100,11 @@ class ElkThermostat(ElkEntity, ClimateEntity): def target_temperature(self) -> float | None: """Return the temperature we are trying to reach.""" if self._element.mode in ( - ThermostatMode.HEAT.value, - ThermostatMode.EMERGENCY_HEAT.value, + ThermostatMode.HEAT, + ThermostatMode.EMERGENCY_HEAT, ): return self._element.heat_setpoint - if self._element.mode == ThermostatMode.COOL.value: + if self._element.mode == ThermostatMode.COOL: return self._element.cool_setpoint return None @@ -129,7 +129,7 @@ class ElkThermostat(ElkEntity, ClimateEntity): return self._element.humidity @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> str | None: """Return current operation ie. heat, cool, idle.""" return self._state @@ -146,7 +146,7 @@ class ElkThermostat(ElkEntity, ClimateEntity): @property def is_aux_heat(self) -> bool: """Return if aux heater is on.""" - return self._element.mode == ThermostatMode.EMERGENCY_HEAT.value + return self._element.mode == ThermostatMode.EMERGENCY_HEAT @property def min_temp(self) -> float: @@ -159,15 +159,17 @@ class ElkThermostat(ElkEntity, ClimateEntity): return 99 @property - def fan_mode(self) -> str: + def fan_mode(self) -> str | None: """Return the fan setting.""" + if self._element.fan is None: + return None return ELK_TO_HASS_FAN_MODES[self._element.fan] - def _elk_set(self, mode: int | None, fan: int | None) -> None: + def _elk_set(self, mode: ThermostatMode | None, fan: ThermostatFan | None) -> None: if mode is not None: - self._element.set(ThermostatSetting.MODE.value, mode) + self._element.set(ThermostatSetting.MODE, mode) if fan is not None: - self._element.set(ThermostatSetting.FAN.value, fan) + self._element.set(ThermostatSetting.FAN, fan) async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set thermostat operation mode.""" @@ -176,11 +178,11 @@ class ElkThermostat(ElkEntity, ClimateEntity): async def async_turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" - self._elk_set(ThermostatMode.EMERGENCY_HEAT.value, None) + self._elk_set(ThermostatMode.EMERGENCY_HEAT, None) async def async_turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" - self._elk_set(ThermostatMode.HEAT.value, None) + self._elk_set(ThermostatMode.HEAT, None) @property def fan_modes(self) -> list[str]: @@ -197,11 +199,14 @@ class ElkThermostat(ElkEntity, ClimateEntity): low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) if low_temp is not None: - self._element.set(ThermostatSetting.HEAT_SETPOINT.value, round(low_temp)) + self._element.set(ThermostatSetting.HEAT_SETPOINT, round(low_temp)) if high_temp is not None: - self._element.set(ThermostatSetting.COOL_SETPOINT.value, round(high_temp)) + self._element.set(ThermostatSetting.COOL_SETPOINT, round(high_temp)) 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 + if self._element.mode is None: + self._state = None + else: + self._state = ELK_TO_HASS_HVAC_MODES[self._element.mode] + if self._state == HVACMode.OFF and self._element.fan == ThermostatFan.ON: + self._state = HVACMode.FAN_ONLY diff --git a/homeassistant/components/elkm1/const.py b/homeassistant/components/elkm1/const.py index fd4856bd5d5..a2bb5744c11 100644 --- a/homeassistant/components/elkm1/const.py +++ b/homeassistant/components/elkm1/const.py @@ -43,6 +43,7 @@ EVENT_ELKM1_KEYPAD_KEY_PRESSED = "elkm1.keypad_key_pressed" ATTR_KEYPAD_ID = "keypad_id" ATTR_KEY = "key" ATTR_KEY_NAME = "key_name" +ATTR_KEYPAD_NAME = "keypad_name" ATTR_CHANGED_BY_KEYPAD = "changed_by_keypad" ATTR_CHANGED_BY_ID = "changed_by_id" ATTR_CHANGED_BY_TIME = "changed_by_time" diff --git a/homeassistant/components/elkm1/logbook.py b/homeassistant/components/elkm1/logbook.py new file mode 100644 index 00000000000..9aa85b599e0 --- /dev/null +++ b/homeassistant/components/elkm1/logbook.py @@ -0,0 +1,43 @@ +"""Describe elkm1 logbook events.""" +from __future__ import annotations + +from collections.abc import Callable + +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) +from homeassistant.core import Event, HomeAssistant, callback + +from .const import ( + ATTR_KEY, + ATTR_KEY_NAME, + ATTR_KEYPAD_ID, + ATTR_KEYPAD_NAME, + DOMAIN, + EVENT_ELKM1_KEYPAD_KEY_PRESSED, +) + + +@callback +def async_describe_events( + hass: HomeAssistant, + async_describe_event: Callable[[str, str, Callable[[Event], dict[str, str]]], None], +) -> None: + """Describe logbook events.""" + + @callback + def async_describe_button_event(event: Event) -> dict[str, str]: + """Describe elkm1 logbook event.""" + data = event.data + keypad_name = data.get( + ATTR_KEYPAD_NAME, data[ATTR_KEYPAD_ID] + ) # added in 2022.6 + return { + LOGBOOK_ENTRY_NAME: f"Elk Keypad {keypad_name}", + LOGBOOK_ENTRY_MESSAGE: f"pressed {data[ATTR_KEY_NAME]} ({data[ATTR_KEY]})", + } + + async_describe_event( + DOMAIN, EVENT_ELKM1_KEYPAD_KEY_PRESSED, async_describe_button_event + ) diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json index ad5634c1e9f..13f8ba8401f 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.3.5"], + "requirements": ["elkm1-lib==2.0.0"], "dhcp": [{ "registered_devices": true }, { "macaddress": "00409D*" }], "codeowners": ["@gwww", "@bdraco"], "dependencies": ["network"], diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index e41d09e2e76..57f989d5cb5 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -3,12 +3,7 @@ from __future__ import annotations from typing import Any -from elkm1_lib.const import ( - SettingFormat, - ZoneLogicalStatus, - ZonePhysicalStatus, - ZoneType, -) +from elkm1_lib.const import SettingFormat, ZoneType from elkm1_lib.counters import Counter from elkm1_lib.elements import Element from elkm1_lib.elk import Elk @@ -237,25 +232,25 @@ class ElkZone(ElkSensor): def icon(self) -> str: """Icon to use in the frontend.""" zone_icons = { - ZoneType.FIRE_ALARM.value: "fire", - ZoneType.FIRE_VERIFIED.value: "fire", - ZoneType.FIRE_SUPERVISORY.value: "fire", - ZoneType.KEYFOB.value: "key", - ZoneType.NON_ALARM.value: "alarm-off", - ZoneType.MEDICAL_ALARM.value: "medical-bag", - ZoneType.POLICE_ALARM.value: "alarm-light", - ZoneType.POLICE_NO_INDICATION.value: "alarm-light", - ZoneType.KEY_MOMENTARY_ARM_DISARM.value: "power", - ZoneType.KEY_MOMENTARY_ARM_AWAY.value: "power", - ZoneType.KEY_MOMENTARY_ARM_STAY.value: "power", - ZoneType.KEY_MOMENTARY_DISARM.value: "power", - ZoneType.KEY_ON_OFF.value: "toggle-switch", - ZoneType.MUTE_AUDIBLES.value: "volume-mute", - ZoneType.POWER_SUPERVISORY.value: "power-plug", - ZoneType.TEMPERATURE.value: "thermometer-lines", - ZoneType.ANALOG_ZONE.value: "speedometer", - ZoneType.PHONE_KEY.value: "phone-classic", - ZoneType.INTERCOM_KEY.value: "deskphone", + ZoneType.FIRE_ALARM: "fire", + ZoneType.FIRE_VERIFIED: "fire", + ZoneType.FIRE_SUPERVISORY: "fire", + ZoneType.KEYFOB: "key", + ZoneType.NON_ALARM: "alarm-off", + ZoneType.MEDICAL_ALARM: "medical-bag", + ZoneType.POLICE_ALARM: "alarm-light", + ZoneType.POLICE_NO_INDICATION: "alarm-light", + ZoneType.KEY_MOMENTARY_ARM_DISARM: "power", + ZoneType.KEY_MOMENTARY_ARM_AWAY: "power", + ZoneType.KEY_MOMENTARY_ARM_STAY: "power", + ZoneType.KEY_MOMENTARY_DISARM: "power", + ZoneType.KEY_ON_OFF: "toggle-switch", + ZoneType.MUTE_AUDIBLES: "volume-mute", + ZoneType.POWER_SUPERVISORY: "power-plug", + ZoneType.TEMPERATURE: "thermometer-lines", + ZoneType.ANALOG_ZONE: "speedometer", + ZoneType.PHONE_KEY: "phone-classic", + ZoneType.INTERCOM_KEY: "deskphone", } return f"mdi:{zone_icons.get(self._element.definition, 'alarm-bell')}" @@ -263,13 +258,9 @@ class ElkZone(ElkSensor): def extra_state_attributes(self) -> dict[str, Any]: """Attributes of the sensor.""" attrs: dict[str, Any] = self.initial_attrs() - attrs["physical_status"] = ZonePhysicalStatus( - self._element.physical_status - ).name.lower() - attrs["logical_status"] = ZoneLogicalStatus( - self._element.logical_status - ).name.lower() - attrs["definition"] = ZoneType(self._element.definition).name.lower() + attrs["physical_status"] = self._element.physical_status.name.lower() + attrs["logical_status"] = self._element.logical_status.name.lower() + attrs["definition"] = self._element.definition.name.lower() attrs["area"] = self._element.area + 1 attrs["triggered_alarm"] = self._element.triggered_alarm return attrs @@ -277,27 +268,25 @@ class ElkZone(ElkSensor): @property def temperature_unit(self) -> str | None: """Return the temperature unit.""" - if self._element.definition == ZoneType.TEMPERATURE.value: + if self._element.definition == ZoneType.TEMPERATURE: return self._temperature_unit return None @property def native_unit_of_measurement(self) -> str | None: """Return the unit of measurement.""" - if self._element.definition == ZoneType.TEMPERATURE.value: + if self._element.definition == ZoneType.TEMPERATURE: return self._temperature_unit - if self._element.definition == ZoneType.ANALOG_ZONE.value: + if self._element.definition == ZoneType.ANALOG_ZONE: return ELECTRIC_POTENTIAL_VOLT return None def _element_changed(self, _: Element, changeset: Any) -> None: - if self._element.definition == ZoneType.TEMPERATURE.value: + if self._element.definition == ZoneType.TEMPERATURE: self._state = temperature_to_state( self._element.temperature, UNDEFINED_TEMPERATURE ) - elif self._element.definition == ZoneType.ANALOG_ZONE.value: + elif self._element.definition == ZoneType.ANALOG_ZONE: self._state = f"{self._element.voltage}" else: - self._state = pretty_const( - ZoneLogicalStatus(self._element.logical_status).name - ) + self._state = pretty_const(self._element.logical_status.name) diff --git a/homeassistant/components/elkm1/translations/es.json b/homeassistant/components/elkm1/translations/es.json index 46e25dd288f..2bd75f83e2f 100644 --- a/homeassistant/components/elkm1/translations/es.json +++ b/homeassistant/components/elkm1/translations/es.json @@ -4,7 +4,8 @@ "address_already_configured": "Ya est\u00e1 configurado un Elk-M1 con esta direcci\u00f3n", "already_configured": "Ya est\u00e1 configurado un Elk-M1 con este prefijo", "already_in_progress": "La configuraci\u00f3n ya se encuentra en proceso", - "cannot_connect": "Error al conectar" + "cannot_connect": "Error al conectar", + "unknown": "Error inesperado" }, "error": { "cannot_connect": "No se pudo conectar", @@ -20,7 +21,7 @@ "temperature_unit": "La unidad de temperatura que el ElkM1 usa.", "username": "Usuario" }, - "description": "Con\u00e9ctese al sistema detectado: {mac_address} ({host})", + "description": "Con\u00e9ctate al sistema descubierto: {mac_address} ({host})", "title": "Conectar con Control Elk-M1" }, "manual_connection": { diff --git a/homeassistant/components/elkm1/translations/ko.json b/homeassistant/components/elkm1/translations/ko.json index 6ffa3679946..5b16671c05b 100644 --- a/homeassistant/components/elkm1/translations/ko.json +++ b/homeassistant/components/elkm1/translations/ko.json @@ -10,6 +10,14 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "discovered_connection": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "\uc790\ub3d9\uac80\uc0c9\ub41c \uc2dc\uc2a4\ud15c\uc5d0 \uc5f0\uacb0: {mac_address} ({host})", + "title": "Elk-M1 \uc81c\uc5b4\uc5d0 \uc5f0\uacb0\ud558\uae30" + }, "user": { "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/nl.json b/homeassistant/components/elkm1/translations/nl.json index c27293ce050..c33aa601433 100644 --- a/homeassistant/components/elkm1/translations/nl.json +++ b/homeassistant/components/elkm1/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "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", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/elmax/translations/bg.json b/homeassistant/components/elmax/translations/bg.json index babce99193f..aa6a3dc3ef5 100644 --- a/homeassistant/components/elmax/translations/bg.json +++ b/homeassistant/components/elmax/translations/bg.json @@ -4,12 +4,10 @@ "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": { - "bad_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\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", "invalid_pin": "\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d\u0438\u044f\u0442 \u041f\u0418\u041d \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d", "network_error": "\u0412\u044a\u0437\u043d\u0438\u043a\u043d\u0430 \u043c\u0440\u0435\u0436\u043e\u0432\u0430 \u0433\u0440\u0435\u0448\u043a\u0430", - "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430", - "unknown_error": "\u0412\u044a\u0437\u043d\u0438\u043a\u043d\u0430 \u043d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, "step": { "panels": { diff --git a/homeassistant/components/elmax/translations/ca.json b/homeassistant/components/elmax/translations/ca.json index 74d9f72dce5..bac698a9656 100644 --- a/homeassistant/components/elmax/translations/ca.json +++ b/homeassistant/components/elmax/translations/ca.json @@ -4,13 +4,11 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "bad_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_pin": "El pin proporcionat no \u00e9s v\u00e0lid", "network_error": "S'ha produ\u00eft un error de xarxa", "no_panel_online": "No s'ha trobat cap panell de control d'Elmax en l\u00ednia.", - "unknown": "Error inesperat", - "unknown_error": "S'ha produ\u00eft un error desconegut" + "unknown": "Error inesperat" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Nom del panell", "panel_pin": "Codi PIN" }, - "description": "Selecciona quin panell vols controlar amb aquesta integraci\u00f3. Tingues en compte que el panell ha d'estar ON per poder ser configurat.", - "title": "Selecci\u00f3 del panell" + "description": "Selecciona quin panell vols controlar amb aquesta integraci\u00f3. Tingues en compte que el panell ha d'estar ON per poder ser configurat." }, "user": { "data": { "password": "Contrasenya", "username": "Nom d'usuari" }, - "description": "Inicia sessi\u00f3 a Elmax Cloud amb les teves credencials", - "title": "Inici de sessi\u00f3" + "description": "Inicia sessi\u00f3 a Elmax Cloud amb les teves credencials" } } - }, - "title": "Configuraci\u00f3 d'Elmax Cloud" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/cs.json b/homeassistant/components/elmax/translations/cs.json index a3e919af352..6c534c1a8a6 100644 --- a/homeassistant/components/elmax/translations/cs.json +++ b/homeassistant/components/elmax/translations/cs.json @@ -1,13 +1,11 @@ { "config": { "error": { - "bad_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "invalid_pin": "Poskytnut\u00fd k\u00f3d PIN je neplatn\u00fd", "network_error": "Do\u0161lo k chyb\u011b s\u00edt\u011b", "no_panel_online": "Nebyl nalezen \u017e\u00e1dn\u00fd online ovl\u00e1dac\u00ed panel Elmax.", - "unknown": "Neo\u010dek\u00e1van\u00e1 chyba", - "unknown_error": "Vyskytla se neo\u010dek\u00e1van\u00e1 chyba" + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "step": { "panels": { @@ -16,18 +14,15 @@ "panel_name": "N\u00e1zev panelu", "panel_pin": "PIN k\u00f3d" }, - "description": "Vyberte, kter\u00fd panel chcete touto integrac\u00ed ovl\u00e1dat. Vezm\u011bte pros\u00edm na v\u011bdom\u00ed, \u017ee panel mus\u00ed b\u00fdt zapnut\u00fd, aby mohl b\u00fdt nakonfigurov\u00e1n.", - "title": "V\u00fdb\u011br panelu" + "description": "Vyberte, kter\u00fd panel chcete touto integrac\u00ed ovl\u00e1dat. Vezm\u011bte pros\u00edm na v\u011bdom\u00ed, \u017ee panel mus\u00ed b\u00fdt zapnut\u00fd, aby mohl b\u00fdt nakonfigurov\u00e1n." }, "user": { "data": { "password": "Heslo", "username": "U\u017eivatelsk\u00e9 jm\u00e9no" }, - "description": "P\u0159ihlaste se do cloudu Elmax pomoc\u00ed sv\u00fdch p\u0159ihla\u0161ovac\u00edch \u00fadaj\u016f", - "title": "P\u0159ihl\u00e1\u0161en\u00ed k \u00fa\u010dtu" + "description": "P\u0159ihlaste se do cloudu Elmax pomoc\u00ed sv\u00fdch p\u0159ihla\u0161ovac\u00edch \u00fadaj\u016f" } } - }, - "title": "Nastaven\u00ed Elmax Cloud" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/de.json b/homeassistant/components/elmax/translations/de.json index 241802e82d4..55e947d5bd0 100644 --- a/homeassistant/components/elmax/translations/de.json +++ b/homeassistant/components/elmax/translations/de.json @@ -4,13 +4,11 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "bad_auth": "Ung\u00fcltige Authentifizierung", "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_pin": "Die angegebene Pin ist ung\u00fcltig", "network_error": "Ein Netzwerkfehler ist aufgetreten", "no_panel_online": "Es wurde kein Elmax-Bedienfeld gefunden, das online ist.", - "unknown": "Unerwarteter Fehler", - "unknown_error": "Ein unerwarteter Fehler ist aufgetreten" + "unknown": "Unerwarteter Fehler" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Panel-Name", "panel_pin": "PIN-Code" }, - "description": "W\u00e4hle die Zentrale aus, die du mit dieser Integration steuern m\u00f6chtest. Bitte beachte, dass die Zentrale eingeschaltet sein muss, damit sie konfiguriert werden kann.", - "title": "Panelauswahl" + "description": "W\u00e4hle die Zentrale aus, die du mit dieser Integration steuern m\u00f6chtest. Bitte beachte, dass die Zentrale eingeschaltet sein muss, damit sie konfiguriert werden kann." }, "user": { "data": { "password": "Passwort", "username": "Benutzername" }, - "description": "Bitte melde dich mit deinen Zugangsdaten bei der Elmax-Cloud an", - "title": "Konto-Anmeldung" + "description": "Bitte melde dich mit deinen Zugangsdaten bei der Elmax-Cloud an" } } - }, - "title": "Elmax Cloud-Einrichtung" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/el.json b/homeassistant/components/elmax/translations/el.json index 916ab585935..2b99f715ccc 100644 --- a/homeassistant/components/elmax/translations/el.json +++ b/homeassistant/components/elmax/translations/el.json @@ -4,13 +4,11 @@ "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": { - "bad_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_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_pin": "\u03a4\u03bf \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf pin \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf", "network_error": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", "no_panel_online": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc\u03c2 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 Elmax.", - "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1", - "unknown_error": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03bc\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1", "panel_pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b8\u03ad\u03bb\u03b1\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03c4\u03b5 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7. \u03a3\u03b7\u03bc\u03b5\u03b9\u03ce\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af.", - "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c0\u03af\u03bd\u03b1\u03ba\u03b1" + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b8\u03ad\u03bb\u03b1\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03c4\u03b5 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7. \u03a3\u03b7\u03bc\u03b5\u03b9\u03ce\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af." }, "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": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf cloud \u03c4\u03b7\u03c2 Elmax \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2", - "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd" + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf cloud \u03c4\u03b7\u03c2 Elmax \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2" } } - }, - "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 Elmax Cloud" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/en.json b/homeassistant/components/elmax/translations/en.json index 60da5d88dc3..dabd58fc941 100644 --- a/homeassistant/components/elmax/translations/en.json +++ b/homeassistant/components/elmax/translations/en.json @@ -4,13 +4,11 @@ "already_configured": "Device is already configured" }, "error": { - "bad_auth": "Invalid authentication", "invalid_auth": "Invalid authentication", "invalid_pin": "The provided pin is invalid", "network_error": "A network error occurred", "no_panel_online": "No online Elmax control panel was found.", - "unknown": "Unexpected error", - "unknown_error": "An unexpected error occurred" + "unknown": "Unexpected error" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Panel Name", "panel_pin": "PIN Code" }, - "description": "Select which panel you would like to control with this integration. Please note that the panel must be ON in order to be configured.", - "title": "Panel selection" + "description": "Select which panel you would like to control with this integration. Please note that the panel must be ON in order to be configured." }, "user": { "data": { "password": "Password", "username": "Username" }, - "description": "Please login to the Elmax cloud using your credentials", - "title": "Account Login" + "description": "Please login to the Elmax cloud using your credentials" } } - }, - "title": "Elmax Cloud Setup" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/es.json b/homeassistant/components/elmax/translations/es.json index 76d3d14aeab..a8130c70d7e 100644 --- a/homeassistant/components/elmax/translations/es.json +++ b/homeassistant/components/elmax/translations/es.json @@ -4,13 +4,11 @@ "already_configured": "El dispositivo ya est\u00e1 configurado" }, "error": { - "bad_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_pin": "El pin proporcionado no es v\u00e1lido", "network_error": "Se ha producido un error de red", "no_panel_online": "No se encontr\u00f3 ning\u00fan panel de control de Elmax en l\u00ednea.", - "unknown": "Error inesperado", - "unknown_error": "Error inesperado" + "unknown": "Error inesperado" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Nombre del panel", "panel_pin": "C\u00f3digo PIN" }, - "description": "Seleccione el panel que desea controlar con esta integraci\u00f3n. Tenga en cuenta que el panel debe estar activado para poder configurarlo.", - "title": "Selecci\u00f3n de panel" + "description": "Seleccione el panel que desea controlar con esta integraci\u00f3n. Tenga en cuenta que el panel debe estar activado para poder configurarlo." }, "user": { "data": { "password": "Contrase\u00f1a", "username": "Nombre de usuario" }, - "description": "Inicie sesi\u00f3n en Elmax Cloud con sus credenciales", - "title": "Inicio de sesi\u00f3n de cuenta" + "description": "Inicie sesi\u00f3n en Elmax Cloud con sus credenciales" } } - }, - "title": "Configuraci\u00f3n de Elmax Cloud" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/et.json b/homeassistant/components/elmax/translations/et.json index 593bd00713f..5db777a8666 100644 --- a/homeassistant/components/elmax/translations/et.json +++ b/homeassistant/components/elmax/translations/et.json @@ -4,13 +4,11 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud" }, "error": { - "bad_auth": "Vigane autentimine", "invalid_auth": "Tuvastamine nurjus", "invalid_pin": "Sisestatud pin on kehtetu", "network_error": "Ilmnes v\u00f5rgut\u00f5rge", "no_panel_online": "V\u00f5rgus olevat Elmaxi juhtpaneeli ei leitud.", - "unknown": "Ootamatu t\u00f5rge", - "unknown_error": "Ilmnes ootamatu t\u00f5rge" + "unknown": "Ootamatu t\u00f5rge" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Paneeli nimi", "panel_pin": "PIN kood" }, - "description": "Vali millist paneeli soovid selle sidumisega juhtida. Pane t\u00e4hele, et paneel peab seadistamiseks olema SEES.", - "title": "Paneeli valik" + "description": "Vali millist paneeli soovid selle sidumisega juhtida. Pane t\u00e4hele, et paneel peab seadistamiseks olema SEES." }, "user": { "data": { "password": "Salas\u00f5na", "username": "Kasutajanimi" }, - "description": "Logi oma mandaate kasutades Elmaxi pilve sisse", - "title": "Kontole sisselogimine" + "description": "Logi oma mandaate kasutades Elmaxi pilve sisse" } } - }, - "title": "Elmaxi pilve seadistamine" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/fr.json b/homeassistant/components/elmax/translations/fr.json index 7fe57a8290f..8ca02f3232a 100644 --- a/homeassistant/components/elmax/translations/fr.json +++ b/homeassistant/components/elmax/translations/fr.json @@ -4,13 +4,11 @@ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, "error": { - "bad_auth": "Authentification non valide", "invalid_auth": "Authentification non valide", "invalid_pin": "Le code PIN fourni n\u2019est pas valide", "network_error": "Une erreur r\u00e9seau s'est produite", "no_panel_online": "Aucun panneau de contr\u00f4le Elmax en ligne n'a \u00e9t\u00e9 trouv\u00e9.", - "unknown": "Erreur inattendue", - "unknown_error": "une erreur inattendue est apparue" + "unknown": "Erreur inattendue" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Nom du panneau", "panel_pin": "Code PIN" }, - "description": "S\u00e9lectionnez le panneau que vous souhaitez contr\u00f4ler avec cette int\u00e9gration. Veuillez noter que le panneau doit \u00eatre allum\u00e9 pour \u00eatre configur\u00e9.", - "title": "S\u00e9lection du panneau" + "description": "S\u00e9lectionnez le panneau que vous souhaitez contr\u00f4ler avec cette int\u00e9gration. Veuillez noter que le panneau doit \u00eatre allum\u00e9 pour \u00eatre configur\u00e9." }, "user": { "data": { "password": "Mot de passe", "username": "Nom d'utilisateur" }, - "description": "Veuillez vous connecter au cloud Elmax en utilisant vos informations d'identification", - "title": "Connexion au compte" + "description": "Veuillez vous connecter au cloud Elmax en utilisant vos informations d'identification" } } - }, - "title": "Configuration d\u2019Elmax Cloud" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/hu.json b/homeassistant/components/elmax/translations/hu.json index eac087baf27..9a5c70beba8 100644 --- a/homeassistant/components/elmax/translations/hu.json +++ b/homeassistant/components/elmax/translations/hu.json @@ -4,13 +4,11 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "bad_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "invalid_pin": "A megadott PIN-k\u00f3d \u00e9rv\u00e9nytelen", "network_error": "H\u00e1l\u00f3zati hiba t\u00f6rt\u00e9nt", "no_panel_online": "Nem tal\u00e1lhat\u00f3 online Elmax vez\u00e9rl\u0151panel.", - "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", - "unknown_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Panel neve", "panel_pin": "PIN-k\u00f3d" }, - "description": "V\u00e1lassza ki, hogy melyik panelt szeretn\u00e9 vez\u00e9relni ezzel az integr\u00e1ci\u00f3val. A panelnek bekapcsolt \u00e1llapotban kell lennie a konfigur\u00e1l\u00e1shoz.", - "title": "Panel kiv\u00e1laszt\u00e1sa" + "description": "V\u00e1lassza ki, hogy melyik panelt szeretn\u00e9 vez\u00e9relni ezzel az integr\u00e1ci\u00f3val. A panelnek bekapcsolt \u00e1llapotban kell lennie a konfigur\u00e1l\u00e1shoz." }, "user": { "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, - "description": "K\u00e9rem, jelentkezzen be az Elmax felh\u0151be a hiteles\u00edt\u0151 adataival", - "title": "Fi\u00f3k bejelentkez\u00e9s" + "description": "K\u00e9rem, jelentkezzen be az Elmax felh\u0151be a hiteles\u00edt\u0151 adataival" } } - }, - "title": "Elmax Cloud be\u00e1ll\u00edt\u00e1sa" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/id.json b/homeassistant/components/elmax/translations/id.json index 1df5e47679d..9b12a0d166e 100644 --- a/homeassistant/components/elmax/translations/id.json +++ b/homeassistant/components/elmax/translations/id.json @@ -4,13 +4,11 @@ "already_configured": "Perangkat sudah dikonfigurasi" }, "error": { - "bad_auth": "Autentikasi tidak valid", "invalid_auth": "Autentikasi tidak valid", "invalid_pin": "PIN yang diberikan tidak valid", "network_error": "Terjadi kesalahan jaringan", "no_panel_online": "Tidak ada panel kontrol Elmax online yang ditemukan.", - "unknown": "Kesalahan yang tidak diharapkan", - "unknown_error": "Terjadi kesalahan tak terduga" + "unknown": "Kesalahan yang tidak diharapkan" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Nama Panel", "panel_pin": "Kode PIN" }, - "description": "Pilih panel mana yang ingin dikontrol dengan integrasi ini. Perhatikan bahwa panel harus AKTIF agar dapat dikonfigurasi.", - "title": "Pemilihan panel" + "description": "Pilih panel mana yang ingin dikontrol dengan integrasi ini. Perhatikan bahwa panel harus AKTIF agar dapat dikonfigurasi." }, "user": { "data": { "password": "Kata Sandi", "username": "Nama Pengguna" }, - "description": "Masuk ke cloud Elmax menggunakan kredensial Anda", - "title": "Akun Masuk" + "description": "Masuk ke cloud Elmax menggunakan kredensial Anda" } } - }, - "title": "Penyiapan Elmax Cloud" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/it.json b/homeassistant/components/elmax/translations/it.json index 30098163498..5db43bdb935 100644 --- a/homeassistant/components/elmax/translations/it.json +++ b/homeassistant/components/elmax/translations/it.json @@ -4,13 +4,11 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "bad_auth": "Autenticazione non valida", "invalid_auth": "Autenticazione non valida", - "invalid_pin": "Il pin fornito non \u00e8 valido", + "invalid_pin": "Il PIN fornito non \u00e8 valido", "network_error": "Si \u00e8 verificato un errore di rete", "no_panel_online": "Non \u00e8 stato trovato alcun pannello di controllo Elmax in linea.", - "unknown": "Errore imprevisto", - "unknown_error": "Si \u00e8 verificato un errore imprevisto" + "unknown": "Errore imprevisto" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Nome del pannello", "panel_pin": "Codice PIN" }, - "description": "Seleziona quale pannello vuoi controllare con questa integrazione. Nota che il pannello deve essere acceso per essere configurato.", - "title": "Selezione del pannello" + "description": "Seleziona quale pannello vuoi controllare con questa integrazione. Nota che il pannello deve essere acceso per essere configurato." }, "user": { "data": { "password": "Password", "username": "Nome utente" }, - "description": "Esegui l'accesso al cloud di Elmax utilizzando le tue credenziali", - "title": "Accesso all'account" + "description": "Esegui l'accesso al cloud di Elmax utilizzando le tue credenziali" } } - }, - "title": "Configurazione di Elmax Cloud" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/ja.json b/homeassistant/components/elmax/translations/ja.json index ffed5307921..8730ae4791b 100644 --- a/homeassistant/components/elmax/translations/ja.json +++ b/homeassistant/components/elmax/translations/ja.json @@ -4,13 +4,11 @@ "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" }, "error": { - "bad_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "invalid_pin": "\u63d0\u4f9b\u3055\u308c\u305f\u30d4\u30f3\u304c\u7121\u52b9\u3067\u3059", "network_error": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f", "no_panel_online": "\u30aa\u30f3\u30e9\u30a4\u30f3\u306eElmax\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d1\u30cd\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002", - "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc", - "unknown_error": "\u4e88\u671f\u305b\u306c\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f" + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "\u30d1\u30cd\u30eb\u540d", "panel_pin": "PIN\u30b3\u30fc\u30c9" }, - "description": "\u3053\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u5236\u5fa1\u3059\u308b\u30d1\u30cd\u30eb\u3092\u9078\u629e\u3057\u307e\u3059\u3002\u8a2d\u5b9a\u3059\u308b\u306b\u306f\u3001\u30d1\u30cd\u30eb\u304c\u30aa\u30f3\u306b\u306a\u3063\u3066\u3044\u308b\u5fc5\u8981\u304c\u3042\u308b\u3053\u3068\u306b\u6ce8\u610f\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "\u30d1\u30cd\u30eb\u306e\u9078\u629e" + "description": "\u3053\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u5236\u5fa1\u3059\u308b\u30d1\u30cd\u30eb\u3092\u9078\u629e\u3057\u307e\u3059\u3002\u8a2d\u5b9a\u3059\u308b\u306b\u306f\u3001\u30d1\u30cd\u30eb\u304c\u30aa\u30f3\u306b\u306a\u3063\u3066\u3044\u308b\u5fc5\u8981\u304c\u3042\u308b\u3053\u3068\u306b\u6ce8\u610f\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, "user": { "data": { "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", "username": "\u30e6\u30fc\u30b6\u30fc\u540d" }, - "description": "\u3042\u306a\u305f\u306e\u8a8d\u8a3c\u60c5\u5831\u3092\u4f7f\u7528\u3057\u3066\u3001Elmax\u30af\u30e9\u30a6\u30c9\u306b\u30ed\u30b0\u30a4\u30f3\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "\u30a2\u30ab\u30a6\u30f3\u30c8 \u30ed\u30b0\u30a4\u30f3" + "description": "\u3042\u306a\u305f\u306e\u8a8d\u8a3c\u60c5\u5831\u3092\u4f7f\u7528\u3057\u3066\u3001Elmax\u30af\u30e9\u30a6\u30c9\u306b\u30ed\u30b0\u30a4\u30f3\u3057\u3066\u304f\u3060\u3055\u3044\u3002" } } - }, - "title": "Elmax Cloud\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/ko.json b/homeassistant/components/elmax/translations/ko.json new file mode 100644 index 00000000000..b9f74068f56 --- /dev/null +++ b/homeassistant/components/elmax/translations/ko.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "panels": { + "data": { + "panel_pin": "PIN \ucf54\ub4dc" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/lt.json b/homeassistant/components/elmax/translations/lt.json index d59749831b1..955dc21bf1c 100644 --- a/homeassistant/components/elmax/translations/lt.json +++ b/homeassistant/components/elmax/translations/lt.json @@ -14,10 +14,8 @@ "password": "Slapta\u017eodis", "username": "Prisijungimo vardas" }, - "description": "Prisijunkite prie \"Elmax\" debesies naudodami savo prisijungimo duomenis", - "title": "Paskyros prisijungimas" + "description": "Prisijunkite prie \"Elmax\" debesies naudodami savo prisijungimo duomenis" } } - }, - "title": "Elmax Cloud nustatymai" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/nl.json b/homeassistant/components/elmax/translations/nl.json index ed82cbf82c4..0a282680ce6 100644 --- a/homeassistant/components/elmax/translations/nl.json +++ b/homeassistant/components/elmax/translations/nl.json @@ -4,13 +4,11 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "bad_auth": "Ongeldige authenticatie", "invalid_auth": "Ongeldige authenticatie", "invalid_pin": "De opgegeven pincode is ongeldig", "network_error": "Er is een netwerkfout opgetreden", "no_panel_online": "Er is geen online Elmax-controlepaneel gevonden.", - "unknown": "Onverwachte fout", - "unknown_error": "Een onbekende fout is opgetreden." + "unknown": "Onverwachte fout" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Paneelnaam:", "panel_pin": "PIN Code" }, - "description": "Selecteer welk paneel je wilt bedienen met deze integratie. Houd er rekening mee dat het paneel AAN moet staan om te kunnen worden geconfigureerd.", - "title": "Paneelselectie" + "description": "Selecteer welk paneel je wilt bedienen met deze integratie. Houd er rekening mee dat het paneel AAN moet staan om te kunnen worden geconfigureerd." }, "user": { "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" }, - "description": "Log in op de Elmax-cloud met uw inloggegevens", - "title": "Account Login" + "description": "Log in op de Elmax-cloud met uw inloggegevens" } } - }, - "title": "Elmax Cloud Setup" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/no.json b/homeassistant/components/elmax/translations/no.json index 35d8bb409f5..ef806106848 100644 --- a/homeassistant/components/elmax/translations/no.json +++ b/homeassistant/components/elmax/translations/no.json @@ -4,13 +4,11 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { - "bad_auth": "Ugyldig godkjenning", "invalid_auth": "Ugyldig godkjenning", "invalid_pin": "Den angitte PIN-koden er ugyldig", "network_error": "Det oppstod en nettverksfeil", "no_panel_online": "Ingen online Elmax kontrollpanel ble funnet.", - "unknown": "Uventet feil", - "unknown_error": "Uventet feil" + "unknown": "Uventet feil" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Navn p\u00e5 panel", "panel_pin": "PIN kode" }, - "description": "Velg hvilket panel du vil kontrollere med denne integrasjonen. V\u00e6r oppmerksom p\u00e5 at panelet m\u00e5 v\u00e6re P\u00c5 for \u00e5 kunne konfigureres.", - "title": "Valg av panel" + "description": "Velg hvilket panel du vil kontrollere med denne integrasjonen. V\u00e6r oppmerksom p\u00e5 at panelet m\u00e5 v\u00e6re P\u00c5 for \u00e5 kunne konfigureres." }, "user": { "data": { "password": "Passord", "username": "Brukernavn" }, - "description": "Logg p\u00e5 Elmax-skyen ved \u00e5 bruke legitimasjonen din", - "title": "P\u00e5logging til konto" + "description": "Logg p\u00e5 Elmax-skyen ved \u00e5 bruke legitimasjonen din" } } - }, - "title": "Elmax Cloud Setup" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/pl.json b/homeassistant/components/elmax/translations/pl.json index d5776432f5f..a66a1e810cd 100644 --- a/homeassistant/components/elmax/translations/pl.json +++ b/homeassistant/components/elmax/translations/pl.json @@ -4,13 +4,11 @@ "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "bad_auth": "Niepoprawne uwierzytelnienie", "invalid_auth": "Niepoprawne uwierzytelnienie", "invalid_pin": "Podany kod PIN jest nieprawid\u0142owy", "network_error": "Wyst\u0105pi\u0142 b\u0142\u0105d sieci.", "no_panel_online": "Nie znaleziono panelu sterowania online Elmax.", - "unknown": "Nieoczekiwany b\u0142\u0105d", - "unknown_error": "Nieoczekiwany b\u0142\u0105d" + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Nazwa panelu", "panel_pin": "Kod PIN" }, - "description": "Wybierz panel, kt\u00f3rym chcesz sterowa\u0107 za pomoc\u0105 tej integracji. Nale\u017cy pami\u0119ta\u0107, \u017ce panel musi by\u0107 w\u0142\u0105czony, aby mo\u017cna by\u0142o go skonfigurowa\u0107.", - "title": "Wyb\u00f3r panelu" + "description": "Wybierz panel, kt\u00f3rym chcesz sterowa\u0107 za pomoc\u0105 tej integracji. Nale\u017cy pami\u0119ta\u0107, \u017ce panel musi by\u0107 w\u0142\u0105czony, aby mo\u017cna by\u0142o go skonfigurowa\u0107." }, "user": { "data": { "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" }, - "description": "Prosz\u0119 zalogowa\u0107 si\u0119 do chmury Elmax za pomoc\u0105 swoich danych uwierzytelniaj\u0105cych", - "title": "Logowanie do konta" + "description": "Prosz\u0119 zalogowa\u0107 si\u0119 do chmury Elmax za pomoc\u0105 swoich danych uwierzytelniaj\u0105cych" } } - }, - "title": "Konfiguracja chmury Elmax" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/pt-BR.json b/homeassistant/components/elmax/translations/pt-BR.json index 9db13c83fbc..c68583bd431 100644 --- a/homeassistant/components/elmax/translations/pt-BR.json +++ b/homeassistant/components/elmax/translations/pt-BR.json @@ -4,13 +4,11 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "bad_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "invalid_pin": "O C\u00f3digo PIN fornecido \u00e9 inv\u00e1lido", "network_error": "Ocorreu um erro de rede", "no_panel_online": "Nenhum painel de controle on-line Elmax foi encontrado.", - "unknown": "Erro inesperado", - "unknown_error": "Erro inesperado" + "unknown": "Erro inesperado" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Nome do painel", "panel_pin": "C\u00f3digo PIN" }, - "description": "Selecione qual painel voc\u00ea gostaria de controlar com esta integra\u00e7\u00e3o. Observe que o painel deve estar LIGADO para ser configurado.", - "title": "Sele\u00e7\u00e3o do painel" + "description": "Selecione qual painel voc\u00ea gostaria de controlar com esta integra\u00e7\u00e3o. Observe que o painel deve estar LIGADO para ser configurado." }, "user": { "data": { "password": "Senha", "username": "Usu\u00e1rio" }, - "description": "Fa\u00e7a login na nuvem Elmax usando suas credenciais", - "title": "Login da conta" + "description": "Fa\u00e7a login na nuvem Elmax usando suas credenciais" } } - }, - "title": "Configura\u00e7\u00e3o de nuvem Elmax" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/ru.json b/homeassistant/components/elmax/translations/ru.json index d5b3483ad32..35dc047efbb 100644 --- a/homeassistant/components/elmax/translations/ru.json +++ b/homeassistant/components/elmax/translations/ru.json @@ -4,13 +4,11 @@ "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": { - "bad_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_pin": "\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 PIN-\u043a\u043e\u0434 \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d.", "network_error": "\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u0432 \u0441\u0435\u0442\u0438.", "no_panel_online": "\u041f\u0430\u043d\u0435\u043b\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f Elmax \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430.", - "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430.", - "unknown_error": "\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0430\u043d\u0435\u043b\u0438", "panel_pin": "PIN-\u043a\u043e\u0434" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u043d\u0435\u043b\u044c, \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u044d\u0442\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0430\u043d\u0435\u043b\u044c \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430.", - "title": "\u0412\u044b\u0431\u043e\u0440 \u043f\u0430\u043d\u0435\u043b\u0438" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u043d\u0435\u043b\u044c, \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u044d\u0442\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0430\u043d\u0435\u043b\u044c \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430." }, "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": "\u0412\u043e\u0439\u0434\u0438\u0442\u0435 \u0432 Elmax Cloud, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u0432\u043e\u0438 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", - "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f" + "description": "\u0412\u043e\u0439\u0434\u0438\u0442\u0435 \u0432 Elmax Cloud, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u0432\u043e\u0438 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435." } } - }, - "title": "Elmax Cloud" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/sk.json b/homeassistant/components/elmax/translations/sk.json index ae37c2ed275..5ada995aa6e 100644 --- a/homeassistant/components/elmax/translations/sk.json +++ b/homeassistant/components/elmax/translations/sk.json @@ -1,8 +1,7 @@ { "config": { "error": { - "invalid_auth": "Neplatn\u00e9 overenie", - "unknown_error": "Vyskytla sa neo\u010dak\u00e1van\u00e1 chyba" + "invalid_auth": "Neplatn\u00e9 overenie" } } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/tr.json b/homeassistant/components/elmax/translations/tr.json index 5bcea9da388..dc43e42ab14 100644 --- a/homeassistant/components/elmax/translations/tr.json +++ b/homeassistant/components/elmax/translations/tr.json @@ -4,13 +4,11 @@ "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" }, "error": { - "bad_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "invalid_pin": "Sa\u011flanan pin ge\u00e7ersiz", "network_error": "Bir a\u011f hatas\u0131 olu\u015ftu", "no_panel_online": "\u00c7evrimi\u00e7i Elmax kontrol paneli bulunamad\u0131.", - "unknown": "Beklenmeyen hata", - "unknown_error": "Beklenmeyen bir hata olu\u015ftu" + "unknown": "Beklenmeyen hata" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "Panel Ad\u0131", "panel_pin": "PIN Kodu" }, - "description": "Bu entegrasyon ile hangi paneli kontrol etmek istedi\u011finizi se\u00e7in. L\u00fctfen konfig\u00fcre edilebilmesi i\u00e7in panelin A\u00c7IK olmas\u0131 gerekti\u011fini unutmay\u0131n.", - "title": "Panel se\u00e7imi" + "description": "Bu entegrasyon ile hangi paneli kontrol etmek istedi\u011finizi se\u00e7in. L\u00fctfen konfig\u00fcre edilebilmesi i\u00e7in panelin A\u00c7IK olmas\u0131 gerekti\u011fini unutmay\u0131n." }, "user": { "data": { "password": "Parola", "username": "Kullan\u0131c\u0131 Ad\u0131" }, - "description": "L\u00fctfen kimlik bilgilerinizi kullanarak Elmax bulutuna giri\u015f yap\u0131n", - "title": "Hesap Giri\u015fi" + "description": "L\u00fctfen kimlik bilgilerinizi kullanarak Elmax bulutuna giri\u015f yap\u0131n" } } - }, - "title": "Elmax Bulut Kurulumu" + } } \ No newline at end of file diff --git a/homeassistant/components/elmax/translations/zh-Hans.json b/homeassistant/components/elmax/translations/zh-Hans.json index 0d0c87c6a5b..b07f985a2c7 100644 --- a/homeassistant/components/elmax/translations/zh-Hans.json +++ b/homeassistant/components/elmax/translations/zh-Hans.json @@ -10,8 +10,7 @@ "data": { "password": "\u5bc6\u7801", "username": "\u7528\u6237\u540d" - }, - "title": "\u8d26\u6237\u767b\u5f55" + } } } } diff --git a/homeassistant/components/elmax/translations/zh-Hant.json b/homeassistant/components/elmax/translations/zh-Hant.json index b1fc8886d1c..c67fcddaa7a 100644 --- a/homeassistant/components/elmax/translations/zh-Hant.json +++ b/homeassistant/components/elmax/translations/zh-Hant.json @@ -4,13 +4,11 @@ "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "bad_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "invalid_pin": "\u6240\u63d0\u4f9b\u7684 PIN \u7121\u6548\u3002", "network_error": "\u767c\u751f\u7db2\u8def\u932f\u8aa4", "no_panel_online": "\u627e\u4e0d\u5230 Elmax \u63a7\u5236\u9762\u677f\u3002", - "unknown": "\u672a\u9810\u671f\u932f\u8aa4", - "unknown_error": "\u767c\u751f\u672a\u77e5\u932f\u8aa4" + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "panels": { @@ -19,18 +17,15 @@ "panel_name": "\u9762\u677f\u540d\u7a31", "panel_pin": "PIN \u78bc" }, - "description": "\u9078\u64c7\u6574\u5408\u6240\u8981\u4f7f\u7528\u7684\u9762\u677f\u3002\u8acb\u6ce8\u610f\u3001\u9762\u677f\u5fc5\u9808\u70ba\u958b\u555f\u72c0\u614b\u65b9\u80fd\u9032\u884c\u8a2d\u5b9a\u3002", - "title": "\u9078\u64c7\u9762\u677f" + "description": "\u9078\u64c7\u6574\u5408\u6240\u8981\u4f7f\u7528\u7684\u9762\u677f\u3002\u8acb\u6ce8\u610f\u3001\u9762\u677f\u5fc5\u9808\u70ba\u958b\u555f\u72c0\u614b\u65b9\u80fd\u9032\u884c\u8a2d\u5b9a\u3002" }, "user": { "data": { "password": "\u5bc6\u78bc", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, - "description": "\u8acb\u4f7f\u7528\u6191\u8b49\u4ee5\u767b\u5165 Elmax \u96f2\u670d\u52d9", - "title": "\u767b\u5165\u5e33\u865f" + "description": "\u8acb\u4f7f\u7528\u6191\u8b49\u4ee5\u767b\u5165 Elmax \u96f2\u670d\u52d9" } } - }, - "title": "Elmax \u96f2\u670d\u52d9\u8a2d\u5b9a" + } } \ No newline at end of file diff --git a/homeassistant/components/emulated_kasa/__init__.py b/homeassistant/components/emulated_kasa/__init__.py index 9643962ecb4..41198f922cf 100644 --- a/homeassistant/components/emulated_kasa/__init__.py +++ b/homeassistant/components/emulated_kasa/__init__.py @@ -14,8 +14,8 @@ from homeassistant.const import ( STATE_ON, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity_registry import RegistryEntry from homeassistant.helpers.template import Template, is_template_string from homeassistant.helpers.typing import ConfigType @@ -79,7 +79,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def validate_configs(hass, entity_configs): """Validate that entities exist and ensure templates are ready to use.""" - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) for entity_id, entity_config in entity_configs.items(): if (state := hass.states.get(entity_id)) is None: _LOGGER.debug("Entity not found: %s", entity_id) @@ -108,7 +108,7 @@ async def validate_configs(hass, entity_configs): _LOGGER.debug("No power value defined for: %s", entity_id) -def get_system_unique_id(entity: RegistryEntry): +def get_system_unique_id(entity: er.RegistryEntry): """Determine the system wide unique_id for an entity.""" return f"{entity.platform}.{entity.domain}.{entity.unique_id}" diff --git a/homeassistant/components/emulated_roku/translations/es.json b/homeassistant/components/emulated_roku/translations/es.json index 1bc53c03f19..abbcac75479 100644 --- a/homeassistant/components/emulated_roku/translations/es.json +++ b/homeassistant/components/emulated_roku/translations/es.json @@ -7,7 +7,7 @@ "user": { "data": { "advertise_ip": "IP para anunciar", - "advertise_port": "Puerto para anunciar", + "advertise_port": "Puerto de advertencias", "host_ip": "Direcci\u00f3n IP del host", "listen_port": "Puerto de escucha", "name": "Nombre", diff --git a/homeassistant/components/enocean/binary_sensor.py b/homeassistant/components/enocean/binary_sensor.py index 3afa7110657..e18542241da 100644 --- a/homeassistant/components/enocean/binary_sensor.py +++ b/homeassistant/components/enocean/binary_sensor.py @@ -1,6 +1,7 @@ """Support for EnOcean binary sensors.""" from __future__ import annotations +from enocean.utils import combine_hex import voluptuous as vol from homeassistant.components.binary_sensor import ( @@ -57,6 +58,7 @@ class EnOceanBinarySensor(EnOceanEntity, BinarySensorEntity): self._device_class = device_class self.which = -1 self.onoff = -1 + self._attr_unique_id = f"{combine_hex(dev_id)}-{device_class}" @property def name(self): diff --git a/homeassistant/components/enocean/device.py b/homeassistant/components/enocean/device.py index b57b053f4a7..0bd084742b5 100644 --- a/homeassistant/components/enocean/device.py +++ b/homeassistant/components/enocean/device.py @@ -2,7 +2,7 @@ from enocean.protocol.packet import Packet from enocean.utils import combine_hex -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity from .const import SIGNAL_RECEIVE_MESSAGE, SIGNAL_SEND_MESSAGE @@ -37,4 +37,4 @@ class EnOceanEntity(Entity): """Send a command via the EnOcean dongle.""" packet = Packet(packet_type, data=data, optional=optional) - self.hass.helpers.dispatcher.dispatcher_send(SIGNAL_SEND_MESSAGE, packet) + dispatcher_send(self.hass, SIGNAL_SEND_MESSAGE, packet) diff --git a/homeassistant/components/enocean/dongle.py b/homeassistant/components/enocean/dongle.py index 63ab3e86925..9ccaeba504c 100644 --- a/homeassistant/components/enocean/dongle.py +++ b/homeassistant/components/enocean/dongle.py @@ -7,7 +7,7 @@ from enocean.communicators import SerialCommunicator from enocean.protocol.packet import RadioPacket import serial -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from .const import SIGNAL_RECEIVE_MESSAGE, SIGNAL_SEND_MESSAGE @@ -58,7 +58,7 @@ class EnOceanDongle: if isinstance(packet, RadioPacket): _LOGGER.debug("Received radio packet: %s", packet) - self.hass.helpers.dispatcher.dispatcher_send(SIGNAL_RECEIVE_MESSAGE, packet) + dispatcher_send(self.hass, SIGNAL_RECEIVE_MESSAGE, packet) def detect(): diff --git a/homeassistant/components/enocean/light.py b/homeassistant/components/enocean/light.py index c7bf076c738..8ecc3f63831 100644 --- a/homeassistant/components/enocean/light.py +++ b/homeassistant/components/enocean/light.py @@ -3,6 +3,7 @@ from __future__ import annotations import math +from enocean.utils import combine_hex import voluptuous as vol from homeassistant.components.light import ( @@ -58,6 +59,7 @@ class EnOceanLight(EnOceanEntity, LightEntity): self._on_state = False self._brightness = 50 self._sender_id = sender_id + self._attr_unique_id = f"{combine_hex(dev_id)}" @property def name(self): diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index e48f117648a..06ea50d4cdb 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -1,6 +1,10 @@ """Support for EnOcean sensors.""" from __future__ import annotations +from collections.abc import Callable +from dataclasses import dataclass + +from enocean.utils import combine_hex import voluptuous as vol from homeassistant.components.sensor import ( @@ -40,37 +44,56 @@ SENSOR_TYPE_POWER = "powersensor" SENSOR_TYPE_TEMPERATURE = "temperature" SENSOR_TYPE_WINDOWHANDLE = "windowhandle" -SENSOR_DESC_TEMPERATURE = SensorEntityDescription( + +@dataclass +class EnOceanSensorEntityDescriptionMixin: + """Mixin for required keys.""" + + unique_id: Callable[[list[int]], str | None] + + +@dataclass +class EnOceanSensorEntityDescription( + SensorEntityDescription, EnOceanSensorEntityDescriptionMixin +): + """Describes EnOcean sensor entity.""" + + +SENSOR_DESC_TEMPERATURE = EnOceanSensorEntityDescription( key=SENSOR_TYPE_TEMPERATURE, name="Temperature", native_unit_of_measurement=TEMP_CELSIUS, icon="mdi:thermometer", device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, + unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_TEMPERATURE}", ) -SENSOR_DESC_HUMIDITY = SensorEntityDescription( +SENSOR_DESC_HUMIDITY = EnOceanSensorEntityDescription( key=SENSOR_TYPE_HUMIDITY, name="Humidity", native_unit_of_measurement=PERCENTAGE, icon="mdi:water-percent", device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, + unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_HUMIDITY}", ) -SENSOR_DESC_POWER = SensorEntityDescription( +SENSOR_DESC_POWER = EnOceanSensorEntityDescription( key=SENSOR_TYPE_POWER, name="Power", native_unit_of_measurement=POWER_WATT, icon="mdi:power-plug", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, + unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_POWER}", ) -SENSOR_DESC_WINDOWHANDLE = SensorEntityDescription( +SENSOR_DESC_WINDOWHANDLE = EnOceanSensorEntityDescription( key=SENSOR_TYPE_WINDOWHANDLE, name="WindowHandle", - icon="mdi:window", + icon="mdi:window-open-variant", + unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_WINDOWHANDLE}", ) @@ -132,11 +155,12 @@ def setup_platform( class EnOceanSensor(EnOceanEntity, RestoreEntity, SensorEntity): """Representation of an EnOcean sensor device such as a power meter.""" - def __init__(self, dev_id, dev_name, description: SensorEntityDescription): + def __init__(self, dev_id, dev_name, description: EnOceanSensorEntityDescription): """Initialize the EnOcean sensor device.""" super().__init__(dev_id, dev_name) self.entity_description = description self._attr_name = f"{description.name} {dev_name}" + self._attr_unique_id = description.unique_id(dev_id) async def async_added_to_hass(self): """Call when entity about to be added to hass.""" @@ -194,7 +218,7 @@ class EnOceanTemperatureSensor(EnOceanSensor): self, dev_id, dev_name, - description: SensorEntityDescription, + description: EnOceanSensorEntityDescription, *, scale_min, scale_max, @@ -248,7 +272,6 @@ class EnOceanWindowHandle(EnOceanSensor): def value_changed(self, packet): """Update the internal state of the sensor.""" - action = (packet.data[1] & 0x70) >> 4 if action == 0x07: diff --git a/homeassistant/components/enocean/switch.py b/homeassistant/components/enocean/switch.py index fc788e88d72..a53f691df19 100644 --- a/homeassistant/components/enocean/switch.py +++ b/homeassistant/components/enocean/switch.py @@ -1,6 +1,7 @@ """Support for EnOcean switches.""" from __future__ import annotations +from enocean.utils import combine_hex import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity @@ -48,6 +49,7 @@ class EnOceanSwitch(EnOceanEntity, SwitchEntity): self._on_state = False self._on_state2 = False self.channel = channel + self._attr_unique_id = f"{combine_hex(dev_id)}" @property def is_on(self): diff --git a/homeassistant/components/enocean/translations/nl.json b/homeassistant/components/enocean/translations/nl.json index 79e0ab6dfec..6641dfeaaa8 100644 --- a/homeassistant/components/enocean/translations/nl.json +++ b/homeassistant/components/enocean/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "invalid_dongle_path": "Ongeldig dongle-pad", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "invalid_dongle_path": "Geen geldige dongle gevonden voor dit pad" diff --git a/homeassistant/components/enphase_envoy/translations/nl.json b/homeassistant/components/enphase_envoy/translations/nl.json index 26a96d5bde2..32975a82f7a 100644 --- a/homeassistant/components/enphase_envoy/translations/nl.json +++ b/homeassistant/components/enphase_envoy/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index c00073b4432..4c5a94afe0f 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -20,6 +20,7 @@ from aioesphomeapi import ( FanInfo, LightInfo, LockInfo, + MediaPlayerInfo, NumberInfo, SelectInfo, SensorInfo, @@ -46,6 +47,7 @@ INFO_TYPE_TO_PLATFORM: dict[type[EntityInfo], str] = { FanInfo: "fan", LightInfo: "light", LockInfo: "lock", + MediaPlayerInfo: "media_player", NumberInfo: "number", SelectInfo: "select", SensorInfo: "sensor", diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 72a36076bf4..b89671c6f90 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -3,7 +3,7 @@ "name": "ESPHome", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/esphome", - "requirements": ["aioesphomeapi==10.8.2"], + "requirements": ["aioesphomeapi==10.10.0"], "zeroconf": ["_esphomelib._tcp.local."], "codeowners": ["@OttoWinter", "@jesserockz"], "after_dependencies": ["zeroconf", "tag"], diff --git a/homeassistant/components/esphome/media_player.py b/homeassistant/components/esphome/media_player.py new file mode 100644 index 00000000000..f9027142ae2 --- /dev/null +++ b/homeassistant/components/esphome/media_player.py @@ -0,0 +1,153 @@ +"""Support for ESPHome media players.""" +from __future__ import annotations + +from typing import Any + +from aioesphomeapi import ( + MediaPlayerCommand, + MediaPlayerEntityState, + MediaPlayerInfo, + MediaPlayerState, +) + +from homeassistant.components import media_source +from homeassistant.components.media_player import ( + MediaPlayerDeviceClass, + MediaPlayerEntity, +) +from homeassistant.components.media_player.browse_media import ( + BrowseMedia, + async_process_play_media_url, +) +from homeassistant.components.media_player.const import MediaPlayerEntityFeature +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import EsphomeEntity, EsphomeEnumMapper, platform_async_setup_entry + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up esphome media players based on a config entry.""" + await platform_async_setup_entry( + hass, + entry, + async_add_entities, + component_key="media_player", + info_type=MediaPlayerInfo, + entity_type=EsphomeMediaPlayer, + state_type=MediaPlayerEntityState, + ) + + +_STATES: EsphomeEnumMapper[MediaPlayerState, str] = EsphomeEnumMapper( + { + MediaPlayerState.IDLE: STATE_IDLE, + MediaPlayerState.PLAYING: STATE_PLAYING, + MediaPlayerState.PAUSED: STATE_PAUSED, + } +) + + +class EsphomeMediaPlayer( + EsphomeEntity[MediaPlayerInfo, MediaPlayerEntityState], MediaPlayerEntity +): + """A media player implementation for esphome.""" + + _attr_device_class = MediaPlayerDeviceClass.SPEAKER + + @property + def state(self) -> str | None: + """Return current state.""" + return _STATES.from_esphome(self._state.state) + + @property + def is_volume_muted(self) -> bool: + """Return true if volume is muted.""" + return self._state.muted + + @property + def volume_level(self) -> float | None: + """Volume level of the media player (0..1).""" + return self._state.volume + + @property + def supported_features(self) -> int: + """Flag supported features.""" + flags = ( + MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + ) + if self._static_info.supports_pause: + flags |= MediaPlayerEntityFeature.PAUSE | MediaPlayerEntityFeature.PLAY + return flags + + async def async_play_media( + self, media_type: str, media_id: str, **kwargs: Any + ) -> None: + """Send the play command with media url to the media player.""" + if media_source.is_media_source_id(media_id): + sourced_media = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) + media_id = sourced_media.url + + media_id = async_process_play_media_url(self.hass, media_id) + + await self._client.media_player_command( + self._static_info.key, + media_url=media_id, + ) + + async def async_browse_media( + self, media_content_type: str | None = None, media_content_id: str | None = 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 async_set_volume_level(self, volume: float) -> None: + """Set volume level, range 0..1.""" + await self._client.media_player_command( + self._static_info.key, + volume=volume, + ) + + async def async_media_pause(self) -> None: + """Send pause command.""" + await self._client.media_player_command( + self._static_info.key, + command=MediaPlayerCommand.PAUSE, + ) + + async def async_media_play(self) -> None: + """Send play command.""" + await self._client.media_player_command( + self._static_info.key, + command=MediaPlayerCommand.PLAY, + ) + + async def async_media_stop(self) -> None: + """Send stop command.""" + await self._client.media_player_command( + self._static_info.key, + command=MediaPlayerCommand.STOP, + ) + + async def async_mute_volume(self, mute: bool) -> None: + """Mute the volume.""" + await self._client.media_player_command( + self._static_info.key, + command=MediaPlayerCommand.MUTE if mute else MediaPlayerCommand.UNMUTE, + ) diff --git a/homeassistant/components/esphome/translations/es.json b/homeassistant/components/esphome/translations/es.json index f7fd73cd227..c682ddab294 100644 --- a/homeassistant/components/esphome/translations/es.json +++ b/homeassistant/components/esphome/translations/es.json @@ -11,7 +11,7 @@ "invalid_psk": "La clave de transporte cifrado no es v\u00e1lida. Por favor, aseg\u00farese de que coincide con la que tiene en su configuraci\u00f3n", "resolve_error": "No se puede resolver la direcci\u00f3n de ESP. Si el error persiste, configura una direcci\u00f3n IP est\u00e1tica: 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/nl.json b/homeassistant/components/esphome/translations/nl.json index 7f6f821104c..aae5b639eb8 100644 --- a/homeassistant/components/esphome/translations/nl.json +++ b/homeassistant/components/esphome/translations/nl.json @@ -2,8 +2,8 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al begonnen", - "reauth_successful": "Herauthenticatie was succesvol" + "already_in_progress": "De configuratie is momenteel al bezig", + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "connection_error": "Kan geen verbinding maken met ESP. Zorg ervoor dat uw YAML-bestand een regel 'api:' bevat.", @@ -20,8 +20,8 @@ "description": "Voer het wachtwoord in dat u in uw configuratie heeft ingesteld voor {name}." }, "discovery_confirm": { - "description": "Wil je de ESPHome-node `{name}` toevoegen aan de Home Assistant?", - "title": "ESPHome node ontdekt" + "description": "Wil je de ESPHome-apparaat `{name}` toevoegen aan de Home Assistant?", + "title": "ESPHome apparaat ontdekt" }, "encryption_key": { "data": { @@ -40,7 +40,7 @@ "host": "Host", "port": "Poort" }, - "description": "Voer de verbindingsinstellingen in van uw [ESPHome](https://esphomelib.com/) node." + "description": "Voer de verbindingsinstellingen van je [ESPHome](https://esphome.io/) apparaat in." } } } diff --git a/homeassistant/components/esphome/translations/sv.json b/homeassistant/components/esphome/translations/sv.json index 40fbbf86bc6..88a9ca92377 100644 --- a/homeassistant/components/esphome/translations/sv.json +++ b/homeassistant/components/esphome/translations/sv.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "ESP \u00e4r redan konfigurerad", + "already_in_progress": "Konfigurationsfl\u00f6det p\u00e5g\u00e5r redan", "reauth_successful": "\u00c5terautentisering lyckades" }, "error": { diff --git a/homeassistant/components/evil_genius_labs/util.py b/homeassistant/components/evil_genius_labs/util.py index 40ba375a2b4..1071b953027 100644 --- a/homeassistant/components/evil_genius_labs/util.py +++ b/homeassistant/components/evil_genius_labs/util.py @@ -9,21 +9,23 @@ from typing_extensions import Concatenate, ParamSpec from . import EvilGeniusEntity -_T = TypeVar("_T", bound=EvilGeniusEntity) +_EvilGeniusEntityT = TypeVar("_EvilGeniusEntityT", bound=EvilGeniusEntity) _R = TypeVar("_R") _P = ParamSpec("_P") def update_when_done( - func: Callable[Concatenate[_T, _P], Awaitable[_R]] # type: ignore[misc] -) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, _R]]: # type: ignore[misc] + func: Callable[Concatenate[_EvilGeniusEntityT, _P], Awaitable[_R]] +) -> Callable[Concatenate[_EvilGeniusEntityT, _P], Coroutine[Any, Any, _R]]: """Decorate function to trigger update when function is done.""" @wraps(func) - async def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R: + async def wrapper( + self: _EvilGeniusEntityT, *args: _P.args, **kwargs: _P.kwargs + ) -> _R: """Wrap function.""" result = await func(self, *args, **kwargs) await self.coordinator.async_request_refresh() - return result # type: ignore[no-any-return] # mypy can't yet infer 'func' + return result return wrapper diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index 5f83caa8648..908dff48aef 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -24,6 +24,7 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant, ServiceCall, callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform @@ -32,8 +33,9 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_send, ) from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.event import async_call_later, async_track_time_interval from homeassistant.helpers.service import verify_domain_control +from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType import homeassistant.util.dt as dt_util @@ -197,7 +199,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: user_data = tokens.pop(USER_DATA, None) return (tokens, user_data) - store = hass.helpers.storage.Store(STORAGE_VER, STORAGE_KEY) + store = Store(hass, STORAGE_VER, STORAGE_KEY) tokens, user_data = await load_auth_tokens(store) client_v2 = evohomeasync2.EvohomeClient( @@ -257,8 +259,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async_load_platform(hass, Platform.WATER_HEATER, DOMAIN, {}, config) ) - hass.helpers.event.async_track_time_interval( - broker.async_update, config[DOMAIN][CONF_SCAN_INTERVAL] + async_track_time_interval( + hass, broker.async_update, config[DOMAIN][CONF_SCAN_INTERVAL] ) setup_service_functions(hass, broker) @@ -297,7 +299,7 @@ def setup_service_functions(hass: HomeAssistant, broker): """Set the zone override (setpoint).""" entity_id = call.data[ATTR_ENTITY_ID] - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) registry_entry = registry.async_get(entity_id) if registry_entry is None or registry_entry.platform != DOMAIN: diff --git a/homeassistant/components/ezviz/camera.py b/homeassistant/components/ezviz/camera.py index 2fa410e268c..91bab5f83af 100644 --- a/homeassistant/components/ezviz/camera.py +++ b/homeassistant/components/ezviz/camera.py @@ -9,6 +9,7 @@ import voluptuous as vol from homeassistant.components import ffmpeg from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.components.ffmpeg import get_ffmpeg_manager +from homeassistant.components.stream import CONF_USE_WALLCLOCK_AS_TIMESTAMPS from homeassistant.config_entries import ( SOURCE_IGNORE, SOURCE_INTEGRATION_DISCOVERY, @@ -186,6 +187,7 @@ class EzvizCamera(EzvizEntity, Camera): """Initialize a Ezviz security camera.""" super().__init__(coordinator, serial) Camera.__init__(self) + self.stream_options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] = True self._username = camera_username self._password = camera_password self._rtsp_stream = camera_rtsp_stream diff --git a/homeassistant/components/ezviz/manifest.json b/homeassistant/components/ezviz/manifest.json index 211e500cc7d..cfdfd6e441d 100644 --- a/homeassistant/components/ezviz/manifest.json +++ b/homeassistant/components/ezviz/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/ezviz", "dependencies": ["ffmpeg"], "codeowners": ["@RenierM26", "@baqs"], - "requirements": ["pyezviz==0.2.0.6"], + "requirements": ["pyezviz==0.2.0.8"], "config_flow": true, "iot_class": "cloud_polling", "loggers": ["paho_mqtt", "pyezviz"] diff --git a/homeassistant/components/ezviz/translations/es.json b/homeassistant/components/ezviz/translations/es.json index a0f624bf8df..eef8343c17a 100644 --- a/homeassistant/components/ezviz/translations/es.json +++ b/homeassistant/components/ezviz/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured_account": "La cuenta ya ha sido configurada", + "already_configured_account": "La cuenta ya est\u00e1 configurada", "ezviz_cloud_account_missing": "Falta la cuenta de Ezviz Cloud. Por favor, reconfigura la cuenta de Ezviz Cloud", "unknown": "Error inesperado" }, diff --git a/homeassistant/components/ezviz/translations/ko.json b/homeassistant/components/ezviz/translations/ko.json index 8ed11a874b0..8b44c79ce40 100644 --- a/homeassistant/components/ezviz/translations/ko.json +++ b/homeassistant/components/ezviz/translations/ko.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured_account": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "ezviz_cloud_account_missing": "Ezviz \ud074\ub77c\uc6b0\ub4dc \uacc4\uc815\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. Ezviz \ud074\ub77c\uc6b0\ub4dc \uacc4\uc815\uc744 \ub2e4\uc2dc \uad6c\uc131\ud558\uc2ed\uc2dc\uc624.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -15,21 +16,26 @@ "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "description": "IP\uac00 {ip_address} \uc778 Ezviz \uce74\uba54\ub77c {serial} \uc5d0 \ub300\ud55c RTSP \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud558\uc138\uc694.", + "title": "Ezviz \uce74\uba54\ub77c \ubc1c\uacac" }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", "url": "URL \uc8fc\uc18c", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "title": "Ezviz \ud074\ub77c\uc6b0\ub4dc\uc5d0 \uc5f0\uacb0" }, "user_custom_url": { "data": { "password": "\ube44\ubc00\ubc88\ud638", "url": "URL \uc8fc\uc18c", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "description": "URL\uc744 \uc218\ub3d9\uc73c\ub85c \uc785\ub825\ud558\uc138\uc694", + "title": "\uc0ac\uc6a9\uc790 \uc9c0\uc815 Ezviz URL\uc5d0 \uc5f0\uacb0" } } }, @@ -37,6 +43,7 @@ "step": { "init": { "data": { + "ffmpeg_arguments": "\uce74\uba54\ub77c\uc5d0 \ub300\ud55c ffmpeg \uc804\ub2ec \uc778\uc218", "timeout": "\uc694\uccad \uc81c\ud55c \uc2dc\uac04 (\ucd08)" } } diff --git a/homeassistant/components/faa_delays/translations/sk.json b/homeassistant/components/faa_delays/translations/sk.json new file mode 100644 index 00000000000..bbadecd391b --- /dev/null +++ b/homeassistant/components/faa_delays/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_airport": "K\u00f3d letiska je neplatn\u00fd" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fan/device_action.py b/homeassistant/components/fan/device_action.py index 0482c31b929..55bd862349b 100644 --- a/homeassistant/components/fan/device_action.py +++ b/homeassistant/components/fan/device_action.py @@ -3,64 +3,30 @@ from __future__ import annotations import voluptuous as vol -from homeassistant.const import ( - ATTR_ENTITY_ID, - CONF_DEVICE_ID, - CONF_DOMAIN, - CONF_ENTITY_ID, - CONF_TYPE, - SERVICE_TURN_OFF, - SERVICE_TURN_ON, -) +from homeassistant.components.device_automation import toggle_entity +from homeassistant.const import CONF_DOMAIN from homeassistant.core import Context, HomeAssistant -from homeassistant.helpers import entity_registry -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from . import DOMAIN -ACTION_TYPES = {"turn_on", "turn_off"} - -ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( - { - vol.Required(CONF_TYPE): vol.In(ACTION_TYPES), - vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN), - } -) +ACTION_SCHEMA = toggle_entity.ACTION_SCHEMA.extend({vol.Required(CONF_DOMAIN): DOMAIN}) async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Fan devices.""" - registry = await entity_registry.async_get_registry(hass) - actions = [] - - # Get all the integrations entities for this device - for entry in entity_registry.async_entries_for_device(registry, device_id): - if entry.domain != DOMAIN: - continue - - base_action = { - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, - CONF_ENTITY_ID: entry.entity_id, - } - actions += [{**base_action, CONF_TYPE: action} for action in ACTION_TYPES] - - return actions + return await toggle_entity.async_get_actions(hass, device_id, DOMAIN) async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} - - if config[CONF_TYPE] == "turn_on": - service = SERVICE_TURN_ON - elif config[CONF_TYPE] == "turn_off": - service = SERVICE_TURN_OFF - - await hass.services.async_call( - DOMAIN, service, service_data, blocking=True, context=context + await toggle_entity.async_call_action_from_config( + hass, config, variables, context, DOMAIN ) diff --git a/homeassistant/components/fan/device_condition.py b/homeassistant/components/fan/device_condition.py index b0882137d7f..7e27ea29f98 100644 --- a/homeassistant/components/fan/device_condition.py +++ b/homeassistant/components/fan/device_condition.py @@ -34,7 +34,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Fan devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) conditions = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/fan/device_trigger.py b/homeassistant/components/fan/device_trigger.py index 1d81ef18f4d..35eb5a9f50c 100644 --- a/homeassistant/components/fan/device_trigger.py +++ b/homeassistant/components/fan/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for Fan.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -24,7 +22,7 @@ TRIGGER_SCHEMA = vol.All( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Fan devices.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/fan/strings.json b/homeassistant/components/fan/strings.json index 403a63f99d5..fdd95a822de 100644 --- a/homeassistant/components/fan/strings.json +++ b/homeassistant/components/fan/strings.json @@ -11,6 +11,7 @@ "turned_off": "{entity_name} turned off" }, "action_type": { + "toggle": "Toggle {entity_name}", "turn_on": "Turn on {entity_name}", "turn_off": "Turn off {entity_name}" } diff --git a/homeassistant/components/fan/translations/ca.json b/homeassistant/components/fan/translations/ca.json index 274ddddbbaf..c253be819c9 100644 --- a/homeassistant/components/fan/translations/ca.json +++ b/homeassistant/components/fan/translations/ca.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Commuta {entity_name}", "turn_off": "Apaga {entity_name}", "turn_on": "Enc\u00e9n {entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} s'enc\u00e9n o s'apaga", - "toggled": "{entity_name} s'enc\u00e9n o s'apaga", "turned_off": "{entity_name} s'ha apagat", "turned_on": "{entity_name} s'ha enc\u00e8s" } diff --git a/homeassistant/components/fan/translations/cs.json b/homeassistant/components/fan/translations/cs.json index 98336d5cfbe..9a2f89e5caa 100644 --- a/homeassistant/components/fan/translations/cs.json +++ b/homeassistant/components/fan/translations/cs.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "P\u0159epnout {entity_name}", "turn_off": "Vypnout {entity_name}", "turn_on": "Zapnout {entity_name}" }, @@ -9,7 +10,6 @@ "is_on": "{entity_name} je zapnuto" }, "trigger_type": { - "toggled": "{entity_name} zapnuto nebo vypnuto", "turned_off": "{entity_name} bylo vypnuto", "turned_on": "{entity_name} bylo zapnuto" } diff --git a/homeassistant/components/fan/translations/de.json b/homeassistant/components/fan/translations/de.json index 43a7a42f808..6f27d79ad81 100644 --- a/homeassistant/components/fan/translations/de.json +++ b/homeassistant/components/fan/translations/de.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "{entity_name} umschalten", "turn_off": "Schalte {entity_name} aus.", "turn_on": "Schalte {entity_name} ein." }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} ein- oder ausgeschaltet", - "toggled": "{entity_name} ein- oder ausgeschaltet", "turned_off": "{entity_name} ausgeschaltet", "turned_on": "{entity_name} eingeschaltet" } diff --git a/homeassistant/components/fan/translations/el.json b/homeassistant/components/fan/translations/el.json index b9c4962a7a6..78e78722bbc 100644 --- a/homeassistant/components/fan/translations/el.json +++ b/homeassistant/components/fan/translations/el.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae {entity_name}", "turn_off": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 {entity_name}", "turn_on": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 {entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "\u03a4\u03bf {entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", - "toggled": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_off": "{entity_name} \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5" } diff --git a/homeassistant/components/fan/translations/en.json b/homeassistant/components/fan/translations/en.json index f4f27e8fd2d..94335a91799 100644 --- a/homeassistant/components/fan/translations/en.json +++ b/homeassistant/components/fan/translations/en.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Toggle {entity_name}", "turn_off": "Turn off {entity_name}", "turn_on": "Turn on {entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} turned on or off", - "toggled": "{entity_name} turned on or off", "turned_off": "{entity_name} turned off", "turned_on": "{entity_name} turned on" } diff --git a/homeassistant/components/fan/translations/es.json b/homeassistant/components/fan/translations/es.json index c4edae6f9ee..b66ca0b0256 100644 --- a/homeassistant/components/fan/translations/es.json +++ b/homeassistant/components/fan/translations/es.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Cambiar {entity_name}", "turn_off": "Desactivar {entity_name}", "turn_on": "Activar {entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} activado o desactivado", - "toggled": "{entity_name} activado o desactivado", "turned_off": "{entity_name} desactivado", "turned_on": "{entity_name} activado" } diff --git a/homeassistant/components/fan/translations/et.json b/homeassistant/components/fan/translations/et.json index 4a46384ae3a..23066054d37 100644 --- a/homeassistant/components/fan/translations/et.json +++ b/homeassistant/components/fan/translations/et.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Muuda {entity_name} olekut", "turn_off": "L\u00fclita {entity_name} v\u00e4lja", "turn_on": "L\u00fclita {entity_name} sisse" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} l\u00fclitus sisse v\u00f5i v\u00e4lja", - "toggled": "{entity_name} l\u00fclitus sisse v\u00f5i v\u00e4lja", "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", "turned_on": "{entity_name} l\u00fclitus sisse" } diff --git a/homeassistant/components/fan/translations/fr.json b/homeassistant/components/fan/translations/fr.json index e9cf666b725..27615a525dd 100644 --- a/homeassistant/components/fan/translations/fr.json +++ b/homeassistant/components/fan/translations/fr.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Basculer {entity_name}", "turn_off": "\u00c9teindre {entity_name}", "turn_on": "Allumer {entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} allum\u00e9 ou \u00e9teint", - "toggled": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "turned_off": "{entity_name} est \u00e9teint", "turned_on": "{entity_name} allum\u00e9" } diff --git a/homeassistant/components/fan/translations/he.json b/homeassistant/components/fan/translations/he.json index a97b300060a..5d1a6aea1c3 100644 --- a/homeassistant/components/fan/translations/he.json +++ b/homeassistant/components/fan/translations/he.json @@ -10,7 +10,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05db\u05d5\u05d1\u05d4", - "toggled": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05db\u05d5\u05d1\u05d4", "turned_off": "{entity_name} \u05db\u05d5\u05d1\u05d4", "turned_on": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc" } diff --git a/homeassistant/components/fan/translations/hu.json b/homeassistant/components/fan/translations/hu.json index 50e659b001a..626004a9f69 100644 --- a/homeassistant/components/fan/translations/hu.json +++ b/homeassistant/components/fan/translations/hu.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "{entity_name} kapcsol\u00e1sa", "turn_off": "{entity_name} kikapcsol\u00e1sa", "turn_on": "{entity_name} bekapcsol\u00e1sa" }, @@ -10,7 +11,6 @@ }, "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/fan/translations/id.json b/homeassistant/components/fan/translations/id.json index c21b90a495a..b87c2b5a2bc 100644 --- a/homeassistant/components/fan/translations/id.json +++ b/homeassistant/components/fan/translations/id.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Nyala/matikan {entity_name}", "turn_off": "Matikan {entity_name}", "turn_on": "Nyalakan {entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} diaktifkan atau dinonaktifkan", - "toggled": "{entity_name} diaktifkan atau dinonaktifkan", "turned_off": "{entity_name} dimatikan", "turned_on": "{entity_name} dinyalakan" } diff --git a/homeassistant/components/fan/translations/it.json b/homeassistant/components/fan/translations/it.json index ccde6f0a3a5..af4aff2691d 100644 --- a/homeassistant/components/fan/translations/it.json +++ b/homeassistant/components/fan/translations/it.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Attiva/disattiva {entity_name}", "turn_off": "Spegnere {entity_name}", "turn_on": "Accendere {entity_name}" }, @@ -9,8 +10,7 @@ "is_on": "{entity_name} \u00e8 acceso" }, "trigger_type": { - "changed_states": "{entity_name} attivata o disattivat", - "toggled": "{entity_name} attiva o disattiva", + "changed_states": "{entity_name} attivata o disattivata", "turned_off": "{entity_name} disattivato", "turned_on": "{entity_name} attivato" } diff --git a/homeassistant/components/fan/translations/ja.json b/homeassistant/components/fan/translations/ja.json index 62b37a0b5df..e3fe8e8ba1a 100644 --- a/homeassistant/components/fan/translations/ja.json +++ b/homeassistant/components/fan/translations/ja.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "\u30c8\u30b0\u30eb {entity_name}", "turn_off": "\u30aa\u30d5\u306b\u3059\u308b {entity_name}", "turn_on": "\u30aa\u30f3\u306b\u3059\u308b {entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u304c\u30aa\u30f3\u307e\u305f\u306f\u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", - "toggled": "{entity_name} \u304c\u30aa\u30f3\u307e\u305f\u306f\u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "turned_off": "{entity_name} \u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "turned_on": "{entity_name} \u30aa\u30f3\u306b\u306a\u3063\u3066\u3044\u307e\u3059" } diff --git a/homeassistant/components/fan/translations/ko.json b/homeassistant/components/fan/translations/ko.json index c2157f29e72..6616d7a39f4 100644 --- a/homeassistant/components/fan/translations/ko.json +++ b/homeassistant/components/fan/translations/ko.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "{entity_name} \ud1a0\uae00", "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, diff --git a/homeassistant/components/fan/translations/nl.json b/homeassistant/components/fan/translations/nl.json index aa4474a782f..d24f439ff16 100644 --- a/homeassistant/components/fan/translations/nl.json +++ b/homeassistant/components/fan/translations/nl.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Schakel {entity_name}", "turn_off": "Schakel {entity_name} uit", "turn_on": "Schakel {entity_name} in" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} in- of uitgeschakeld", - "toggled": "{entity_name} in- of uitgeschakeld", "turned_off": "{entity_name} uitgeschakeld", "turned_on": "{entity_name} ingeschakeld" } diff --git a/homeassistant/components/fan/translations/no.json b/homeassistant/components/fan/translations/no.json index b576e2aca7d..0c3649ecbc5 100644 --- a/homeassistant/components/fan/translations/no.json +++ b/homeassistant/components/fan/translations/no.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Veksle {entity_name}", "turn_off": "Sl\u00e5 av {entity_name}", "turn_on": "Sl\u00e5 p\u00e5 {entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} sl\u00e5tt p\u00e5 eller av", - "toggled": "{entity_name} sl\u00e5tt p\u00e5 eller av", "turned_off": "{entity_name} sl\u00e5tt av", "turned_on": "{entity_name} sl\u00e5tt p\u00e5" } diff --git a/homeassistant/components/fan/translations/pl.json b/homeassistant/components/fan/translations/pl.json index 9e4f6b45341..817dbdde339 100644 --- a/homeassistant/components/fan/translations/pl.json +++ b/homeassistant/components/fan/translations/pl.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "prze\u0142\u0105cz {entity_name}", "turn_off": "wy\u0142\u0105cz {entity_name}", "turn_on": "w\u0142\u0105cz {entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", - "toggled": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "turned_off": "nast\u0105pi wy\u0142\u0105czenie {entity_name}", "turned_on": "nast\u0105pi w\u0142\u0105czenie {entity_name}" } diff --git a/homeassistant/components/fan/translations/pt-BR.json b/homeassistant/components/fan/translations/pt-BR.json index 97145dd8a60..8ebc5b2c73f 100644 --- a/homeassistant/components/fan/translations/pt-BR.json +++ b/homeassistant/components/fan/translations/pt-BR.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Alternar {entity_name}", "turn_off": "Desligar {entity_name}", "turn_on": "Ligar {entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} ligado ou desligado", - "toggled": "{entity_name} ligado ou desligado", "turned_off": "{entity_name} for desligado", "turned_on": "{entity_name} for ligado" } diff --git a/homeassistant/components/fan/translations/ru.json b/homeassistant/components/fan/translations/ru.json index ec667b78ee1..fcf8b6ec4a5 100644 --- a/homeassistant/components/fan/translations/ru.json +++ b/homeassistant/components/fan/translations/ru.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "{entity_name}: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c", "turn_off": "{entity_name}: \u0432\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c", "turn_on": "{entity_name}: \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", - "toggled": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "turned_off": "{entity_name} \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "turned_on": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f" } diff --git a/homeassistant/components/fan/translations/sv.json b/homeassistant/components/fan/translations/sv.json index fd1fec6cdfc..dd1aaad4052 100644 --- a/homeassistant/components/fan/translations/sv.json +++ b/homeassistant/components/fan/translations/sv.json @@ -9,7 +9,6 @@ "is_on": "{entity_name} \u00e4r p\u00e5" }, "trigger_type": { - "toggled": "{entity_name} slogs p\u00e5 eller av", "turned_off": "{entity_name} st\u00e4ngdes av", "turned_on": "{entity_name} aktiverades" } diff --git a/homeassistant/components/fan/translations/tr.json b/homeassistant/components/fan/translations/tr.json index 37091db264c..5c06cddf5ee 100644 --- a/homeassistant/components/fan/translations/tr.json +++ b/homeassistant/components/fan/translations/tr.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "{entity_name} de\u011fi\u015ftir", "turn_off": "{entity_name} kapat", "turn_on": "{entity_name} a\u00e7\u0131n" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} a\u00e7\u0131ld\u0131 veya kapat\u0131ld\u0131", - "toggled": "{entity_name} a\u00e7\u0131ld\u0131 veya kapat\u0131ld\u0131", "turned_off": "{entity_name} kapat\u0131ld\u0131", "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" } diff --git a/homeassistant/components/fan/translations/zh-Hans.json b/homeassistant/components/fan/translations/zh-Hans.json index 082133913bc..13b6917f4ad 100644 --- a/homeassistant/components/fan/translations/zh-Hans.json +++ b/homeassistant/components/fan/translations/zh-Hans.json @@ -9,7 +9,6 @@ "is_on": "{entity_name} \u5df2\u5f00\u542f" }, "trigger_type": { - "toggled": "{entity_name} \u88ab\u6253\u5f00\u6216\u5173\u95ed", "turned_off": "{entity_name} \u88ab\u5173\u95ed", "turned_on": "{entity_name} \u88ab\u6253\u5f00" } diff --git a/homeassistant/components/fan/translations/zh-Hant.json b/homeassistant/components/fan/translations/zh-Hant.json index 571040c820a..8151ee76a77 100644 --- a/homeassistant/components/fan/translations/zh-Hant.json +++ b/homeassistant/components/fan/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "\u5207\u63db{entity_name}", "turn_off": "\u95dc\u9589{entity_name}", "turn_on": "\u958b\u555f{entity_name}" }, @@ -10,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name}\u5df2\u958b\u555f\u6216\u95dc\u9589", - "toggled": "{entity_name}\u5df2\u958b\u555f\u6216\u95dc\u9589", "turned_off": "{entity_name}\u5df2\u95dc\u9589", "turned_on": "{entity_name}\u5df2\u958b\u555f" } diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index c9b0cb345e1..fdb0f894a40 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -78,6 +78,7 @@ FIBARO_TYPEMAP = { "com.fibaro.FGT001": Platform.CLIMATE, "com.fibaro.thermostatDanfoss": Platform.CLIMATE, "com.fibaro.doorLock": Platform.LOCK, + "com.fibaro.binarySensor": Platform.BINARY_SENSOR, } DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema( diff --git a/homeassistant/components/fibaro/cover.py b/homeassistant/components/fibaro/cover.py index e347d0368d2..fc6d0a67d3c 100644 --- a/homeassistant/components/fibaro/cover.py +++ b/homeassistant/components/fibaro/cover.py @@ -6,6 +6,7 @@ from homeassistant.components.cover import ( ATTR_TILT_POSITION, ENTITY_ID_FORMAT, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform @@ -41,6 +42,11 @@ class FibaroCover(FibaroDevice, CoverEntity): super().__init__(fibaro_device) self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) + if self._is_open_close_only(): + self._attr_supported_features = ( + CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE + ) + @staticmethod def bound(position): """Normalize the position.""" @@ -53,6 +59,14 @@ class FibaroCover(FibaroDevice, CoverEntity): return 100 return position + def _is_open_close_only(self) -> bool: + """Return if only open / close is supported.""" + # Normally positionable devices report the position over value, + # so if it is missing we have a device which supports open / close only + if "value" not in self.fibaro_device.properties: + return True + return False + @property def current_cover_position(self): """Return current position of cover. 0 is closed, 100 is open.""" @@ -74,6 +88,9 @@ class FibaroCover(FibaroDevice, CoverEntity): @property def is_closed(self): """Return if the cover is closed.""" + if self._is_open_close_only(): + return self.fibaro_device.properties.state.lower() == "closed" + if self.current_cover_position is None: return None return self.current_cover_position == 0 diff --git a/homeassistant/components/fibaro/light.py b/homeassistant/components/fibaro/light.py index 0115e0301c3..300b7c4c5f8 100644 --- a/homeassistant/components/fibaro/light.py +++ b/homeassistant/components/fibaro/light.py @@ -86,7 +86,10 @@ class FibaroLight(FibaroDevice, LightEntity): or "RGBW" in fibaro_device.type or "rgbw" in fibaro_device.type ) - supports_dimming = "levelChange" in fibaro_device.interfaces + supports_dimming = ( + "levelChange" in fibaro_device.interfaces + and "setValue" in fibaro_device.actions + ) if supports_color and supports_white_v: self._attr_supported_color_modes = {ColorMode.RGBW} diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index bd80a9d0181..acaa97ee2a2 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -33,27 +33,43 @@ SENSOR_TYPES = { None, None, SensorDeviceClass.TEMPERATURE, + SensorStateClass.MEASUREMENT, ], "com.fibaro.smokeSensor": [ "Smoke", CONCENTRATION_PARTS_PER_MILLION, "mdi:fire", None, + None, ], "CO2": [ "CO2", CONCENTRATION_PARTS_PER_MILLION, None, - None, SensorDeviceClass.CO2, + SensorStateClass.MEASUREMENT, ], "com.fibaro.humiditySensor": [ "Humidity", PERCENTAGE, None, SensorDeviceClass.HUMIDITY, + SensorStateClass.MEASUREMENT, + ], + "com.fibaro.lightSensor": [ + "Light", + LIGHT_LUX, + None, + SensorDeviceClass.ILLUMINANCE, + SensorStateClass.MEASUREMENT, + ], + "com.fibaro.energyMeter": [ + "Energy", + ENERGY_KILO_WATT_HOUR, + None, + SensorDeviceClass.ENERGY, + SensorStateClass.TOTAL_INCREASING, ], - "com.fibaro.lightSensor": ["Light", LIGHT_LUX, None, SensorDeviceClass.ILLUMINANCE], } @@ -66,7 +82,7 @@ async def async_setup_entry( entities: list[SensorEntity] = [] for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][Platform.SENSOR]: entities.append(FibaroSensor(device)) - for platform in (Platform.COVER, Platform.LIGHT, Platform.SWITCH): + for platform in (Platform.COVER, Platform.LIGHT, Platform.SENSOR, Platform.SWITCH): for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][platform]: if "energy" in device.interfaces: entities.append(FibaroEnergySensor(device)) @@ -89,6 +105,7 @@ class FibaroSensor(FibaroDevice, SensorEntity): self._unit = SENSOR_TYPES[fibaro_device.type][1] self._icon = SENSOR_TYPES[fibaro_device.type][2] self._device_class = SENSOR_TYPES[fibaro_device.type][3] + self._attr_state_class = SENSOR_TYPES[fibaro_device.type][4] else: self._unit = None self._icon = None diff --git a/homeassistant/components/fibaro/translations/es.json b/homeassistant/components/fibaro/translations/es.json new file mode 100644 index 00000000000..00a7eeb8ece --- /dev/null +++ b/homeassistant/components/fibaro/translations/es.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "url": "URL en el format http://HOST/api/", + "username": "Usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/nl.json b/homeassistant/components/fibaro/translations/nl.json index 72b8588d1e4..049168c73d8 100644 --- a/homeassistant/components/fibaro/translations/nl.json +++ b/homeassistant/components/fibaro/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/fibaro/translations/sk.json b/homeassistant/components/fibaro/translations/sk.json new file mode 100644 index 00000000000..649a1e186ee --- /dev/null +++ b/homeassistant/components/fibaro/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "U\u017e\u00edvate\u013esk\u00e9 meno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/filesize/__init__.py b/homeassistant/components/filesize/__init__.py index 8f5b098d221..61ef26f0a66 100644 --- a/homeassistant/components/filesize/__init__.py +++ b/homeassistant/components/filesize/__init__.py @@ -1,7 +1,6 @@ """The filesize component.""" from __future__ import annotations -import logging import pathlib from homeassistant.config_entries import ConfigEntry @@ -11,16 +10,20 @@ from homeassistant.exceptions import ConfigEntryNotReady from .const import PLATFORMS -_LOGGER = logging.getLogger(__name__) + +def check_path(path: pathlib.Path) -> bool: + """Check path.""" + return path.exists() and path.is_file() async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up from a config entry.""" path = entry.data[CONF_FILE_PATH] - get_path = await hass.async_add_executor_job(pathlib.Path, path) + get_path = pathlib.Path(path) - if not get_path.exists() and not get_path.is_file(): + check_file = await hass.async_add_executor_job(check_path, get_path) + if not check_file: raise ConfigEntryNotReady(f"Can not access file {path}") if not hass.config.is_allowed_path(path): diff --git a/homeassistant/components/filesize/config_flow.py b/homeassistant/components/filesize/config_flow.py index 7838353fa12..3f58e636b0e 100644 --- a/homeassistant/components/filesize/config_flow.py +++ b/homeassistant/components/filesize/config_flow.py @@ -11,6 +11,7 @@ from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_FILE_PATH from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -19,19 +20,20 @@ DATA_SCHEMA = vol.Schema({vol.Required(CONF_FILE_PATH): str}) _LOGGER = logging.getLogger(__name__) -def validate_path(hass: HomeAssistant, path: str) -> pathlib.Path: +def validate_path(hass: HomeAssistant, path: str) -> str: """Validate path.""" - try: - get_path = pathlib.Path(path) - except OSError as error: - _LOGGER.error("Can not access file %s, error %s", path, error) - raise NotValidError from error + get_path = pathlib.Path(path) + if not get_path.exists() or not get_path.is_file(): + _LOGGER.error("Can not access file %s", path) + raise NotValidError if not hass.config.is_allowed_path(path): - _LOGGER.error("Filepath %s is not valid or allowed", path) + _LOGGER.error("Filepath %s is not allowed", path) raise NotAllowedError - return get_path + full_path = get_path.absolute() + + return str(full_path) class FilesizeConfigFlow(ConfigFlow, domain=DOMAIN): @@ -47,14 +49,13 @@ class FilesizeConfigFlow(ConfigFlow, domain=DOMAIN): if user_input is not None: try: - get_path = validate_path(self.hass, user_input[CONF_FILE_PATH]) + full_path = validate_path(self.hass, user_input[CONF_FILE_PATH]) except NotValidError: errors["base"] = "not_valid" except NotAllowedError: errors["base"] = "not_allowed" else: - fullpath = str(get_path.absolute()) - await self.async_set_unique_id(fullpath) + await self.async_set_unique_id(full_path) self._abort_if_unique_id_configured() name = str(user_input[CONF_FILE_PATH]).rsplit("/", maxsplit=1)[-1] @@ -67,14 +68,10 @@ class FilesizeConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: - """Handle import from configuration.yaml.""" - return await self.async_step_user(user_input) - -class NotValidError(Exception): +class NotValidError(HomeAssistantError): """Path is not valid error.""" -class NotAllowedError(Exception): +class NotAllowedError(HomeAssistantError): """Path is not allowed error.""" diff --git a/homeassistant/components/filesize/const.py b/homeassistant/components/filesize/const.py index a47f1f99d38..a3180f762c0 100644 --- a/homeassistant/components/filesize/const.py +++ b/homeassistant/components/filesize/const.py @@ -4,5 +4,3 @@ from homeassistant.const import Platform DOMAIN = "filesize" PLATFORMS = [Platform.SENSOR] - -CONF_FILE_PATHS = "file_paths" diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index 52bb869827a..5f66aefaab5 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -6,23 +6,18 @@ import logging import os import pathlib -import voluptuous as vol - from homeassistant.components.sensor import ( - PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, SensorDeviceClass, SensorEntity, SensorEntityDescription, SensorStateClass, ) -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_FILE_PATH, DATA_BYTES, DATA_MEGABYTES from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -30,7 +25,7 @@ from homeassistant.helpers.update_coordinator import ( ) import homeassistant.util.dt as dt_util -from .const import CONF_FILE_PATHS, DOMAIN +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -64,34 +59,6 @@ SENSOR_TYPES = ( ), ) -PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( - {vol.Required(CONF_FILE_PATHS): vol.All(cv.ensure_list, [cv.isfile])} -) - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the file size sensor.""" - _LOGGER.warning( - # Filesize config flow added in 2022.4 and should be removed in 2022.6 - "Configuration of the Filesize 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" - ) - for path in config[CONF_FILE_PATHS]: - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_FILE_PATH: path}, - ) - ) - async def async_setup_entry( hass: HomeAssistant, @@ -107,13 +74,10 @@ async def async_setup_entry( coordinator = FileSizeCoordinator(hass, fullpath) await coordinator.async_config_entry_first_refresh() - if get_path.exists() and get_path.is_file(): - async_add_entities( - [ - FilesizeEntity(description, fullpath, entry.entry_id, coordinator) - for description in SENSOR_TYPES - ] - ) + async_add_entities( + FilesizeEntity(description, fullpath, entry.entry_id, coordinator) + for description in SENSOR_TYPES + ) class FileSizeCoordinator(DataUpdateCoordinator): @@ -152,7 +116,7 @@ class FileSizeCoordinator(DataUpdateCoordinator): class FilesizeEntity(CoordinatorEntity[FileSizeCoordinator], SensorEntity): - """Encapsulates file size information.""" + """Filesize sensor.""" entity_description: SensorEntityDescription @@ -163,7 +127,7 @@ class FilesizeEntity(CoordinatorEntity[FileSizeCoordinator], SensorEntity): entry_id: str, coordinator: FileSizeCoordinator, ) -> None: - """Initialize the data object.""" + """Initialize the Filesize sensor.""" super().__init__(coordinator) base_name = path.split("/")[-1] self._attr_name = f"{base_name} {description.name}" diff --git a/homeassistant/components/filesize/translations/es.json b/homeassistant/components/filesize/translations/es.json new file mode 100644 index 00000000000..e2bc079b961 --- /dev/null +++ b/homeassistant/components/filesize/translations/es.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "not_allowed": "Ruta no permitida" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/nl.json b/homeassistant/components/filesize/translations/nl.json index d8a35446044..4b72bc53292 100644 --- a/homeassistant/components/filesize/translations/nl.json +++ b/homeassistant/components/filesize/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "not_allowed": "Pad is niet toegestaan", diff --git a/homeassistant/components/fireservicerota/translations/es.json b/homeassistant/components/fireservicerota/translations/es.json index 9f27181dfe5..085bce8f343 100644 --- a/homeassistant/components/fireservicerota/translations/es.json +++ b/homeassistant/components/fireservicerota/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "create_entry": { diff --git a/homeassistant/components/fireservicerota/translations/nl.json b/homeassistant/components/fireservicerota/translations/nl.json index 3a6ba936dee..62085c9a333 100644 --- a/homeassistant/components/fireservicerota/translations/nl.json +++ b/homeassistant/components/fireservicerota/translations/nl.json @@ -2,10 +2,10 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie" diff --git a/homeassistant/components/fivem/translations/ca.json b/homeassistant/components/fivem/translations/ca.json index 8e2a192a18c..ae322b4748c 100644 --- a/homeassistant/components/fivem/translations/ca.json +++ b/homeassistant/components/fivem/translations/ca.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "Ha fallat la connexi\u00f3. Comprova l'amfitri\u00f3 i el port i torna-ho a provar. Assegurat que est\u00e0s utilitzant la versi\u00f3 del servidor FiveM m\u00e9s recent.", "invalid_game_name": "L'API del joc al qual est\u00e0s intentant connectar-te no \u00e9s d'un joc FiveM.", - "invalid_gamename": "L'API del joc al qual est\u00e0s intentant connectar-te no \u00e9s d'un joc FiveM.", "unknown_error": "Error inesperat" }, "step": { diff --git a/homeassistant/components/fivem/translations/de.json b/homeassistant/components/fivem/translations/de.json index 5c6852a126e..bb3b3439cbf 100644 --- a/homeassistant/components/fivem/translations/de.json +++ b/homeassistant/components/fivem/translations/de.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen. Bitte \u00fcberpr\u00fcfe den Host und den Port und versuche es erneut. Vergewissere dich auch, dass du den neuesten FiveM-Server verwendest.", "invalid_game_name": "Die API des Spiels, mit dem du dich verbinden willst, ist kein FiveM-Spiel.", - "invalid_gamename": "Die API des Spiels, mit dem du dich verbinden willst, ist kein FiveM-Spiel.", "unknown_error": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/fivem/translations/el.json b/homeassistant/components/fivem/translations/el.json index 29e798361ff..2f038de5ba9 100644 --- a/homeassistant/components/fivem/translations/el.json +++ b/homeassistant/components/fivem/translations/el.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac. \u0392\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03cc\u03c4\u03b9 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b9\u03bf \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae FiveM.", "invalid_game_name": "\u03a4\u03bf api \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 FiveM.", - "invalid_gamename": "\u03a4\u03bf api \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 FiveM.", "unknown_error": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { diff --git a/homeassistant/components/fivem/translations/en.json b/homeassistant/components/fivem/translations/en.json index e07c0666e24..8c4f7a54156 100644 --- a/homeassistant/components/fivem/translations/en.json +++ b/homeassistant/components/fivem/translations/en.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "Failed to connect. Please check the host and port and try again. Also ensure that you are running the latest FiveM server.", "invalid_game_name": "The api of the game you are trying to connect to is not a FiveM game.", - "invalid_gamename": "The api of the game you are trying to connect to is not a FiveM game.", "unknown_error": "Unexpected error" }, "step": { diff --git a/homeassistant/components/fivem/translations/es.json b/homeassistant/components/fivem/translations/es.json index d8b3f3c6e1c..273435a4e5e 100644 --- a/homeassistant/components/fivem/translations/es.json +++ b/homeassistant/components/fivem/translations/es.json @@ -6,13 +6,12 @@ "error": { "cannot_connect": "Error al conectarse. Compruebe el host y el puerto e int\u00e9ntelo de nuevo. Aseg\u00farese tambi\u00e9n de que est\u00e1 ejecutando el servidor FiveM m\u00e1s reciente.", "invalid_game_name": "La API del juego al que intentas conectarte no es un juego de FiveM.", - "invalid_gamename": "La API del juego al que intentas conectarte no es un juego de FiveM.", "unknown_error": "Error inesperado" }, "step": { "user": { "data": { - "host": "Anfitri\u00f3n", + "host": "Host", "name": "Nombre", "port": "Puerto" } diff --git a/homeassistant/components/fivem/translations/et.json b/homeassistant/components/fivem/translations/et.json index 0ca491bd232..49edde59e1f 100644 --- a/homeassistant/components/fivem/translations/et.json +++ b/homeassistant/components/fivem/translations/et.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "\u00dchendamine eba\u00f5nnestus. Kontrolli hosti ja porti ning proovi uuesti. Veendu, et kasutad uusimat FiveM-i serverit.", "invalid_game_name": "M\u00e4ngu API, millega proovid \u00fchendust luua, ei ole FiveM-m\u00e4ng.", - "invalid_gamename": "M\u00e4ngu API, millega proovid \u00fchendust luua, ei ole FiveM-m\u00e4ng.", "unknown_error": "Ootamatu t\u00f5rge" }, "step": { diff --git a/homeassistant/components/fivem/translations/fr.json b/homeassistant/components/fivem/translations/fr.json index 4cd16be65a3..be8254a74de 100644 --- a/homeassistant/components/fivem/translations/fr.json +++ b/homeassistant/components/fivem/translations/fr.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "\u00c9chec de connexion. Veuillez v\u00e9rifier l'h\u00f4te et le port et r\u00e9essayer. Assurez-vous \u00e9galement que vous utilisez le dernier serveur FiveM.", "invalid_game_name": "L'API du jeu auquel vous essayez de vous connecter n'est pas un jeu FiveM.", - "invalid_gamename": "L\u2019API du jeu auquel vous essayez de vous connecter n\u2019est pas un jeu FiveM.", "unknown_error": "Erreur inattendue" }, "step": { diff --git a/homeassistant/components/fivem/translations/hu.json b/homeassistant/components/fivem/translations/hu.json index 4e56dc3c17f..d29d0c9a802 100644 --- a/homeassistant/components/fivem/translations/hu.json +++ b/homeassistant/components/fivem/translations/hu.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "Nem siker\u00fclt csatlakozni. K\u00e9rj\u00fck, ellen\u0151rizze a c\u00edmet \u00e9s a portot, \u00e9s pr\u00f3b\u00e1lja meg \u00fajra. Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l is, hogy a leg\u00fajabb FiveM szervert futtatja.", "invalid_game_name": "A j\u00e1t\u00e9k API-ja, amelyhez csatlakozni pr\u00f3b\u00e1l, nem FiveM.", - "invalid_gamename": "A j\u00e1t\u00e9k API-ja, amelyhez csatlakozni pr\u00f3b\u00e1l, nem FiveM.", "unknown_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { diff --git a/homeassistant/components/fivem/translations/id.json b/homeassistant/components/fivem/translations/id.json index 3cf44f86f5d..95e9e4decde 100644 --- a/homeassistant/components/fivem/translations/id.json +++ b/homeassistant/components/fivem/translations/id.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "Gagal terhubung ke server. Periksa host dan port lalu coba lagi. Pastikan juga Anda menjalankan server FiveM terbaru.", "invalid_game_name": "API dari permainan yang Anda coba hubungkan bukanlah game FiveM.", - "invalid_gamename": "API dari permainan yang Anda coba hubungkan bukanlah game FiveM.", "unknown_error": "Kesalahan yang tidak diharapkan" }, "step": { diff --git a/homeassistant/components/fivem/translations/it.json b/homeassistant/components/fivem/translations/it.json index 0128d1fbeca..e14e8492835 100644 --- a/homeassistant/components/fivem/translations/it.json +++ b/homeassistant/components/fivem/translations/it.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "Connessione non riuscita. Controlla l'host e la porta e riprova. Assicurati inoltre di eseguire il server FiveM pi\u00f9 recente.", "invalid_game_name": "L'API del gioco a cui stai tentando di connetterti non \u00e8 un gioco FiveM.", - "invalid_gamename": "L'API del gioco a cui stai tentando di connetterti non \u00e8 un gioco FiveM.", "unknown_error": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/fivem/translations/ja.json b/homeassistant/components/fivem/translations/ja.json index eb398cccff4..6ac9af80f2c 100644 --- a/homeassistant/components/fivem/translations/ja.json +++ b/homeassistant/components/fivem/translations/ja.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30db\u30b9\u30c8\u3068\u30dd\u30fc\u30c8\u3092\u78ba\u8a8d\u3057\u3066\u3001\u3082\u3046\u4e00\u5ea6\u3084\u308a\u76f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u307e\u305f\u3001\u6700\u65b0\u306eFiveM\u30b5\u30fc\u30d0\u30fc\u3092\u5b9f\u884c\u3057\u3066\u3044\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "invalid_game_name": "\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u30b2\u30fc\u30e0\u306eAPI\u306f\u3001FiveM\u306e\u30b2\u30fc\u30e0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002", - "invalid_gamename": "\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u30b2\u30fc\u30e0\u306eAPI\u306f\u3001FiveM\u306e\u30b2\u30fc\u30e0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002", "unknown_error": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { diff --git a/homeassistant/components/fivem/translations/nl.json b/homeassistant/components/fivem/translations/nl.json index 599bcbc771e..ef5952cb60a 100644 --- a/homeassistant/components/fivem/translations/nl.json +++ b/homeassistant/components/fivem/translations/nl.json @@ -1,12 +1,11 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken. Controleer de host en poort en probeer het opnieuw. Zorg er ook voor dat u de nieuwste FiveM-server gebruikt.", "invalid_game_name": "De api van het spel waarmee je probeert te verbinden is geen FiveM spel.", - "invalid_gamename": "De api van het spel waarmee je probeert te verbinden is geen FiveM spel.", "unknown_error": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/fivem/translations/no.json b/homeassistant/components/fivem/translations/no.json index ac292c10b64..b609f8ad64f 100644 --- a/homeassistant/components/fivem/translations/no.json +++ b/homeassistant/components/fivem/translations/no.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "Tilkobling mislyktes. Kontroller verten og porten og pr\u00f8v igjen. S\u00f8rg ogs\u00e5 for at du kj\u00f8rer den nyeste FiveM-serveren.", "invalid_game_name": "API-et til spillet du pr\u00f8ver \u00e5 koble til er ikke et FiveM-spill.", - "invalid_gamename": "API-et til spillet du pr\u00f8ver \u00e5 koble til er ikke et FiveM-spill.", "unknown_error": "Uventet feil" }, "step": { diff --git a/homeassistant/components/fivem/translations/pl.json b/homeassistant/components/fivem/translations/pl.json index 420ebca7463..f60bd6c23df 100644 --- a/homeassistant/components/fivem/translations/pl.json +++ b/homeassistant/components/fivem/translations/pl.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia. Sprawd\u017a adres hosta oraz port i spr\u00f3buj ponownie. Upewnij si\u0119, \u017ce posiadasz najnowsz\u0105 wersj\u0119 serwera FiveM.", "invalid_game_name": "API gry, do kt\u00f3rej pr\u00f3bujesz si\u0119 po\u0142\u0105czy\u0107, nie jest gr\u0105 FiveM.", - "invalid_gamename": "API gry, do kt\u00f3rej pr\u00f3bujesz si\u0119 po\u0142\u0105czy\u0107, nie jest gr\u0105 FiveM.", "unknown_error": "Nieoczekiwany b\u0142\u0105d" }, "step": { diff --git a/homeassistant/components/fivem/translations/pt-BR.json b/homeassistant/components/fivem/translations/pt-BR.json index b576192718f..af5980a90f1 100644 --- a/homeassistant/components/fivem/translations/pt-BR.json +++ b/homeassistant/components/fivem/translations/pt-BR.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "Falha ao se conectar. Verifique o host e a porta e tente novamente. Verifique tamb\u00e9m se voc\u00ea est\u00e1 executando o servidor FiveM mais recente.", "invalid_game_name": "A API do jogo ao qual voc\u00ea est\u00e1 tentando se conectar n\u00e3o \u00e9 um jogo FiveM.", - "invalid_gamename": "A API do jogo ao qual voc\u00ea est\u00e1 tentando se conectar n\u00e3o \u00e9 um jogo FiveM.", "unknown_error": "Erro inesperado" }, "step": { diff --git a/homeassistant/components/fivem/translations/ru.json b/homeassistant/components/fivem/translations/ru.json index c6da81663ca..2f2d3d3be73 100644 --- a/homeassistant/components/fivem/translations/ru.json +++ b/homeassistant/components/fivem/translations/ru.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430 \u0438 \u043f\u043e\u0440\u0442 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443. \u0422\u0430\u043a\u0436\u0435 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0412\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 FiveM.", "invalid_game_name": "API \u0438\u0433\u0440\u044b, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0412\u044b \u043f\u044b\u0442\u0430\u0435\u0442\u0435\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0433\u0440\u043e\u0439 FiveM.", - "invalid_gamename": "API \u0438\u0433\u0440\u044b, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0412\u044b \u043f\u044b\u0442\u0430\u0435\u0442\u0435\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0433\u0440\u043e\u0439 FiveM.", "unknown_error": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { diff --git a/homeassistant/components/fivem/translations/tr.json b/homeassistant/components/fivem/translations/tr.json index 46921dd33c0..f6bfc9cf274 100644 --- a/homeassistant/components/fivem/translations/tr.json +++ b/homeassistant/components/fivem/translations/tr.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "Ba\u011flanma hatas\u0131. L\u00fctfen ana bilgisayar\u0131 ve ba\u011flant\u0131 noktas\u0131n\u0131 kontrol edin ve tekrar deneyin. Ayr\u0131ca en son FiveM sunucusunu \u00e7al\u0131\u015ft\u0131rd\u0131\u011f\u0131n\u0131zdan emin olun.", "invalid_game_name": "Ba\u011flanmaya \u00e7al\u0131\u015ft\u0131\u011f\u0131n\u0131z oyunun api'si bir FiveM oyunu de\u011fil.", - "invalid_gamename": "Ba\u011flanmaya \u00e7al\u0131\u015ft\u0131\u011f\u0131n\u0131z oyunun api'si bir FiveM oyunu de\u011fil.", "unknown_error": "Beklenmeyen hata" }, "step": { diff --git a/homeassistant/components/fivem/translations/zh-Hant.json b/homeassistant/components/fivem/translations/zh-Hant.json index 3b1527f7a35..a05ecfb0dd4 100644 --- a/homeassistant/components/fivem/translations/zh-Hant.json +++ b/homeassistant/components/fivem/translations/zh-Hant.json @@ -6,7 +6,6 @@ "error": { "cannot_connect": "\u4f3a\u670d\u5668\u9023\u7dda\u5931\u6557\u3002\u8acb\u6aa2\u67e5\u4e3b\u6a5f\u7aef\u8207\u901a\u8a0a\u57e0\u5f8c\u518d\u8a66\u4e00\u6b21\u3002\u53e6\u8acb\u78ba\u8a8d\u57f7\u884c\u6700\u65b0\u7248 FiveM \u4f3a\u670d\u5668\u3002", "invalid_game_name": "\u5617\u8a66\u9023\u7dda\u7684\u904a\u6232 API \u4e26\u975e FiveM \u904a\u6232\u3002", - "invalid_gamename": "\u5617\u8a66\u9023\u7dda\u7684\u904a\u6232 API \u4e26\u975e FiveM \u904a\u6232\u3002", "unknown_error": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/fjaraskupan/__init__.py b/homeassistant/components/fjaraskupan/__init__.py index 64962a746f7..ec4528bc079 100644 --- a/homeassistant/components/fjaraskupan/__init__.py +++ b/homeassistant/components/fjaraskupan/__init__.py @@ -9,7 +9,7 @@ import logging from bleak import BleakScanner from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementData -from fjaraskupan import UUID_SERVICE, Device, State, device_filter +from fjaraskupan import DEVICE_NAME, Device, State, device_filter from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform @@ -20,7 +20,7 @@ from homeassistant.helpers.dispatcher import ( ) from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DISPATCH_DETECTION, DOMAIN @@ -35,13 +35,48 @@ PLATFORMS = [ _LOGGER = logging.getLogger(__name__) -@dataclass -class DeviceState: - """Store state of a device.""" +class Coordinator(DataUpdateCoordinator[State]): + """Update coordinator for each device.""" - device: Device - coordinator: DataUpdateCoordinator[State] - device_info: DeviceInfo + def __init__( + self, hass: HomeAssistant, device: Device, device_info: DeviceInfo + ) -> None: + """Initialize the coordinator.""" + self.device = device + self.device_info = device_info + self._refresh_was_scheduled = False + + super().__init__( + hass, _LOGGER, name="Fjäråskupan", update_interval=timedelta(seconds=120) + ) + + async def _async_refresh( + self, + log_failures: bool = True, + raise_on_auth_failed: bool = False, + scheduled: bool = False, + ) -> None: + self._refresh_was_scheduled = scheduled + await super()._async_refresh( + log_failures=log_failures, + raise_on_auth_failed=raise_on_auth_failed, + scheduled=scheduled, + ) + + async def _async_update_data(self) -> State: + """Handle an explicit update request.""" + if self._refresh_was_scheduled: + raise UpdateFailed("No data received within schedule.") + + await self.device.update() + return self.device.state + + def detection_callback( + self, ble_device: BLEDevice, advertisement_data: AdvertisementData + ) -> None: + """Handle a new announcement of data.""" + self.device.detection_callback(ble_device, advertisement_data) + self.async_set_updated_data(self.device.state) @dataclass @@ -49,13 +84,13 @@ class EntryState: """Store state of config entry.""" scanner: BleakScanner - devices: dict[str, DeviceState] + coordinators: dict[str, Coordinator] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Fjäråskupan from a config entry.""" - scanner = BleakScanner(filters={"UUIDs": [str(UUID_SERVICE)]}) + scanner = BleakScanner(filters={"Pattern": DEVICE_NAME, "DuplicateData": True}) state = EntryState(scanner, {}) hass.data.setdefault(DOMAIN, {}) @@ -64,13 +99,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def detection_callback( ble_device: BLEDevice, advertisement_data: AdvertisementData ) -> None: - if data := state.devices.get(ble_device.address): + if data := state.coordinators.get(ble_device.address): _LOGGER.debug( "Update: %s %s - %s", ble_device.name, ble_device, advertisement_data ) - data.device.detection_callback(ble_device, advertisement_data) - data.coordinator.async_set_updated_data(data.device.state) + data.detection_callback(ble_device, advertisement_data) else: if not device_filter(ble_device, advertisement_data): return @@ -80,31 +114,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) device = Device(ble_device) - device.detection_callback(ble_device, advertisement_data) - - async def async_update_data(): - """Handle an explicit update request.""" - await device.update() - return device.state - - coordinator: DataUpdateCoordinator[State] = DataUpdateCoordinator( - hass, - logger=_LOGGER, - name="Fjaraskupan Updater", - update_interval=timedelta(seconds=120), - update_method=async_update_data, - ) - coordinator.async_set_updated_data(device.state) - device_info = DeviceInfo( identifiers={(DOMAIN, ble_device.address)}, manufacturer="Fjäråskupan", name="Fjäråskupan", ) - device_state = DeviceState(device, coordinator, device_info) - state.devices[ble_device.address] = device_state + + coordinator: Coordinator = Coordinator(hass, device, device_info) + coordinator.detection_callback(ble_device, advertisement_data) + + state.coordinators[ble_device.address] = coordinator async_dispatcher_send( - hass, f"{DISPATCH_DETECTION}.{entry.entry_id}", device_state + hass, f"{DISPATCH_DETECTION}.{entry.entry_id}", coordinator ) scanner.register_detection_callback(detection_callback) @@ -119,20 +140,20 @@ def async_setup_entry_platform( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback, - constructor: Callable[[DeviceState], list[Entity]], + constructor: Callable[[Coordinator], list[Entity]], ) -> None: """Set up a platform with added entities.""" entry_state: EntryState = hass.data[DOMAIN][entry.entry_id] async_add_entities( entity - for device_state in entry_state.devices.values() - for entity in constructor(device_state) + for coordinator in entry_state.coordinators.values() + for entity in constructor(coordinator) ) @callback - def _detection(device_state: DeviceState) -> None: - async_add_entities(constructor(device_state)) + def _detection(coordinator: Coordinator) -> None: + async_add_entities(constructor(coordinator)) entry.async_on_unload( async_dispatcher_connect( diff --git a/homeassistant/components/fjaraskupan/binary_sensor.py b/homeassistant/components/fjaraskupan/binary_sensor.py index f1672530c45..ef4f64c5ecd 100644 --- a/homeassistant/components/fjaraskupan/binary_sensor.py +++ b/homeassistant/components/fjaraskupan/binary_sensor.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass -from fjaraskupan import Device, State +from fjaraskupan import Device from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, @@ -15,12 +15,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, -) +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DeviceState, async_setup_entry_platform +from . import Coordinator, async_setup_entry_platform @dataclass @@ -53,12 +50,12 @@ async def async_setup_entry( ) -> None: """Set up sensors dynamically through discovery.""" - def _constructor(device_state: DeviceState) -> list[Entity]: + def _constructor(coordinator: Coordinator) -> list[Entity]: return [ BinarySensor( - device_state.coordinator, - device_state.device, - device_state.device_info, + coordinator, + coordinator.device, + coordinator.device_info, entity_description, ) for entity_description in SENSORS @@ -67,14 +64,14 @@ async def async_setup_entry( async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor) -class BinarySensor(CoordinatorEntity[DataUpdateCoordinator[State]], BinarySensorEntity): +class BinarySensor(CoordinatorEntity[Coordinator], BinarySensorEntity): """Grease filter sensor.""" entity_description: EntityDescription def __init__( self, - coordinator: DataUpdateCoordinator[State], + coordinator: Coordinator, device: Device, device_info: DeviceInfo, entity_description: EntityDescription, diff --git a/homeassistant/components/fjaraskupan/config_flow.py b/homeassistant/components/fjaraskupan/config_flow.py index da0a7f1dd2b..3af34c0eef6 100644 --- a/homeassistant/components/fjaraskupan/config_flow.py +++ b/homeassistant/components/fjaraskupan/config_flow.py @@ -7,7 +7,7 @@ import async_timeout from bleak import BleakScanner from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementData -from fjaraskupan import UUID_SERVICE, device_filter +from fjaraskupan import DEVICE_NAME, device_filter from homeassistant.core import HomeAssistant from homeassistant.helpers.config_entry_flow import register_discovery_flow @@ -27,7 +27,8 @@ async def _async_has_devices(hass: HomeAssistant) -> bool: event.set() async with BleakScanner( - detection_callback=detection, filters={"UUIDs": [str(UUID_SERVICE)]} + detection_callback=detection, + filters={"Pattern": DEVICE_NAME, "DuplicateData": True}, ): try: async with async_timeout.timeout(CONST_WAIT_TIME): diff --git a/homeassistant/components/fjaraskupan/fan.py b/homeassistant/components/fjaraskupan/fan.py index a8f8e13f3da..fcd95090400 100644 --- a/homeassistant/components/fjaraskupan/fan.py +++ b/homeassistant/components/fjaraskupan/fan.py @@ -16,16 +16,13 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, -) +from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util.percentage import ( ordered_list_item_to_percentage, percentage_to_ordered_list_item, ) -from . import DeviceState, async_setup_entry_platform +from . import Coordinator, async_setup_entry_platform ORDERED_NAMED_FAN_SPEEDS = ["1", "2", "3", "4", "5", "6", "7", "8"] @@ -58,22 +55,20 @@ async def async_setup_entry( ) -> None: """Set up sensors dynamically through discovery.""" - def _constructor(device_state: DeviceState): - return [ - Fan(device_state.coordinator, device_state.device, device_state.device_info) - ] + def _constructor(coordinator: Coordinator): + return [Fan(coordinator, coordinator.device, coordinator.device_info)] async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor) -class Fan(CoordinatorEntity[DataUpdateCoordinator[State]], FanEntity): +class Fan(CoordinatorEntity[Coordinator], FanEntity): """Fan entity.""" _attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE def __init__( self, - coordinator: DataUpdateCoordinator[State], + coordinator: Coordinator, device: Device, device_info: DeviceInfo, ) -> None: diff --git a/homeassistant/components/fjaraskupan/light.py b/homeassistant/components/fjaraskupan/light.py index 6c3e6e458f8..9d52c5cac82 100644 --- a/homeassistant/components/fjaraskupan/light.py +++ b/homeassistant/components/fjaraskupan/light.py @@ -1,19 +1,16 @@ """Support for lights.""" from __future__ import annotations -from fjaraskupan import COMMAND_LIGHT_ON_OFF, Device, State +from fjaraskupan import COMMAND_LIGHT_ON_OFF, Device 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 from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, -) +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DeviceState, async_setup_entry_platform +from . import Coordinator, async_setup_entry_platform async def async_setup_entry( @@ -23,22 +20,18 @@ async def async_setup_entry( ) -> None: """Set up tuya sensors dynamically through tuya discovery.""" - def _constructor(device_state: DeviceState) -> list[Entity]: - return [ - Light( - device_state.coordinator, device_state.device, device_state.device_info - ) - ] + def _constructor(coordinator: Coordinator) -> list[Entity]: + return [Light(coordinator, coordinator.device, coordinator.device_info)] async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor) -class Light(CoordinatorEntity[DataUpdateCoordinator[State]], LightEntity): +class Light(CoordinatorEntity[Coordinator], LightEntity): """Light device.""" def __init__( self, - coordinator: DataUpdateCoordinator[State], + coordinator: Coordinator, device: Device, device_info: DeviceInfo, ) -> None: diff --git a/homeassistant/components/fjaraskupan/number.py b/homeassistant/components/fjaraskupan/number.py index bbde9bd8898..6314b9c9cc1 100644 --- a/homeassistant/components/fjaraskupan/number.py +++ b/homeassistant/components/fjaraskupan/number.py @@ -1,7 +1,7 @@ """Support for sensors.""" from __future__ import annotations -from fjaraskupan import Device, State +from fjaraskupan import Device from homeassistant.components.number import NumberEntity from homeassistant.config_entries import ConfigEntry @@ -9,12 +9,9 @@ from homeassistant.const import TIME_MINUTES from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo, Entity, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, -) +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DeviceState, async_setup_entry_platform +from . import Coordinator, async_setup_entry_platform async def async_setup_entry( @@ -24,19 +21,17 @@ async def async_setup_entry( ) -> None: """Set up number entities dynamically through discovery.""" - def _constructor(device_state: DeviceState) -> list[Entity]: + def _constructor(coordinator: Coordinator) -> list[Entity]: return [ PeriodicVentingTime( - device_state.coordinator, device_state.device, device_state.device_info + coordinator, coordinator.device, coordinator.device_info ), ] async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor) -class PeriodicVentingTime( - CoordinatorEntity[DataUpdateCoordinator[State]], NumberEntity -): +class PeriodicVentingTime(CoordinatorEntity[Coordinator], NumberEntity): """Periodic Venting.""" _attr_max_value: float = 59 @@ -47,7 +42,7 @@ class PeriodicVentingTime( def __init__( self, - coordinator: DataUpdateCoordinator[State], + coordinator: Coordinator, device: Device, device_info: DeviceInfo, ) -> None: diff --git a/homeassistant/components/fjaraskupan/sensor.py b/homeassistant/components/fjaraskupan/sensor.py index fbd9d5f6d08..e4dbffab38e 100644 --- a/homeassistant/components/fjaraskupan/sensor.py +++ b/homeassistant/components/fjaraskupan/sensor.py @@ -1,7 +1,7 @@ """Support for sensors.""" from __future__ import annotations -from fjaraskupan import Device, State +from fjaraskupan import Device from homeassistant.components.sensor import ( SensorDeviceClass, @@ -14,12 +14,9 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo, Entity, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, -) +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DeviceState, async_setup_entry_platform +from . import Coordinator, async_setup_entry_platform async def async_setup_entry( @@ -29,22 +26,18 @@ async def async_setup_entry( ) -> None: """Set up sensors dynamically through discovery.""" - def _constructor(device_state: DeviceState) -> list[Entity]: - return [ - RssiSensor( - device_state.coordinator, device_state.device, device_state.device_info - ) - ] + def _constructor(coordinator: Coordinator) -> list[Entity]: + return [RssiSensor(coordinator, coordinator.device, coordinator.device_info)] async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor) -class RssiSensor(CoordinatorEntity[DataUpdateCoordinator[State]], SensorEntity): +class RssiSensor(CoordinatorEntity[Coordinator], SensorEntity): """Sensor device.""" def __init__( self, - coordinator: DataUpdateCoordinator[State], + coordinator: Coordinator, device: Device, device_info: DeviceInfo, ) -> None: diff --git a/homeassistant/components/fjaraskupan/translations/ko.json b/homeassistant/components/fjaraskupan/translations/ko.json new file mode 100644 index 00000000000..0a4087d0200 --- /dev/null +++ b/homeassistant/components/fjaraskupan/translations/ko.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c \uc548\uc5d0 \ubc1c\uacac\ub41c \ub514\ubc14\uc774\uc2a4 \uc5c6\uc74c" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fjaraskupan/translations/nl.json b/homeassistant/components/fjaraskupan/translations/nl.json index 498ef7af1be..1c973e28d69 100644 --- a/homeassistant/components/fjaraskupan/translations/nl.json +++ b/homeassistant/components/fjaraskupan/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/flick_electric/translations/es.json b/homeassistant/components/flick_electric/translations/es.json index 435ead31d9a..424c73da24f 100644 --- a/homeassistant/components/flick_electric/translations/es.json +++ b/homeassistant/components/flick_electric/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada" + "already_configured": "La cuenta ya est\u00e1 configurada" }, "error": { "cannot_connect": "No se pudo conectar", diff --git a/homeassistant/components/flick_electric/translations/nl.json b/homeassistant/components/flick_electric/translations/nl.json index f90ee71301b..c7c6e2bec81 100644 --- a/homeassistant/components/flick_electric/translations/nl.json +++ b/homeassistant/components/flick_electric/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Account is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/flo/translations/nl.json b/homeassistant/components/flo/translations/nl.json index d4c409802c1..8ad15260b0d 100644 --- a/homeassistant/components/flo/translations/nl.json +++ b/homeassistant/components/flo/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/flume/translations/ko.json b/homeassistant/components/flume/translations/ko.json index c82f0a990d8..fc3970e69ad 100644 --- a/homeassistant/components/flume/translations/ko.json +++ b/homeassistant/components/flume/translations/ko.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -9,6 +10,13 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "reauth_confirm": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638" + }, + "description": "{username} \uc758 \ube44\ubc00\ubc88\ud638\uac00 \ub354 \uc774\uc0c1 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "title": "Flume \uacc4\uc815 \uc7ac\uc778\uc99d" + }, "user": { "data": { "client_id": "\ud074\ub77c\uc774\uc5b8\ud2b8 ID", diff --git a/homeassistant/components/flume/translations/nl.json b/homeassistant/components/flume/translations/nl.json index de0e225be03..b08b37ca21c 100644 --- a/homeassistant/components/flume/translations/nl.json +++ b/homeassistant/components/flume/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/flunearyou/translations/es.json b/homeassistant/components/flunearyou/translations/es.json index 5d0c05c0c54..5d7b8fb6a6f 100644 --- a/homeassistant/components/flunearyou/translations/es.json +++ b/homeassistant/components/flunearyou/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Estas coordenadas ya est\u00e1n registradas." + "already_configured": "La ubicaci\u00f3n ya est\u00e1 configurada" }, "error": { "unknown": "Error inesperado" diff --git a/homeassistant/components/flunearyou/translations/nl.json b/homeassistant/components/flunearyou/translations/nl.json index d78abfcc187..0938bd45206 100644 --- a/homeassistant/components/flunearyou/translations/nl.json +++ b/homeassistant/components/flunearyou/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { "unknown": "Onverwachte fout" diff --git a/homeassistant/components/flux_led/__init__.py b/homeassistant/components/flux_led/__init__.py index 17dc28a5edf..e6c1393154a 100644 --- a/homeassistant/components/flux_led/__init__.py +++ b/homeassistant/components/flux_led/__init__.py @@ -15,7 +15,10 @@ from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STARTED, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr, entity_registry as er -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.event import ( async_track_time_change, async_track_time_interval, @@ -27,6 +30,7 @@ from .const import ( DISCOVER_SCAN_TIMEOUT, DOMAIN, FLUX_LED_DISCOVERY, + FLUX_LED_DISCOVERY_SIGNAL, FLUX_LED_EXCEPTIONS, SIGNAL_STATE_UPDATED, STARTUP_SCAN_TIMEOUT, @@ -196,6 +200,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # to avoid a race condition where the add_update_listener is not # in place in time for the check in async_update_entry_from_discovery entry.async_on_unload(entry.add_update_listener(_async_update_listener)) + + async def _async_handle_discovered_device() -> None: + """Handle device discovery.""" + # Force a refresh if the device is now available + if not coordinator.last_update_success: + coordinator.force_next_update = True + await coordinator.async_refresh() + + entry.async_on_unload( + async_dispatcher_connect( + hass, + FLUX_LED_DISCOVERY_SIGNAL.format(entry_id=entry.entry_id), + _async_handle_discovered_device, + ) + ) return True diff --git a/homeassistant/components/flux_led/config_flow.py b/homeassistant/components/flux_led/config_flow.py index dfb6ff4a174..61395d744b3 100644 --- a/homeassistant/components/flux_led/config_flow.py +++ b/homeassistant/components/flux_led/config_flow.py @@ -21,6 +21,7 @@ from homeassistant.const import CONF_HOST from homeassistant.core import callback from homeassistant.data_entry_flow import AbortFlow, FlowResult from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import DiscoveryInfoType from . import async_wifi_bulb_for_host @@ -31,6 +32,7 @@ from .const import ( DEFAULT_EFFECT_SPEED, DISCOVER_SCAN_TIMEOUT, DOMAIN, + FLUX_LED_DISCOVERY_SIGNAL, FLUX_LED_EXCEPTIONS, TRANSITION_GRADUAL, TRANSITION_JUMP, @@ -109,12 +111,20 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): and ":" in entry.unique_id and mac_matches_by_one(entry.unique_id, mac) ): - if async_update_entry_from_discovery( - self.hass, entry, device, None, allow_update_mac + if ( + async_update_entry_from_discovery( + self.hass, entry, device, None, allow_update_mac + ) + or entry.state == config_entries.ConfigEntryState.SETUP_RETRY ): self.hass.async_create_task( self.hass.config_entries.async_reload(entry.entry_id) ) + else: + async_dispatcher_send( + self.hass, + FLUX_LED_DISCOVERY_SIGNAL.format(entry_id=entry.entry_id), + ) raise AbortFlow("already_configured") async def _async_handle_discovery(self) -> FlowResult: diff --git a/homeassistant/components/flux_led/const.py b/homeassistant/components/flux_led/const.py index 7fa841ec77f..db545aa1e68 100644 --- a/homeassistant/components/flux_led/const.py +++ b/homeassistant/components/flux_led/const.py @@ -74,3 +74,5 @@ EFFECT_SPEED_SUPPORT_MODES: Final = {ColorMode.RGB, ColorMode.RGBW, ColorMode.RG CONF_CUSTOM_EFFECT_COLORS: Final = "custom_effect_colors" CONF_CUSTOM_EFFECT_SPEED_PCT: Final = "custom_effect_speed_pct" CONF_CUSTOM_EFFECT_TRANSITION: Final = "custom_effect_transition" + +FLUX_LED_DISCOVERY_SIGNAL = "flux_led_discovery_{entry_id}" diff --git a/homeassistant/components/flux_led/coordinator.py b/homeassistant/components/flux_led/coordinator.py index 5f2c3c097c0..5a7b3c89216 100644 --- a/homeassistant/components/flux_led/coordinator.py +++ b/homeassistant/components/flux_led/coordinator.py @@ -30,6 +30,7 @@ class FluxLedUpdateCoordinator(DataUpdateCoordinator): self.device = device self.title = entry.title self.entry = entry + self.force_next_update = False super().__init__( hass, _LOGGER, @@ -45,6 +46,8 @@ class FluxLedUpdateCoordinator(DataUpdateCoordinator): async def _async_update_data(self) -> None: """Fetch all device and sensor data from api.""" try: - await self.device.async_update() + await self.device.async_update(force=self.force_next_update) except FLUX_LED_EXCEPTIONS as ex: raise UpdateFailed(ex) from ex + finally: + self.force_next_update = False diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index e28b55869c7..7ccd708f89b 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.28"], + "requirements": ["flux_led==0.28.30"], "quality_scale": "platinum", "codeowners": ["@icemanch", "@bdraco"], "iot_class": "local_push", diff --git a/homeassistant/components/flux_led/translations/it.json b/homeassistant/components/flux_led/translations/it.json index 13b522906ba..f2d20bd45df 100644 --- a/homeassistant/components/flux_led/translations/it.json +++ b/homeassistant/components/flux_led/translations/it.json @@ -27,7 +27,7 @@ "data": { "custom_effect_colors": "Effetto personalizzato: Lista da 1 a 16 colori [R,G,B]. Esempio: [255,0,255],[60,128,0]", "custom_effect_speed_pct": "Effetto personalizzato: Velocit\u00e0 in percentuale per l'effetto che cambia colore.", - "custom_effect_transition": "Effetto personalizzato: Tipo di transizione tra i colori.", + "custom_effect_transition": "Effetto personalizzato: tipo di transizione tra i colori.", "mode": "La modalit\u00e0 di luminosit\u00e0 scelta." } } diff --git a/homeassistant/components/flux_led/translations/ja.json b/homeassistant/components/flux_led/translations/ja.json index 3b6a34d7e5b..248df586ee9 100644 --- a/homeassistant/components/flux_led/translations/ja.json +++ b/homeassistant/components/flux_led/translations/ja.json @@ -17,7 +17,7 @@ "data": { "host": "\u30db\u30b9\u30c8" }, - "description": "\u30db\u30b9\u30c8\u3092\u7a7a\u306b\u3057\u3066\u304a\u304f\u3068\u3001\u30c7\u30a3\u30b9\u30ab\u30d0\u30ea\u30fc\u3092\u4f7f\u3063\u3066\u30c7\u30d0\u30a4\u30b9\u3092\u691c\u7d22\u3057\u307e\u3059\u3002" + "description": "\u30db\u30b9\u30c8\u3092\u7a7a\u767d\u306b\u3057\u3066\u304a\u304f\u3068\u3001\u30c7\u30a3\u30b9\u30ab\u30d0\u30ea\u30fc\u3092\u4f7f\u3063\u3066\u30c7\u30d0\u30a4\u30b9\u3092\u691c\u7d22\u3057\u307e\u3059\u3002" } } }, diff --git a/homeassistant/components/flux_led/translations/nl.json b/homeassistant/components/flux_led/translations/nl.json index fd9e04bd475..2b64353e94c 100644 --- a/homeassistant/components/flux_led/translations/nl.json +++ b/homeassistant/components/flux_led/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "no_devices_found": "Geen apparaten gevonden op het netwerk" }, "error": { diff --git a/homeassistant/components/folder_watcher/manifest.json b/homeassistant/components/folder_watcher/manifest.json index 918492c82e9..f7562633ba0 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.7"], + "requirements": ["watchdog==2.1.8"], "codeowners": [], "quality_scale": "internal", "iot_class": "local_polling", diff --git a/homeassistant/components/forecast_solar/translations/es.json b/homeassistant/components/forecast_solar/translations/es.json index d688c577024..d82bd944202 100644 --- a/homeassistant/components/forecast_solar/translations/es.json +++ b/homeassistant/components/forecast_solar/translations/es.json @@ -22,6 +22,7 @@ "azimuth": "Azimut (360 grados, 0 = Norte, 90 = Este, 180 = Sur, 270 = Oeste)", "damping": "Factor de amortiguaci\u00f3n: ajusta los resultados por la ma\u00f1ana y por la noche", "declination": "Declinaci\u00f3n (0 = Horizontal, 90 = Vertical)", + "inverter_size": "Potencia del inversor (Watts)", "modules power": "Potencia pico total en vatios de tus m\u00f3dulos solares" }, "description": "Estos valores permiten ajustar el resultado de Solar.Forecast. Consulte la documentaci\u00f3n si un campo no est\u00e1 claro." diff --git a/homeassistant/components/forked_daapd/media_player.py b/homeassistant/components/forked_daapd/media_player.py index f2c64fa81da..25695dceeb5 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -666,7 +666,9 @@ class ForkedDaapdMaster(MediaPlayerEntity): """Play a URI.""" if media_source.is_media_source_id(media_id): media_type = MEDIA_TYPE_MUSIC - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = play_item.url if media_type == MEDIA_TYPE_MUSIC: diff --git a/homeassistant/components/forked_daapd/translations/es.json b/homeassistant/components/forked_daapd/translations/es.json index 7ec30e72b1a..54b1adb479f 100644 --- a/homeassistant/components/forked_daapd/translations/es.json +++ b/homeassistant/components/forked_daapd/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado.", + "already_configured": "El dispositivo ya est\u00e1 configurado", "not_forked_daapd": "El dispositivo no es un servidor forked-daapd." }, "error": { @@ -12,7 +12,7 @@ "wrong_password": "Contrase\u00f1a incorrecta.", "wrong_server_type": "La integraci\u00f3n forked-daapd requiere un servidor forked-daapd con versi\u00f3n >= 27.0." }, - "flow_title": "Servidor forked-daapd: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index 0d20545fdcd..70fc7b86a40 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -18,6 +18,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.storage import Store from homeassistant.util import slugify from .const import ( @@ -32,7 +33,7 @@ from .const import ( async def get_api(hass: HomeAssistant, host: str) -> Freepybox: """Get the Freebox API.""" - freebox_path = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY).path + freebox_path = Store(hass, STORAGE_VERSION, STORAGE_KEY).path if not os.path.exists(freebox_path): await hass.async_add_executor_job(os.makedirs, freebox_path) diff --git a/homeassistant/components/freebox/translations/ca.json b/homeassistant/components/freebox/translations/ca.json index 2568c3db0b4..e02ca372a3e 100644 --- a/homeassistant/components/freebox/translations/ca.json +++ b/homeassistant/components/freebox/translations/ca.json @@ -17,8 +17,7 @@ "data": { "host": "Amfitri\u00f3", "port": "Port" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/cs.json b/homeassistant/components/freebox/translations/cs.json index 4c11d4b442b..0bb07fc6535 100644 --- a/homeassistant/components/freebox/translations/cs.json +++ b/homeassistant/components/freebox/translations/cs.json @@ -17,8 +17,7 @@ "data": { "host": "Hostitel", "port": "Port" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/de.json b/homeassistant/components/freebox/translations/de.json index 50644a87982..864ce5f0c99 100644 --- a/homeassistant/components/freebox/translations/de.json +++ b/homeassistant/components/freebox/translations/de.json @@ -17,8 +17,7 @@ "data": { "host": "Host", "port": "Port" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/el.json b/homeassistant/components/freebox/translations/el.json index e881230014b..60c9f807765 100644 --- a/homeassistant/components/freebox/translations/el.json +++ b/homeassistant/components/freebox/translations/el.json @@ -17,8 +17,7 @@ "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" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/en.json b/homeassistant/components/freebox/translations/en.json index 539cfbcfe1e..f47fd810117 100644 --- a/homeassistant/components/freebox/translations/en.json +++ b/homeassistant/components/freebox/translations/en.json @@ -17,8 +17,7 @@ "data": { "host": "Host", "port": "Port" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/es-419.json b/homeassistant/components/freebox/translations/es-419.json index 1c99bc8b472..c013a730ae8 100644 --- a/homeassistant/components/freebox/translations/es-419.json +++ b/homeassistant/components/freebox/translations/es-419.json @@ -17,8 +17,7 @@ "data": { "host": "Host", "port": "Puerto" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/es.json b/homeassistant/components/freebox/translations/es.json index fb2da18c6c6..de176304d1f 100644 --- a/homeassistant/components/freebox/translations/es.json +++ b/homeassistant/components/freebox/translations/es.json @@ -4,9 +4,9 @@ "already_configured": "El dispositivo ya est\u00e1 configurado." }, "error": { - "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.", + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", "register_failed": "No se pudo registrar, int\u00e9ntalo de nuevo", - "unknown": "Error desconocido: por favor, int\u00e9ntalo de nuevo m\u00e1s" + "unknown": "Error inesperado" }, "step": { "link": { @@ -17,8 +17,7 @@ "data": { "host": "Host", "port": "Puerto" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/et.json b/homeassistant/components/freebox/translations/et.json index 4f0239350e3..88bd7260291 100644 --- a/homeassistant/components/freebox/translations/et.json +++ b/homeassistant/components/freebox/translations/et.json @@ -17,8 +17,7 @@ "data": { "host": "", "port": "" - }, - "title": "" + } } } } diff --git a/homeassistant/components/freebox/translations/fr.json b/homeassistant/components/freebox/translations/fr.json index 7b459ebc0ab..60289791408 100644 --- a/homeassistant/components/freebox/translations/fr.json +++ b/homeassistant/components/freebox/translations/fr.json @@ -17,8 +17,7 @@ "data": { "host": "H\u00f4te", "port": "Port" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/hu.json b/homeassistant/components/freebox/translations/hu.json index 39cfe189449..5f35297b538 100644 --- a/homeassistant/components/freebox/translations/hu.json +++ b/homeassistant/components/freebox/translations/hu.json @@ -17,8 +17,7 @@ "data": { "host": "C\u00edm", "port": "Port" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/id.json b/homeassistant/components/freebox/translations/id.json index b03ec248edb..85800e695dd 100644 --- a/homeassistant/components/freebox/translations/id.json +++ b/homeassistant/components/freebox/translations/id.json @@ -17,8 +17,7 @@ "data": { "host": "Host", "port": "Port" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/it.json b/homeassistant/components/freebox/translations/it.json index f1978217547..7bf36f69ff4 100644 --- a/homeassistant/components/freebox/translations/it.json +++ b/homeassistant/components/freebox/translations/it.json @@ -17,8 +17,7 @@ "data": { "host": "Host", "port": "Porta" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/ja.json b/homeassistant/components/freebox/translations/ja.json index fa11e1c4822..5034f2bcc72 100644 --- a/homeassistant/components/freebox/translations/ja.json +++ b/homeassistant/components/freebox/translations/ja.json @@ -17,8 +17,7 @@ "data": { "host": "\u30db\u30b9\u30c8", "port": "\u30dd\u30fc\u30c8" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/ko.json b/homeassistant/components/freebox/translations/ko.json index c3bb5a9bd40..ff8e6b95282 100644 --- a/homeassistant/components/freebox/translations/ko.json +++ b/homeassistant/components/freebox/translations/ko.json @@ -17,8 +17,7 @@ "data": { "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/lb.json b/homeassistant/components/freebox/translations/lb.json index e2fdaf18352..6d6155d4f68 100644 --- a/homeassistant/components/freebox/translations/lb.json +++ b/homeassistant/components/freebox/translations/lb.json @@ -17,8 +17,7 @@ "data": { "host": "Apparat", "port": "Port" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/nl.json b/homeassistant/components/freebox/translations/nl.json index 7fbd57dd6ff..2d3972a59af 100644 --- a/homeassistant/components/freebox/translations/nl.json +++ b/homeassistant/components/freebox/translations/nl.json @@ -17,8 +17,7 @@ "data": { "host": "Host", "port": "Poort" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/no.json b/homeassistant/components/freebox/translations/no.json index 69ef2af9c7f..7f157a5dc31 100644 --- a/homeassistant/components/freebox/translations/no.json +++ b/homeassistant/components/freebox/translations/no.json @@ -17,8 +17,7 @@ "data": { "host": "Vert", "port": "Port" - }, - "title": "" + } } } } diff --git a/homeassistant/components/freebox/translations/pl.json b/homeassistant/components/freebox/translations/pl.json index fe6aa517c88..c195a7f2a1f 100644 --- a/homeassistant/components/freebox/translations/pl.json +++ b/homeassistant/components/freebox/translations/pl.json @@ -17,8 +17,7 @@ "data": { "host": "Nazwa hosta lub adres IP", "port": "Port" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/pt-BR.json b/homeassistant/components/freebox/translations/pt-BR.json index 021ab5c902a..1e8e4b60315 100644 --- a/homeassistant/components/freebox/translations/pt-BR.json +++ b/homeassistant/components/freebox/translations/pt-BR.json @@ -17,8 +17,7 @@ "data": { "host": "Nome do host", "port": "Porta" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/ru.json b/homeassistant/components/freebox/translations/ru.json index 4ec5b68516c..c1012dbc743 100644 --- a/homeassistant/components/freebox/translations/ru.json +++ b/homeassistant/components/freebox/translations/ru.json @@ -17,8 +17,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/sl.json b/homeassistant/components/freebox/translations/sl.json index a161bfa494e..4dc79b32256 100644 --- a/homeassistant/components/freebox/translations/sl.json +++ b/homeassistant/components/freebox/translations/sl.json @@ -17,8 +17,7 @@ "data": { "host": "Gostitelj", "port": "Vrata" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/tr.json b/homeassistant/components/freebox/translations/tr.json index 6690b2a5b23..cfd42fbdba5 100644 --- a/homeassistant/components/freebox/translations/tr.json +++ b/homeassistant/components/freebox/translations/tr.json @@ -17,8 +17,7 @@ "data": { "host": "Sunucu", "port": "Port" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/uk.json b/homeassistant/components/freebox/translations/uk.json index 8676c9164a1..8492d3b3eff 100644 --- a/homeassistant/components/freebox/translations/uk.json +++ b/homeassistant/components/freebox/translations/uk.json @@ -17,8 +17,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freebox/translations/zh-Hant.json b/homeassistant/components/freebox/translations/zh-Hant.json index 6cf0a90f4c0..9f45cdb44d4 100644 --- a/homeassistant/components/freebox/translations/zh-Hant.json +++ b/homeassistant/components/freebox/translations/zh-Hant.json @@ -17,8 +17,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0" - }, - "title": "Freebox" + } } } } diff --git a/homeassistant/components/freedns/__init__.py b/homeassistant/components/freedns/__init__.py index bc651e726ec..a5c507c3857 100644 --- a/homeassistant/components/freedns/__init__.py +++ b/homeassistant/components/freedns/__init__.py @@ -11,6 +11,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN, CONF_SCAN_INTERVAL, CONF_URL from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -56,9 +57,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Update the FreeDNS entry.""" await _update_freedns(hass, session, url, auth_token) - hass.helpers.event.async_track_time_interval( - update_domain_callback, update_interval - ) + async_track_time_interval(hass, update_domain_callback, update_interval) return True diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index d7b8d916803..4a01367cc20 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -174,6 +174,7 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): self._current_firmware: str | None = None self._latest_firmware: str | None = None self._update_available: bool = False + self._release_url: str | None = None async def async_setup( self, options: MappingProxyType[str, Any] | None = None @@ -224,7 +225,11 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): self._model = info.get("NewModelName") self._current_firmware = info.get("NewSoftwareVersion") - self._update_available, self._latest_firmware = self._update_device_info() + ( + self._update_available, + self._latest_firmware, + self._release_url, + ) = self._update_device_info() if "Layer3Forwarding1" in self.connection.services: if connection_type := self.connection.call_action( "Layer3Forwarding1", "GetDefaultConnectionService" @@ -274,6 +279,11 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): """Return if new SW version is available.""" return self._update_available + @property + def release_url(self) -> str | None: + """Return the info URL for latest firmware.""" + return self._release_url + @property def mac(self) -> str: """Return device Mac address.""" @@ -305,12 +315,12 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): raise HomeAssistantError("Error refreshing hosts info") from ex return [] - def _update_device_info(self) -> tuple[bool, str | None]: + def _update_device_info(self) -> tuple[bool, str | None, str | None]: """Retrieve latest device information from the FRITZ!Box.""" - version = self.connection.call_action("UserInterface1", "GetInfo").get( - "NewX_AVM-DE_Version" - ) - return bool(version), version + info = self.connection.call_action("UserInterface1", "GetInfo") + version = info.get("NewX_AVM-DE_Version") + release_url = info.get("NewX_AVM-DE_InfoURL") + return bool(version), version, release_url def _get_wan_access(self, ip_address: str) -> bool | None: """Get WAN access rule for given IP address.""" @@ -361,7 +371,11 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): return _LOGGER.debug("Checking host info for FRITZ!Box device %s", self.host) - self._update_available, self._latest_firmware = self._update_device_info() + ( + self._update_available, + self._latest_firmware, + self._release_url, + ) = self._update_device_info() _LOGGER.debug("Checking devices for FRITZ!Box device %s", self.host) _default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds() diff --git a/homeassistant/components/fritz/translations/bg.json b/homeassistant/components/fritz/translations/bg.json index 3fca53d1013..43f9aaf5357 100644 --- a/homeassistant/components/fritz/translations/bg.json +++ b/homeassistant/components/fritz/translations/bg.json @@ -4,11 +4,6 @@ "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "step": { - "start_config": { - "data": { - "port": "\u041f\u043e\u0440\u0442" - } - }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442", diff --git a/homeassistant/components/fritz/translations/ca.json b/homeassistant/components/fritz/translations/ca.json index 04d5b14cac3..df78d9ec4b0 100644 --- a/homeassistant/components/fritz/translations/ca.json +++ b/homeassistant/components/fritz/translations/ca.json @@ -10,7 +10,6 @@ "already_configured": "El dispositiu ja est\u00e0 configurat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "cannot_connect": "Ha fallat la connexi\u00f3", - "connection_error": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "upnp_not_configured": "Falta la configuraci\u00f3 UPnP al dispositiu." }, @@ -32,16 +31,6 @@ "description": "Actualitza les credencials de FRITZ!Box Tools de: {host}.\n\nFRITZ!Box Tools no pot iniciar sessi\u00f3 a FRITZ!Box.", "title": "Actualitzant les credencials de FRITZ!Box Tools" }, - "start_config": { - "data": { - "host": "Amfitri\u00f3", - "password": "Contrasenya", - "port": "Port", - "username": "Nom d'usuari" - }, - "description": "Configura FRITZ!Box Tools per poder controlar FRITZ!Box.\nEl m\u00ednim necessari \u00e9s: nom d'usuari i contrasenya.", - "title": "Configuraci\u00f3 de FRITZ!Box Tools - obligatori" - }, "user": { "data": { "host": "Amfitri\u00f3", diff --git a/homeassistant/components/fritz/translations/cs.json b/homeassistant/components/fritz/translations/cs.json index 9afc3d85536..55326bb1803 100644 --- a/homeassistant/components/fritz/translations/cs.json +++ b/homeassistant/components/fritz/translations/cs.json @@ -8,7 +8,6 @@ "error": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", - "connection_error": "Nepoda\u0159ilo se p\u0159ipojit", "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" }, "step": { @@ -24,14 +23,6 @@ "username": "U\u017eivatelsk\u00e9 jm\u00e9no" } }, - "start_config": { - "data": { - "host": "Hostitel", - "password": "Heslo", - "port": "Port", - "username": "U\u017eivatelsk\u00e9 jm\u00e9no" - } - }, "user": { "data": { "port": "Port" diff --git a/homeassistant/components/fritz/translations/de.json b/homeassistant/components/fritz/translations/de.json index d64845ba9b7..16d2be68adb 100644 --- a/homeassistant/components/fritz/translations/de.json +++ b/homeassistant/components/fritz/translations/de.json @@ -10,7 +10,6 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "cannot_connect": "Verbindung fehlgeschlagen", - "connection_error": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "upnp_not_configured": "Fehlende UPnP-Einstellungen auf dem Ger\u00e4t." }, @@ -32,16 +31,6 @@ "description": "Aktualisiere die Anmeldeinformationen von FRITZ!Box Tools f\u00fcr: {host}. \n\nFRITZ!Box Tools kann sich nicht an deiner FRITZ!Box anmelden.", "title": "Aktualisieren der FRITZ!Box Tools - Anmeldeinformationen" }, - "start_config": { - "data": { - "host": "Host", - "password": "Passwort", - "port": "Port", - "username": "Benutzername" - }, - "description": "Einrichten der FRITZ!Box Tools zur Steuerung deiner FRITZ!Box.\nBen\u00f6tigt: Benutzername, Passwort.", - "title": "Setup FRITZ!Box Tools - obligatorisch" - }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritz/translations/el.json b/homeassistant/components/fritz/translations/el.json index 6dfc67e08d6..0cbaa10ef20 100644 --- a/homeassistant/components/fritz/translations/el.json +++ b/homeassistant/components/fritz/translations/el.json @@ -10,7 +10,6 @@ "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", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "connection_error": "\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", "upnp_not_configured": "\u039b\u03b5\u03af\u03c0\u03bf\u03c5\u03bd \u03bf\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 UPnP \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." }, @@ -32,16 +31,6 @@ "description": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c4\u03bf\u03c5 FRITZ!Box Tools \u03b3\u03b9\u03b1: {host} . \n\n \u03a4\u03bf FRITZ!Box Tools \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03bf FRITZ!Box \u03c3\u03b1\u03c2.", "title": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 FRITZ!Box Tools - \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1" }, - "start_config": { - "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" - }, - "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf FRITZ!Box Tools \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03c4\u03b5 \u03c4\u03bf FRITZ!Box \u03c3\u03b1\u03c2.\n \u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf: \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.", - "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 FRITZ!Box Tools - \u03c5\u03c0\u03bf\u03c7\u03c1\u03b5\u03c9\u03c4\u03b9\u03ba\u03cc" - }, "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/fritz/translations/en.json b/homeassistant/components/fritz/translations/en.json index 3ce31866c2f..e7ee1568684 100644 --- a/homeassistant/components/fritz/translations/en.json +++ b/homeassistant/components/fritz/translations/en.json @@ -10,7 +10,6 @@ "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." }, @@ -32,16 +31,6 @@ "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/es.json b/homeassistant/components/fritz/translations/es.json index 7decdcd01f5..964db5b5325 100644 --- a/homeassistant/components/fritz/translations/es.json +++ b/homeassistant/components/fritz/translations/es.json @@ -3,13 +3,13 @@ "abort": { "already_configured": "El dispositivo ya est\u00e1 configurado", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", + "ignore_ip6_link_local": "El enlace con direcciones IPv6 locales no est\u00e1 permitido", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { "already_configured": "El dispositivo ya est\u00e1 configurado", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "cannot_connect": "No se pudo conectar", - "connection_error": "No se pudo conectar", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" }, "flow_title": "FRITZ!Box Tools: {name}", @@ -30,16 +30,6 @@ "description": "Actualizar credenciales de FRITZ!Box Tools para: {host}.\n\n FRITZ!Box Tools no puede iniciar sesi\u00f3n en tu FRITZ!Box.", "title": "Actualizando FRITZ!Box Tools - credenciales" }, - "start_config": { - "data": { - "host": "Host", - "password": "Contrase\u00f1a", - "port": "Puerto", - "username": "Usuario" - }, - "description": "Configurar FRITZ!Box Tools para controlar tu FRITZ!Box.\nM\u00ednimo necesario: usuario, contrase\u00f1a.", - "title": "Configurar FRITZ!Box Tools - obligatorio" - }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritz/translations/et.json b/homeassistant/components/fritz/translations/et.json index 89bf4a3b7c9..ac2c9dbfe40 100644 --- a/homeassistant/components/fritz/translations/et.json +++ b/homeassistant/components/fritz/translations/et.json @@ -10,7 +10,6 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "already_in_progress": "Seadistamine on juba k\u00e4ivitatud", "cannot_connect": "\u00dchendamine nurjus", - "connection_error": "\u00dchendamine nurjus", "invalid_auth": "Tuvastamine nurjus", "upnp_not_configured": "Puuduvad seadme UPnP-seaded." }, @@ -32,16 +31,6 @@ "description": "V\u00e4rskenda FRITZ!Box Tools'i volitusi: {host}.\n\nFRITZ!Box Tools ei saa FRITZ!Boxi sisse logida.", "title": "FRITZ!Boxi t\u00f6\u00f6riistade uuendamine - volitused" }, - "start_config": { - "data": { - "host": "Host", - "password": "Salas\u00f5na", - "port": "Port", - "username": "Kasutajanimi" - }, - "description": "Seadista FRITZ!Boxi t\u00f6\u00f6riistad oma FRITZ!Boxi juhtimiseks.\n Minimaalselt vaja: kasutajanimi ja salas\u00f5na.", - "title": "FRITZ! Boxi t\u00f6\u00f6riistade seadistamine - kohustuslik" - }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritz/translations/fr.json b/homeassistant/components/fritz/translations/fr.json index cfcd942b530..538e542c4b6 100644 --- a/homeassistant/components/fritz/translations/fr.json +++ b/homeassistant/components/fritz/translations/fr.json @@ -10,7 +10,6 @@ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "cannot_connect": "\u00c9chec de connexion", - "connection_error": "\u00c9chec de connexion", "invalid_auth": "Authentification non valide", "upnp_not_configured": "Param\u00e8tres UPnP manquants sur l'appareil." }, @@ -32,16 +31,6 @@ "description": "Mettre \u00e0 jour les informations d'identification FRITZ!Box Tools pour : {host}.", "title": "Mise \u00e0 jour de FRITZ!Box Tools - informations d'identification" }, - "start_config": { - "data": { - "host": "H\u00f4te", - "password": "Mot de passe", - "port": "Port", - "username": "Nom d'utilisateur" - }, - "description": "Configuration de FRITZ!Box Tools pour contr\u00f4ler votre FRITZ!Box.\nMinimum requis: nom d'utilisateur, mot de passe.", - "title": "Configuration FRITZ!Box Tools - obligatoire" - }, "user": { "data": { "host": "H\u00f4te", diff --git a/homeassistant/components/fritz/translations/he.json b/homeassistant/components/fritz/translations/he.json index 783f215cc40..1b27fdb7581 100644 --- a/homeassistant/components/fritz/translations/he.json +++ b/homeassistant/components/fritz/translations/he.json @@ -9,7 +9,6 @@ "already_configured": "\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", "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", - "connection_error": "\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" }, "flow_title": "{name}", @@ -26,14 +25,6 @@ "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" } }, - "start_config": { - "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" - } - }, "user": { "data": { "host": "\u05de\u05d0\u05e8\u05d7", diff --git a/homeassistant/components/fritz/translations/hu.json b/homeassistant/components/fritz/translations/hu.json index d1ff2c0c6bf..71e6c0233ae 100644 --- a/homeassistant/components/fritz/translations/hu.json +++ b/homeassistant/components/fritz/translations/hu.json @@ -10,7 +10,6 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van", "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "connection_error": "Nem siker\u00fclt csatlakozni", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "upnp_not_configured": "Hi\u00e1nyz\u00f3 UPnP-be\u00e1ll\u00edt\u00e1sok az eszk\u00f6z\u00f6n." }, @@ -32,16 +31,6 @@ "description": "{host} FRITZ! Box Tools hiteles\u00edt\u0151 adatait. \n\n A FRITZ! Box Tools nem tud bejelentkezni a FRITZ! Box eszk\u00f6zbe.", "title": "A FRITZ! Box Tools friss\u00edt\u00e9se - hiteles\u00edt\u0151 adatok" }, - "start_config": { - "data": { - "host": "C\u00edm", - "password": "Jelsz\u00f3", - "port": "Port", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - }, - "description": "A FRITZ! Box eszk\u00f6z\u00f6k be\u00e1ll\u00edt\u00e1sa a FRITZ! Box vez\u00e9rl\u00e9s\u00e9hez.\n Minimum sz\u00fcks\u00e9ges: felhaszn\u00e1l\u00f3n\u00e9v, jelsz\u00f3.", - "title": "A FRITZ! Box Tools be\u00e1ll\u00edt\u00e1sa - k\u00f6telez\u0151" - }, "user": { "data": { "host": "C\u00edm", diff --git a/homeassistant/components/fritz/translations/id.json b/homeassistant/components/fritz/translations/id.json index c31bdf8b77c..71e1e3e8246 100644 --- a/homeassistant/components/fritz/translations/id.json +++ b/homeassistant/components/fritz/translations/id.json @@ -10,7 +10,6 @@ "already_configured": "Perangkat sudah dikonfigurasi", "already_in_progress": "Alur konfigurasi sedang berlangsung", "cannot_connect": "Gagal terhubung", - "connection_error": "Gagal terhubung", "invalid_auth": "Autentikasi tidak valid", "upnp_not_configured": "Pengaturan UPnP pada perangkat tidak ada." }, @@ -32,16 +31,6 @@ "description": "Perbarui kredensial FRITZ!Box Tools untuk: {host} . \n\nFRITZ!Box Tools tidak dapat masuk ke FRITZ!Box Anda.", "title": "Memperbarui FRITZ!Box Tools - kredensial" }, - "start_config": { - "data": { - "host": "Host", - "password": "Kata Sandi", - "port": "Port", - "username": "Nama Pengguna" - }, - "description": "Siapkan FRITZ!Box Tools untuk mengontrol FRITZ!Box Anda.\nDiperlukan minimal: nama pengguna dan kata sandi.", - "title": "Siapkan FRITZ!Box Tools - wajib" - }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritz/translations/it.json b/homeassistant/components/fritz/translations/it.json index 0516449f5db..b1fa200731e 100644 --- a/homeassistant/components/fritz/translations/it.json +++ b/homeassistant/components/fritz/translations/it.json @@ -10,7 +10,6 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "cannot_connect": "Impossibile connettersi", - "connection_error": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "upnp_not_configured": "Impostazioni UPnP mancanti sul dispositivo." }, @@ -32,16 +31,6 @@ "description": "Aggiorna le credenziali di FRITZ!Box Tools per: {host} . \n\n FRITZ!Box Tools non riesce ad accedere al tuo FRITZ! Box.", "title": "Aggiornamento degli strumenti del FRITZ!Box - credenziali" }, - "start_config": { - "data": { - "host": "Host", - "password": "Password", - "port": "Porta", - "username": "Nome utente" - }, - "description": "Configura gli strumenti FRITZ!Box per controllare il tuo FRITZ!Box.\n Minimo necessario: nome utente, password.", - "title": "Configurazione degli strumenti FRITZ!Box - obbligatorio" - }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritz/translations/ja.json b/homeassistant/components/fritz/translations/ja.json index 8bd9747c9bc..1b915b28577 100644 --- a/homeassistant/components/fritz/translations/ja.json +++ b/homeassistant/components/fritz/translations/ja.json @@ -10,7 +10,6 @@ "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", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", - "connection_error": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "upnp_not_configured": "\u30c7\u30d0\u30a4\u30b9\u306bUPnP\u306e\u8a2d\u5b9a\u304c\u3042\u308a\u307e\u305b\u3093\u3002" }, @@ -32,16 +31,6 @@ "description": "FRITZ!Box Tools\u306e\u8a8d\u8a3c\u3092\u66f4\u65b0\u3057\u307e\u3059: {host}\n\nFRITZ!Box Tools\u304c\u3001FRITZ!Box\u306b\u30ed\u30b0\u30a4\u30f3\u3067\u304d\u307e\u305b\u3093\u3002", "title": "FRITZ!Box Tools\u306e\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8 - \u8a8d\u8a3c\u60c5\u5831" }, - "start_config": { - "data": { - "host": "\u30db\u30b9\u30c8", - "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", - "port": "\u30dd\u30fc\u30c8", - "username": "\u30e6\u30fc\u30b6\u30fc\u540d" - }, - "description": "FRITZ!Box Tools\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u3066FRITZ!Box\u3092\u5236\u5fa1\u3057\u307e\u3059\u3002\n\u6700\u4f4e\u9650\u5fc5\u8981\u306a\u3082\u306e: \u30e6\u30fc\u30b6\u30fc\u540d\u3001\u30d1\u30b9\u30ef\u30fc\u30c9\u3002", - "title": "FRITZ!Box Tools\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7 - \u5fc5\u9808" - }, "user": { "data": { "host": "\u30db\u30b9\u30c8", diff --git a/homeassistant/components/fritz/translations/ko.json b/homeassistant/components/fritz/translations/ko.json index 718b105df33..314e8cf7729 100644 --- a/homeassistant/components/fritz/translations/ko.json +++ b/homeassistant/components/fritz/translations/ko.json @@ -3,12 +3,13 @@ "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "ignore_ip6_link_local": "IPv6 \ub9c1\ud06c \ub85c\uceec \uc8fc\uc18c\ub294 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", - "connection_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "flow_title": "{name}", @@ -17,7 +18,9 @@ "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "description": "\ubc1c\uacac\ub41c FRITZ!Box: {name} \n\n FRITZ!Box Tool\uc744 \uc124\uc815\ud558\uc5ec \ub2e4\uc74c\uc744 \uc81c\uc5b4\ud574\ubcf4\uc138\uc694: {name}", + "title": "FRITZ!Box Tool \uc124\uc815" }, "reauth_confirm": { "data": { @@ -25,12 +28,23 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } }, - "start_config": { + "user": { "data": { "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "FRITZ!Box Tool\uc744 \uc124\uc815\ud558\uc5ec FRITZ!Box\ub97c \uc81c\uc5b4\ud569\ub2c8\ub2e4.\n \ud544\uc218 \uc785\ub825\uc0ac\ud56d: \uc0ac\uc6a9\uc790 \uc774\ub984, \ube44\ubc00\ubc88\ud638.", + "title": "FRITZ!Box Tools \uc124\uc815" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "old_discovery": "\uc774\uc804 \uc7a5\uce58\uac80\uc0c9 \ubc29\ubc95 \uc0ac\uc6a9" } } } diff --git a/homeassistant/components/fritz/translations/nb.json b/homeassistant/components/fritz/translations/nb.json index 5e712fccbd9..a7e24e2ed11 100644 --- a/homeassistant/components/fritz/translations/nb.json +++ b/homeassistant/components/fritz/translations/nb.json @@ -11,11 +11,6 @@ "username": "Brukernavn" } }, - "start_config": { - "data": { - "username": "Brukernavn" - } - }, "user": { "data": { "username": "Brukernavn" diff --git a/homeassistant/components/fritz/translations/nl.json b/homeassistant/components/fritz/translations/nl.json index 11fabceaf8d..6bceac5dcc5 100644 --- a/homeassistant/components/fritz/translations/nl.json +++ b/homeassistant/components/fritz/translations/nl.json @@ -2,15 +2,14 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "ignore_ip6_link_local": "Lokaal IPv6-linkadres wordt niet ondersteund.", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken", - "connection_error": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "upnp_not_configured": "Ontbrekende UPnP instellingen op apparaat." }, @@ -32,16 +31,6 @@ "description": "Update FRITZ! Box Tools-inloggegevens voor: {host}. \n\n FRITZ! Box Tools kan niet inloggen op uw FRITZ!Box.", "title": "Updating FRITZ!Box Tools - referenties" }, - "start_config": { - "data": { - "host": "Host", - "password": "Wachtwoord", - "port": "Poort", - "username": "Gebruikersnaam" - }, - "description": "Stel FRITZ!Box Tools in om uw FRITZ!Box te bedienen.\nMinimaal nodig: gebruikersnaam, wachtwoord.", - "title": "Configureer FRITZ! Box Tools - verplicht" - }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritz/translations/no.json b/homeassistant/components/fritz/translations/no.json index a18df1a7b14..5ed97636675 100644 --- a/homeassistant/components/fritz/translations/no.json +++ b/homeassistant/components/fritz/translations/no.json @@ -10,7 +10,6 @@ "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "cannot_connect": "Tilkobling mislyktes", - "connection_error": "Tilkobling mislyktes", "invalid_auth": "Ugyldig godkjenning", "upnp_not_configured": "Mangler UPnP-innstillinger p\u00e5 enheten." }, @@ -32,16 +31,6 @@ "description": "Oppdater legitimasjonen til FRITZ!Box Tools for: {host} . \n\n FRITZ!Box Tools kan ikke logge p\u00e5 FRITZ! Box.", "title": "Oppdaterer FRITZ!Box verkt\u00f8y - legitimasjon" }, - "start_config": { - "data": { - "host": "Vert", - "password": "Passord", - "port": "Port", - "username": "Brukernavn" - }, - "description": "Sett opp FRITZ!Box verkt\u00f8y for \u00e5 kontrollere fritz! Boksen.\nMinimum n\u00f8dvendig: brukernavn, passord.", - "title": "Sett opp FRITZ!Box verkt\u00f8y - obligatorisk" - }, "user": { "data": { "host": "Vert", diff --git a/homeassistant/components/fritz/translations/pl.json b/homeassistant/components/fritz/translations/pl.json index fed010f1987..f14d41ed399 100644 --- a/homeassistant/components/fritz/translations/pl.json +++ b/homeassistant/components/fritz/translations/pl.json @@ -10,7 +10,6 @@ "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_auth": "Niepoprawne uwierzytelnienie", "upnp_not_configured": "Brak ustawie\u0144 UPnP w urz\u0105dzeniu." }, @@ -32,16 +31,6 @@ "description": "Zaktualizuj dane logowania narz\u0119dzi FRITZ!Box dla: {host} . \n\nNarz\u0119dzia FRITZ!Box nie mo\u017ce zalogowa\u0107 si\u0119 do urz\u0105dzenia FRITZ!Box.", "title": "Aktualizacja danych logowania narz\u0119dzi FRITZ!Box" }, - "start_config": { - "data": { - "host": "Nazwa hosta lub adres IP", - "password": "Has\u0142o", - "port": "Port", - "username": "Nazwa u\u017cytkownika" - }, - "description": "Skonfiguruj narz\u0119dzia FRITZ!Box, aby sterowa\u0107 urz\u0105dzeniem FRITZ! Box.\nMinimalne wymagania: nazwa u\u017cytkownika, has\u0142o.", - "title": "Konfiguracja narz\u0119dzi FRITZ!Box - obowi\u0105zkowe" - }, "user": { "data": { "host": "Nazwa hosta lub adres IP", diff --git a/homeassistant/components/fritz/translations/pt-BR.json b/homeassistant/components/fritz/translations/pt-BR.json index ceb295310c7..29bb04ebf3f 100644 --- a/homeassistant/components/fritz/translations/pt-BR.json +++ b/homeassistant/components/fritz/translations/pt-BR.json @@ -10,7 +10,6 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "cannot_connect": "Falha ao conectar", - "connection_error": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "upnp_not_configured": "Faltam configura\u00e7\u00f5es de UPnP no dispositivo." }, @@ -32,16 +31,6 @@ "description": "Atualize as credenciais do FRITZ!Box Tools para: {host} . \n\n O FRITZ!Box Tools n\u00e3o consegue iniciar sess\u00e3o no seu FRITZ!Box.", "title": "Atualizando as Ferramentas do FRITZ!Box - credenciais" }, - "start_config": { - "data": { - "host": "Nome do host", - "password": "Senha", - "port": "Porta", - "username": "Usu\u00e1rio" - }, - "description": "Configure as Ferramentas do FRITZ!Box para controlar o seu FRITZ!Box.\n M\u00ednimo necess\u00e1rio: nome de usu\u00e1rio, senha.", - "title": "Configurar as Ferramentas do FRITZ!Box - obrigat\u00f3rio" - }, "user": { "data": { "host": "Nome do host", diff --git a/homeassistant/components/fritz/translations/ru.json b/homeassistant/components/fritz/translations/ru.json index e45c36fe736..63d4463a055 100644 --- a/homeassistant/components/fritz/translations/ru.json +++ b/homeassistant/components/fritz/translations/ru.json @@ -10,7 +10,6 @@ "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.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\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.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "upnp_not_configured": "\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 UPnP \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435." }, @@ -32,16 +31,6 @@ "description": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 FRITZ!Box Tools \u0434\u043b\u044f {host}.\n\n\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e FRITZ!Box Tools \u043d\u0430 \u0412\u0430\u0448\u0435\u043c FRITZ!Box.", "title": "FRITZ!Box Tools" }, - "start_config": { - "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" - }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 FRITZ!Box Tools \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0412\u0430\u0448\u0438\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c FRITZ!Box.\n\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u0430\u043a \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", - "title": "FRITZ!Box Tools" - }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442", diff --git a/homeassistant/components/fritz/translations/sk.json b/homeassistant/components/fritz/translations/sk.json index 9d83bc4b756..17cb7bfc78b 100644 --- a/homeassistant/components/fritz/translations/sk.json +++ b/homeassistant/components/fritz/translations/sk.json @@ -9,11 +9,6 @@ "invalid_auth": "Neplatn\u00e9 overenie" }, "step": { - "start_config": { - "data": { - "port": "Port" - } - }, "user": { "data": { "port": "Port" diff --git a/homeassistant/components/fritz/translations/tr.json b/homeassistant/components/fritz/translations/tr.json index 86cabbb782b..af443cac976 100644 --- a/homeassistant/components/fritz/translations/tr.json +++ b/homeassistant/components/fritz/translations/tr.json @@ -10,7 +10,6 @@ "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "cannot_connect": "Ba\u011flanma hatas\u0131", - "connection_error": "Ba\u011flanma hatas\u0131", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "upnp_not_configured": "Cihazda UPnP ayarlar\u0131 eksik." }, @@ -32,16 +31,6 @@ "description": "{host} i\u00e7in FRITZ!Box Tools kimlik bilgilerini g\u00fcncelleyin. \n\n FRITZ!Box Tools, FRITZ!Box'\u0131n\u0131zda oturum a\u00e7am\u0131yor.", "title": "FRITZ!Box Tools - kimlik bilgilerinin g\u00fcncellenmesi" }, - "start_config": { - "data": { - "host": "Sunucu", - "password": "Parola", - "port": "Port", - "username": "Kullan\u0131c\u0131 Ad\u0131" - }, - "description": "FRITZ!Box'\u0131n\u0131z\u0131 kontrol etmek i\u00e7in FRITZ!Box Tools'u kurun.\n Minimum gerekli: kullan\u0131c\u0131 ad\u0131, \u015fifre.", - "title": "FRITZ!Box Tools Kurulumu - zorunlu" - }, "user": { "data": { "host": "Sunucu", diff --git a/homeassistant/components/fritz/translations/zh-Hans.json b/homeassistant/components/fritz/translations/zh-Hans.json index 91d68989675..3f5a3f2afa0 100644 --- a/homeassistant/components/fritz/translations/zh-Hans.json +++ b/homeassistant/components/fritz/translations/zh-Hans.json @@ -11,11 +11,6 @@ "password": "\u5bc6\u7801" } }, - "start_config": { - "data": { - "password": "\u5bc6\u7801" - } - }, "user": { "data": { "password": "\u5bc6\u7801" diff --git a/homeassistant/components/fritz/translations/zh-Hant.json b/homeassistant/components/fritz/translations/zh-Hant.json index 778c38e36f7..0565a1e292a 100644 --- a/homeassistant/components/fritz/translations/zh-Hant.json +++ b/homeassistant/components/fritz/translations/zh-Hant.json @@ -10,7 +10,6 @@ "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "cannot_connect": "\u9023\u7dda\u5931\u6557", - "connection_error": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "upnp_not_configured": "\u672a\u8a2d\u5b9a\u88dd\u7f6e UPnP \u8a2d\u5b9a\u3002" }, @@ -32,16 +31,6 @@ "description": "\u66f4\u65b0 FRITZ!Box Tools \u6191\u8b49\uff1a{host}\u3002\n\nFRITZ!Box Tools \u7121\u6cd5\u767b\u5165 FRITZ!Box\u3002", "title": "\u66f4\u65b0 FRITZ!Box Tools - \u6191\u8b49" }, - "start_config": { - "data": { - "host": "\u4e3b\u6a5f\u7aef", - "password": "\u5bc6\u78bc", - "port": "\u901a\u8a0a\u57e0", - "username": "\u4f7f\u7528\u8005\u540d\u7a31" - }, - "description": "\u8a2d\u5b9a FRITZ!Box Tools \u4ee5\u63a7\u5236 FRITZ!Box\u3002\n\u9700\u8981\u8f38\u5165\uff1a\u4f7f\u7528\u8005\u540d\u7a31\u3001\u5bc6\u78bc\u3002", - "title": "\u8a2d\u5b9a FRITZ!Box Tools - \u5f37\u5236" - }, "user": { "data": { "host": "\u4e3b\u6a5f\u7aef", diff --git a/homeassistant/components/fritz/update.py b/homeassistant/components/fritz/update.py index 620b932999e..03cffc3cae6 100644 --- a/homeassistant/components/fritz/update.py +++ b/homeassistant/components/fritz/update.py @@ -55,6 +55,11 @@ class FritzBoxUpdateEntity(FritzBoxBaseEntity, UpdateEntity): return self._avm_wrapper.latest_firmware return self._avm_wrapper.current_firmware + @property + def release_url(self) -> str | None: + """URL to the full release notes of the latest version available.""" + return self._avm_wrapper.release_url + async def async_install( self, version: str | None, backup: bool, **kwargs: Any ) -> None: diff --git a/homeassistant/components/fritzbox/light.py b/homeassistant/components/fritzbox/light.py index 65ed96dd603..e01fb76ec11 100644 --- a/homeassistant/components/fritzbox/light.py +++ b/homeassistant/components/fritzbox/light.py @@ -3,6 +3,8 @@ from __future__ import annotations from typing import Any +from requests.exceptions import HTTPError + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, @@ -21,6 +23,7 @@ from .const import ( COLOR_TEMP_MODE, CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN, + LOGGER, ) from .coordinator import FritzboxDataUpdateCoordinator @@ -118,7 +121,14 @@ class FritzboxLight(FritzBoxEntity, LightEntity): return color.color_temperature_kelvin_to_mired(kelvin) @property - def supported_color_modes(self) -> set: + def color_mode(self) -> ColorMode: + """Return the color mode of the light.""" + if self.device.color_mode == COLOR_MODE: + return ColorMode.HS + return ColorMode.COLOR_TEMP + + @property + def supported_color_modes(self) -> set[ColorMode]: """Flag supported color modes.""" return SUPPORTED_COLOR_MODES @@ -128,16 +138,33 @@ class FritzboxLight(FritzBoxEntity, LightEntity): level = kwargs[ATTR_BRIGHTNESS] await self.hass.async_add_executor_job(self.device.set_level, level) if kwargs.get(ATTR_HS_COLOR) is not None: - hass_hue = int(kwargs[ATTR_HS_COLOR][0]) - hass_saturation = round(kwargs[ATTR_HS_COLOR][1] * 255.0 / 100.0) - # find supported hs values closest to what user selected - hue = min(self._supported_hs.keys(), key=lambda x: abs(x - hass_hue)) - saturation = min( - self._supported_hs[hue], key=lambda x: abs(x - hass_saturation) - ) - await self.hass.async_add_executor_job( - self.device.set_color, (hue, saturation) - ) + # Try setunmappedcolor first. This allows free color selection, + # but we don't know if its supported by all devices. + try: + # HA gives 0..360 for hue, fritz light only supports 0..359 + unmapped_hue = int(kwargs[ATTR_HS_COLOR][0] % 360) + unmapped_saturation = round(kwargs[ATTR_HS_COLOR][1] * 255.0 / 100.0) + await self.hass.async_add_executor_job( + self.device.set_unmapped_color, (unmapped_hue, unmapped_saturation) + ) + # This will raise 400 BAD REQUEST if the setunmappedcolor is not available + except HTTPError as err: + if err.response.status_code != 400: + raise + LOGGER.debug( + "fritzbox does not support method 'setunmappedcolor', fallback to 'setcolor'" + ) + # find supported hs values closest to what user selected + hue = min( + self._supported_hs.keys(), key=lambda x: abs(x - unmapped_hue) + ) + saturation = min( + self._supported_hs[hue], + key=lambda x: abs(x - unmapped_saturation), + ) + await self.hass.async_add_executor_job( + self.device.set_color, (hue, saturation) + ) if kwargs.get(ATTR_COLOR_TEMP) is not None: kelvin = color.color_temperature_kelvin_to_mired(kwargs[ATTR_COLOR_TEMP]) diff --git a/homeassistant/components/fritzbox/translations/es.json b/homeassistant/components/fritzbox/translations/es.json index fcb240deb77..77c8bb64ee5 100644 --- a/homeassistant/components/fritzbox/translations/es.json +++ b/homeassistant/components/fritzbox/translations/es.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Este AVM FRITZ!Box ya est\u00e1 configurado.", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", + "ignore_ip6_link_local": "El enlace con direcciones IPv6 locales no est\u00e1 permitido", "no_devices_found": "No se encontraron dispositivos en la red", "not_supported": "Conectado a AVM FRITZ!Box pero no es capaz de controlar dispositivos Smart Home.", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index 43d51df760d..524cf40f018 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -2,11 +2,11 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "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" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie" diff --git a/homeassistant/components/fritzbox_callmonitor/translations/es.json b/homeassistant/components/fritzbox_callmonitor/translations/es.json index d6891db5ef9..61e295d3b99 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/es.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/es.json @@ -8,7 +8,7 @@ "error": { "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" }, - "flow_title": "Monitor de llamadas de AVM FRITZ! Box: {name}", + "flow_title": "{name}", "step": { "phonebook": { "data": { diff --git a/homeassistant/components/fronius/__init__.py b/homeassistant/components/fronius/__init__.py index 03340f19081..f6607aed11f 100644 --- a/homeassistant/components/fronius/__init__.py +++ b/homeassistant/components/fronius/__init__.py @@ -161,7 +161,7 @@ class FroniusSolarNet: "value" ] - device_registry = await dr.async_get_registry(self.hass) + device_registry = dr.async_get(self.hass) device_registry.async_get_or_create( config_entry_id=self.config_entry.entry_id, **solar_net_device, diff --git a/homeassistant/components/fronius/translations/ko.json b/homeassistant/components/fronius/translations/ko.json new file mode 100644 index 00000000000..5e3238d1be1 --- /dev/null +++ b/homeassistant/components/fronius/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm_discovery": { + "description": "{device} \ub97c \ud648\uc5b4\uc2dc\uc2a4\ud134\ud2b8\uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 0c540a477ef..c1deb02fc6a 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -22,6 +22,7 @@ from homeassistant.const import CONF_MODE, CONF_NAME, EVENT_THEMES_UPDATED from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.helpers import service import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.storage import Store from homeassistant.helpers.translation import async_get_translations from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_integration, bind_hass @@ -389,11 +390,12 @@ async def _async_setup_themes( """Set up themes data and services.""" hass.data[DATA_THEMES] = themes or {} - store = hass.data[DATA_THEMES_STORE] = hass.helpers.storage.Store( - THEMES_STORAGE_VERSION, THEMES_STORAGE_KEY + store = hass.data[DATA_THEMES_STORE] = Store( + hass, THEMES_STORAGE_VERSION, THEMES_STORAGE_KEY ) - theme_data = await store.async_load() or {} + if not (theme_data := await store.async_load()) or not isinstance(theme_data, dict): + theme_data = {} theme_name = theme_data.get(DATA_DEFAULT_THEME, DEFAULT_THEME) dark_theme_name = theme_data.get(DATA_DEFAULT_DARK_THEME) @@ -667,7 +669,7 @@ def websocket_get_themes( "type": "frontend/get_translations", vol.Required("language"): str, vol.Required("category"): str, - vol.Optional("integration"): str, + vol.Optional("integration"): vol.All(cv.ensure_list, [str]), vol.Optional("config_flow"): bool, } ) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 8c475d51abb..d9e80b4eff8 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==20220504.1"], + "requirements": ["home-assistant-frontend==20220531.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/components/frontend/storage.py b/homeassistant/components/frontend/storage.py index e5100ca3ab2..bfda566de55 100644 --- a/homeassistant/components/frontend/storage.py +++ b/homeassistant/components/frontend/storage.py @@ -35,8 +35,10 @@ def with_store(orig_func: Callable) -> Callable: user_id = connection.user.id if (store := stores.get(user_id)) is None: - store = stores[user_id] = hass.helpers.storage.Store( - STORAGE_VERSION_USER_DATA, f"frontend.user_data_{connection.user.id}" + store = stores[user_id] = Store( + hass, + STORAGE_VERSION_USER_DATA, + f"frontend.user_data_{connection.user.id}", ) if user_id not in data: diff --git a/homeassistant/components/garages_amsterdam/translations/ko.json b/homeassistant/components/garages_amsterdam/translations/ko.json new file mode 100644 index 00000000000..f5088fca3e1 --- /dev/null +++ b/homeassistant/components/garages_amsterdam/translations/ko.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/geo_location.py b/homeassistant/components/gdacs/geo_location.py index 2b6ffd556a9..5bfbd915b6c 100644 --- a/homeassistant/components/gdacs/geo_location.py +++ b/homeassistant/components/gdacs/geo_location.py @@ -12,9 +12,9 @@ from homeassistant.const import ( LENGTH_MILES, ) 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 homeassistant.helpers.entity_registry import async_get_registry from homeassistant.util.unit_system import IMPERIAL_SYSTEM from .const import DEFAULT_ICON, DOMAIN, FEED @@ -114,7 +114,7 @@ class GdacsEvent(GeolocationEvent): self._remove_signal_delete() self._remove_signal_update() # Remove from entity registry. - entity_registry = await async_get_registry(self.hass) + entity_registry = er.async_get(self.hass) if self.entity_id in entity_registry.entities: entity_registry.async_remove(self.entity_id) diff --git a/homeassistant/components/gdacs/translations/nl.json b/homeassistant/components/gdacs/translations/nl.json index 6dd3e5aa196..d46f8dc0a50 100644 --- a/homeassistant/components/gdacs/translations/nl.json +++ b/homeassistant/components/gdacs/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index 197890efad3..edc51430f0d 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -12,6 +12,11 @@ from homeassistant.components.camera import ( Camera, CameraEntityFeature, ) +from homeassistant.components.stream import ( + CONF_RTSP_TRANSPORT, + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, + RTSP_TRANSPORTS, +) from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_AUTHENTICATION, @@ -34,14 +39,10 @@ from .const import ( CONF_CONTENT_TYPE, CONF_FRAMERATE, CONF_LIMIT_REFETCH_TO_URL_CHANGE, - CONF_RTSP_TRANSPORT, CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE, - CONF_USE_WALLCLOCK_AS_TIMESTAMPS, DEFAULT_NAME, - FFMPEG_OPTION_MAP, GET_IMAGE_TIMEOUT, - RTSP_TRANSPORTS, ) _LOGGER = logging.getLogger(__name__) @@ -63,7 +64,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( cv.small_float, cv.positive_int ), vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, - vol.Optional(CONF_RTSP_TRANSPORT): vol.In(RTSP_TRANSPORTS.keys()), + vol.Optional(CONF_RTSP_TRANSPORT): vol.In(RTSP_TRANSPORTS), } ) @@ -157,14 +158,10 @@ class GenericCamera(Camera): self.content_type = device_info[CONF_CONTENT_TYPE] self.verify_ssl = device_info[CONF_VERIFY_SSL] if device_info.get(CONF_RTSP_TRANSPORT): - self.stream_options[FFMPEG_OPTION_MAP[CONF_RTSP_TRANSPORT]] = device_info[ - CONF_RTSP_TRANSPORT - ] + self.stream_options[CONF_RTSP_TRANSPORT] = device_info[CONF_RTSP_TRANSPORT] self._auth = generate_auth(device_info) if device_info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS): - self.stream_options[ - FFMPEG_OPTION_MAP[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] - ] = "1" + self.stream_options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] = True self._last_url = None self._last_image = None diff --git a/homeassistant/components/generic/config_flow.py b/homeassistant/components/generic/config_flow.py index 0a49393d9cc..272c7a2d98e 100644 --- a/homeassistant/components/generic/config_flow.py +++ b/homeassistant/components/generic/config_flow.py @@ -16,7 +16,13 @@ from httpx import HTTPStatusError, RequestError, TimeoutException import voluptuous as vol import yarl -from homeassistant.components.stream.const import SOURCE_TIMEOUT +from homeassistant.components.stream import ( + CONF_RTSP_TRANSPORT, + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, + RTSP_TRANSPORTS, + SOURCE_TIMEOUT, + convert_stream_options, +) from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import ( CONF_AUTHENTICATION, @@ -38,15 +44,11 @@ from .const import ( CONF_CONTENT_TYPE, CONF_FRAMERATE, CONF_LIMIT_REFETCH_TO_URL_CHANGE, - CONF_RTSP_TRANSPORT, CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE, - CONF_USE_WALLCLOCK_AS_TIMESTAMPS, DEFAULT_NAME, DOMAIN, - FFMPEG_OPTION_MAP, GET_IMAGE_TIMEOUT, - RTSP_TRANSPORTS, ) _LOGGER = logging.getLogger(__name__) @@ -196,26 +198,35 @@ async def async_test_stream(hass, info) -> dict[str, str]: """Verify that the stream is valid before we create an entity.""" if not (stream_source := info.get(CONF_STREAM_SOURCE)): return {} + if not isinstance(stream_source, template_helper.Template): + stream_source = template_helper.Template(stream_source, hass) + try: + stream_source = stream_source.async_render(parse_result=False) + except TemplateError as err: + _LOGGER.warning("Problem rendering template %s: %s", stream_source, err) + return {CONF_STREAM_SOURCE: "template_error"} try: # For RTSP streams, prefer TCP. This code is duplicated from # homeassistant.components.stream.__init__.py:create_stream() # It may be possible & better to call create_stream() directly. - stream_options: dict[str, str] = {} + stream_options: dict[str, bool | str] = {} + if rtsp_transport := info.get(CONF_RTSP_TRANSPORT): + stream_options[CONF_RTSP_TRANSPORT] = rtsp_transport + if info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS): + stream_options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] = True + pyav_options = convert_stream_options(stream_options) if isinstance(stream_source, str) and stream_source[:7] == "rtsp://": - stream_options = { + pyav_options = { "rtsp_flags": "prefer_tcp", "stimeout": "5000000", + **pyav_options, } - if rtsp_transport := info.get(CONF_RTSP_TRANSPORT): - stream_options[FFMPEG_OPTION_MAP[CONF_RTSP_TRANSPORT]] = rtsp_transport - if info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS): - stream_options[FFMPEG_OPTION_MAP[CONF_USE_WALLCLOCK_AS_TIMESTAMPS]] = "1" _LOGGER.debug("Attempting to open stream %s", stream_source) container = await hass.async_add_executor_job( partial( av.open, stream_source, - options=stream_options, + options=pyav_options, timeout=SOURCE_TIMEOUT, ) ) @@ -368,7 +379,10 @@ class GenericOptionsFlowHandler(OptionsFlow): CONF_FRAMERATE: user_input[CONF_FRAMERATE], CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL], CONF_USE_WALLCLOCK_AS_TIMESTAMPS: user_input.get( - CONF_USE_WALLCLOCK_AS_TIMESTAMPS + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, + self.config_entry.options.get( + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, False + ), ), } return self.async_create_entry( diff --git a/homeassistant/components/generic/const.py b/homeassistant/components/generic/const.py index 8ae5f16c4c4..eb0d81d493c 100644 --- a/homeassistant/components/generic/const.py +++ b/homeassistant/components/generic/const.py @@ -7,18 +7,6 @@ CONF_LIMIT_REFETCH_TO_URL_CHANGE = "limit_refetch_to_url_change" CONF_STILL_IMAGE_URL = "still_image_url" CONF_STREAM_SOURCE = "stream_source" CONF_FRAMERATE = "framerate" -CONF_RTSP_TRANSPORT = "rtsp_transport" -CONF_USE_WALLCLOCK_AS_TIMESTAMPS = "use_wallclock_as_timestamps" -FFMPEG_OPTION_MAP = { - CONF_RTSP_TRANSPORT: "rtsp_transport", - CONF_USE_WALLCLOCK_AS_TIMESTAMPS: "use_wallclock_as_timestamps", -} -RTSP_TRANSPORTS = { - "tcp": "TCP", - "udp": "UDP", - "udp_multicast": "UDP Multicast", - "http": "HTTP", -} GET_IMAGE_TIMEOUT = 10 DEFAULT_USERNAME = None diff --git a/homeassistant/components/generic/diagnostics.py b/homeassistant/components/generic/diagnostics.py new file mode 100644 index 00000000000..00be287f053 --- /dev/null +++ b/homeassistant/components/generic/diagnostics.py @@ -0,0 +1,49 @@ +"""Diagnostics support for generic (IP camera).""" +from __future__ import annotations + +from typing import Any + +import yarl + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from .const import CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE + +TO_REDACT = { + CONF_PASSWORD, + CONF_USERNAME, +} + + +# A very similar redact function is in components.sql. Possible to be made common. +def redact_url(data: str) -> str: + """Redact credentials from string url.""" + url_in = yarl.URL(data) + if url_in.user: + url = url_in.with_user("****") + if url_in.password: + url = url.with_password("****") + if url_in.path: + url = url.with_path("****") + if url_in.query_string: + url = url.with_query("****=****") + return str(url) + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + options = async_redact_data(entry.options, TO_REDACT) + for key in (CONF_STREAM_SOURCE, CONF_STILL_IMAGE_URL): + if (value := options.get(key)) is not None: + options[key] = redact_url(value) + + return { + "title": entry.title, + "data": async_redact_data(entry.data, TO_REDACT), + "options": options, + } diff --git a/homeassistant/components/generic/manifest.json b/homeassistant/components/generic/manifest.json index ae14c3c4d03..c590ddfffcd 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": ["av==9.2.0", "pillow==9.1.0"], + "requirements": ["av==9.2.0", "pillow==9.1.1"], "documentation": "https://www.home-assistant.io/integrations/generic", "codeowners": ["@davet2001"], "iot_class": "local_push" diff --git a/homeassistant/components/generic/strings.json b/homeassistant/components/generic/strings.json index 0954656f71d..6b73c70cf3d 100644 --- a/homeassistant/components/generic/strings.json +++ b/homeassistant/components/generic/strings.json @@ -8,6 +8,7 @@ "invalid_still_image": "URL did not return a valid still image", "stream_file_not_found": "File not found while trying to connect to stream (is ffmpeg installed?)", "stream_http_not_found": "HTTP 404 Not found while trying to connect to stream", + "template_error": "Error rendering template. Review log for more info.", "timeout": "Timeout while loading URL", "stream_no_route_to_host": "Could not find host while trying to connect to stream", "stream_io_error": "Input/Output error while trying to connect to stream. Wrong RTSP transport protocol?", @@ -79,6 +80,7 @@ "invalid_still_image": "[%key:component::generic::config::error::invalid_still_image%]", "stream_file_not_found": "[%key:component::generic::config::error::stream_file_not_found%]", "stream_http_not_found": "[%key:component::generic::config::error::stream_http_not_found%]", + "template_error": "[%key:component::generic::config::error::template_error%]", "timeout": "[%key:component::generic::config::error::timeout%]", "stream_no_route_to_host": "[%key:component::generic::config::error::stream_no_route_to_host%]", "stream_io_error": "[%key:component::generic::config::error::stream_io_error%]", diff --git a/homeassistant/components/generic/translations/bg.json b/homeassistant/components/generic/translations/bg.json index dc9bd439f0b..ebb2d32c21d 100644 --- a/homeassistant/components/generic/translations/bg.json +++ b/homeassistant/components/generic/translations/bg.json @@ -20,7 +20,6 @@ "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" @@ -42,7 +41,6 @@ "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" diff --git a/homeassistant/components/generic/translations/ca.json b/homeassistant/components/generic/translations/ca.json index 3c2f5055ba5..3f2cd6afa47 100644 --- a/homeassistant/components/generic/translations/ca.json +++ b/homeassistant/components/generic/translations/ca.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "Utilitza el rellotge de paret com a marca de temps", "username": "Nom d'usuari", "verify_ssl": "Verifica el certificat SSL" + }, + "data_description": { + "use_wallclock_as_timestamps": "Aquesta opci\u00f3 pot corregir problemes de segmentaci\u00f3 o bloqueig en algunes c\u00e0meres derivats d'implementacions de marca de temps amb errors" } } } diff --git a/homeassistant/components/generic/translations/de.json b/homeassistant/components/generic/translations/de.json index 849555dcc5e..d4c5c268eed 100644 --- a/homeassistant/components/generic/translations/de.json +++ b/homeassistant/components/generic/translations/de.json @@ -32,7 +32,6 @@ "user": { "data": { "authentication": "Authentifizierung", - "content_type": "Inhaltstyp", "framerate": "Bildfrequenz (Hz)", "limit_refetch_to_url_change": "Neuabruf auf URL-\u00c4nderung beschr\u00e4nken", "password": "Passwort", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "Wanduhr als Zeitstempel verwenden", "username": "Benutzername", "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" + }, + "data_description": { + "use_wallclock_as_timestamps": "Diese Option kann Segmentierungs- oder Absturzprobleme beheben, die durch fehlerhafte Zeitstempel-Implementierungen auf einigen Kameras entstehen" } } } diff --git a/homeassistant/components/generic/translations/el.json b/homeassistant/components/generic/translations/el.json index f3fae37f5ca..be1c963740a 100644 --- a/homeassistant/components/generic/translations/el.json +++ b/homeassistant/components/generic/translations/el.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03c1\u03bf\u03bb\u03bf\u03b3\u03b9\u03bf\u03cd \u03c4\u03bf\u03af\u03c7\u03bf\u03c5 \u03c9\u03c2 \u03c7\u03c1\u03bf\u03bd\u03bf\u03c3\u03c6\u03c1\u03b1\u03b3\u03af\u03b4\u03b5\u03c2", "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" + }, + "data_description": { + "use_wallclock_as_timestamps": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03bf\u03c1\u03b8\u03ce\u03c3\u03b5\u03b9 \u03b6\u03b7\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1 \u03c4\u03bc\u03b7\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 \u03ae \u03c3\u03c6\u03b1\u03bb\u03bc\u03ac\u03c4\u03c9\u03bd \u03c0\u03bf\u03c5 \u03c0\u03c1\u03bf\u03ba\u03cd\u03c0\u03c4\u03bf\u03c5\u03bd \u03b1\u03c0\u03cc \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ad\u03c2 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03ce\u03bd \u03c3\u03c6\u03c1\u03b1\u03b3\u03af\u03b4\u03c9\u03bd \u03bc\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1\u03c4\u03b1 \u03c3\u03b5 \u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b5\u03c2" } } } diff --git a/homeassistant/components/generic/translations/en.json b/homeassistant/components/generic/translations/en.json index b552c780d29..d01e6e59a4b 100644 --- a/homeassistant/components/generic/translations/en.json +++ b/homeassistant/components/generic/translations/en.json @@ -15,6 +15,7 @@ "stream_no_video": "Stream has no video", "stream_not_permitted": "Operation not permitted while trying to connect to stream. Wrong RTSP transport protocol?", "stream_unauthorised": "Authorisation failed while trying to connect to stream", + "template_error": "Error rendering template. Review log for more info.", "timeout": "Timeout while loading URL", "unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.", "unknown": "Unexpected error" @@ -57,6 +58,7 @@ "stream_no_video": "Stream has no video", "stream_not_permitted": "Operation not permitted while trying to connect to stream. Wrong RTSP transport protocol?", "stream_unauthorised": "Authorisation failed while trying to connect to stream", + "template_error": "Error rendering template. Review log for more info.", "timeout": "Timeout while loading URL", "unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.", "unknown": "Unexpected error" diff --git a/homeassistant/components/generic/translations/es.json b/homeassistant/components/generic/translations/es.json new file mode 100644 index 00000000000..4d8019002bb --- /dev/null +++ b/homeassistant/components/generic/translations/es.json @@ -0,0 +1,66 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." + }, + "error": { + "already_exists": "Ya hay una c\u00e1mara con esa URL de configuraci\u00f3n.", + "invalid_still_image": "La URL no ha devuelto una imagen fija v\u00e1lida", + "no_still_image_or_stream_url": "Tienes que especificar al menos una imagen una URL de flujo", + "stream_http_not_found": "HTTP 404 'Not found' al intentar conectarse al flujo de datos ('stream')", + "stream_no_route_to_host": "No se pudo encontrar el anfitri\u00f3n mientras intentaba conectar al flujo de datos", + "stream_no_video": "El flujo no contiene v\u00eddeo", + "stream_unauthorised": "La autorizaci\u00f3n ha fallado mientras se intentaba conectar con el flujo de datos", + "timeout": "El tiempo m\u00e1ximo de carga de la URL ha expirado", + "unknown": "Error inesperado" + }, + "step": { + "content_type": { + "data": { + "content_type": "Tipos de contenido" + }, + "description": "Especifique el tipo de contenido para el flujo de datos (stream)." + }, + "user": { + "data": { + "authentication": "Autenticaci\u00f3n", + "framerate": "Frecuencia de visualizaci\u00f3n (Hz)", + "limit_refetch_to_url_change": "Limita la lectura al cambio de URL", + "still_image_url": "URL de imagen fija (ej. http://...)", + "stream_source": "URL origen del flux (p. ex. rtsp://...)", + "username": "Usuario", + "verify_ssl": "Verifica el certificat SSL" + } + } + } + }, + "options": { + "error": { + "already_exists": "Ya hay una c\u00e1mara con esa URL de configuraci\u00f3n.", + "stream_no_route_to_host": "No se pudo encontrar el anfitri\u00f3n mientras intentaba conectar al flujo de datos", + "stream_unauthorised": "La autorizaci\u00f3n ha fallado mientras se intentaba conectar con el flujo de datos", + "timeout": "El tiempo m\u00e1ximo de carga de la URL ha expirado", + "unknown": "Error inesperado" + }, + "step": { + "content_type": { + "data": { + "content_type": "Tipos de contenido" + }, + "description": "Especifique el tipo de contenido para el flujo de datos (stream)." + }, + "init": { + "data": { + "authentication": "Autenticaci\u00f3n", + "framerate": "Frecuencia de visualizaci\u00f3n (Hz)", + "limit_refetch_to_url_change": "Limita la lectura al cambio de URL", + "password": "Contrase\u00f1a", + "rtsp_transport": "Protocolo de transporte RTSP", + "still_image_url": "URL de imagen fija (ej. http://...)", + "username": "Usuario", + "verify_ssl": "Verifica el certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/et.json b/homeassistant/components/generic/translations/et.json index ae6f5aa75f8..41e1881573e 100644 --- a/homeassistant/components/generic/translations/et.json +++ b/homeassistant/components/generic/translations/et.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "Wallclocki kasutamine ajatemplitena", "username": "Kasutajanimi", "verify_ssl": "Kontrolli SSL sertifikaati" + }, + "data_description": { + "use_wallclock_as_timestamps": "See suvand v\u00f5ib parandada segmentimis- v\u00f5i krahhiprobleeme, mis tulenevad m\u00f5ne kaamera vigastest ajatemplirakendustest" } } } diff --git a/homeassistant/components/generic/translations/fr.json b/homeassistant/components/generic/translations/fr.json index 4905afb64e7..6215c579be7 100644 --- a/homeassistant/components/generic/translations/fr.json +++ b/homeassistant/components/generic/translations/fr.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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)", + "use_wallclock_as_timestamps": "Utiliser l'horloge murale comme horodatage", "username": "Nom d'utilisateur", "verify_ssl": "V\u00e9rifier le certificat SSL" + }, + "data_description": { + "use_wallclock_as_timestamps": "Cette option peut corriger des probl\u00e8mes de segmentation ou de plantage dus \u00e0 des impl\u00e9mentations d'horodatage d\u00e9fectueuses sur certaines cam\u00e9ras" } } } diff --git a/homeassistant/components/generic/translations/he.json b/homeassistant/components/generic/translations/he.json index b20b0ea88a7..f39f78074f5 100644 --- a/homeassistant/components/generic/translations/he.json +++ b/homeassistant/components/generic/translations/he.json @@ -32,7 +32,6 @@ "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", @@ -72,7 +71,6 @@ "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", diff --git a/homeassistant/components/generic/translations/hu.json b/homeassistant/components/generic/translations/hu.json index 4e97e594d1e..59840b3195b 100644 --- a/homeassistant/components/generic/translations/hu.json +++ b/homeassistant/components/generic/translations/hu.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "Wallclock haszn\u00e1lata id\u0151b\u00e9lyegz\u0151k\u00e9nt", "username": "Felhaszn\u00e1l\u00f3n\u00e9v", "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + }, + "data_description": { + "use_wallclock_as_timestamps": "Ez az opci\u00f3 korrig\u00e1lhatja az egyes kamer\u00e1k hib\u00e1s id\u0151b\u00e9lyegz\u0151 implement\u00e1ci\u00f3j\u00e1b\u00f3l ered\u0151 szegment\u00e1l\u00e1si vagy \u00f6sszeoml\u00e1si probl\u00e9m\u00e1kat." } } } diff --git a/homeassistant/components/generic/translations/id.json b/homeassistant/components/generic/translations/id.json index 4b3106ad1df..95c6d6e2ae2 100644 --- a/homeassistant/components/generic/translations/id.json +++ b/homeassistant/components/generic/translations/id.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "Gunakan jam dinding sebagai stempel waktu", "username": "Nama Pengguna", "verify_ssl": "Verifikasi sertifikat SSL" + }, + "data_description": { + "use_wallclock_as_timestamps": "Opsi ini dapat memperbaiki masalah segmentasi atau mogok yang timbul dari implementasi stempel waktu bermasalah pada beberapa kamera" } } } diff --git a/homeassistant/components/generic/translations/it.json b/homeassistant/components/generic/translations/it.json index 880fcff500b..ff4b9822601 100644 --- a/homeassistant/components/generic/translations/it.json +++ b/homeassistant/components/generic/translations/it.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "Usa wallclock come marca temporale", "username": "Nome utente", "verify_ssl": "Verifica il certificato SSL" + }, + "data_description": { + "use_wallclock_as_timestamps": "Questa opzione pu\u00f2 correggere problemi di segmentazione o di arresto anomalo derivanti da implementazioni difettose di marche temporali su alcune telecamere" } } } diff --git a/homeassistant/components/generic/translations/ja.json b/homeassistant/components/generic/translations/ja.json index 916142125e6..a106cbf4cdc 100644 --- a/homeassistant/components/generic/translations/ja.json +++ b/homeassistant/components/generic/translations/ja.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "\u30bf\u30a4\u30e0\u30b9\u30bf\u30f3\u30d7\u306bwallclock\u3092\u4f7f\u7528", "username": "\u30e6\u30fc\u30b6\u30fc\u540d", "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" + }, + "data_description": { + "use_wallclock_as_timestamps": "\u3053\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u306f\u3001\u4e00\u90e8\u306e\u30ab\u30e1\u30e9\u306e\u30bf\u30a4\u30e0\u30b9\u30bf\u30f3\u30d7\u5b9f\u88c5\u306e\u30d0\u30b0\u304b\u3089\u767a\u751f\u3059\u308b\u30bb\u30b0\u30e1\u30f3\u30c6\u30fc\u30b7\u30e7\u30f3\u3084\u30af\u30e9\u30c3\u30b7\u30e5\u306e\u554f\u984c\u3092\u4fee\u6b63\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059" } } } diff --git a/homeassistant/components/generic/translations/ko.json b/homeassistant/components/generic/translations/ko.json new file mode 100644 index 00000000000..20ad990e862 --- /dev/null +++ b/homeassistant/components/generic/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/nl.json b/homeassistant/components/generic/translations/nl.json index 167374c7aeb..354f944148f 100644 --- a/homeassistant/components/generic/translations/nl.json +++ b/homeassistant/components/generic/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "already_exists": "Een camera met deze URL instellingen bestaat al.", @@ -21,7 +21,7 @@ }, "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" }, "content_type": { "data": { @@ -32,7 +32,6 @@ "user": { "data": { "authentication": "Authenticatie", - "content_type": "Inhoudstype", "framerate": "Framesnelheid (Hz)", "limit_refetch_to_url_change": "Beperk refetch tot url verandering", "password": "Wachtwoord", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "Gebruik de wandklok als tijdstempel", "username": "Gebruikersnaam", "verify_ssl": "SSL-certificaat verifi\u00ebren" + }, + "data_description": { + "use_wallclock_as_timestamps": "Deze optie kan problemen met segmenteren of crashen verhelpen die het gevolg zijn van buggy timestamp implementaties op sommige camera's" } } } diff --git a/homeassistant/components/generic/translations/no.json b/homeassistant/components/generic/translations/no.json index 1f9eedaced4..0c228e314d2 100644 --- a/homeassistant/components/generic/translations/no.json +++ b/homeassistant/components/generic/translations/no.json @@ -32,7 +32,6 @@ "user": { "data": { "authentication": "Godkjenning", - "content_type": "Innholdstype", "framerate": "Bildefrekvens (Hz)", "limit_refetch_to_url_change": "Begrens gjenhenting til endring av nettadresse", "password": "Passord", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "Bruk veggklokke som tidsstempler", "username": "Brukernavn", "verify_ssl": "Verifisere SSL-sertifikat" + }, + "data_description": { + "use_wallclock_as_timestamps": "Dette alternativet kan korrigere segmenterings- eller krasjproblemer som oppst\u00e5r fra buggy-tidsstempelimplementeringer p\u00e5 enkelte kameraer" } } } diff --git a/homeassistant/components/generic/translations/pl.json b/homeassistant/components/generic/translations/pl.json index a791a56c065..3669fd78e3f 100644 --- a/homeassistant/components/generic/translations/pl.json +++ b/homeassistant/components/generic/translations/pl.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "U\u017cyj wallclock jako znacznika czasu", "username": "Nazwa u\u017cytkownika", "verify_ssl": "Weryfikacja certyfikatu SSL" + }, + "data_description": { + "use_wallclock_as_timestamps": "Ta opcja mo\u017ce rozwi\u0105za\u0107 problemy z segmentacj\u0105 lub awariami wynikaj\u0105ce z wadliwych implementacji znacznik\u00f3w czasu w niekt\u00f3rych kamerach." } } } diff --git a/homeassistant/components/generic/translations/pt-BR.json b/homeassistant/components/generic/translations/pt-BR.json index 0e3ce2bd75d..ea1ede92f22 100644 --- a/homeassistant/components/generic/translations/pt-BR.json +++ b/homeassistant/components/generic/translations/pt-BR.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "Use o rel\u00f3gio de parede como data/hora", "username": "Usu\u00e1rio", "verify_ssl": "Verifique o certificado SSL" + }, + "data_description": { + "use_wallclock_as_timestamps": "Esta op\u00e7\u00e3o pode corrigir problemas de segmenta\u00e7\u00e3o ou travamento decorrentes de implementa\u00e7\u00f5es de data/hora com erros em algumas c\u00e2meras" } } } diff --git a/homeassistant/components/generic/translations/ru.json b/homeassistant/components/generic/translations/ru.json index 06f41613a50..f9fed6b5831 100644 --- a/homeassistant/components/generic/translations/ru.json +++ b/homeassistant/components/generic/translations/ru.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0435\u043d\u043d\u044b\u0435 \u0447\u0430\u0441\u044b \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u043a", "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" + }, + "data_description": { + "use_wallclock_as_timestamps": "\u042d\u0442\u043e\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0441\u0435\u0433\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439 \u0438\u043b\u0438 \u0441\u0431\u043e\u0435\u043c, \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0449\u0438\u0435 \u0438\u0437-\u0437\u0430 \u043e\u0448\u0438\u0431\u043e\u0447\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u043a \u043d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u0430\u043c\u0435\u0440\u0430\u0445." } } } diff --git a/homeassistant/components/generic/translations/sv.json b/homeassistant/components/generic/translations/sv.json new file mode 100644 index 00000000000..78033d17c6e --- /dev/null +++ b/homeassistant/components/generic/translations/sv.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "no_devices_found": "Inga enheter hittades i n\u00e4tverket" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/tr.json b/homeassistant/components/generic/translations/tr.json index c2fb343f227..7b6ab65f948 100644 --- a/homeassistant/components/generic/translations/tr.json +++ b/homeassistant/components/generic/translations/tr.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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://...)", + "use_wallclock_as_timestamps": "Wallclock'u zaman damgas\u0131 olarak kullan\u0131n", "username": "Kullan\u0131c\u0131 Ad\u0131", "verify_ssl": "SSL sertifikas\u0131n\u0131 do\u011frulay\u0131n" + }, + "data_description": { + "use_wallclock_as_timestamps": "Bu se\u00e7enek, baz\u0131 kameralarda hatal\u0131 zaman damgas\u0131 uygulamalar\u0131ndan kaynaklanan segmentlere ay\u0131rma veya kilitlenme sorunlar\u0131n\u0131 d\u00fczeltebilir" } } } diff --git a/homeassistant/components/generic/translations/zh-Hans.json b/homeassistant/components/generic/translations/zh-Hans.json new file mode 100644 index 00000000000..f13ba39c5b8 --- /dev/null +++ b/homeassistant/components/generic/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data_description": { + "use_wallclock_as_timestamps": "\u6b64\u9009\u9879\u53ef\u80fd\u4f1a\u7ea0\u6b63\u7531\u4e8e\u67d0\u4e9b\u6444\u50cf\u5934\u4e0a\u7684\u9519\u8bef\u65f6\u95f4\u6233\u5f15\u8d77\u7684\u5206\u6bb5\u6216\u5d29\u6e83\u95ee\u9898" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/zh-Hant.json b/homeassistant/components/generic/translations/zh-Hant.json index 1b72dcb0ce4..d1079724252 100644 --- a/homeassistant/components/generic/translations/zh-Hant.json +++ b/homeassistant/components/generic/translations/zh-Hant.json @@ -32,7 +32,6 @@ "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", @@ -72,15 +71,18 @@ "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", + "use_wallclock_as_timestamps": "\u4f7f\u7528\u639b\u9418\u4f5c\u70ba\u6642\u9593\u6233", "username": "\u4f7f\u7528\u8005\u540d\u7a31", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" + }, + "data_description": { + "use_wallclock_as_timestamps": "\u6b64\u9078\u9805\u53ef\u80fd\u4fee\u6b63\u67d0\u4fee\u93e1\u982d\u4e0a\u932f\u8aa4\u7684\u6642\u9593\u6233\u5c0e\u81f4\u7684\u5206\u6bb5\u6216\u7576\u6a5f\u554f\u984c" } } } diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index 947dd325064..3c5cc22af81 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -21,7 +21,7 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant, ServiceCall, callback -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import ( @@ -146,7 +146,7 @@ def setup_service_functions(hass: HomeAssistant, broker): """Set the system mode.""" entity_id = call.data[ATTR_ENTITY_ID] - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) registry_entry = registry.async_get(entity_id) if registry_entry is None or registry_entry.platform != DOMAIN: diff --git a/homeassistant/components/geo_location/trigger.py b/homeassistant/components/geo_location/trigger.py index e57c7a9aec6..9f2b56a31c6 100644 --- a/homeassistant/components/geo_location/trigger.py +++ b/homeassistant/components/geo_location/trigger.py @@ -3,11 +3,16 @@ import logging import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import CONF_EVENT, CONF_PLATFORM, CONF_SOURCE, CONF_ZONE -from homeassistant.core import HassJob, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import condition, config_validation as cv from homeassistant.helpers.config_validation import entity_domain from homeassistant.helpers.event import TrackStates, async_track_state_change_filtered +from homeassistant.helpers.typing import ConfigType from . import DOMAIN @@ -36,10 +41,15 @@ def source_match(state, source): return state and state.attributes.get("source") == source -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" trigger_data = automation_info["trigger_data"] - source = config.get(CONF_SOURCE).lower() + source: str = config[CONF_SOURCE].lower() zone_entity_id = config.get(CONF_ZONE) trigger_event = config.get(CONF_EVENT) job = HassJob(action) diff --git a/homeassistant/components/geocaching/__init__.py b/homeassistant/components/geocaching/__init__.py new file mode 100644 index 00000000000..430cbc9a8d0 --- /dev/null +++ b/homeassistant/components/geocaching/__init__.py @@ -0,0 +1,39 @@ +"""The Geocaching integration.""" + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.config_entry_oauth2_flow import ( + OAuth2Session, + async_get_config_entry_implementation, +) + +from .const import DOMAIN +from .coordinator import GeocachingDataUpdateCoordinator + +PLATFORMS = [Platform.SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Geocaching from a config entry.""" + implementation = await async_get_config_entry_implementation(hass, entry) + + oauth_session = OAuth2Session(hass, entry, implementation) + coordinator = GeocachingDataUpdateCoordinator( + hass, entry=entry, session=oauth_session + ) + + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {}) + 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): + del hass.data[DOMAIN][entry.entry_id] + return unload_ok diff --git a/homeassistant/components/geocaching/application_credentials.py b/homeassistant/components/geocaching/application_credentials.py new file mode 100644 index 00000000000..e3d35f57a81 --- /dev/null +++ b/homeassistant/components/geocaching/application_credentials.py @@ -0,0 +1,14 @@ +"""application_credentials platform for Geocaching.""" + +from homeassistant.components.application_credentials import ClientCredential +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow + +from .oauth import GeocachingOAuth2Implementation + + +async def async_get_auth_implementation( + hass: HomeAssistant, auth_domain: str, credential: ClientCredential +) -> config_entry_oauth2_flow.AbstractOAuth2Implementation: + """Return auth implementation.""" + return GeocachingOAuth2Implementation(hass, auth_domain, credential) diff --git a/homeassistant/components/geocaching/config_flow.py b/homeassistant/components/geocaching/config_flow.py new file mode 100644 index 00000000000..83c9ed17586 --- /dev/null +++ b/homeassistant/components/geocaching/config_flow.py @@ -0,0 +1,56 @@ +"""Config flow for Geocaching.""" +from __future__ import annotations + +import logging +from typing import Any + +from geocachingapi.geocachingapi import GeocachingApi + +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler + +from .const import DOMAIN, ENVIRONMENT + + +class GeocachingFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): + """Config flow to handle Geocaching OAuth2 authentication.""" + + DOMAIN = DOMAIN + VERSION = 1 + + @property + def logger(self) -> logging.Logger: + """Return logger.""" + return logging.getLogger(__name__) + + async def async_step_reauth(self, user_input: dict[str, Any]) -> FlowResult: + """Perform reauth upon an API authentication error.""" + return await self.async_step_reauth_confirm(user_input=user_input) + + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Dialog that informs the user that reauth is required.""" + if user_input is None: + return self.async_show_form(step_id="reauth_confirm") + return await self.async_step_user() + + async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + """Create an oauth config entry or update existing entry for reauth.""" + api = GeocachingApi( + environment=ENVIRONMENT, + token=data["token"]["access_token"], + session=async_get_clientsession(self.hass), + ) + status = await api.update() + if not status.user or not status.user.username: + return self.async_abort(reason="oauth_error") + + if existing_entry := await self.async_set_unique_id( + status.user.username.lower() + ): + 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=status.user.username, data=data) diff --git a/homeassistant/components/geocaching/const.py b/homeassistant/components/geocaching/const.py new file mode 100644 index 00000000000..13b42b318c0 --- /dev/null +++ b/homeassistant/components/geocaching/const.py @@ -0,0 +1,27 @@ +"""Constants for the Geocaching integration.""" +from __future__ import annotations + +from datetime import timedelta +import logging +from typing import Final + +from geocachingapi.models import GeocachingApiEnvironment + +from .models import GeocachingOAuthApiUrls + +DOMAIN: Final = "geocaching" +LOGGER = logging.getLogger(__package__) +UPDATE_INTERVAL = timedelta(hours=1) + +ENVIRONMENT_URLS = { + GeocachingApiEnvironment.Staging: GeocachingOAuthApiUrls( + authorize_url="https://staging.geocaching.com/oauth/authorize.aspx", + token_url="https://oauth-staging.geocaching.com/token", + ), + GeocachingApiEnvironment.Production: GeocachingOAuthApiUrls( + authorize_url="https://www.geocaching.com/oauth/authorize.aspx", + token_url="https://oauth.geocaching.com/token", + ), +} + +ENVIRONMENT = GeocachingApiEnvironment.Production diff --git a/homeassistant/components/geocaching/coordinator.py b/homeassistant/components/geocaching/coordinator.py new file mode 100644 index 00000000000..f02cccf544b --- /dev/null +++ b/homeassistant/components/geocaching/coordinator.py @@ -0,0 +1,47 @@ +"""Provides the Geocaching DataUpdateCoordinator.""" +from __future__ import annotations + +from geocachingapi.exceptions import GeocachingApiError +from geocachingapi.geocachingapi import GeocachingApi +from geocachingapi.models import GeocachingStatus + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN, ENVIRONMENT, LOGGER, UPDATE_INTERVAL + + +class GeocachingDataUpdateCoordinator(DataUpdateCoordinator[GeocachingStatus]): + """Class to manage fetching Geocaching data from single endpoint.""" + + def __init__( + self, hass: HomeAssistant, *, entry: ConfigEntry, session: OAuth2Session + ) -> None: + """Initialize global Geocaching data updater.""" + self.session = session + self.entry = entry + + async def async_token_refresh() -> str: + await session.async_ensure_token_valid() + token = session.token["access_token"] + LOGGER.debug(str(token)) + return str(token) + + client_session = async_get_clientsession(hass) + self.geocaching = GeocachingApi( + environment=ENVIRONMENT, + token=session.token["access_token"], + session=client_session, + token_refresh_method=async_token_refresh, + ) + + super().__init__(hass, LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) + + async def _async_update_data(self) -> GeocachingStatus: + try: + return await self.geocaching.update() + except GeocachingApiError as error: + raise UpdateFailed(f"Invalid response from API: {error}") from error diff --git a/homeassistant/components/geocaching/manifest.json b/homeassistant/components/geocaching/manifest.json new file mode 100644 index 00000000000..59c3da45cf5 --- /dev/null +++ b/homeassistant/components/geocaching/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "geocaching", + "name": "Geocaching", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/geocaching", + "requirements": ["geocachingapi==0.2.1"], + "dependencies": ["application_credentials"], + "codeowners": ["@Sholofly", "@reinder83"], + "iot_class": "cloud_polling" +} diff --git a/homeassistant/components/geocaching/models.py b/homeassistant/components/geocaching/models.py new file mode 100644 index 00000000000..60ee4e05978 --- /dev/null +++ b/homeassistant/components/geocaching/models.py @@ -0,0 +1,9 @@ +"""Models for the Geocaching integration.""" +from typing import TypedDict + + +class GeocachingOAuthApiUrls(TypedDict): + """oAuth2 urls for a single environment.""" + + authorize_url: str + token_url: str diff --git a/homeassistant/components/geocaching/oauth.py b/homeassistant/components/geocaching/oauth.py new file mode 100644 index 00000000000..e0120344cdb --- /dev/null +++ b/homeassistant/components/geocaching/oauth.py @@ -0,0 +1,77 @@ +"""oAuth2 functions and classes for Geocaching API integration.""" +from __future__ import annotations + +from typing import Any, cast + +from homeassistant.components.application_credentials import ( + AuthImplementation, + AuthorizationServer, + ClientCredential, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import ENVIRONMENT, ENVIRONMENT_URLS + + +class GeocachingOAuth2Implementation(AuthImplementation): + """Local OAuth2 implementation for Geocaching.""" + + def __init__( + self, + hass: HomeAssistant, + auth_domain: str, + credential: ClientCredential, + ) -> None: + """Local Geocaching Oauth Implementation.""" + super().__init__( + hass=hass, + auth_domain=auth_domain, + credential=credential, + authorization_server=AuthorizationServer( + authorize_url=ENVIRONMENT_URLS[ENVIRONMENT]["authorize_url"], + token_url=ENVIRONMENT_URLS[ENVIRONMENT]["token_url"], + ), + ) + + @property + def extra_authorize_data(self) -> dict: + """Extra data that needs to be appended to the authorize url.""" + return {"scope": "*", "response_type": "code"} + + async def async_resolve_external_data(self, external_data: Any) -> dict: + """Initialize local Geocaching API auth implementation.""" + redirect_uri = external_data["state"]["redirect_uri"] + data = { + "grant_type": "authorization_code", + "code": external_data["code"], + "redirect_uri": redirect_uri, + } + token = await self._token_request(data) + # Store the redirect_uri (Needed for refreshing token, but not according to oAuth2 spec!) + token["redirect_uri"] = redirect_uri + return token + + async def _async_refresh_token(self, token: dict) -> dict: + """Refresh tokens.""" + data = { + "client_id": self.client_id, + "client_secret": self.client_secret, + "grant_type": "refresh_token", + "refresh_token": token["refresh_token"], + # Add previously stored redirect_uri (Mandatory, but not according to oAuth2 spec!) + "redirect_uri": token["redirect_uri"], + } + + new_token = await self._token_request(data) + return {**token, **new_token} + + async def _token_request(self, data: dict) -> dict: + """Make a token request.""" + data["client_id"] = self.client_id + if self.client_secret is not None: + data["client_secret"] = self.client_secret + session = async_get_clientsession(self.hass) + resp = await session.post(ENVIRONMENT_URLS[ENVIRONMENT]["token_url"], data=data) + resp.raise_for_status() + return cast(dict, await resp.json()) diff --git a/homeassistant/components/geocaching/sensor.py b/homeassistant/components/geocaching/sensor.py new file mode 100644 index 00000000000..0c719c463a4 --- /dev/null +++ b/homeassistant/components/geocaching/sensor.py @@ -0,0 +1,119 @@ +"""Platform for sensor integration.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import cast + +from geocachingapi.models import GeocachingStatus + +from homeassistant.components.sensor import SensorEntity, SensorEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .coordinator import GeocachingDataUpdateCoordinator + + +@dataclass +class GeocachingRequiredKeysMixin: + """Mixin for required keys.""" + + value_fn: Callable[[GeocachingStatus], str | int | None] + + +@dataclass +class GeocachingSensorEntityDescription( + SensorEntityDescription, GeocachingRequiredKeysMixin +): + """Define Sensor entity description class.""" + + +SENSORS: tuple[GeocachingSensorEntityDescription, ...] = ( + GeocachingSensorEntityDescription( + key="find_count", + name="Total finds", + icon="mdi:notebook-edit-outline", + native_unit_of_measurement="caches", + value_fn=lambda status: status.user.find_count, + ), + GeocachingSensorEntityDescription( + key="hide_count", + name="Total hides", + icon="mdi:eye-off-outline", + native_unit_of_measurement="caches", + entity_registry_visible_default=False, + value_fn=lambda status: status.user.hide_count, + ), + GeocachingSensorEntityDescription( + key="favorite_points", + name="Favorite points", + icon="mdi:heart-outline", + native_unit_of_measurement="points", + entity_registry_visible_default=False, + value_fn=lambda status: status.user.favorite_points, + ), + GeocachingSensorEntityDescription( + key="souvenir_count", + name="Total souvenirs", + icon="mdi:license", + native_unit_of_measurement="souvenirs", + value_fn=lambda status: status.user.souvenir_count, + ), + GeocachingSensorEntityDescription( + key="awarded_favorite_points", + name="Awarded favorite points", + icon="mdi:heart", + native_unit_of_measurement="points", + entity_registry_visible_default=False, + value_fn=lambda status: status.user.awarded_favorite_points, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up a Geocaching sensor entry.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + GeocachingSensor(coordinator, description) for description in SENSORS + ) + + +class GeocachingSensor( + CoordinatorEntity[GeocachingDataUpdateCoordinator], SensorEntity +): + """Representation of a Sensor.""" + + entity_description: GeocachingSensorEntityDescription + + def __init__( + self, + coordinator: GeocachingDataUpdateCoordinator, + description: GeocachingSensorEntityDescription, + ) -> None: + """Initialize the Geocaching sensor.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_name = ( + f"Geocaching {coordinator.data.user.username} {description.name}" + ) + self._attr_unique_id = ( + f"{coordinator.data.user.reference_code}_{description.key}" + ) + self._attr_device_info = DeviceInfo( + name=f"Geocaching {coordinator.data.user.username}", + identifiers={(DOMAIN, cast(str, coordinator.data.user.reference_code))}, + entry_type=DeviceEntryType.SERVICE, + manufacturer="Groundspeak, Inc.", + ) + + @property + def native_value(self) -> str | int | None: + """Return the state of the sensor.""" + return self.entity_description.value_fn(self.coordinator.data) diff --git a/homeassistant/components/geocaching/strings.json b/homeassistant/components/geocaching/strings.json new file mode 100644 index 00000000000..7c8547805d1 --- /dev/null +++ b/homeassistant/components/geocaching/strings.json @@ -0,0 +1,25 @@ +{ + "config": { + "step": { + "pick_implementation": { + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" + }, + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "The Geocaching integration needs to re-authenticate your account" + } + }, + "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%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + }, + "create_entry": { + "default": "[%key:common::config_flow::create_entry::authenticated%]" + } + } +} diff --git a/homeassistant/components/geocaching/translations/bg.json b/homeassistant/components/geocaching/translations/bg.json new file mode 100644 index 00000000000..d3ca579dff7 --- /dev/null +++ b/homeassistant/components/geocaching/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": { + "pick_implementation": { + "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u043c\u0435\u0442\u043e\u0434 \u0437\u0430 \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" + }, + "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/geocaching/translations/ca.json b/homeassistant/components/geocaching/translations/ca.json new file mode 100644 index 00000000000..09e241d8f2a --- /dev/null +++ b/homeassistant/components/geocaching/translations/ca.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" + }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" + }, + "reauth_confirm": { + "description": "La integraci\u00f3 Geocaching 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/geocaching/translations/de.json b/homeassistant/components/geocaching/translations/de.json new file mode 100644 index 00000000000..1712153fbce --- /dev/null +++ b/homeassistant/components/geocaching/translations/de.json @@ -0,0 +1,25 @@ +{ + "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": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "oauth_error": "Ung\u00fcltige Token-Daten empfangen.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "create_entry": { + "default": "Erfolgreich authentifiziert" + }, + "step": { + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" + }, + "reauth_confirm": { + "description": "Die Geocaching-Integration muss dein Konto erneut authentifizieren", + "title": "Integration erneut authentifizieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/el.json b/homeassistant/components/geocaching/translations/el.json new file mode 100644 index 00000000000..e07b121ceee --- /dev/null +++ b/homeassistant/components/geocaching/translations/el.json @@ -0,0 +1,25 @@ +{ + "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.", + "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" + }, + "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" + }, + "reauth_confirm": { + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Geocaching \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \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" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/en.json b/homeassistant/components/geocaching/translations/en.json new file mode 100644 index 00000000000..7f76bce4f3d --- /dev/null +++ b/homeassistant/components/geocaching/translations/en.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Re-authentication was successful" + }, + "create_entry": { + "default": "Successfully authenticated" + }, + "step": { + "pick_implementation": { + "title": "Pick Authentication Method" + }, + "reauth_confirm": { + "description": "The Geocaching integration needs to re-authenticate your account", + "title": "Reauthenticate Integration" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/es.json b/homeassistant/components/geocaching/translations/es.json new file mode 100644 index 00000000000..8b03adca234 --- /dev/null +++ b/homeassistant/components/geocaching/translations/es.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "La cuenta ya est\u00e1 configurada", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en curso", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", + "missing_configuration": "El componente no est\u00e1 configurado. Mira su documentaci\u00f3n.", + "oauth_error": "Se han recibido datos token inv\u00e1lidos.", + "reauth_successful": "Re-autenticaci\u00f3n realizada correctamente" + }, + "create_entry": { + "default": "Autenticaci\u00f3n exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e9todo de autenticaci\u00f3n" + }, + "reauth_confirm": { + "description": "La integraci\u00f3n Geocaching debe volver a autenticarse con tu cuenta", + "title": "Reautenticaci\u00f3n de la integraci\u00f3n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/et.json b/homeassistant/components/geocaching/translations/et.json new file mode 100644 index 00000000000..075e41d1f24 --- /dev/null +++ b/homeassistant/components/geocaching/translations/et.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "create_entry": { + "default": "Tuvastamine \u00f5nnestus" + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + }, + "reauth_confirm": { + "description": "Geocachingu sidumine peab konto taastuvastama", + "title": "Taastuvasta sidumine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/fr.json b/homeassistant/components/geocaching/translations/fr.json new file mode 100644 index 00000000000..33bd549c762 --- /dev/null +++ b/homeassistant/components/geocaching/translations/fr.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" + }, + "create_entry": { + "default": "Authentification r\u00e9ussie" + }, + "step": { + "pick_implementation": { + "title": "S\u00e9lectionner une m\u00e9thode d'authentification" + }, + "reauth_confirm": { + "description": "L'int\u00e9gration Geocaching doit r\u00e9-authentifier votre compte", + "title": "R\u00e9-authentifier l'int\u00e9gration" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/he.json b/homeassistant/components/geocaching/translations/he.json new file mode 100644 index 00000000000..525624782ac --- /dev/null +++ b/homeassistant/components/geocaching/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", + "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.", + "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": { + "pick_implementation": { + "title": "\u05d1\u05d7\u05e8 \u05e9\u05d9\u05d8\u05ea \u05d0\u05d9\u05de\u05d5\u05ea" + }, + "reauth_confirm": { + "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/geocaching/translations/hu.json b/homeassistant/components/geocaching/translations/hu.json new file mode 100644 index 00000000000..b7bbe55e397 --- /dev/null +++ b/homeassistant/components/geocaching/translations/hu.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "step": { + "pick_implementation": { + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" + }, + "reauth_confirm": { + "description": "A Geocaching integr\u00e1ci\u00f3nak \u00fajra kell hiteles\u00edtenie a fi\u00f3kj\u00e1t.", + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/id.json b/homeassistant/components/geocaching/translations/id.json new file mode 100644 index 00000000000..c52366419d5 --- /dev/null +++ b/homeassistant/components/geocaching/translations/id.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "reauth_confirm": { + "description": "Integrasi Geocaching perlu mengautentikasi ulang akun Anda", + "title": "Autentikasi Ulang Integrasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/it.json b/homeassistant/components/geocaching/translations/it.json new file mode 100644 index 00000000000..2758f190756 --- /dev/null +++ b/homeassistant/components/geocaching/translations/it.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "create_entry": { + "default": "Autenticazione riuscita" + }, + "step": { + "pick_implementation": { + "title": "Scegli il metodo di autenticazione" + }, + "reauth_confirm": { + "description": "L'integrazione Geocaching deve autenticare nuovamente il tuo account", + "title": "Autentica nuovamente l'integrazione" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/ja.json b/homeassistant/components/geocaching/translations/ja.json new file mode 100644 index 00000000000..41a238aa637 --- /dev/null +++ b/homeassistant/components/geocaching/translations/ja.json @@ -0,0 +1,25 @@ +{ + "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", + "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" + }, + "step": { + "pick_implementation": { + "title": "\u8a8d\u8a3c\u65b9\u6cd5\u306e\u9078\u629e" + }, + "reauth_confirm": { + "description": "Geocaching\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", + "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/geocaching/translations/ko.json b/homeassistant/components/geocaching/translations/ko.json new file mode 100644 index 00000000000..0d46ef64d4b --- /dev/null +++ b/homeassistant/components/geocaching/translations/ko.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "oauth_error": "\uc798\ubabb\ub41c \ud1a0\ud070 \ub370\uc774\ud130\ub97c \ubc1b\uc558\uc2b5\ub2c8\ub2e4.", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "create_entry": { + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, + "reauth_confirm": { + "description": "Geocaching \ud1b5\ud569\uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/nl.json b/homeassistant/components/geocaching/translations/nl.json new file mode 100644 index 00000000000..f221532a77e --- /dev/null +++ b/homeassistant/components/geocaching/translations/nl.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "already_in_progress": "De configuratie is momenteel al bezig", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", + "oauth_error": "Ongeldige tokengegevens ontvangen.", + "reauth_successful": "Herauthenticatie geslaagd" + }, + "create_entry": { + "default": "Authenticatie geslaagd" + }, + "step": { + "pick_implementation": { + "title": "Kies een authenticatie methode" + }, + "reauth_confirm": { + "description": "De Geocaching integratie moet uw account herauthenticeren", + "title": "Integratie herauthenticeren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/no.json b/homeassistant/components/geocaching/translations/no.json new file mode 100644 index 00000000000..f0b0b724861 --- /dev/null +++ b/homeassistant/components/geocaching/translations/no.json @@ -0,0 +1,25 @@ +{ + "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, vennligst f\u00f8lg dokumentasjonen", + "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk hjelpseksjonen]({docs_url})", + "oauth_error": "Mottatt ugyldige token data.", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "create_entry": { + "default": "Vellykket godkjenning" + }, + "step": { + "pick_implementation": { + "title": "Velg godkjenningsmetode" + }, + "reauth_confirm": { + "description": "Geocaching-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/geocaching/translations/pl.json b/homeassistant/components/geocaching/translations/pl.json new file mode 100644 index 00000000000..f38f7e137f6 --- /dev/null +++ b/homeassistant/components/geocaching/translations/pl.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "create_entry": { + "default": "Pomy\u015blnie uwierzytelniono" + }, + "step": { + "pick_implementation": { + "title": "Wybierz metod\u0119 uwierzytelniania" + }, + "reauth_confirm": { + "description": "Integracja Geocaching wymaga ponownego uwierzytelnienia Twojego konta", + "title": "Ponownie uwierzytelnij integracj\u0119" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/pt-BR.json b/homeassistant/components/geocaching/translations/pt-BR.json new file mode 100644 index 00000000000..4a6c20919d0 --- /dev/null +++ b/homeassistant/components/geocaching/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 est\u00e1 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": "Nenhuma URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre este erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "oauth_error": "Dados de token inv\u00e1lidos recebidos.", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "create_entry": { + "default": "Autenticado com sucesso" + }, + "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, + "reauth_confirm": { + "description": "A integra\u00e7\u00e3o Geocaching precisa reautenticar sua conta", + "title": "Reautenticar Integra\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/ru.json b/homeassistant/components/geocaching/translations/ru.json new file mode 100644 index 00000000000..96bc2b576a1 --- /dev/null +++ b/homeassistant/components/geocaching/translations/ru.json @@ -0,0 +1,25 @@ +{ + "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.", + "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." + }, + "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" + }, + "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 Geocaching.", + "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/geocaching/translations/tr.json b/homeassistant/components/geocaching/translations/tr.json new file mode 100644 index 00000000000..b2c7a1031a8 --- /dev/null +++ b/homeassistant/components/geocaching/translations/tr.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "create_entry": { + "default": "Ba\u015far\u0131yla do\u011fruland\u0131" + }, + "step": { + "pick_implementation": { + "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7" + }, + "reauth_confirm": { + "description": "Geocaching 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/geocaching/translations/zh-Hans.json b/homeassistant/components/geocaching/translations/zh-Hans.json new file mode 100644 index 00000000000..21e284119ed --- /dev/null +++ b/homeassistant/components/geocaching/translations/zh-Hans.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "no_url_available": "\u6ca1\u6709\u53ef\u7528\u7684\u7f51\u5740\u3002\u6709\u5173\u6b64\u9519\u8bef\u7684\u4fe1\u606f\uff0c\u8bf7[\u68c0\u67e5\u5e2e\u52a9\u90e8\u5206]\uff08{docs_url}\uff09", + "oauth_error": "\u6536\u5230\u7684 token \u6570\u636e\u65e0\u6548\u3002", + "reauth_successful": "\u91cd\u65b0\u8ba4\u8bc1\u6210\u529f" + }, + "create_entry": { + "default": "\u8ba4\u8bc1\u6210\u529f" + }, + "step": { + "pick_implementation": { + "title": "\u8bf7\u9009\u62e9\u8ba4\u8bc1\u65b9\u6cd5" + }, + "reauth_confirm": { + "description": "Geocaching \u96c6\u6210\u9700\u8981\u91cd\u65b0\u9a8c\u8bc1\u60a8\u7684\u5e10\u6237", + "title": "\u4f7f\u96c6\u6210\u91cd\u65b0\u8fdb\u884c\u8eab\u4efd\u8ba4\u8bc1" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geocaching/translations/zh-Hant.json b/homeassistant/components/geocaching/translations/zh-Hant.json new file mode 100644 index 00000000000..80c501be734 --- /dev/null +++ b/homeassistant/components/geocaching/translations/zh-Hant.json @@ -0,0 +1,25 @@ +{ + "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", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, + "step": { + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" + }, + "reauth_confirm": { + "description": "Geocaching \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/geofency/device_tracker.py b/homeassistant/components/geofency/device_tracker.py index c92b2905be2..bd4b2852019 100644 --- a/homeassistant/components/geofency/device_tracker.py +++ b/homeassistant/components/geofency/device_tracker.py @@ -35,7 +35,7 @@ async def async_setup_entry( ] = async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) # Restore previously loaded devices - dev_reg = await device_registry.async_get_registry(hass) + dev_reg = device_registry.async_get(hass) dev_ids = { identifier[1] for device in dev_reg.devices.values() diff --git a/homeassistant/components/geofency/translations/fr.json b/homeassistant/components/geofency/translations/fr.json index 84da031d50d..0e268eae673 100644 --- a/homeassistant/components/geofency/translations/fr.json +++ b/homeassistant/components/geofency/translations/fr.json @@ -10,7 +10,7 @@ }, "step": { "user": { - "description": "\u00cates-vous s\u00fbr de vouloir configurer le Webhook Geofency ?", + "description": "Voulez-vous vraiment configurer le webhook Geofency\u00a0?", "title": "Configurer le Webhook Geofency" } } diff --git a/homeassistant/components/geofency/translations/nl.json b/homeassistant/components/geofency/translations/nl.json index 30a2acfdadd..096817de9a6 100644 --- a/homeassistant/components/geofency/translations/nl.json +++ b/homeassistant/components/geofency/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." + "webhook_not_internet_accessible": "Je Home Assistant-instantie moet toegankelijk zijn vanaf internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Om locaties naar Home Assistant te sturen, moet u de Webhook-functie instellen in Geofency.\n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n\n Zie [de documentatie]({docs_url}) voor meer informatie." diff --git a/homeassistant/components/geonetnz_quakes/geo_location.py b/homeassistant/components/geonetnz_quakes/geo_location.py index 860a80f99c5..515fce56439 100644 --- a/homeassistant/components/geonetnz_quakes/geo_location.py +++ b/homeassistant/components/geonetnz_quakes/geo_location.py @@ -13,9 +13,9 @@ from homeassistant.const import ( LENGTH_MILES, ) 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 homeassistant.helpers.entity_registry import async_get_registry from homeassistant.util.unit_system import IMPERIAL_SYSTEM from .const import DOMAIN, FEED @@ -100,7 +100,7 @@ class GeonetnzQuakesEvent(GeolocationEvent): self._remove_signal_delete() self._remove_signal_update() # Remove from entity registry. - entity_registry = await async_get_registry(self.hass) + entity_registry = er.async_get(self.hass) if self.entity_id in entity_registry.entities: entity_registry.async_remove(self.entity_id) diff --git a/homeassistant/components/geonetnz_quakes/translations/nl.json b/homeassistant/components/geonetnz_quakes/translations/nl.json index 74766300e11..62f4649f429 100644 --- a/homeassistant/components/geonetnz_quakes/translations/nl.json +++ b/homeassistant/components/geonetnz_quakes/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/gios/__init__.py b/homeassistant/components/gios/__init__.py index 579b5ae0ab3..73d773561f5 100644 --- a/homeassistant/components/gios/__init__.py +++ b/homeassistant/components/gios/__init__.py @@ -13,9 +13,8 @@ from homeassistant.components.air_quality import DOMAIN as AIR_QUALITY_PLATFORM from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry +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 async_get_registry from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import API_TIMEOUT, CONF_STATION_ID, DOMAIN, SCAN_INTERVAL @@ -35,7 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_update_entry(entry, unique_id=str(station_id)) # type: ignore[unreachable] # We used to use int in device_entry identifiers, convert this to str. - device_registry = await async_get_registry(hass) + device_registry = dr.async_get(hass) old_ids = (DOMAIN, station_id) device_entry = device_registry.async_get_device({old_ids}) # type: ignore[arg-type] if device_entry and entry.entry_id in device_entry.config_entries: @@ -53,7 +52,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_setup_platforms(entry, PLATFORMS) # Remove air_quality entities from registry if they exist - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) unique_id = str(coordinator.gios.station_id) if entity_id := ent_reg.async_get_entity_id( AIR_QUALITY_PLATFORM, DOMAIN, unique_id diff --git a/homeassistant/components/gios/sensor.py b/homeassistant/components/gios/sensor.py index c14a99051e4..391976ad793 100644 --- a/homeassistant/components/gios/sensor.py +++ b/homeassistant/components/gios/sensor.py @@ -8,10 +8,10 @@ from homeassistant.components.sensor import DOMAIN as PLATFORM, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION, ATTR_NAME, CONF_NAME from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -43,7 +43,7 @@ async def async_setup_entry( # Due to the change of the attribute name of one sensor, it is necessary to migrate # the unique_id to the new name. - entity_registry = await async_get_registry(hass) + entity_registry = er.async_get(hass) old_unique_id = f"{coordinator.gios.station_id}-pm2.5" if entity_id := entity_registry.async_get_entity_id( PLATFORM, DOMAIN, old_unique_id diff --git a/homeassistant/components/gios/translations/ca.json b/homeassistant/components/gios/translations/ca.json index 8150e309b20..abfa55fef29 100644 --- a/homeassistant/components/gios/translations/ca.json +++ b/homeassistant/components/gios/translations/ca.json @@ -14,7 +14,6 @@ "name": "Nom", "station_id": "ID de l'estaci\u00f3 de mesura" }, - "description": "Integraci\u00f3 de mesura de qualitat de l'aire GIO\u015a (Polish Chief Inspectorate Of Environmental Protection). Si necessites ajuda amb la configuraci\u00f3, fes un cop d'ull a: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" } } diff --git a/homeassistant/components/gios/translations/cs.json b/homeassistant/components/gios/translations/cs.json index 8dea1f5e013..a80a5ae03bd 100644 --- a/homeassistant/components/gios/translations/cs.json +++ b/homeassistant/components/gios/translations/cs.json @@ -14,7 +14,6 @@ "name": "Jm\u00e9no", "station_id": "ID m\u011b\u0159ic\u00ed stanice" }, - "description": "Nastaven\u00ed integrace kvality ovzdu\u0161\u00ed GIO\u015a (polsk\u00fd hlavn\u00ed inspektor\u00e1t ochrany \u017eivotn\u00edho prost\u0159ed\u00ed). Pokud pot\u0159ebujete pomoc s nastaven\u00edm, pod\u00edvejte se na: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (polsk\u00fd hlavn\u00ed inspektor\u00e1t ochrany \u017eivotn\u00edho prost\u0159ed\u00ed)" } } diff --git a/homeassistant/components/gios/translations/da.json b/homeassistant/components/gios/translations/da.json index d4442982e1e..2d3a2e3e5e9 100644 --- a/homeassistant/components/gios/translations/da.json +++ b/homeassistant/components/gios/translations/da.json @@ -14,7 +14,6 @@ "name": "Navn p\u00e5 integrationen", "station_id": "ID for m\u00e5lestationen" }, - "description": "Ops\u00e6t GIO\u015a (polsk inspektorat for milj\u00f8beskyttelse) luftkvalitet-integration. Hvis du har brug for hj\u00e6lp med konfigurationen, kig her: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" } } diff --git a/homeassistant/components/gios/translations/de.json b/homeassistant/components/gios/translations/de.json index 99548187601..c22b2a197dd 100644 --- a/homeassistant/components/gios/translations/de.json +++ b/homeassistant/components/gios/translations/de.json @@ -14,7 +14,6 @@ "name": "Name", "station_id": "ID der Messstation" }, - "description": "Einrichtung von GIO\u015a (Polnische Hauptinspektion f\u00fcr Umweltschutz) Integration der Luftqualit\u00e4t. Wenn du Hilfe bei der Konfiguration ben\u00f6tigst, schaue hier: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Polnische Hauptinspektion f\u00fcr Umweltschutz)" } } diff --git a/homeassistant/components/gios/translations/el.json b/homeassistant/components/gios/translations/el.json index ae916d70667..46bcc2d0b9c 100644 --- a/homeassistant/components/gios/translations/el.json +++ b/homeassistant/components/gios/translations/el.json @@ -14,7 +14,6 @@ "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "station_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2" }, - "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03bf\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c4\u03bf\u03c5 \u03b1\u03ad\u03c1\u03b1 GIO\u015a (\u03a0\u03bf\u03bb\u03c9\u03bd\u03b9\u03ba\u03ae \u0395\u03c0\u03b9\u03ba\u03b5\u03c6\u03b1\u03bb\u03ae\u03c2 \u0395\u03c0\u03b9\u03b8\u03b5\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2 \u03a0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c3\u03af\u03b1\u03c2 \u03c4\u03bf\u03c5 \u03a0\u03b5\u03c1\u03b9\u03b2\u03ac\u03bb\u03bb\u03bf\u03bd\u03c4\u03bf\u03c2). \u0395\u03ac\u03bd \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7, \u03c1\u03af\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03bc\u03b1\u03c4\u03b9\u03ac \u03b5\u03b4\u03ce: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (\u03a0\u03bf\u03bb\u03c9\u03bd\u03b9\u03ba\u03ae \u0393\u03b5\u03bd\u03b9\u03ba\u03ae \u0395\u03c0\u03b9\u03b8\u03b5\u03ce\u03c1\u03b7\u03c3\u03b7 \u03a0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c3\u03af\u03b1\u03c2 \u03a0\u03b5\u03c1\u03b9\u03b2\u03ac\u03bb\u03bb\u03bf\u03bd\u03c4\u03bf\u03c2)" } } diff --git a/homeassistant/components/gios/translations/en.json b/homeassistant/components/gios/translations/en.json index 86f05b8987e..232389291be 100644 --- a/homeassistant/components/gios/translations/en.json +++ b/homeassistant/components/gios/translations/en.json @@ -14,7 +14,6 @@ "name": "Name", "station_id": "ID of the measuring station" }, - "description": "Set up GIO\u015a (Polish Chief Inspectorate Of Environmental Protection) air quality integration. If you need help with the configuration have a look here: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" } } diff --git a/homeassistant/components/gios/translations/es-419.json b/homeassistant/components/gios/translations/es-419.json index 848247bdf75..52640708ce8 100644 --- a/homeassistant/components/gios/translations/es-419.json +++ b/homeassistant/components/gios/translations/es-419.json @@ -14,7 +14,6 @@ "name": "Nombre de la integraci\u00f3n", "station_id": "Identificaci\u00f3n de la estaci\u00f3n de medici\u00f3n" }, - "description": "Establecer la integraci\u00f3n de la calidad del aire GIO\u015a (Inspecci\u00f3n Jefe de Protecci\u00f3n Ambiental de Polonia). Si necesita ayuda con la configuraci\u00f3n, eche un vistazo aqu\u00ed: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" } } diff --git a/homeassistant/components/gios/translations/es.json b/homeassistant/components/gios/translations/es.json index 011ddd0b6f7..3d6bfc57150 100644 --- a/homeassistant/components/gios/translations/es.json +++ b/homeassistant/components/gios/translations/es.json @@ -14,7 +14,6 @@ "name": "Nombre", "station_id": "ID de la estaci\u00f3n de medici\u00f3n" }, - "description": "Configurar la integraci\u00f3n de la calidad del aire GIO\u015a (Inspecci\u00f3n Jefe de Protecci\u00f3n Ambiental de Polonia). Si necesita ayuda con la configuraci\u00f3n, eche un vistazo aqu\u00ed: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Inspecci\u00f3n Jefe de Protecci\u00f3n del Medio Ambiente de Polonia)" } } diff --git a/homeassistant/components/gios/translations/et.json b/homeassistant/components/gios/translations/et.json index 2d0906f73ab..8565849250e 100644 --- a/homeassistant/components/gios/translations/et.json +++ b/homeassistant/components/gios/translations/et.json @@ -14,7 +14,6 @@ "name": "Nimi", "station_id": "M\u00f5\u00f5tejaama ID" }, - "description": "Loo GIO\u015a (Poola keskkonnakaitse peainspektsioon) \u00f5hukvaliteedi sidumnie. Kui vajad seadistamisel abi, vaats siit: https://www.home-assistant.io/integrations/gios", "title": "" } } diff --git a/homeassistant/components/gios/translations/fr.json b/homeassistant/components/gios/translations/fr.json index af107914c06..5533e9c48bf 100644 --- a/homeassistant/components/gios/translations/fr.json +++ b/homeassistant/components/gios/translations/fr.json @@ -14,7 +14,6 @@ "name": "Nom", "station_id": "Identifiant de la station de mesure" }, - "description": "Mettre en place l'int\u00e9gration de la qualit\u00e9 de l'air GIO\u015a (Inspection g\u00e9n\u00e9rale polonaise de la protection de l'environnement). Si vous avez besoin d'aide pour la configuration, regardez ici: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Inspection g\u00e9n\u00e9rale polonaise de la protection de l'environnement)" } } diff --git a/homeassistant/components/gios/translations/hu.json b/homeassistant/components/gios/translations/hu.json index 1de37cee96a..87f54802368 100644 --- a/homeassistant/components/gios/translations/hu.json +++ b/homeassistant/components/gios/translations/hu.json @@ -14,7 +14,6 @@ "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", "title": "GIO\u015a (Lengyel K\u00f6rnyezetv\u00e9delmi F\u0151fel\u00fcgyel\u0151s\u00e9g)" } } diff --git a/homeassistant/components/gios/translations/id.json b/homeassistant/components/gios/translations/id.json index b32210c30d5..844b1e794b5 100644 --- a/homeassistant/components/gios/translations/id.json +++ b/homeassistant/components/gios/translations/id.json @@ -14,7 +14,6 @@ "name": "Nama", "station_id": "ID stasiun pengukuran" }, - "description": "Siapkan integrasi kualitas udara GIO\u015a (Inspektorat Jenderal Perlindungan Lingkungan Polandia). Jika Anda memerlukan bantuan tentang konfigurasi, baca di sini: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Inspektorat Jenderal Perlindungan Lingkungan Polandia)" } } diff --git a/homeassistant/components/gios/translations/it.json b/homeassistant/components/gios/translations/it.json index cb1f1cea6b0..bd88ded42b6 100644 --- a/homeassistant/components/gios/translations/it.json +++ b/homeassistant/components/gios/translations/it.json @@ -14,7 +14,6 @@ "name": "Nome", "station_id": "ID della stazione di misura" }, - "description": "Imposta l'integrazione della qualit\u00e0 dell'aria GIO\u015a (Ispettorato capo polacco di protezione ambientale). Se hai bisogno di aiuto con la configurazione dai un'occhiata qui: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Ispettorato capo polacco di protezione ambientale)" } } diff --git a/homeassistant/components/gios/translations/ja.json b/homeassistant/components/gios/translations/ja.json index ac05abbf923..f3c9464a7a5 100644 --- a/homeassistant/components/gios/translations/ja.json +++ b/homeassistant/components/gios/translations/ja.json @@ -14,7 +14,6 @@ "name": "\u540d\u524d", "station_id": "\u6e2c\u5b9a\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u306eID" }, - "description": "GIO\u015a(Polish Chief Inspectorate Of Environmental Protection)\u306e\u5927\u6c17\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002\u8a2d\u5b9a\u306b\u3064\u3044\u3066\u30d8\u30eb\u30d7\u304c\u5fc5\u8981\u306a\u5834\u5408\u306f\u3001https://www.home-assistant.io/integrations/gios \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044", "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" } } diff --git a/homeassistant/components/gios/translations/ko.json b/homeassistant/components/gios/translations/ko.json index e462ef4e3b6..c49bd258c50 100644 --- a/homeassistant/components/gios/translations/ko.json +++ b/homeassistant/components/gios/translations/ko.json @@ -14,7 +14,6 @@ "name": "\uc774\ub984", "station_id": "\uce21\uc815 \uc2a4\ud14c\uc774\uc158\uc758 ID" }, - "description": "\ud3f4\ub780\ub4dc \ud658\uacbd\uccad (GIO\u015a) \ub300\uae30\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. \uad6c\uc131\uc5d0 \ub3c4\uc6c0\uc774 \ud544\uc694\ud55c \uacbd\uc6b0 https://www.home-assistant.io/integrations/gios \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", "title": "\ud3f4\ub780\ub4dc \ud658\uacbd\uccad (GIO\u015a)" } } diff --git a/homeassistant/components/gios/translations/lb.json b/homeassistant/components/gios/translations/lb.json index cafea72fb78..22db9df424f 100644 --- a/homeassistant/components/gios/translations/lb.json +++ b/homeassistant/components/gios/translations/lb.json @@ -14,7 +14,6 @@ "name": "Numm", "station_id": "ID vun der Miess Statioun" }, - "description": "GIO\u015a (Polnesch Chefinspektorat vum \u00cbmweltschutz) Loft Qualit\u00e9it Integratioun ariichten. Fir w\u00e9ider H\u00ebllef mat der Konfiuratioun kuckt hei: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Polnesch Chefinspektorat vum \u00cbmweltschutz)" } } diff --git a/homeassistant/components/gios/translations/nl.json b/homeassistant/components/gios/translations/nl.json index 87104523a31..3f714a0fd1e 100644 --- a/homeassistant/components/gios/translations/nl.json +++ b/homeassistant/components/gios/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -14,7 +14,6 @@ "name": "Naam", "station_id": "ID van het meetstation" }, - "description": "GIO\u015a (Poolse hoofdinspectie van milieubescherming) luchtkwaliteitintegratie instellen. Als u hulp nodig hebt bij de configuratie, kijk dan hier: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Poolse hoofdinspectie van milieubescherming)" } } diff --git a/homeassistant/components/gios/translations/no.json b/homeassistant/components/gios/translations/no.json index 038cbdc20a3..68d02c7deec 100644 --- a/homeassistant/components/gios/translations/no.json +++ b/homeassistant/components/gios/translations/no.json @@ -14,7 +14,6 @@ "name": "Navn", "station_id": "ID til m\u00e5lestasjon" }, - "description": "Sett opp GIO\u015a (Polish Chief Inspectorate Of Environmental Protection) luftkvalitet integrasjon. Hvis du trenger hjelp med konfigurasjonen ta en titt her: https://www.home-assistant.io/integrations/gios", "title": "" } } diff --git a/homeassistant/components/gios/translations/pl.json b/homeassistant/components/gios/translations/pl.json index 8bc909e2bab..6a7d2aa7064 100644 --- a/homeassistant/components/gios/translations/pl.json +++ b/homeassistant/components/gios/translations/pl.json @@ -14,7 +14,6 @@ "name": "Nazwa", "station_id": "Identyfikator stacji pomiarowej" }, - "description": "Konfiguracja integracji jako\u015bci powietrza GIO\u015a (G\u0142\u00f3wny Inspektorat Ochrony \u015arodowiska). Je\u015bli potrzebujesz pomocy z konfiguracj\u0105, przejd\u017a na stron\u0119: https://www.home-assistant.io/integrations/gios", "title": "G\u0142\u00f3wny Inspektorat Ochrony \u015arodowiska (GIO\u015a)" } } diff --git a/homeassistant/components/gios/translations/pt-BR.json b/homeassistant/components/gios/translations/pt-BR.json index 39472c3b420..e4aad1faf74 100644 --- a/homeassistant/components/gios/translations/pt-BR.json +++ b/homeassistant/components/gios/translations/pt-BR.json @@ -14,7 +14,6 @@ "name": "Nome", "station_id": "ID da esta\u00e7\u00e3o de medi\u00e7\u00e3o" }, - "description": "Configurar a integra\u00e7\u00e3o da qualidade do ar GIO\u015a (Polish Chief Inspectorate Of Environmental Protection). Se precisar de ajuda com a configura\u00e7\u00e3o, d\u00ea uma olhada em https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Inspetor-Chefe Polon\u00eas de Prote\u00e7\u00e3o Ambiental)" } } diff --git a/homeassistant/components/gios/translations/ru.json b/homeassistant/components/gios/translations/ru.json index 68d6ee44b0b..ede5f2d9a70 100644 --- a/homeassistant/components/gios/translations/ru.json +++ b/homeassistant/components/gios/translations/ru.json @@ -14,7 +14,6 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "station_id": "ID \u0438\u0437\u043c\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0430\u043d\u0446\u0438\u0438" }, - "description": "\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0432\u043e\u0437\u0434\u0443\u0445\u0430 \u043e\u0442 \u041f\u043e\u043b\u044c\u0441\u043a\u043e\u0439 \u0438\u043d\u0441\u043f\u0435\u043a\u0446\u0438\u0438 \u043f\u043e \u043e\u0445\u0440\u0430\u043d\u0435 \u043e\u043a\u0440\u0443\u0436\u0430\u044e\u0449\u0435\u0439 \u0441\u0440\u0435\u0434\u044b (GIO\u015a). \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/gios.", "title": "GIO\u015a (\u041f\u043e\u043b\u044c\u0441\u043a\u0430\u044f \u0438\u043d\u0441\u043f\u0435\u043a\u0446\u0438\u044f \u043f\u043e \u043e\u0445\u0440\u0430\u043d\u0435 \u043e\u043a\u0440\u0443\u0436\u0430\u044e\u0449\u0435\u0439 \u0441\u0440\u0435\u0434\u044b)" } } diff --git a/homeassistant/components/gios/translations/sl.json b/homeassistant/components/gios/translations/sl.json index 4bbc28bfedd..bc8cac9941c 100644 --- a/homeassistant/components/gios/translations/sl.json +++ b/homeassistant/components/gios/translations/sl.json @@ -14,7 +14,6 @@ "name": "Ime integracije", "station_id": "ID merilne postaje" }, - "description": "Nastavite GIO\u015a (poljski glavni in\u0161pektorat za varstvo okolja) integracijo kakovosti zraka. \u010ce potrebujete pomo\u010d pri konfiguraciji si oglejte tukaj: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (glavni poljski in\u0161pektorat za varstvo okolja)" } } diff --git a/homeassistant/components/gios/translations/sv.json b/homeassistant/components/gios/translations/sv.json index a8bafa50119..98e9333c821 100644 --- a/homeassistant/components/gios/translations/sv.json +++ b/homeassistant/components/gios/translations/sv.json @@ -14,7 +14,6 @@ "name": "Integrationens namn", "station_id": "M\u00e4tstationens ID" }, - "description": "St\u00e4ll in luftkvalitetintegration f\u00f6r GIO\u015a (polsk chefinspektorat f\u00f6r milj\u00f6skydd). Om du beh\u00f6ver hj\u00e4lp med konfigurationen titta h\u00e4r: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" } } diff --git a/homeassistant/components/gios/translations/tr.json b/homeassistant/components/gios/translations/tr.json index c0444ed99ed..e9419d53963 100644 --- a/homeassistant/components/gios/translations/tr.json +++ b/homeassistant/components/gios/translations/tr.json @@ -14,7 +14,6 @@ "name": "Ad", "station_id": "\u00d6l\u00e7\u00fcm istasyonunun kimli\u011fi" }, - "description": "GIO\u015a (Polonya \u00c7evre Koruma Ba\u015f M\u00fcfetti\u015fli\u011fi) hava kalitesi entegrasyonunu kurun. Yap\u0131land\u0131rmayla ilgili yard\u0131ma ihtiyac\u0131n\u0131z varsa buraya bak\u0131n: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Polonya \u00c7evre Koruma Ba\u015f M\u00fcfetti\u015fli\u011fi)" } } diff --git a/homeassistant/components/gios/translations/uk.json b/homeassistant/components/gios/translations/uk.json index f62408c5e8e..8151bf21809 100644 --- a/homeassistant/components/gios/translations/uk.json +++ b/homeassistant/components/gios/translations/uk.json @@ -14,7 +14,6 @@ "name": "\u041d\u0430\u0437\u0432\u0430", "station_id": "ID \u0432\u0438\u043c\u0456\u0440\u044e\u0432\u0430\u043b\u044c\u043d\u043e\u0457 \u0441\u0442\u0430\u043d\u0446\u0456\u0457" }, - "description": "\u0406\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f \u043f\u0440\u043e \u044f\u043a\u0456\u0441\u0442\u044c \u043f\u043e\u0432\u0456\u0442\u0440\u044f \u0432\u0456\u0434 \u041f\u043e\u043b\u044c\u0441\u044c\u043a\u043e\u0457 \u0456\u043d\u0441\u043f\u0435\u043a\u0446\u0456\u0457 \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \u043d\u0430\u0432\u043a\u043e\u043b\u0438\u0448\u043d\u044c\u043e\u0433\u043e \u0441\u0435\u0440\u0435\u0434\u043e\u0432\u0438\u0449\u0430 (GIO\u015a). \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/gios.", "title": "GIO\u015a (\u041f\u043e\u043b\u044c\u0441\u044c\u043a\u0430 \u0456\u043d\u0441\u043f\u0435\u043a\u0446\u0456\u044f \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \u043d\u0430\u0432\u043a\u043e\u043b\u0438\u0448\u043d\u044c\u043e\u0433\u043e \u0441\u0435\u0440\u0435\u0434\u043e\u0432\u0438\u0449\u0430)" } } diff --git a/homeassistant/components/gios/translations/zh-Hant.json b/homeassistant/components/gios/translations/zh-Hant.json index 98a62385ee1..209367511e7 100644 --- a/homeassistant/components/gios/translations/zh-Hant.json +++ b/homeassistant/components/gios/translations/zh-Hant.json @@ -14,7 +14,6 @@ "name": "\u540d\u7a31", "station_id": "\u76e3\u6e2c\u7ad9 ID" }, - "description": "\u8a2d\u5b9a GIO\u015a\uff08\u6ce2\u862d\u7e3d\u74b0\u5883\u4fdd\u8b77\u7763\u5bdf\u8655\uff09\u7a7a\u6c23\u54c1\u8cea\u6574\u5408\u3002\u5047\u5982\u9700\u8981\u5354\u52a9\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/gios", "title": "GIO\u015a\uff08\u6ce2\u862d\u7e3d\u74b0\u5883\u4fdd\u8b77\u7763\u5bdf\u8655\uff09" } } diff --git a/homeassistant/components/github/translations/es.json b/homeassistant/components/github/translations/es.json new file mode 100644 index 00000000000..d53004c3cb5 --- /dev/null +++ b/homeassistant/components/github/translations/es.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "El servicio ya est\u00e1 configurado", + "could_not_register": "No se pudo registrar la integraci\u00f3n con GitHub" + }, + "step": { + "repositories": { + "data": { + "repositories": "Seleccione los repositorios a seguir." + }, + "title": "Configuraci\u00f3n de repositorios" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/github/translations/nl.json b/homeassistant/components/github/translations/nl.json index f7cb2ac22e4..8a53a0b38d4 100644 --- a/homeassistant/components/github/translations/nl.json +++ b/homeassistant/components/github/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", + "already_configured": "Dienst is al geconfigureerd", "could_not_register": "Kan integratie niet met GitHub registreren" }, "progress": { diff --git a/homeassistant/components/glances/translations/nl.json b/homeassistant/components/glances/translations/nl.json index 6cb7fb445bb..ca414a92ab9 100644 --- a/homeassistant/components/glances/translations/nl.json +++ b/homeassistant/components/glances/translations/nl.json @@ -14,7 +14,7 @@ "name": "Naam", "password": "Wachtwoord", "port": "Poort", - "ssl": "Gebruik een SSL-certificaat", + "ssl": "Maakt gebruik van een SSL-certificaat", "username": "Gebruikersnaam", "verify_ssl": "SSL-certificaat verifi\u00ebren", "version": "Glances API-versie (2 of 3)" diff --git a/homeassistant/components/goalzero/translations/ca.json b/homeassistant/components/goalzero/translations/ca.json index 3e7d6e714d3..d5ef3e9eda3 100644 --- a/homeassistant/components/goalzero/translations/ca.json +++ b/homeassistant/components/goalzero/translations/ca.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "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.", - "title": "Goal Zero Yeti" + "description": "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." }, "user": { "data": { "host": "Amfitri\u00f3", "name": "Nom" }, - "description": "Consulta la documentaci\u00f3 per assegurar-te que compleixes tots els requisits.", - "title": "Goal Zero Yeti" + "description": "Consulta la documentaci\u00f3 per assegurar-te que compleixes tots els requisits." } } } diff --git a/homeassistant/components/goalzero/translations/cs.json b/homeassistant/components/goalzero/translations/cs.json index 2f2735f3c45..c8d0ab45fd6 100644 --- a/homeassistant/components/goalzero/translations/cs.json +++ b/homeassistant/components/goalzero/translations/cs.json @@ -15,8 +15,7 @@ "host": "Hostitel", "name": "Jm\u00e9no" }, - "description": "Nejprve si mus\u00edte st\u00e1hnout aplikaci Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\nPodle pokyn\u016f p\u0159ipojte za\u0159\u00edzen\u00ed Yeti k s\u00edti Wifi. Pot\u00e9 z\u00edskejte hostitelskou IP z routeru. Aby se IP hostitele nezm\u011bnila, mus\u00ed b\u00fdt v nastaven\u00ed routeru pro za\u0159\u00edzen\u00ed nastaven DHCP. Informace nalezenete v u\u017eivatelsk\u00e9 p\u0159\u00edru\u010dce k routeru.", - "title": "Goal Zero Yeti" + "description": "Nejprve si mus\u00edte st\u00e1hnout aplikaci Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\nPodle pokyn\u016f p\u0159ipojte za\u0159\u00edzen\u00ed Yeti k s\u00edti Wifi. Pot\u00e9 z\u00edskejte hostitelskou IP z routeru. Aby se IP hostitele nezm\u011bnila, mus\u00ed b\u00fdt v nastaven\u00ed routeru pro za\u0159\u00edzen\u00ed nastaven DHCP. Informace nalezenete v u\u017eivatelsk\u00e9 p\u0159\u00edru\u010dce k routeru." } } } diff --git a/homeassistant/components/goalzero/translations/de.json b/homeassistant/components/goalzero/translations/de.json index a9f7e3fa887..d41a2238854 100644 --- a/homeassistant/components/goalzero/translations/de.json +++ b/homeassistant/components/goalzero/translations/de.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "Eine DHCP-Reservierung auf deinem Router wird empfohlen. Wenn sie nicht eingerichtet ist, ist das Ger\u00e4t m\u00f6glicherweise nicht mehr verf\u00fcgbar, bis Home Assistant die neue IP-Adresse erkennt. Schlage im Benutzerhandbuch deines Routers nach.", - "title": "Goal Zero Yeti" + "description": "Eine DHCP-Reservierung auf deinem Router wird empfohlen. Wenn sie nicht eingerichtet ist, ist das Ger\u00e4t m\u00f6glicherweise nicht mehr verf\u00fcgbar, bis Home Assistant die neue IP-Adresse erkennt. Schlage im Benutzerhandbuch deines Routers nach." }, "user": { "data": { "host": "Host", "name": "Name" }, - "description": "Bitte lies die Dokumentation, um sicherzustellen, dass alle Anforderungen erf\u00fcllt sind.", - "title": "Goal Zero Yeti" + "description": "Bitte lies die Dokumentation, um sicherzustellen, dass alle Anforderungen erf\u00fcllt sind." } } } diff --git a/homeassistant/components/goalzero/translations/el.json b/homeassistant/components/goalzero/translations/el.json index 865106aa33c..6e793797046 100644 --- a/homeassistant/components/goalzero/translations/el.json +++ b/homeassistant/components/goalzero/translations/el.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "\u03a3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9 \u03b7 \u03ba\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 DHCP \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2. \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bd\u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03bc\u03b7\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03ad\u03c9\u03c2 \u03cc\u03c4\u03bf\u03c5 \u03c4\u03bf Home Assistant \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03c4\u03b7 \u03bd\u03ad\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 ip. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03b3\u03c7\u03b5\u03b9\u03c1\u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2.", - "title": "Goal Zero Yeti" + "description": "\u03a3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9 \u03b7 \u03ba\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 DHCP \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2. \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bd\u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03bc\u03b7\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03ad\u03c9\u03c2 \u03cc\u03c4\u03bf\u03c5 \u03c4\u03bf Home Assistant \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03c4\u03b7 \u03bd\u03ad\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 ip. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03b3\u03c7\u03b5\u03b9\u03c1\u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2." }, "user": { "data": { "host": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, - "description": "\u03a0\u03c1\u03ce\u03c4\u03b1, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b5\u03b2\u03ac\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\n \u0391\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Yeti \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf Wi-Fi. \u03a3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9 \u03b7 \u03ba\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 DHCP \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2. \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bd\u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03bc\u03b7\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03ad\u03c9\u03c2 \u03cc\u03c4\u03bf\u03c5 \u03bf \u0392\u03bf\u03b7\u03b8\u03cc\u03c2 \u039f\u03b9\u03ba\u03af\u03b1\u03c2 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03c4\u03b7 \u03bd\u03ad\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03b3\u03c7\u03b5\u03b9\u03c1\u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2.", - "title": "Goal Zero Yeti" + "description": "\u03a0\u03c1\u03ce\u03c4\u03b1, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b5\u03b2\u03ac\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\n \u0391\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Yeti \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf Wi-Fi. \u03a3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9 \u03b7 \u03ba\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 DHCP \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2. \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03bd\u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03bc\u03b7\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03ad\u03c9\u03c2 \u03cc\u03c4\u03bf\u03c5 \u03bf \u0392\u03bf\u03b7\u03b8\u03cc\u03c2 \u039f\u03b9\u03ba\u03af\u03b1\u03c2 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03c4\u03b7 \u03bd\u03ad\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03b3\u03c7\u03b5\u03b9\u03c1\u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2." } } } diff --git a/homeassistant/components/goalzero/translations/en.json b/homeassistant/components/goalzero/translations/en.json index fd27892c794..7c7237ce0af 100644 --- a/homeassistant/components/goalzero/translations/en.json +++ b/homeassistant/components/goalzero/translations/en.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "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.", - "title": "Goal Zero Yeti" + "description": "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." }, "user": { "data": { "host": "Host", "name": "Name" }, - "description": "Please refer to the documentation to make sure all requirements are met.", - "title": "Goal Zero Yeti" + "description": "Please refer to the documentation to make sure all requirements are met." } } } diff --git a/homeassistant/components/goalzero/translations/es.json b/homeassistant/components/goalzero/translations/es.json index fa54d6d6afc..e02b06d32f5 100644 --- a/homeassistant/components/goalzero/translations/es.json +++ b/homeassistant/components/goalzero/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "invalid_host": "Nombre de host o direcci\u00f3n IP no v\u00e1lidos", "unknown": "Error inesperado" }, @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "Se recomienda reservar el DHCP en el router. Si no se configura, el dispositivo puede dejar de estar disponible hasta que el Home Assistant detecte la nueva direcci\u00f3n ip. Consulte el manual de usuario de su router.", - "title": "Goal Zero Yeti" + "description": "Se recomienda reservar el DHCP en el router. Si no se configura, el dispositivo puede dejar de estar disponible hasta que el Home Assistant detecte la nueva direcci\u00f3n ip. Consulte el manual de usuario de su router." }, "user": { "data": { "host": "Host", "name": "Nombre" }, - "description": "Primero, tienes que descargar la aplicaci\u00f3n Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nSigue las instrucciones para conectar tu Yeti a tu red Wifi. Luego obt\u00e9n la IP de tu router. El DHCP debe estar configurado en los ajustes de tu router para asegurar que la IP de host del dispositivo no cambie. Consulta el manual de usuario de tu router.", - "title": "Goal Zero Yeti" + "description": "Primero, tienes que descargar la aplicaci\u00f3n Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nSigue las instrucciones para conectar tu Yeti a tu red Wifi. Luego obt\u00e9n la IP de tu router. El DHCP debe estar configurado en los ajustes de tu router para asegurar que la IP de host del dispositivo no cambie. Consulta el manual de usuario de tu router." } } } diff --git a/homeassistant/components/goalzero/translations/et.json b/homeassistant/components/goalzero/translations/et.json index e529f0a3a4e..2947a5b82b6 100644 --- a/homeassistant/components/goalzero/translations/et.json +++ b/homeassistant/components/goalzero/translations/et.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "Soovitatav on DHCP aadressi reserveerimine ruuteris. Kui seda pole seadistatud, v\u00f5ib seade osutuda k\u00e4ttesaamatuks kuni Home Assistant tuvastab uue IP-aadressi. Vaata ruuteri kasutusjuhendit.", - "title": "Goal Zero Yeti" + "description": "Soovitatav on DHCP aadressi reserveerimine ruuteris. Kui seda pole seadistatud, v\u00f5ib seade osutuda k\u00e4ttesaamatuks kuni Home Assistant tuvastab uue IP-aadressi. Vaata ruuteri kasutusjuhendit." }, "user": { "data": { "host": "", "name": "Nimi" }, - "description": "Vaata dokumentatsiooni, et veenduda, et k\u00f5ik n\u00f5uded on t\u00e4idetud.", - "title": "" + "description": "Vaata dokumentatsiooni, et veenduda, et k\u00f5ik n\u00f5uded on t\u00e4idetud." } } } diff --git a/homeassistant/components/goalzero/translations/fr.json b/homeassistant/components/goalzero/translations/fr.json index 430ad9927c4..4c2e987e096 100644 --- a/homeassistant/components/goalzero/translations/fr.json +++ b/homeassistant/components/goalzero/translations/fr.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "La r\u00e9servation DHCP sur votre routeur est recommand\u00e9e. S'il n'est pas configur\u00e9, l'appareil peut devenir indisponible jusqu'\u00e0 ce que Home Assistant d\u00e9tecte la nouvelle adresse IP. Reportez-vous au manuel d'utilisation de votre routeur.", - "title": "Objectif Z\u00e9ro Y\u00e9ti" + "description": "La r\u00e9servation DHCP sur votre routeur est recommand\u00e9e. S'il n'est pas configur\u00e9, l'appareil peut devenir indisponible jusqu'\u00e0 ce que Home Assistant d\u00e9tecte la nouvelle adresse IP. Reportez-vous au manuel d'utilisation de votre routeur." }, "user": { "data": { "host": "H\u00f4te", "name": "Nom" }, - "description": "Veuillez consulter la documentation afin de vous assurer que toutes les conditions sont respect\u00e9es.", - "title": "Goal Zero Yeti" + "description": "Veuillez consulter la documentation afin de vous assurer que toutes les conditions sont respect\u00e9es." } } } diff --git a/homeassistant/components/goalzero/translations/hu.json b/homeassistant/components/goalzero/translations/hu.json index 00196952e0c..c0376fc29b9 100644 --- a/homeassistant/components/goalzero/translations/hu.json +++ b/homeassistant/components/goalzero/translations/hu.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "DHCP foglal\u00e1s aj\u00e1nlott az routeren. Ha nincs be\u00e1ll\u00edtva, akkor az eszk\u00f6z el\u00e9rhetetlenn\u00e9 v\u00e1lhat, am\u00edg Home Assistant \u00e9szleli az \u00faj IP-c\u00edmet. Olvassa el az router felhaszn\u00e1l\u00f3i k\u00e9zik\u00f6nyv\u00e9t.", - "title": "Goal Zero Yeti" + "description": "DHCP foglal\u00e1s aj\u00e1nlott az routeren. Ha nincs be\u00e1ll\u00edtva, akkor az eszk\u00f6z el\u00e9rhetetlenn\u00e9 v\u00e1lhat, am\u00edg Home Assistant \u00e9szleli az \u00faj IP-c\u00edmet. Olvassa el az router felhaszn\u00e1l\u00f3i k\u00e9zik\u00f6nyv\u00e9t." }, "user": { "data": { "host": "C\u00edm", "name": "Elnevez\u00e9s" }, - "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" + "description": "K\u00e9rj\u00fck, olvassa el a dokument\u00e1ci\u00f3t, hogy megbizonyosodjon arr\u00f3l, hogy minden k\u00f6vetelm\u00e9ny teljes\u00fcl." } } } diff --git a/homeassistant/components/goalzero/translations/id.json b/homeassistant/components/goalzero/translations/id.json index d524b13bb0c..c34fd9edfd9 100644 --- a/homeassistant/components/goalzero/translations/id.json +++ b/homeassistant/components/goalzero/translations/id.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "Dianjurkan untuk menggunakan reservasi DHCP pada router Anda. Jika tidak diatur, perangkat mungkin tidak tersedia hingga Home Assistant mendeteksi alamat IP baru. Lihat panduan pengguna router Anda.", - "title": "Goal Zero Yeti" + "description": "Dianjurkan untuk menggunakan reservasi DHCP pada router Anda. Jika tidak diatur, perangkat mungkin tidak tersedia hingga Home Assistant mendeteksi alamat IP baru. Lihat panduan pengguna router Anda." }, "user": { "data": { "host": "Host", "name": "Nama" }, - "description": "Rujuk ke dokumentasi untuk memastikan semua persyaratan terpenuhi.", - "title": "Goal Zero Yeti" + "description": "Rujuk ke dokumentasi untuk memastikan semua persyaratan terpenuhi." } } } diff --git a/homeassistant/components/goalzero/translations/it.json b/homeassistant/components/goalzero/translations/it.json index dbd71c82f4a..af88e8713e8 100644 --- a/homeassistant/components/goalzero/translations/it.json +++ b/homeassistant/components/goalzero/translations/it.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "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. Fai riferimento al manuale utente del router.", - "title": "Goal Zero Yeti" + "description": "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. Fai riferimento al manuale utente del router." }, "user": { "data": { "host": "Host", "name": "Nome" }, - "description": "Fai riferimento alla documentazione per assicurarti che tutti i requisiti siano soddisfatti.", - "title": "Goal Zero Yeti" + "description": "Fai riferimento alla documentazione per assicurarti che tutti i requisiti siano soddisfatti." } } } diff --git a/homeassistant/components/goalzero/translations/ja.json b/homeassistant/components/goalzero/translations/ja.json index 3e2e33bc302..72818b6627f 100644 --- a/homeassistant/components/goalzero/translations/ja.json +++ b/homeassistant/components/goalzero/translations/ja.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "\u30eb\u30fc\u30bf\u30fc\u306eDHCP\u4e88\u7d04(DHCP reservation)\u3092\u304a\u52e7\u3081\u3057\u307e\u3059\u3002\u3053\u306e\u8a2d\u5b9a\u3092\u884c\u3063\u3066\u3044\u306a\u3044\u5834\u5408\u306b\u306f\u3001Home Assistant\u304c\u65b0\u3057\u3044IP\u30a2\u30c9\u30ec\u30b9\u3092\u691c\u51fa\u3059\u308b\u307e\u3067\u3001\u30c7\u30d0\u30a4\u30b9\u304c\u4f7f\u7528\u3067\u304d\u306a\u304f\u306a\u308b\u3053\u3068\u304c\u3042\u308a\u307e\u3059\u3002\u30eb\u30fc\u30bf\u30fc\u306e\u30e6\u30fc\u30b6\u30fc\u30de\u30cb\u30e5\u30a2\u30eb\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "Goal Zero Yeti" + "description": "\u30eb\u30fc\u30bf\u30fc\u306eDHCP\u4e88\u7d04(DHCP reservation)\u3092\u304a\u52e7\u3081\u3057\u307e\u3059\u3002\u3053\u306e\u8a2d\u5b9a\u3092\u884c\u3063\u3066\u3044\u306a\u3044\u5834\u5408\u306b\u306f\u3001Home Assistant\u304c\u65b0\u3057\u3044IP\u30a2\u30c9\u30ec\u30b9\u3092\u691c\u51fa\u3059\u308b\u307e\u3067\u3001\u30c7\u30d0\u30a4\u30b9\u304c\u4f7f\u7528\u3067\u304d\u306a\u304f\u306a\u308b\u3053\u3068\u304c\u3042\u308a\u307e\u3059\u3002\u30eb\u30fc\u30bf\u30fc\u306e\u30e6\u30fc\u30b6\u30fc\u30de\u30cb\u30e5\u30a2\u30eb\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, "user": { "data": { "host": "\u30db\u30b9\u30c8", "name": "\u540d\u524d" }, - "description": "\u307e\u305a\u3001Goal Zero app\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059: https://www.goalzero.com/product-features/yeti-app/\n\n\u30eb\u30fc\u30bf\u30fc\u306eDHCP\u4e88\u7d04(DHCP reservation)\u3092\u304a\u52e7\u3081\u3057\u307e\u3059\u3002\u3053\u306e\u8a2d\u5b9a\u3092\u884c\u3063\u3066\u3044\u306a\u3044\u5834\u5408\u306b\u306f\u3001Home Assistant\u304c\u65b0\u3057\u3044IP\u30a2\u30c9\u30ec\u30b9\u3092\u691c\u51fa\u3059\u308b\u307e\u3067\u3001\u30c7\u30d0\u30a4\u30b9\u304c\u4f7f\u7528\u3067\u304d\u306a\u304f\u306a\u308b\u3053\u3068\u304c\u3042\u308a\u307e\u3059\u3002\u30eb\u30fc\u30bf\u30fc\u306e\u30e6\u30fc\u30b6\u30fc\u30de\u30cb\u30e5\u30a2\u30eb\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "Goal Zero Yeti" + "description": "\u307e\u305a\u3001Goal Zero app\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059: https://www.goalzero.com/product-features/yeti-app/\n\n\u30eb\u30fc\u30bf\u30fc\u306eDHCP\u4e88\u7d04(DHCP reservation)\u3092\u304a\u52e7\u3081\u3057\u307e\u3059\u3002\u3053\u306e\u8a2d\u5b9a\u3092\u884c\u3063\u3066\u3044\u306a\u3044\u5834\u5408\u306b\u306f\u3001Home Assistant\u304c\u65b0\u3057\u3044IP\u30a2\u30c9\u30ec\u30b9\u3092\u691c\u51fa\u3059\u308b\u307e\u3067\u3001\u30c7\u30d0\u30a4\u30b9\u304c\u4f7f\u7528\u3067\u304d\u306a\u304f\u306a\u308b\u3053\u3068\u304c\u3042\u308a\u307e\u3059\u3002\u30eb\u30fc\u30bf\u30fc\u306e\u30e6\u30fc\u30b6\u30fc\u30de\u30cb\u30e5\u30a2\u30eb\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" } } } diff --git a/homeassistant/components/goalzero/translations/ko.json b/homeassistant/components/goalzero/translations/ko.json index d5119363002..dd947530c16 100644 --- a/homeassistant/components/goalzero/translations/ko.json +++ b/homeassistant/components/goalzero/translations/ko.json @@ -14,8 +14,7 @@ "host": "\ud638\uc2a4\ud2b8", "name": "\uc774\ub984" }, - "description": "\uba3c\uc800 Goal Zero \uc571\uc744 \ub2e4\uc6b4\ub85c\ub4dc\ud574\uc57c \ud569\ub2c8\ub2e4. https://www.goalzero.com/product-features/yeti-app/\n\n\uc9c0\uce68\uc5d0 \ub530\ub77c Yeti\ub97c Wifi \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ud55c \ub2e4\uc74c \ub77c\uc6b0\ud130\uc5d0\uc11c \ud638\uc2a4\ud2b8 IP\ub97c \uac00\uc838\uc640\uc8fc\uc138\uc694. \ud638\uc2a4\ud2b8 IP\uac00 \ubcc0\uacbd\ub418\uc9c0 \uc54a\ub3c4\ub85d \ud558\ub824\uba74 \uae30\uae30\uc5d0 \ub300\ud574 \ub77c\uc6b0\ud130\uc5d0\uc11c DHCP\ub97c \uc54c\ub9de\uac8c \uc124\uc815\ud574\uc8fc\uc5b4\uc57c \ud569\ub2c8\ub2e4. \ud574\ub2f9 \ub0b4\uc6a9\uc5d0 \ub300\ud574\uc11c\ub294 \ub77c\uc6b0\ud130\uc758 \uc0ac\uc6a9\uc790 \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "title": "Goal Zero Yeti" + "description": "\uba3c\uc800 Goal Zero \uc571\uc744 \ub2e4\uc6b4\ub85c\ub4dc\ud574\uc57c \ud569\ub2c8\ub2e4. https://www.goalzero.com/product-features/yeti-app/\n\n\uc9c0\uce68\uc5d0 \ub530\ub77c Yeti\ub97c Wifi \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ud55c \ub2e4\uc74c \ub77c\uc6b0\ud130\uc5d0\uc11c \ud638\uc2a4\ud2b8 IP\ub97c \uac00\uc838\uc640\uc8fc\uc138\uc694. \ud638\uc2a4\ud2b8 IP\uac00 \ubcc0\uacbd\ub418\uc9c0 \uc54a\ub3c4\ub85d \ud558\ub824\uba74 \uae30\uae30\uc5d0 \ub300\ud574 \ub77c\uc6b0\ud130\uc5d0\uc11c DHCP\ub97c \uc54c\ub9de\uac8c \uc124\uc815\ud574\uc8fc\uc5b4\uc57c \ud569\ub2c8\ub2e4. \ud574\ub2f9 \ub0b4\uc6a9\uc5d0 \ub300\ud574\uc11c\ub294 \ub77c\uc6b0\ud130\uc758 \uc0ac\uc6a9\uc790 \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/goalzero/translations/lb.json b/homeassistant/components/goalzero/translations/lb.json index 87a5590bfe4..203b70a269c 100644 --- a/homeassistant/components/goalzero/translations/lb.json +++ b/homeassistant/components/goalzero/translations/lb.json @@ -14,8 +14,7 @@ "host": "Host", "name": "Numm" }, - "description": "Fir d'\u00e9ischt muss Goal Zero App erofgeluede ginn:\nhttps://www.goalzero.com/product-features/yeti-app/\n\nFolleg d'Instruktioune fir d\u00e4in Yeti mat dengem Wifi ze verbannen. Dann erm\u00ebttel d'IP vum Yeti an dengem Router. DHCP muss aktiv sinn an de Yeti Apparat sollt \u00ebmmer d\u00e9iselwecht IP zougewise kr\u00e9ien. Kuck dat am Guide vun dengen Router Astellungen no.", - "title": "Goal Zero Yeti" + "description": "Fir d'\u00e9ischt muss Goal Zero App erofgeluede ginn:\nhttps://www.goalzero.com/product-features/yeti-app/\n\nFolleg d'Instruktioune fir d\u00e4in Yeti mat dengem Wifi ze verbannen. Dann erm\u00ebttel d'IP vum Yeti an dengem Router. DHCP muss aktiv sinn an de Yeti Apparat sollt \u00ebmmer d\u00e9iselwecht IP zougewise kr\u00e9ien. Kuck dat am Guide vun dengen Router Astellungen no." } } } diff --git a/homeassistant/components/goalzero/translations/nl.json b/homeassistant/components/goalzero/translations/nl.json index 680ff1a10cc..ab70229ace1 100644 --- a/homeassistant/components/goalzero/translations/nl.json +++ b/homeassistant/components/goalzero/translations/nl.json @@ -7,21 +7,19 @@ }, "error": { "cannot_connect": "Kan geen verbinding maken", - "invalid_host": "Onjuiste hostnaam of IP-adres", + "invalid_host": "Ongeldige hostnaam of IP-adres", "unknown": "Onverwachte fout" }, "step": { "confirm_discovery": { - "description": "DHCP-reservering op uw router wordt aanbevolen. Als dit niet het geval is, is het apparaat mogelijk niet meer beschikbaar totdat Home Assistant het nieuwe IP-adres detecteert. Raadpleeg de gebruikershandleiding van uw router.", - "title": "Goal Zero Yeti" + "description": "DHCP-reservering op uw router wordt aanbevolen. Als dit niet het geval is, is het apparaat mogelijk niet meer beschikbaar totdat Home Assistant het nieuwe IP-adres detecteert. Raadpleeg de gebruikershandleiding van uw router." }, "user": { "data": { "host": "Host", "name": "Naam" }, - "description": "Raadpleeg de documentatie om er zeker van te zijn dat aan alle vereisten wordt voldaan.", - "title": "Goal Zero Yeti" + "description": "Raadpleeg de documentatie om er zeker van te zijn dat aan alle vereisten wordt voldaan." } } } diff --git a/homeassistant/components/goalzero/translations/no.json b/homeassistant/components/goalzero/translations/no.json index 6bb00952fab..9c2da19b3e1 100644 --- a/homeassistant/components/goalzero/translations/no.json +++ b/homeassistant/components/goalzero/translations/no.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "DHCP-reservasjon p\u00e5 ruteren din anbefales. Hvis den ikke er konfigurert, kan enheten bli utilgjengelig til Home Assistant oppdager den nye ip-adressen. Se i brukerh\u00e5ndboken til ruteren.", - "title": "Goal Zero Yeti" + "description": "DHCP-reservasjon p\u00e5 ruteren din anbefales. Hvis den ikke er konfigurert, kan enheten bli utilgjengelig til Home Assistant oppdager den nye ip-adressen. Se i brukerh\u00e5ndboken til ruteren." }, "user": { "data": { "host": "Vert", "name": "Navn" }, - "description": "Se dokumentasjonen for \u00e5 sikre at alle krav er oppfylt.", - "title": "" + "description": "Se dokumentasjonen for \u00e5 sikre at alle krav er oppfylt." } } } diff --git a/homeassistant/components/goalzero/translations/pl.json b/homeassistant/components/goalzero/translations/pl.json index ee8f2022a0d..91b73044f11 100644 --- a/homeassistant/components/goalzero/translations/pl.json +++ b/homeassistant/components/goalzero/translations/pl.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "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.", - "title": "Goal Zero Yeti" + "description": "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." }, "user": { "data": { "host": "Nazwa hosta lub adres IP", "name": "Nazwa" }, - "description": "Zapoznaj si\u0119 z dokumentacj\u0105, aby upewni\u0107 si\u0119, \u017ce wszystkie wymagania s\u0105 spe\u0142nione.", - "title": "Goal Zero Yeti" + "description": "Zapoznaj si\u0119 z dokumentacj\u0105, aby upewni\u0107 si\u0119, \u017ce wszystkie wymagania s\u0105 spe\u0142nione." } } } diff --git a/homeassistant/components/goalzero/translations/pt-BR.json b/homeassistant/components/goalzero/translations/pt-BR.json index fa67129b08a..7561acac487 100644 --- a/homeassistant/components/goalzero/translations/pt-BR.json +++ b/homeassistant/components/goalzero/translations/pt-BR.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "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.", - "title": "Gol Zero Yeti" + "description": "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." }, "user": { "data": { "host": "Nome do host", "name": "Nome" }, - "description": "Consulte a documenta\u00e7\u00e3o para garantir que todos os requisitos sejam atendidos.", - "title": "Gol Zero Yeti" + "description": "Consulte a documenta\u00e7\u00e3o para garantir que todos os requisitos sejam atendidos." } } } diff --git a/homeassistant/components/goalzero/translations/ru.json b/homeassistant/components/goalzero/translations/ru.json index ca114f6d92e..7bd8c3df311 100644 --- a/homeassistant/components/goalzero/translations/ru.json +++ b/homeassistant/components/goalzero/translations/ru.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "\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\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u043a \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0412\u0430\u0448\u0435\u0433\u043e \u0440\u043e\u0443\u0442\u0435\u0440\u0430.", - "title": "Goal Zero Yeti" + "description": "\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\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u043a \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0412\u0430\u0448\u0435\u0433\u043e \u0440\u043e\u0443\u0442\u0435\u0440\u0430." }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "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" + "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." } } } diff --git a/homeassistant/components/goalzero/translations/tr.json b/homeassistant/components/goalzero/translations/tr.json index 9be07def514..db04511a8f5 100644 --- a/homeassistant/components/goalzero/translations/tr.json +++ b/homeassistant/components/goalzero/translations/tr.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "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.", - "title": "Goal Zero Yeti" + "description": "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." }, "user": { "data": { "host": "Sunucu", "name": "Ad" }, - "description": "T\u00fcm gereksinimlerin kar\u015f\u0131land\u0131\u011f\u0131ndan emin olmak i\u00e7in l\u00fctfen belgelere bak\u0131n.", - "title": "Goal Zero Yeti" + "description": "T\u00fcm gereksinimlerin kar\u015f\u0131land\u0131\u011f\u0131ndan emin olmak i\u00e7in l\u00fctfen belgelere bak\u0131n." } } } diff --git a/homeassistant/components/goalzero/translations/uk.json b/homeassistant/components/goalzero/translations/uk.json index 6d67d949c28..6232a0af027 100644 --- a/homeassistant/components/goalzero/translations/uk.json +++ b/homeassistant/components/goalzero/translations/uk.json @@ -14,8 +14,7 @@ "host": "\u0425\u043e\u0441\u0442", "name": "\u041d\u0430\u0437\u0432\u0430" }, - "description": "\u0421\u043f\u043e\u0447\u0430\u0442\u043a\u0443 \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Goal Zero: https://www.goalzero.com/product-features/yeti-app/. \n\n \u0414\u043e\u0442\u0440\u0438\u043c\u0443\u0439\u0442\u0435\u0441\u044c \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439 \u043f\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044e Yeti \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456 WiFi. \u041f\u043e\u0442\u0456\u043c \u0434\u0456\u0437\u043d\u0430\u0439\u0442\u0435\u0441\u044f IP \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e, \u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434, \u0437 \u0412\u0430\u0448\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430. \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u0442\u0430\u043a\u0438\u043c\u0438, \u0449\u043e\u0431 IP \u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u0437\u043c\u0456\u043d\u044e\u0432\u0430\u043b\u0430\u0441\u044c \u0437 \u0447\u0430\u0441\u043e\u043c. \u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u0446\u0435 \u0437\u0440\u043e\u0431\u0438\u0442\u0438, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0432 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0457 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 \u0412\u0430\u0448\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430.", - "title": "Goal Zero Yeti" + "description": "\u0421\u043f\u043e\u0447\u0430\u0442\u043a\u0443 \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Goal Zero: https://www.goalzero.com/product-features/yeti-app/. \n\n \u0414\u043e\u0442\u0440\u0438\u043c\u0443\u0439\u0442\u0435\u0441\u044c \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439 \u043f\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044e Yeti \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456 WiFi. \u041f\u043e\u0442\u0456\u043c \u0434\u0456\u0437\u043d\u0430\u0439\u0442\u0435\u0441\u044f IP \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e, \u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434, \u0437 \u0412\u0430\u0448\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430. \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u0442\u0430\u043a\u0438\u043c\u0438, \u0449\u043e\u0431 IP \u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u0437\u043c\u0456\u043d\u044e\u0432\u0430\u043b\u0430\u0441\u044c \u0437 \u0447\u0430\u0441\u043e\u043c. \u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u0446\u0435 \u0437\u0440\u043e\u0431\u0438\u0442\u0438, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0432 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0457 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 \u0412\u0430\u0448\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430." } } } diff --git a/homeassistant/components/goalzero/translations/zh-Hant.json b/homeassistant/components/goalzero/translations/zh-Hant.json index 4f5b01fb9d4..2e5efb73350 100644 --- a/homeassistant/components/goalzero/translations/zh-Hant.json +++ b/homeassistant/components/goalzero/translations/zh-Hant.json @@ -12,16 +12,14 @@ }, "step": { "confirm_discovery": { - "description": "\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", - "title": "Goal Zero Yeti" + "description": "\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" }, "user": { "data": { "host": "\u4e3b\u6a5f\u7aef", "name": "\u540d\u7a31" }, - "description": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u6240\u6709\u5fc5\u8981\u9700\u6c42\u3002", - "title": "Goal Zero Yeti" + "description": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u6240\u6709\u5fc5\u8981\u9700\u6c42\u3002" } } } diff --git a/homeassistant/components/gogogate2/translations/ko.json b/homeassistant/components/gogogate2/translations/ko.json index dc37928db76..e8df5fa95dc 100644 --- a/homeassistant/components/gogogate2/translations/ko.json +++ b/homeassistant/components/gogogate2/translations/ko.json @@ -7,6 +7,7 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, + "flow_title": "{device} ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/gogogate2/translations/nl.json b/homeassistant/components/gogogate2/translations/nl.json index a32bb1af69b..c194a1c21da 100644 --- a/homeassistant/components/gogogate2/translations/nl.json +++ b/homeassistant/components/gogogate2/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie" }, "flow_title": "{device} ({ip_address})", diff --git a/homeassistant/components/airly/translations/nn.json b/homeassistant/components/goodwe/translations/ko.json similarity index 55% rename from homeassistant/components/airly/translations/nn.json rename to homeassistant/components/goodwe/translations/ko.json index 9cf2b5d70fb..9aaa9eacf20 100644 --- a/homeassistant/components/airly/translations/nn.json +++ b/homeassistant/components/goodwe/translations/ko.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "title": "Airly" + "description": "\uc778\ubc84\ud130\uc5d0 \uc5f0\uacb0" } } } diff --git a/homeassistant/components/goodwe/translations/nl.json b/homeassistant/components/goodwe/translations/nl.json index 8986006fdeb..cb4d0e7b0b7 100644 --- a/homeassistant/components/goodwe/translations/nl.json +++ b/homeassistant/components/goodwe/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang" + "already_in_progress": "De configuratie is momenteel al bezig" }, "error": { "connection_error": "Kan geen verbinding maken" diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py index 98eadead101..2a40bfe7043 100644 --- a/homeassistant/components/google/__init__.py +++ b/homeassistant/components/google/__init__.py @@ -10,13 +10,17 @@ from typing import Any import aiohttp from gcal_sync.api import GoogleCalendarService from gcal_sync.exceptions import ApiException -from gcal_sync.model import DateOrDatetime, Event +from gcal_sync.model import Calendar, DateOrDatetime, Event from oauth2client.file import Storage import voluptuous as vol from voluptuous.error import Error as VoluptuousError import yaml from homeassistant import config_entries +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_CLIENT_ID, @@ -39,12 +43,12 @@ 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 ApiAuthImpl, DeviceAuth +from .api import ApiAuthImpl, get_feature_access from .const import ( CONF_CALENDAR_ACCESS, DATA_CONFIG, DATA_SERVICE, + DEVICE_AUTH_IMPL, DISCOVER_CALENDAR, DOMAIN, FeatureAccess, @@ -83,7 +87,6 @@ NOTIFICATION_TITLE = "Google Calendar Setup" GROUP_NAME_ALL_CALENDARS = "Google Calendar Sensors" SERVICE_SCAN_CALENDARS = "scan_for_calendars" -SERVICE_FOUND_CALENDARS = "found_calendar" SERVICE_ADD_EVENT = "add_event" YAML_DEVICES = f"{DOMAIN}_calendars.yaml" @@ -94,18 +97,21 @@ PLATFORMS = ["calendar"] CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_CLIENT_SECRET): cv.string, - vol.Optional(CONF_TRACK_NEW, default=True): cv.boolean, - vol.Optional(CONF_CALENDAR_ACCESS, default="read_write"): cv.enum( - FeatureAccess - ), - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + vol.Optional(CONF_TRACK_NEW, default=True): cv.boolean, + vol.Optional(CONF_CALENDAR_ACCESS, default="read_write"): cv.enum( + FeatureAccess + ), + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) @@ -157,22 +163,28 @@ ADD_EVENT_SERVICE_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Google component.""" + if DOMAIN not in config: + return True + conf = config.get(DOMAIN, {}) hass.data[DOMAIN] = {DATA_CONFIG: conf} - config_flow.OAuth2FlowHandler.async_register_implementation( - hass, - DeviceAuth( + + if CONF_CLIENT_ID in conf and CONF_CLIENT_SECRET in conf: + await async_import_client_credential( hass, - conf[CONF_CLIENT_ID], - conf[CONF_CLIENT_SECRET], - ), - ) + DOMAIN, + ClientCredential( + conf[CONF_CLIENT_ID], + conf[CONF_CLIENT_SECRET], + ), + DEVICE_AUTH_IMPL, + ) # Import credentials from the old token file into the new way as # a ConfigEntry managed by home assistant. storage = Storage(hass.config.path(TOKEN_FILE)) creds = await hass.async_add_executor_job(storage.get) - if creds and conf[CONF_CALENDAR_ACCESS].scope in creds.scopes: + if creds and get_feature_access(hass).scope in creds.scopes: _LOGGER.debug("Importing configuration entry with credentials") hass.async_create_task( hass.config_entries.flow.async_init( @@ -183,11 +195,28 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: }, ) ) + + _LOGGER.warning( + "Configuration of Google Calendar in YAML in configuration.yaml is " + "is deprecated and will be removed in a future release; Your existing " + "OAuth Application Credentials and access settings have been imported " + "into the UI automatically and can be safely removed from your " + "configuration.yaml file" + ) + if conf.get(CONF_TRACK_NEW) is False: + # The track_new as False would previously result in new entries + # in google_calendars.yaml with track set to Fasle which is + # handled at calendar entity creation time. + _LOGGER.warning( + "You must manually set the integration System Options in the " + "UI to disable newly discovered entities going forward" + ) return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Google from a config entry.""" + hass.data.setdefault(DOMAIN, {}) implementation = ( await config_entry_oauth2_flow.async_get_config_entry_implementation( hass, entry @@ -210,8 +239,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except aiohttp.ClientError as err: raise ConfigEntryNotReady from err - required_scope = hass.data[DOMAIN][DATA_CONFIG][CONF_CALENDAR_ACCESS].scope - if required_scope not in session.token.get("scope", []): + if not async_entry_has_scopes(hass, entry): raise ConfigEntryAuthFailed( "Required scopes are not available, reauth required" ) @@ -220,50 +248,69 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) hass.data[DOMAIN][DATA_SERVICE] = calendar_service - await async_setup_services(hass, hass.data[DOMAIN][DATA_CONFIG], calendar_service) + await async_setup_services(hass, calendar_service) + # Only expose the add event service if we have the correct permissions + if get_feature_access(hass, entry) is FeatureAccess.read_write: + await async_setup_add_event_service(hass, calendar_service) hass.config_entries.async_setup_platforms(entry, PLATFORMS) + entry.async_on_unload(entry.add_update_listener(async_reload_entry)) + return True +def async_entry_has_scopes(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Verify that the config entry desired scope is present in the oauth token.""" + access = get_feature_access(hass, entry) + token_scopes = entry.data.get("token", {}).get("scope", []) + return access.scope in token_scopes + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Reload config entry if the access options change.""" + if not async_entry_has_scopes(hass, entry): + await hass.config_entries.async_reload(entry.entry_id) + + async def async_setup_services( hass: HomeAssistant, - config: ConfigType, calendar_service: GoogleCalendarService, ) -> None: """Set up the service listeners.""" - created_calendars = set() calendars = await hass.async_add_executor_job( load_config, hass.config.path(YAML_DEVICES) ) + calendars_file_lock = asyncio.Lock() - async def _found_calendar(call: ServiceCall) -> None: - calendar = get_calendar_info(hass, call.data) - calendar_id = calendar[CONF_CAL_ID] - - if calendar_id in created_calendars: - return - created_calendars.add(calendar_id) - - # Populate the yaml file with all discovered calendars - if calendar_id not in calendars: - calendars[calendar_id] = calendar - await hass.async_add_executor_job( - update_config, hass.config.path(YAML_DEVICES), calendar - ) - else: - # Prefer entity/name information from yaml, overriding api - calendar = calendars[calendar_id] + async def _found_calendar(calendar_item: Calendar) -> None: + calendar = get_calendar_info( + hass, + calendar_item.dict(exclude_unset=True), + ) + calendar_id = calendar_item.id + # If the google_calendars.yaml file already exists, populate it for + # backwards compatibility, but otherwise do not create it if it does + # not exist. + if calendars: + if calendar_id not in calendars: + calendars[calendar_id] = calendar + async with calendars_file_lock: + await hass.async_add_executor_job( + update_config, hass.config.path(YAML_DEVICES), calendar + ) + else: + # Prefer entity/name information from yaml, overriding api + calendar = calendars[calendar_id] async_dispatcher_send(hass, DISCOVER_CALENDAR, calendar) - hass.services.async_register(DOMAIN, SERVICE_FOUND_CALENDARS, _found_calendar) + created_calendars = set() async def _scan_for_calendars(call: ServiceCall) -> None: """Scan for new calendars.""" @@ -273,15 +320,21 @@ async def async_setup_services( raise HomeAssistantError(str(err)) from err tasks = [] 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) - ) + if calendar_item.id in created_calendars: + continue + created_calendars.add(calendar_item.id) + tasks.append(_found_calendar(calendar_item)) await asyncio.gather(*tasks) hass.services.async_register(DOMAIN, SERVICE_SCAN_CALENDARS, _scan_for_calendars) + +async def async_setup_add_event_service( + hass: HomeAssistant, + calendar_service: GoogleCalendarService, +) -> None: + """Add the service to add events.""" + async def _add_event(call: ServiceCall) -> None: """Add a new event to calendar.""" start: DateOrDatetime | None = None @@ -333,11 +386,9 @@ async def async_setup_services( ), ) - # Only expose the add event service if we have the correct permissions - if config.get(CONF_CALENDAR_ACCESS) is FeatureAccess.read_write: - hass.services.async_register( - DOMAIN, SERVICE_ADD_EVENT, _add_event, schema=ADD_EVENT_SERVICE_SCHEMA - ) + hass.services.async_register( + DOMAIN, SERVICE_ADD_EVENT, _add_event, schema=ADD_EVENT_SERVICE_SCHEMA + ) def get_calendar_info( @@ -349,7 +400,6 @@ def get_calendar_info( CONF_CAL_ID: calendar["id"], CONF_ENTITIES: [ { - CONF_TRACK: calendar["track"], CONF_NAME: calendar["summary"], CONF_DEVICE_ID: generate_entity_id( "{}", calendar["summary"], hass=hass diff --git a/homeassistant/components/google/api.py b/homeassistant/components/google/api.py index 5de563393e1..4bb9de5d581 100644 --- a/homeassistant/components/google/api.py +++ b/homeassistant/components/google/api.py @@ -6,11 +6,10 @@ from collections.abc import Awaitable, Callable import datetime import logging import time -from typing import Any +from typing import Any, cast import aiohttp from gcal_sync.auth import AbstractAuth -import oauth2client from oauth2client.client import ( Credentials, DeviceFlowInfo, @@ -19,41 +18,38 @@ from oauth2client.client import ( OAuth2WebServerFlow, ) -from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.components.application_credentials import AuthImplementation +from homeassistant.config_entries import ConfigEntry from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import dt -from .const import CONF_CALENDAR_ACCESS, DATA_CONFIG, DEVICE_AUTH_IMPL, DOMAIN +from .const import ( + CONF_CALENDAR_ACCESS, + DATA_CONFIG, + DEFAULT_FEATURE_ACCESS, + DOMAIN, + FeatureAccess, +) _LOGGER = logging.getLogger(__name__) EVENT_PAGE_SIZE = 100 EXCHANGE_TIMEOUT_SECONDS = 60 +DEVICE_AUTH_CREDS = "creds" class OAuthError(Exception): """OAuth related error.""" -class DeviceAuth(config_entry_oauth2_flow.LocalOAuth2Implementation): +class DeviceAuth(AuthImplementation): """OAuth implementation for Device Auth.""" - def __init__(self, hass: HomeAssistant, client_id: str, client_secret: str) -> None: - """Initialize InstalledAppAuth.""" - super().__init__( - hass, - DEVICE_AUTH_IMPL, - client_id, - client_secret, - oauth2client.GOOGLE_AUTH_URI, - oauth2client.GOOGLE_TOKEN_URI, - ) - async def async_resolve_external_data(self, external_data: Any) -> dict: """Resolve a Google API Credentials object to Home Assistant token.""" - creds: Credentials = external_data["creds"] + creds: Credentials = external_data[DEVICE_AUTH_CREDS] return { "access_token": creds.access_token, "refresh_token": creds.refresh_token, @@ -81,12 +77,12 @@ class DeviceFlow: @property def verification_url(self) -> str: """Return the verification url that the user should visit to enter the code.""" - return self._device_flow_info.verification_url + return self._device_flow_info.verification_url # type: ignore[no-any-return] @property def user_code(self) -> str: """Return the code that the user should enter at the verification url.""" - return self._device_flow_info.user_code + return self._device_flow_info.user_code # type: ignore[no-any-return] async def start_exchange_task( self, finished_cb: Callable[[Credentials | None], Awaitable[None]] @@ -132,13 +128,37 @@ class DeviceFlow: ) -async def async_create_device_flow(hass: HomeAssistant) -> DeviceFlow: +def get_feature_access( + hass: HomeAssistant, config_entry: ConfigEntry | None = None +) -> FeatureAccess: + """Return the desired calendar feature access.""" + if ( + config_entry + and config_entry.options + and CONF_CALENDAR_ACCESS in config_entry.options + ): + return FeatureAccess[config_entry.options[CONF_CALENDAR_ACCESS]] + + # This may be called during config entry setup without integration setup running when there + # is no google entry in configuration.yaml + return cast( + FeatureAccess, + ( + hass.data.get(DOMAIN, {}) + .get(DATA_CONFIG, {}) + .get(CONF_CALENDAR_ACCESS, DEFAULT_FEATURE_ACCESS) + ), + ) + + +async def async_create_device_flow( + hass: HomeAssistant, client_id: str, client_secret: str, access: FeatureAccess +) -> DeviceFlow: """Create a new Device flow.""" - conf = hass.data[DOMAIN][DATA_CONFIG] oauth_flow = OAuth2WebServerFlow( - client_id=conf[CONF_CLIENT_ID], - client_secret=conf[CONF_CLIENT_SECRET], - scope=conf[CONF_CALENDAR_ACCESS].scope, + client_id=client_id, + client_secret=client_secret, + scope=access.scope, redirect_uri="", ) try: @@ -150,7 +170,7 @@ async def async_create_device_flow(hass: HomeAssistant) -> DeviceFlow: return DeviceFlow(hass, oauth_flow, device_flow_info) -class ApiAuthImpl(AbstractAuth): +class ApiAuthImpl(AbstractAuth): # type: ignore[misc] """Authentication implementation for google calendar api library.""" def __init__( @@ -165,4 +185,26 @@ class ApiAuthImpl(AbstractAuth): async def async_get_access_token(self) -> str: """Return a valid access token.""" await self._session.async_ensure_token_valid() - return self._session.token["access_token"] + return cast(str, self._session.token["access_token"]) + + +class AccessTokenAuthImpl(AbstractAuth): # type: ignore[misc] + """Authentication implementation used during config flow, without refresh. + + This exists to allow the config flow to use the API before it has fully + created a config entry required by OAuth2Session. This does not support + refreshing tokens, which is fine since it should have been just created. + """ + + def __init__( + self, + websession: aiohttp.ClientSession, + access_token: str, + ) -> None: + """Init the Google Calendar client library auth implementation.""" + super().__init__(websession) + self._access_token = access_token + + async def async_get_access_token(self) -> str: + """Return the access token.""" + return self._access_token diff --git a/homeassistant/components/google/application_credentials.py b/homeassistant/components/google/application_credentials.py new file mode 100644 index 00000000000..2f1fcba8084 --- /dev/null +++ b/homeassistant/components/google/application_credentials.py @@ -0,0 +1,23 @@ +"""application_credentials platform for nest.""" + +import oauth2client + +from homeassistant.components.application_credentials import ( + AuthorizationServer, + ClientCredential, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow + +from .api import DeviceAuth + +AUTHORIZATION_SERVER = AuthorizationServer( + oauth2client.GOOGLE_AUTH_URI, oauth2client.GOOGLE_TOKEN_URI +) + + +async def async_get_auth_implementation( + hass: HomeAssistant, auth_domain: str, credential: ClientCredential +) -> config_entry_oauth2_flow.AbstractOAuth2Implementation: + """Return auth implementation.""" + return DeviceAuth(hass, auth_domain, credential, AUTHORIZATION_SERVER) diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index b0d13c0c0c6..ba4368fefae 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -1,4 +1,5 @@ """Support for Google Calendar Search binary sensors.""" + from __future__ import annotations import copy @@ -89,14 +90,30 @@ def _async_setup_entities( ) -> None: calendar_service = hass.data[DOMAIN][DATA_SERVICE] entities = [] + num_entities = len(disc_info[CONF_ENTITIES]) for data in disc_info[CONF_ENTITIES]: - if not data[CONF_TRACK]: - continue - entity_id = generate_entity_id( - ENTITY_ID_FORMAT, data[CONF_DEVICE_ID], hass=hass - ) + entity_enabled = data.get(CONF_TRACK, True) + if not entity_enabled: + _LOGGER.warning( + "The 'track' option in google_calendars.yaml has been deprecated. The setting " + "has been imported to the UI, and should now be removed from google_calendars.yaml" + ) + entity_name = data[CONF_DEVICE_ID] + entity_id = generate_entity_id(ENTITY_ID_FORMAT, entity_name, hass=hass) + calendar_id = disc_info[CONF_CAL_ID] + if num_entities > 1: + # The google_calendars.yaml file lets users add multiple entities for + # the same calendar id and needs additional disambiguation + unique_id = f"{calendar_id}-{entity_name}" + else: + unique_id = calendar_id entity = GoogleCalendarEntity( - calendar_service, disc_info[CONF_CAL_ID], data, entity_id + calendar_service, + disc_info[CONF_CAL_ID], + data, + entity_id, + unique_id, + entity_enabled, ) entities.append(entity) @@ -112,6 +129,8 @@ class GoogleCalendarEntity(CalendarEntity): calendar_id: str, data: dict[str, Any], entity_id: str, + unique_id: str, + entity_enabled: bool, ) -> None: """Create the Calendar event device.""" self._calendar_service = calendar_service @@ -123,6 +142,8 @@ class GoogleCalendarEntity(CalendarEntity): self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET) self._offset_value: timedelta | None = None self.entity_id = entity_id + self._attr_unique_id = unique_id + self._attr_entity_registry_enabled_default = entity_enabled @property def extra_state_attributes(self) -> dict[str, bool]: @@ -152,7 +173,7 @@ class GoogleCalendarEntity(CalendarEntity): """Return True if the event is visible.""" if self._ignore_availability: return True - return event.transparency == OPAQUE + return event.transparency == OPAQUE # type: ignore[no-any-return] async def async_get_events( self, hass: HomeAssistant, start_date: datetime, end_date: datetime diff --git a/homeassistant/components/google/config_flow.py b/homeassistant/components/google/config_flow.py index 8bbd2a6c2b1..be516230d2b 100644 --- a/homeassistant/components/google/config_flow.py +++ b/homeassistant/components/google/config_flow.py @@ -4,13 +4,27 @@ from __future__ import annotations import logging from typing import Any +from gcal_sync.api import GoogleCalendarService +from gcal_sync.exceptions import ApiException from oauth2client.client import Credentials +import voluptuous as vol +from homeassistant import config_entries +from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow +from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .api import DeviceFlow, OAuthError, async_create_device_flow -from .const import DOMAIN +from .api import ( + DEVICE_AUTH_CREDS, + AccessTokenAuthImpl, + DeviceAuth, + DeviceFlow, + OAuthError, + async_create_device_flow, + get_feature_access, +) +from .const import CONF_CALENDAR_ACCESS, DOMAIN, FeatureAccess _LOGGER = logging.getLogger(__name__) @@ -25,7 +39,7 @@ class OAuth2FlowHandler( def __init__(self) -> None: """Set up instance.""" super().__init__() - self._reauth = False + self._reauth_config_entry: config_entries.ConfigEntry | None = None self._device_flow: DeviceFlow | None = None @property @@ -49,7 +63,7 @@ class OAuth2FlowHandler( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle external yaml configuration.""" - if not self._reauth and self._async_current_entries(): + if not self._reauth_config_entry and self._async_current_entries(): return self.async_abort(reason="already_configured") return await super().async_step_user(user_input) @@ -67,15 +81,33 @@ class OAuth2FlowHandler( if not self._device_flow: _LOGGER.debug("Creating DeviceAuth flow") + if not isinstance(self.flow_impl, DeviceAuth): + _LOGGER.error( + "Unexpected OAuth implementation does not support device auth: %s", + self.flow_impl, + ) + return self.async_abort(reason="oauth_error") + calendar_access = get_feature_access(self.hass) + if self._reauth_config_entry and self._reauth_config_entry.options: + calendar_access = FeatureAccess[ + self._reauth_config_entry.options[CONF_CALENDAR_ACCESS] + ] try: - device_flow = await async_create_device_flow(self.hass) + device_flow = await async_create_device_flow( + self.hass, + self.flow_impl.client_id, + self.flow_impl.client_secret, + calendar_access, + ) except OAuthError as err: _LOGGER.error("Error initializing device flow: %s", str(err)) return self.async_abort(reason="oauth_error") self._device_flow = device_flow async def _exchange_finished(creds: Credentials | None) -> None: - self.external_data = {"creds": creds} # is None on timeout/expiration + self.external_data = { + DEVICE_AUTH_CREDS: creds + } # is None on timeout/expiration self.hass.async_create_task( self.hass.config_entries.flow.async_configure( flow_id=self.flow_id, user_input={} @@ -97,7 +129,7 @@ class OAuth2FlowHandler( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle external yaml configuration.""" - if self.external_data.get("creds") is None: + if self.external_data.get(DEVICE_AUTH_CREDS) is None: return self.async_abort(reason="code_expired") return await super().async_step_creation(user_input) @@ -110,13 +142,33 @@ class OAuth2FlowHandler( self.hass.config_entries.async_update_entry(entry, data=data) await self.hass.config_entries.async_reload(entry.entry_id) return self.async_abort(reason="reauth_successful") - return self.async_create_entry(title=self.flow_impl.name, data=data) + + calendar_service = GoogleCalendarService( + AccessTokenAuthImpl( + async_get_clientsession(self.hass), data["token"]["access_token"] + ) + ) + try: + primary_calendar = await calendar_service.async_get_calendar("primary") + except ApiException as err: + _LOGGER.debug("Error reading calendar primary calendar: %s", err) + primary_calendar = None + title = primary_calendar.id if primary_calendar else self.flow_impl.name + return self.async_create_entry( + title=title, + data=data, + options={ + CONF_CALENDAR_ACCESS: get_feature_access(self.hass).name, + }, + ) async def async_step_reauth( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Perform reauth upon an API authentication error.""" - self._reauth = True + self._reauth_config_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( @@ -126,3 +178,43 @@ class OAuth2FlowHandler( if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() + + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> config_entries.OptionsFlow: + """Create an options flow.""" + return OptionsFlowHandler(config_entry) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Google Calendar options flow.""" + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Required( + CONF_CALENDAR_ACCESS, + default=self.config_entry.options.get(CONF_CALENDAR_ACCESS), + ): vol.In( + { + "read_write": "Read/Write access (can create events)", + "read_only": "Read-only access", + } + ) + } + ), + ) diff --git a/homeassistant/components/google/const.py b/homeassistant/components/google/const.py index d5cdabb0638..c01ff1ea48b 100644 --- a/homeassistant/components/google/const.py +++ b/homeassistant/components/google/const.py @@ -28,3 +28,6 @@ class FeatureAccess(Enum): def scope(self) -> str: """Google calendar scope for the feature.""" return self._scope + + +DEFAULT_FEATURE_ACCESS = FeatureAccess.read_write diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index 2cf852fc6af..081eae34a95 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -2,9 +2,9 @@ "domain": "google", "name": "Google Calendars", "config_flow": true, - "dependencies": ["auth"], + "dependencies": ["application_credentials"], "documentation": "https://www.home-assistant.io/integrations/calendar.google/", - "requirements": ["gcal-sync==0.7.1", "oauth2client==4.1.3"], + "requirements": ["gcal-sync==0.9.0", "oauth2client==4.1.3"], "codeowners": ["@allenporter"], "iot_class": "cloud_polling", "loggers": ["googleapiclient"] diff --git a/homeassistant/components/google/services.yaml b/homeassistant/components/google/services.yaml index ff053f1be33..21df763374f 100644 --- a/homeassistant/components/google/services.yaml +++ b/homeassistant/components/google/services.yaml @@ -1,6 +1,3 @@ -found_calendar: - name: Found Calendar - description: Add calendar if it has not been already discovered. scan_for_calendars: name: Scan for calendars description: Scan for new calendars. diff --git a/homeassistant/components/google/strings.json b/homeassistant/components/google/strings.json index e8ec7091030..e32223627be 100644 --- a/homeassistant/components/google/strings.json +++ b/homeassistant/components/google/strings.json @@ -27,5 +27,14 @@ "progress": { "exchange": "To link your Google account, visit the [{url}]({url}) and enter code:\n\n{user_code}" } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Home Assistant access to Google Calendar" + } + } + } } } diff --git a/homeassistant/components/google/translations/ca.json b/homeassistant/components/google/translations/ca.json index 829ce1413d5..2c9190e4bfd 100644 --- a/homeassistant/components/google/translations/ca.json +++ b/homeassistant/components/google/translations/ca.json @@ -27,5 +27,14 @@ "title": "Reautenticaci\u00f3 de la integraci\u00f3" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Acc\u00e9s de Home Assistant a Google Calendar" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/de.json b/homeassistant/components/google/translations/de.json index 1b15c465497..c75cc90eb13 100644 --- a/homeassistant/components/google/translations/de.json +++ b/homeassistant/components/google/translations/de.json @@ -27,5 +27,14 @@ "title": "Integration erneut authentifizieren" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Home Assistant-Zugriff auf Google Kalender" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/en.json b/homeassistant/components/google/translations/en.json index 02c8e6d7029..58c89834ca5 100644 --- a/homeassistant/components/google/translations/en.json +++ b/homeassistant/components/google/translations/en.json @@ -27,5 +27,14 @@ "title": "Reauthenticate Integration" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Home Assistant access to Google Calendar" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/es.json b/homeassistant/components/google/translations/es.json index 8072ac95d4b..c6d990c2caa 100644 --- a/homeassistant/components/google/translations/es.json +++ b/homeassistant/components/google/translations/es.json @@ -1,11 +1,19 @@ { "config": { "abort": { - "missing_configuration": "El componente no est\u00e1 configurado. Por favor, sigue la documentaci\u00f3n." + "missing_configuration": "El componente no est\u00e1 configurado. Por favor, sigue la documentaci\u00f3n.", + "oauth_error": "Se han recibido datos token inv\u00e1lidos.", + "reauth_successful": "Re-autenticaci\u00f3n realizada correctamente" + }, + "create_entry": { + "default": "Autenticaci\u00f3n exitosa" }, "step": { "auth": { "title": "Vincular cuenta de Google" + }, + "pick_implementation": { + "title": "Selecciona el m\u00e9todo de autenticaci\u00f3n" } } } diff --git a/homeassistant/components/google/translations/et.json b/homeassistant/components/google/translations/et.json index 27dfa3f5290..a115378f3a2 100644 --- a/homeassistant/components/google/translations/et.json +++ b/homeassistant/components/google/translations/et.json @@ -27,5 +27,14 @@ "title": "Taastuvasta sidumine" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Home Assistanti juurdep\u00e4\u00e4s Google'i kalendrile" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/fr.json b/homeassistant/components/google/translations/fr.json index 1224ba76c9b..06903bdff07 100644 --- a/homeassistant/components/google/translations/fr.json +++ b/homeassistant/components/google/translations/fr.json @@ -27,5 +27,14 @@ "title": "R\u00e9-authentifier l'int\u00e9gration" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Acc\u00e8s de Home Assistant \u00e0 Google Agenda" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/hu.json b/homeassistant/components/google/translations/hu.json index 2c552eca30e..66ea71c72ec 100644 --- a/homeassistant/components/google/translations/hu.json +++ b/homeassistant/components/google/translations/hu.json @@ -27,5 +27,14 @@ "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Home Assistant hozz\u00e1f\u00e9r\u00e9s a Google Napt\u00e1rhoz" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/id.json b/homeassistant/components/google/translations/id.json index 371fa7551b5..25c3e9fa1e6 100644 --- a/homeassistant/components/google/translations/id.json +++ b/homeassistant/components/google/translations/id.json @@ -3,7 +3,7 @@ "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.", + "code_expired": "Kode autentikasi kedaluwarsa atau penyiapan kredensial tidak valid, 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.", @@ -27,5 +27,14 @@ "title": "Autentikasi Ulang Integrasi" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Akses Home Assistant ke Google Kalender" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/it.json b/homeassistant/components/google/translations/it.json index 4530a690ed6..d57998894d9 100644 --- a/homeassistant/components/google/translations/it.json +++ b/homeassistant/components/google/translations/it.json @@ -27,5 +27,14 @@ "title": "Autentica nuovamente l'integrazione" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Accesso di Home Assistant a Google Calendar" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/ja.json b/homeassistant/components/google/translations/ja.json index 7eab5abc6f6..854e7ba1961 100644 --- a/homeassistant/components/google/translations/ja.json +++ b/homeassistant/components/google/translations/ja.json @@ -23,9 +23,18 @@ "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", + "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", "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Home Assistant\u304b\u3089\u3001Google\u30ab\u30ec\u30f3\u30c0\u30fc\u3078\u306e\u30a2\u30af\u30bb\u30b9" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/ko.json b/homeassistant/components/google/translations/ko.json new file mode 100644 index 00000000000..e4fc1875308 --- /dev/null +++ b/homeassistant/components/google/translations/ko.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." + }, + "step": { + "reauth_confirm": { + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/nl.json b/homeassistant/components/google/translations/nl.json index e732fd7cd19..419259d66f8 100644 --- a/homeassistant/components/google/translations/nl.json +++ b/homeassistant/components/google/translations/nl.json @@ -2,15 +2,15 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "already_in_progress": "Configuratie flow is al in bewerking", + "already_in_progress": "De configuratie is momenteel al bezig", "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" + "invalid_access_token": "Ongeldig toegangstoken", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "oauth_error": "Ongeldige tokengegevens ontvangen.", + "reauth_successful": "Herauthenticatie geslaagd" }, "create_entry": { - "default": "Authenticatie succesvol" + "default": "Authenticatie geslaagd" }, "progress": { "exchange": "Om uw Google-account te koppelen, gaat u naar de [ {url} ]( {url} ) en voert u de code in: \n\n {user_code}" @@ -24,7 +24,16 @@ }, "reauth_confirm": { "description": "De Google Agenda-integratie moet uw account opnieuw verifi\u00ebren", - "title": "Herauthentiseer integratie" + "title": "Integratie herauthenticeren" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Home Assistant-toegang tot Google Agenda" + } } } } diff --git a/homeassistant/components/google/translations/no.json b/homeassistant/components/google/translations/no.json index 4065583192c..ef65c7fe9a5 100644 --- a/homeassistant/components/google/translations/no.json +++ b/homeassistant/components/google/translations/no.json @@ -27,5 +27,14 @@ "title": "Godkjenne integrering p\u00e5 nytt" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Home Assistant tilgang til Google Kalender" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/pl.json b/homeassistant/components/google/translations/pl.json index b4f45c0abe4..fff2a20ee39 100644 --- a/homeassistant/components/google/translations/pl.json +++ b/homeassistant/components/google/translations/pl.json @@ -27,5 +27,14 @@ "title": "Ponownie uwierzytelnij integracj\u0119" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Dost\u0119p Home Assistanta do Kalendarza Google" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/pt-BR.json b/homeassistant/components/google/translations/pt-BR.json index 8ab124f1b5c..85c7254b9a7 100644 --- a/homeassistant/components/google/translations/pt-BR.json +++ b/homeassistant/components/google/translations/pt-BR.json @@ -27,5 +27,14 @@ "title": "Reautenticar Integra\u00e7\u00e3o" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Acesso do Home Assistant ao Google Calendar" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/ru.json b/homeassistant/components/google/translations/ru.json index a7db4b2f48b..51badc7226d 100644 --- a/homeassistant/components/google/translations/ru.json +++ b/homeassistant/components/google/translations/ru.json @@ -27,5 +27,14 @@ "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "\u0414\u043e\u0441\u0442\u0443\u043f Home Assistant \u043a Google \u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u044e" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/tr.json b/homeassistant/components/google/translations/tr.json index 9d5fc8d2416..f7b5b6d79ff 100644 --- a/homeassistant/components/google/translations/tr.json +++ b/homeassistant/components/google/translations/tr.json @@ -27,5 +27,14 @@ "title": "Entegrasyonu Yeniden Do\u011frula" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Google Takvim'e Home Assistant eri\u015fimi" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/zh-Hant.json b/homeassistant/components/google/translations/zh-Hant.json index 70e7d81c01e..988c6629af7 100644 --- a/homeassistant/components/google/translations/zh-Hant.json +++ b/homeassistant/components/google/translations/zh-Hant.json @@ -27,5 +27,14 @@ "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" } } + }, + "options": { + "step": { + "init": { + "data": { + "calendar_access": "Home Assistant \u5b58\u53d6 Google Calendar" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index 529778ce1b6..15a8d832403 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -10,6 +10,7 @@ import logging import pprint from aiohttp.web import json_response +from awesomeversion import AwesomeVersion from homeassistant.components import webhook from homeassistant.const import ( @@ -43,6 +44,8 @@ from .error import SmartHomeError SYNC_DELAY = 15 _LOGGER = logging.getLogger(__name__) +LOCAL_SDK_VERSION_HEADER = "HA-Cloud-Version" +LOCAL_SDK_MIN_VERSION = AwesomeVersion("2.1.5") @callback @@ -86,6 +89,7 @@ class AbstractConfig(ABC): self._google_sync_unsub = {} self._local_sdk_active = False self._local_last_active: datetime | None = None + self._local_sdk_version_warn = False async def async_initialize(self): """Perform async initialization of config.""" @@ -209,6 +213,9 @@ class AbstractConfig(ABC): async def async_sync_entities_all(self): """Sync all entities to Google for all registered agents.""" + if not self._store.agent_user_ids: + return 204 + res = await gather( *( self.async_sync_entities(agent_user_id) @@ -327,6 +334,18 @@ class AbstractConfig(ABC): from . import smart_home self._local_last_active = utcnow() + + # Check version local SDK. + version = request.headers.get("HA-Cloud-Version") + if not self._local_sdk_version_warn and ( + not version or AwesomeVersion(version) < LOCAL_SDK_MIN_VERSION + ): + _LOGGER.warning( + "Local SDK version is too old (%s), check documentation on how to update to the latest version", + version, + ) + self._local_sdk_version_warn = True + payload = await request.json() if _LOGGER.isEnabledFor(logging.DEBUG): @@ -577,8 +596,9 @@ class GoogleEntity: device["customData"] = { "webhookId": self.config.get_local_webhook_id(agent_user_id), "httpPort": self.hass.http.server_port, - "httpSSL": self.hass.config.api.use_ssl, "uuid": instance_uuid, + # Below can be removed in HA 2022.9 + "httpSSL": self.hass.config.api.use_ssl, "baseUrl": get_url(self.hass, prefer_external=True), "proxyDeviceId": agent_user_id, } diff --git a/homeassistant/components/google_assistant/logbook.py b/homeassistant/components/google_assistant/logbook.py index 86caa8a9e6c..0ed5745004d 100644 --- a/homeassistant/components/google_assistant/logbook.py +++ b/homeassistant/components/google_assistant/logbook.py @@ -1,4 +1,8 @@ """Describe logbook events.""" +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) from homeassistant.core import callback from .const import DOMAIN, EVENT_COMMAND_RECEIVED, SOURCE_CLOUD @@ -25,6 +29,6 @@ def async_describe_events(hass, async_describe_event): if event.data["source"] != SOURCE_CLOUD: message += f" (via {event.data['source']})" - return {"name": "Google Assistant", "message": message} + return {LOGBOOK_ENTRY_NAME: "Google Assistant", LOGBOOK_ENTRY_MESSAGE: message} async_describe_event(DOMAIN, EVENT_COMMAND_RECEIVED, async_describe_logbook_event) diff --git a/homeassistant/components/google_assistant/report_state.py b/homeassistant/components/google_assistant/report_state.py index c3f8ba3bffd..4e8ac1624cc 100644 --- a/homeassistant/components/google_assistant/report_state.py +++ b/homeassistant/components/google_assistant/report_state.py @@ -6,7 +6,7 @@ import logging from homeassistant.const import MATCH_ALL from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback -from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.event import async_call_later, async_track_state_change from homeassistant.helpers.significant_change import create_checker from .const import DOMAIN @@ -136,9 +136,7 @@ def async_enable_report_state(hass: HomeAssistant, google_config: AbstractConfig await google_config.async_report_state_all({"devices": {"states": entities}}) - unsub = hass.helpers.event.async_track_state_change( - MATCH_ALL, async_entity_state_listener - ) + unsub = async_track_state_change(hass, MATCH_ALL, async_entity_state_listener) unsub = async_call_later(hass, INITIAL_REPORT_DELAY, initial_report) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 20191c61668..42fc43197ea 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -249,7 +249,7 @@ class BrightnessTrait(_Trait): if domain == light.DOMAIN: brightness = self.state.attributes.get(light.ATTR_BRIGHTNESS) if brightness is not None: - response["brightness"] = int(100 * (brightness / 255)) + response["brightness"] = round(100 * (brightness / 255)) else: response["brightness"] = 0 @@ -1948,7 +1948,7 @@ class VolumeTrait(_Trait): level = self.state.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL) if level is not None: # Convert 0.0-1.0 to 0-100 - response["currentVolume"] = int(level * 100) + response["currentVolume"] = round(level * 100) muted = self.state.attributes.get(media_player.ATTR_MEDIA_VOLUME_MUTED) if muted is not None: diff --git a/homeassistant/components/google_domains/__init__.py b/homeassistant/components/google_domains/__init__.py index 8ccc9d78c64..c7f7e632bd6 100644 --- a/homeassistant/components/google_domains/__init__.py +++ b/homeassistant/components/google_domains/__init__.py @@ -11,6 +11,7 @@ from homeassistant.const import CONF_DOMAIN, CONF_PASSWORD, CONF_TIMEOUT, CONF_U from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -56,7 +57,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Update the Google Domains entry.""" await _update_google_domains(hass, session, domain, user, password, timeout) - hass.helpers.event.async_track_time_interval(update_domain_interval, INTERVAL) + async_track_time_interval(hass, update_domain_interval, INTERVAL) return True diff --git a/homeassistant/components/google_travel_time/translations/fr.json b/homeassistant/components/google_travel_time/translations/fr.json index 790fe9117bd..86761315d58 100644 --- a/homeassistant/components/google_travel_time/translations/fr.json +++ b/homeassistant/components/google_travel_time/translations/fr.json @@ -24,7 +24,7 @@ "data": { "avoid": "\u00c9viter de", "language": "Langue", - "mode": "Mode voyage", + "mode": "Mode de d\u00e9placement", "time": "Temps", "time_type": "Type de temps", "transit_mode": "Mode de transit", diff --git a/homeassistant/components/google_travel_time/translations/id.json b/homeassistant/components/google_travel_time/translations/id.json index 16b60148aa9..e960d03c341 100644 --- a/homeassistant/components/google_travel_time/translations/id.json +++ b/homeassistant/components/google_travel_time/translations/id.json @@ -14,7 +14,7 @@ "name": "Nama", "origin": "Asal" }, - "description": "Saat menentukan asal dan tujuan, Anda dapat menyediakan satu atau beberapa lokasi yang dipisahkan oleh karakter pipe, dalam bentuk alamat, koordinat lintang/bujur, atau ID tempat Google. Saat menentukan lokasi menggunakan ID tempat Google, ID harus diawali dengan \"place_id:'." + "description": "Saat menentukan asal dan tujuan, Anda dapat menyediakan satu atau beberapa lokasi yang dipisahkan oleh karakter pipe, dalam bentuk alamat, koordinat lintang/bujur, atau ID tempat Google. Saat menentukan lokasi menggunakan ID tempat Google, ID harus diawali dengan `place_id:`." } } }, @@ -31,7 +31,7 @@ "transit_routing_preference": "Preferensi Perutean Transit", "units": "Unit" }, - "description": "Anda dapat menentukan Waktu Keberangkatan atau Waktu Kedatangan secara opsional. Jika menentukan waktu keberangkatan, Anda dapat memasukkan 'sekarang', stempel waktu Unix, atau string waktu 24 jam seperti 08:00:00`. Jika menentukan waktu kedatangan, Anda dapat menggunakan stempel waktu Unix atau string waktu 24 jam seperti 08:00:00`" + "description": "Anda dapat menentukan Waktu Keberangkatan atau Waktu Kedatangan secara opsional. Jika menentukan waktu keberangkatan, Anda dapat memasukkan `now`, stempel waktu Unix, atau string waktu 24 jam seperti `08:00:00`. Jika menentukan waktu kedatangan, Anda dapat menggunakan stempel waktu Unix atau string waktu 24 jam seperti `08:00:00`." } } }, diff --git a/homeassistant/components/google_travel_time/translations/ja.json b/homeassistant/components/google_travel_time/translations/ja.json index 2fb8ae2883c..002a9cdd3b0 100644 --- a/homeassistant/components/google_travel_time/translations/ja.json +++ b/homeassistant/components/google_travel_time/translations/ja.json @@ -12,7 +12,7 @@ "api_key": "API\u30ad\u30fc", "destination": "\u76ee\u7684\u5730", "name": "\u540d\u524d", - "origin": "\u30aa\u30ea\u30b8\u30f3" + "origin": "\u539f\u70b9(Origin)" }, "description": "\u51fa\u767a\u5730\u3068\u76ee\u7684\u5730\u3092\u6307\u5b9a\u3059\u308b\u5834\u5408\u3001\u4f4f\u6240\u3001\u7def\u5ea6/\u7d4c\u5ea6\u306e\u5ea7\u6a19\u3001\u307e\u305f\u306fGoogle place ID\u306e\u5f62\u5f0f\u3067\u3001\u30d1\u30a4\u30d7\u6587\u5b57\u3067\u533a\u5207\u3089\u308c\u305f1\u3064\u4ee5\u4e0a\u306e\u5834\u6240\u3092\u6307\u5b9a\u3067\u304d\u307e\u3059\u3002Google place ID\u3092\u4f7f\u7528\u3057\u3066\u5834\u6240\u3092\u6307\u5b9a\u3059\u308b\u5834\u5408\u3001ID\u306e\u524d\u306b\u3001`place_id:` \u3092\u4ed8\u3051\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" } diff --git a/homeassistant/components/google_travel_time/translations/nl.json b/homeassistant/components/google_travel_time/translations/nl.json index cb43d8afeae..bb088c97ca5 100644 --- a/homeassistant/components/google_travel_time/translations/nl.json +++ b/homeassistant/components/google_travel_time/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken" diff --git a/homeassistant/components/gpslogger/device_tracker.py b/homeassistant/components/gpslogger/device_tracker.py index fb0e1385fba..ea648ed7495 100644 --- a/homeassistant/components/gpslogger/device_tracker.py +++ b/homeassistant/components/gpslogger/device_tracker.py @@ -45,7 +45,7 @@ async def async_setup_entry( ] = async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) # Restore previously loaded devices - dev_reg = await device_registry.async_get_registry(hass) + dev_reg = device_registry.async_get(hass) dev_ids = { identifier[1] for device in dev_reg.devices.values() diff --git a/homeassistant/components/gpslogger/translations/es.json b/homeassistant/components/gpslogger/translations/es.json index 733871c5ee7..ea3221ee2f5 100644 --- a/homeassistant/components/gpslogger/translations/es.json +++ b/homeassistant/components/gpslogger/translations/es.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No conectado a Home Assistant Cloud.", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n.", "webhook_not_internet_accessible": "Tu instancia de Home Assistant debe estar accesible desde Internet para recibir mensajes webhook." }, diff --git a/homeassistant/components/gpslogger/translations/fr.json b/homeassistant/components/gpslogger/translations/fr.json index 4e207dddfcd..21896884391 100644 --- a/homeassistant/components/gpslogger/translations/fr.json +++ b/homeassistant/components/gpslogger/translations/fr.json @@ -10,7 +10,7 @@ }, "step": { "user": { - "description": "\u00cates-vous s\u00fbr de vouloir configurer le Webhook GPSLogger ?", + "description": "Voulez-vous vraiment configurer le webhook GPSLogger\u00a0?", "title": "Configurer le Webhook GPSLogger" } } diff --git a/homeassistant/components/gpslogger/translations/nl.json b/homeassistant/components/gpslogger/translations/nl.json index 26bfba4eaea..f2b5d3158ad 100644 --- a/homeassistant/components/gpslogger/translations/nl.json +++ b/homeassistant/components/gpslogger/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." + "webhook_not_internet_accessible": "Je Home Assistant-instantie moet toegankelijk zijn vanaf internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Om evenementen naar Home Assistant te verzenden, moet u de webhook-functie instellen in GPSLogger. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ( {docs_url} ) voor meer informatie." diff --git a/homeassistant/components/gree/translations/nl.json b/homeassistant/components/gree/translations/nl.json index 0671f0b3674..6fc4a03e824 100644 --- a/homeassistant/components/gree/translations/nl.json +++ b/homeassistant/components/gree/translations/nl.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 9627ad86734..e6d7e91f035 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -33,6 +33,7 @@ from homeassistant.helpers.entity import Entity, async_generate_entity_id from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, async_process_integration_platforms, ) from homeassistant.helpers.reload import async_reload_integration_platforms @@ -265,6 +266,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if DOMAIN not in hass.data: hass.data[DOMAIN] = EntityComponent(_LOGGER, DOMAIN, hass) + await async_process_integration_platform_for_component(hass, DOMAIN) + component: EntityComponent = hass.data[DOMAIN] hass.data[REG_KEY] = GroupIntegrationRegistry() diff --git a/homeassistant/components/group/recorder.py b/homeassistant/components/group/recorder.py new file mode 100644 index 00000000000..9138b4ef348 --- /dev/null +++ b/homeassistant/components/group/recorder.py @@ -0,0 +1,16 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.core import HomeAssistant, callback + +from . import ATTR_AUTO, ATTR_ENTITY_ID, ATTR_ORDER + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude static attributes from being recorded in the database.""" + return { + ATTR_ENTITY_ID, + ATTR_ORDER, + ATTR_AUTO, + } diff --git a/homeassistant/components/group/translations/bg.json b/homeassistant/components/group/translations/bg.json index 584d48e3f28..6be0657c774 100644 --- a/homeassistant/components/group/translations/bg.json +++ b/homeassistant/components/group/translations/bg.json @@ -8,22 +8,6 @@ }, "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", @@ -32,8 +16,7 @@ }, "media_player": { "data": { - "name": "\u0418\u043c\u0435 \u043d\u0430 \u0433\u0440\u0443\u043f\u0430", - "title": "\u041d\u043e\u0432\u0430 \u0433\u0440\u0443\u043f\u0430" + "name": "\u0418\u043c\u0435 \u043d\u0430 \u0433\u0440\u0443\u043f\u0430" } }, "switch": { @@ -56,42 +39,21 @@ "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" @@ -102,11 +64,6 @@ "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", diff --git a/homeassistant/components/group/translations/ca.json b/homeassistant/components/group/translations/ca.json index bd824fc7428..bb9fba7e184 100644 --- a/homeassistant/components/group/translations/ca.json +++ b/homeassistant/components/group/translations/ca.json @@ -15,57 +15,26 @@ "data": { "entities": "Membres", "hide_members": "Amaga membres", - "name": "Nom", - "title": "Afegeix grup" + "name": "Nom" }, - "description": "Selecciona les opcions del grup", "title": "Afegeix grup" }, - "cover_options": { - "data": { - "entities": "Membres del grup" - }, - "description": "Selecciona les opcions del grup" - }, "fan": { "data": { "entities": "Membres", "hide_members": "Amaga membres", - "name": "Nom", - "title": "Afegeix grup" + "name": "Nom" }, - "description": "Selecciona les opcions del grup", "title": "Afegeix grup" }, - "fan_options": { - "data": { - "entities": "Membres del grup" - }, - "description": "Selecciona les opcions del grup" - }, - "init": { - "data": { - "group_type": "Tipus de grup" - }, - "description": "Selecciona el tipus de grup" - }, "light": { "data": { - "all": "Totes les entitats", "entities": "Membres", "hide_members": "Amaga membres", - "name": "Nom", - "title": "Afegeix grup" + "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": "Afegeix grup" }, - "light_options": { - "data": { - "entities": "Membres del grup" - }, - "description": "Selecciona les opcions del grup" - }, "lock": { "data": { "entities": "Membres", @@ -78,18 +47,10 @@ "data": { "entities": "Membres", "hide_members": "Amaga membres", - "name": "Nom", - "title": "Afegeix grup" + "name": "Nom" }, - "description": "Selecciona les opcions del grup", "title": "Afegeix grup" }, - "media_player_options": { - "data": { - "entities": "Membres del grup" - }, - "description": "Selecciona les opcions del grup" - }, "switch": { "data": { "entities": "Membres", @@ -99,9 +60,6 @@ "title": "Afegeix grup" }, "user": { - "data": { - "group_type": "Tipus de 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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Amaga membres" } }, - "media_player_options": { - "data": { - "entities": "Membres" - } - }, "switch": { "data": { "all": "Totes les entitats", diff --git a/homeassistant/components/group/translations/cs.json b/homeassistant/components/group/translations/cs.json index 232dbe9e629..9d24e3580a2 100644 --- a/homeassistant/components/group/translations/cs.json +++ b/homeassistant/components/group/translations/cs.json @@ -15,57 +15,26 @@ "data": { "entities": "\u010clenov\u00e9", "hide_members": "Skr\u00fdt \u010dleny", - "name": "Jm\u00e9no", - "title": "Nov\u00e1 skupina" + "name": "Jm\u00e9no" }, - "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" + "name": "Jm\u00e9no" }, - "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" + "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" }, - "light_options": { - "data": { - "entities": "\u010clenov\u00e9 skupiny" - }, - "description": "Vyberte mo\u017enosti skupiny" - }, "lock": { "data": { "entities": "\u010clenov\u00e9", @@ -78,18 +47,10 @@ "data": { "entities": "\u010clenov\u00e9", "hide_members": "Skr\u00fdt \u010dleny", - "name": "Jm\u00e9no", - "title": "Nov\u00e1 skupina" + "name": "Jm\u00e9no" }, - "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", @@ -99,9 +60,6 @@ "title": "Nov\u00e1 skupina" }, "user": { - "data": { - "group_type": "Typ skupiny" - }, "description": "Vyberte typ skupiny", "menu_options": { "binary_sensor": "Skupina bin\u00e1rn\u00edch senzor\u016f", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Skr\u00fdt \u010dleny" } }, - "media_player_options": { - "data": { - "entities": "\u010clenov\u00e9" - } - }, "switch": { "data": { "all": "V\u0161echny entity", diff --git a/homeassistant/components/group/translations/de.json b/homeassistant/components/group/translations/de.json index aeaedd3a7cc..00c036d18ee 100644 --- a/homeassistant/components/group/translations/de.json +++ b/homeassistant/components/group/translations/de.json @@ -15,57 +15,26 @@ "data": { "entities": "Mitglieder", "hide_members": "Mitglieder ausblenden", - "name": "Name", - "title": "Gruppe hinzuf\u00fcgen" + "name": "Name" }, - "description": "Gruppenoptionen ausw\u00e4hlen", "title": "Gruppe hinzuf\u00fcgen" }, - "cover_options": { - "data": { - "entities": "Gruppenmitglieder" - }, - "description": "Gruppenoptionen ausw\u00e4hlen" - }, "fan": { "data": { "entities": "Mitglieder", "hide_members": "Mitglieder ausblenden", - "name": "Name", - "title": "Gruppe hinzuf\u00fcgen" + "name": "Name" }, - "description": "Gruppenoptionen ausw\u00e4hlen", "title": "Gruppe hinzuf\u00fcgen" }, - "fan_options": { - "data": { - "entities": "Gruppenmitglieder" - }, - "description": "Gruppenoptionen ausw\u00e4hlen" - }, - "init": { - "data": { - "group_type": "Gruppentyp" - }, - "description": "Gruppentyp ausw\u00e4hlen" - }, "light": { "data": { - "all": "Alle Entit\u00e4ten", "entities": "Mitglieder", "hide_members": "Mitglieder ausblenden", - "name": "Name", - "title": "Gruppe hinzuf\u00fcgen" + "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": "Gruppe hinzuf\u00fcgen" }, - "light_options": { - "data": { - "entities": "Gruppenmitglieder" - }, - "description": "Gruppenoptionen ausw\u00e4hlen" - }, "lock": { "data": { "entities": "Mitglieder", @@ -78,18 +47,10 @@ "data": { "entities": "Mitglieder", "hide_members": "Mitglieder ausblenden", - "name": "Name", - "title": "Gruppe hinzuf\u00fcgen" + "name": "Name" }, - "description": "Gruppenoptionen ausw\u00e4hlen", "title": "Gruppe hinzuf\u00fcgen" }, - "media_player_options": { - "data": { - "entities": "Gruppenmitglieder" - }, - "description": "Gruppenoptionen ausw\u00e4hlen" - }, "switch": { "data": { "entities": "Mitglieder", @@ -99,18 +60,15 @@ "title": "Gruppe hinzuf\u00fcgen" }, "user": { - "data": { - "group_type": "Gruppentyp" - }, "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", + "lock": "Schloss-Gruppe", "media_player": "Media-Player-Gruppe", - "switch": "Gruppe wechseln" + "switch": "Schalter-Gruppe" }, "title": "Gruppe hinzuf\u00fcgen" } @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Mitglieder ausblenden" } }, - "media_player_options": { - "data": { - "entities": "Mitglieder" - } - }, "switch": { "data": { "all": "Alle Entit\u00e4ten", diff --git a/homeassistant/components/group/translations/el.json b/homeassistant/components/group/translations/el.json index edd67034de8..df1a5982f2c 100644 --- a/homeassistant/components/group/translations/el.json +++ b/homeassistant/components/group/translations/el.json @@ -15,57 +15,26 @@ "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" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \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": { - "entities": "\u039c\u03ad\u03bb\u03b7 \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" - }, "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" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \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": { - "entities": "\u039c\u03ad\u03bb\u03b7 \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" - }, - "init": { - "data": { - "group_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03cd\u03c0\u03bf \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" - }, "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" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \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": { - "entities": "\u039c\u03ad\u03bb\u03b7 \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" - }, "lock": { "data": { "entities": "\u039c\u03ad\u03bb\u03b7", @@ -78,18 +47,10 @@ "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" + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \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": { - "entities": "\u039c\u03ad\u03bb\u03b7 \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" - }, "switch": { "data": { "entities": "\u039c\u03ad\u03bb\u03b7", @@ -99,9 +60,6 @@ "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" }, "user": { - "data": { - "group_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" - }, "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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "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", diff --git a/homeassistant/components/group/translations/en.json b/homeassistant/components/group/translations/en.json index f7b3942c696..a67d23b812d 100644 --- a/homeassistant/components/group/translations/en.json +++ b/homeassistant/components/group/translations/en.json @@ -15,57 +15,26 @@ "data": { "entities": "Members", "hide_members": "Hide members", - "name": "Name", - "title": "Add Group" + "name": "Name" }, - "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", - "title": "Add Group" + "name": "Name" }, - "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", - "title": "Add Group" + "name": "Name" }, - "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", @@ -78,18 +47,10 @@ "data": { "entities": "Members", "hide_members": "Hide members", - "name": "Name", - "title": "Add Group" + "name": "Name" }, - "description": "Select group options", "title": "Add Group" }, - "media_player_options": { - "data": { - "entities": "Group members" - }, - "description": "Select group options" - }, "switch": { "data": { "entities": "Members", @@ -99,9 +60,6 @@ "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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Hide members" } }, - "media_player_options": { - "data": { - "entities": "Members" - } - }, "switch": { "data": { "all": "All entities", diff --git a/homeassistant/components/group/translations/es.json b/homeassistant/components/group/translations/es.json index e74adda486d..67742954447 100644 --- a/homeassistant/components/group/translations/es.json +++ b/homeassistant/components/group/translations/es.json @@ -1,15 +1,95 @@ { "config": { "step": { + "binary_sensor": { + "data": { + "hide_members": "Esconde miembros" + }, + "title": "Agregar grupo" + }, "cover": { "data": { + "hide_members": "Esconde miembros", "name": "Nombre del Grupo" - }, - "description": "Seleccionar opciones de grupo" + } }, - "cover_options": { + "fan": { "data": { - "entities": "Miembros del grupo" + "entities": "Miembros", + "hide_members": "Esconde miembros" + }, + "title": "Agregar grupo" + }, + "light": { + "data": { + "entities": "Miembros", + "hide_members": "Esconde miembros" + }, + "title": "Agregar grupo" + }, + "lock": { + "data": { + "entities": "Miembros", + "hide_members": "Esconde miembros" + }, + "title": "Agregar grupo" + }, + "media_player": { + "data": { + "entities": "Miembros", + "hide_members": "Esconde miembros", + "name": "Nombre" + }, + "title": "Agregar grupo" + }, + "switch": { + "data": { + "entities": "Miembros" + } + }, + "user": { + "menu_options": { + "binary_sensor": "Grupo de sensores binarios", + "cover": "Grupo de cubiertas", + "fan": "Grupo de ventiladores", + "switch": "Grupo de conmutadores" + } + } + } + }, + "options": { + "step": { + "binary_sensor": { + "data": { + "all": "Todas las entidades", + "hide_members": "Esconde miembros" + } + }, + "cover": { + "data": { + "hide_members": "Esconde miembros" + } + }, + "fan": { + "data": { + "hide_members": "Esconde miembros" + } + }, + "light": { + "data": { + "entities": "Miembros", + "hide_members": "Esconde miembros" + } + }, + "lock": { + "data": { + "entities": "Miembros" + } + }, + "media_player": { + "data": { + "entities": "Miembros", + "hide_members": "Esconde miembros" } } } @@ -19,7 +99,7 @@ "closed": "Cerrado", "home": "En casa", "locked": "Bloqueado", - "not_home": "Fuera de casa", + "not_home": "Fuera", "off": "Apagado", "ok": "OK", "on": "Encendido", diff --git a/homeassistant/components/group/translations/et.json b/homeassistant/components/group/translations/et.json index d0d1466e26b..709d7b2c929 100644 --- a/homeassistant/components/group/translations/et.json +++ b/homeassistant/components/group/translations/et.json @@ -15,57 +15,26 @@ "data": { "entities": "Liikmed", "hide_members": "Peida grupi liikmed", - "name": "Nimi", - "title": "Uus r\u00fchm" + "name": "Nimi" }, - "description": "R\u00fchmasuvandite valimine", "title": "Uus grupp" }, - "cover_options": { - "data": { - "entities": "R\u00fchma liikmed" - }, - "description": "R\u00fchmasuvandite valimine" - }, "fan": { "data": { "entities": "Liikmed", "hide_members": "Peida grupi liikmed", - "name": "Nimi", - "title": "Uus r\u00fchm" + "name": "Nimi" }, - "description": "R\u00fchmasuvandite valimine", "title": "Uus grupp" }, - "fan_options": { - "data": { - "entities": "R\u00fchma liikmed" - }, - "description": "R\u00fchmasuvandite valimine" - }, - "init": { - "data": { - "group_type": "R\u00fchma t\u00fc\u00fcp" - }, - "description": "Vali r\u00fchma t\u00fc\u00fcp" - }, "light": { "data": { - "all": "K\u00f5ik olemid", "entities": "Liikmed", "hide_members": "Peida grupi liikmed", - "name": "Nimi", - "title": "Uus r\u00fchm" + "name": "Nimi" }, - "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": { - "entities": "R\u00fchma liikmed" - }, - "description": "R\u00fchmasuvandite valimine" - }, "lock": { "data": { "entities": "Liikmed", @@ -78,18 +47,10 @@ "data": { "entities": "Liikmed", "hide_members": "Peida grupi liikmed", - "name": "Nimi", - "title": "Uus r\u00fchm" + "name": "Nimi" }, - "description": "R\u00fchmasuvandite valimine", "title": "Uus grupp" }, - "media_player_options": { - "data": { - "entities": "R\u00fchma liikmed" - }, - "description": "R\u00fchmasuvandite valimine" - }, "switch": { "data": { "entities": "Liikmed", @@ -99,9 +60,6 @@ "title": "Uus grupp" }, "user": { - "data": { - "group_type": "R\u00fchma t\u00fc\u00fcp" - }, "description": "R\u00fchmad v\u00f5imaldavad luua uue olemi,mis esindab mitut sama t\u00fc\u00fcpi olemit.", "menu_options": { "binary_sensor": "Olekuandurite r\u00fchm", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Peida grupi liikmed" } }, - "media_player_options": { - "data": { - "entities": "Liikmed" - } - }, "switch": { "data": { "all": "K\u00f5ik olemid", diff --git a/homeassistant/components/group/translations/fr.json b/homeassistant/components/group/translations/fr.json index e8b74e2f374..811776f8f9d 100644 --- a/homeassistant/components/group/translations/fr.json +++ b/homeassistant/components/group/translations/fr.json @@ -15,57 +15,26 @@ "data": { "entities": "Membres", "hide_members": "Cacher les membres", - "name": "Nom", - "title": "Ajouter un groupe" + "name": "Nom" }, - "description": "S\u00e9lectionnez les options du groupe", "title": "Ajouter un groupe" }, - "cover_options": { - "data": { - "entities": "Membres du groupe" - }, - "description": "S\u00e9lectionnez les options du groupe" - }, "fan": { "data": { "entities": "Membres", "hide_members": "Cacher les membres", - "name": "Nom", - "title": "Ajouter un groupe" + "name": "Nom" }, - "description": "S\u00e9lectionnez les options du groupe", "title": "Ajouter un groupe" }, - "fan_options": { - "data": { - "entities": "Membres du groupe" - }, - "description": "S\u00e9lectionnez les options du groupe" - }, - "init": { - "data": { - "group_type": "Type de groupe" - }, - "description": "S\u00e9lectionnez le type de groupe" - }, "light": { "data": { - "all": "Toutes les entit\u00e9s", "entities": "Membres", "hide_members": "Cacher les membres", - "name": "Nom", - "title": "Ajouter un groupe" + "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": "Ajouter un groupe" }, - "light_options": { - "data": { - "entities": "Membres du groupe" - }, - "description": "S\u00e9lectionnez les options du groupe" - }, "lock": { "data": { "entities": "Membres", @@ -78,18 +47,10 @@ "data": { "entities": "Membres", "hide_members": "Cacher les membres", - "name": "Nom", - "title": "Ajouter un groupe" + "name": "Nom" }, - "description": "S\u00e9lectionnez les options du groupe", "title": "Ajouter un groupe" }, - "media_player_options": { - "data": { - "entities": "Membres du groupe" - }, - "description": "S\u00e9lectionnez les options du groupe" - }, "switch": { "data": { "entities": "Membres", @@ -99,9 +60,6 @@ "title": "Ajouter un groupe" }, "user": { - "data": { - "group_type": "Type de 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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Cacher les membres" } }, - "media_player_options": { - "data": { - "entities": "Membres" - } - }, "switch": { "data": { "all": "Toutes les entit\u00e9s", diff --git a/homeassistant/components/group/translations/he.json b/homeassistant/components/group/translations/he.json index ab7a90cb699..354a435f491 100644 --- a/homeassistant/components/group/translations/he.json +++ b/homeassistant/components/group/translations/he.json @@ -15,57 +15,26 @@ "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" + "name": "\u05e9\u05dd" }, - "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": { - "entities": "\u05d7\u05d1\u05e8\u05d9 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4" - }, - "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4" - }, "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" + "name": "\u05e9\u05dd" }, - "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": { - "entities": "\u05d7\u05d1\u05e8\u05d9 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4" - }, - "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4" - }, - "init": { - "data": { - "group_type": "\u05e1\u05d5\u05d2 \u05e7\u05d1\u05d5\u05e6\u05d4" - }, - "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05e1\u05d5\u05d2 \u05e7\u05d1\u05d5\u05e6\u05d4" - }, "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" + "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.", "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" }, - "light_options": { - "data": { - "entities": "\u05d7\u05d1\u05e8\u05d9 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4" - }, - "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", @@ -78,18 +47,10 @@ "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" + "name": "\u05e9\u05dd" }, - "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": { - "entities": "\u05d7\u05d1\u05e8\u05d9 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4" - }, - "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", @@ -99,9 +60,6 @@ "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", @@ -124,34 +82,18 @@ }, "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", @@ -160,12 +102,6 @@ }, "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", @@ -178,11 +114,6 @@ "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", diff --git a/homeassistant/components/group/translations/hu.json b/homeassistant/components/group/translations/hu.json index ba368532cff..ae455bafebe 100644 --- a/homeassistant/components/group/translations/hu.json +++ b/homeassistant/components/group/translations/hu.json @@ -15,57 +15,26 @@ "data": { "entities": "A csoport tagjai", "hide_members": "Tagok elrejt\u00e9se", - "name": "Elnevez\u00e9s", - "title": "Csoport hozz\u00e1ad\u00e1sa" + "name": "Elnevez\u00e9s" }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai", "title": "Csoport hozz\u00e1ad\u00e1sa" }, - "cover_options": { - "data": { - "entities": "A csoport tagjai" - }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai" - }, "fan": { "data": { "entities": "A csoport tagjai", "hide_members": "Tagok elrejt\u00e9se", - "name": "Elnevez\u00e9s", - "title": "Csoport hozz\u00e1ad\u00e1sa" + "name": "Elnevez\u00e9s" }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai", "title": "Csoport hozz\u00e1ad\u00e1sa" }, - "fan_options": { - "data": { - "entities": "A csoport tagjai" - }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai" - }, - "init": { - "data": { - "group_type": "A csoport t\u00edpusa" - }, - "description": "A csoport t\u00edpusa" - }, "light": { "data": { - "all": "Minden entit\u00e1s", "entities": "A csoport tagjai", "hide_members": "Tagok elrejt\u00e9se", - "name": "Elnevez\u00e9s", - "title": "Csoport hozz\u00e1ad\u00e1sa" + "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": "Csoport hozz\u00e1ad\u00e1sa" }, - "light_options": { - "data": { - "entities": "A csoport tagjai" - }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai" - }, "lock": { "data": { "entities": "A csoport tagjai", @@ -78,18 +47,10 @@ "data": { "entities": "A csoport tagjai", "hide_members": "Tagok elrejt\u00e9se", - "name": "Elnevez\u00e9s", - "title": "Csoport hozz\u00e1ad\u00e1sa" + "name": "Elnevez\u00e9s" }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai", "title": "Csoport hozz\u00e1ad\u00e1sa" }, - "media_player_options": { - "data": { - "entities": "A csoport tagjai" - }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai" - }, "switch": { "data": { "entities": "A csoport tagjai", @@ -99,9 +60,6 @@ "title": "Csoport hozz\u00e1ad\u00e1sa" }, "user": { - "data": { - "group_type": "Csoport t\u00edpusa" - }, "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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Tagok elrejt\u00e9se" } }, - "media_player_options": { - "data": { - "entities": "A csoport tagjai" - } - }, "switch": { "data": { "all": "Minden entit\u00e1s", diff --git a/homeassistant/components/group/translations/id.json b/homeassistant/components/group/translations/id.json index c0425c5ede6..9aa29f472c5 100644 --- a/homeassistant/components/group/translations/id.json +++ b/homeassistant/components/group/translations/id.json @@ -15,57 +15,26 @@ "data": { "entities": "Anggota", "hide_members": "Sembunyikan anggota", - "name": "Nama", - "title": "Tambahkan Grup" + "name": "Nama" }, - "description": "Pilih opsi grup", "title": "Tambahkan Grup" }, - "cover_options": { - "data": { - "entities": "Anggota grup" - }, - "description": "Pilih opsi grup" - }, "fan": { "data": { "entities": "Anggota", "hide_members": "Sembunyikan anggota", - "name": "Nama", - "title": "Tambahkan Grup" + "name": "Nama" }, - "description": "Pilih opsi grup", "title": "Tambahkan Grup" }, - "fan_options": { - "data": { - "entities": "Anggota grup" - }, - "description": "Pilih opsi grup" - }, - "init": { - "data": { - "group_type": "Jenis grup" - }, - "description": "Pilih jenis grup" - }, "light": { "data": { - "all": "Semua entitas", "entities": "Anggota", "hide_members": "Sembunyikan anggota", - "name": "Nama", - "title": "Tambahkan Grup" + "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": "Tambahkan Grup" }, - "light_options": { - "data": { - "entities": "Anggota grup" - }, - "description": "Pilih opsi grup" - }, "lock": { "data": { "entities": "Anggota", @@ -78,18 +47,10 @@ "data": { "entities": "Anggota", "hide_members": "Sembunyikan anggota", - "name": "Nama", - "title": "Tambahkan Grup" + "name": "Nama" }, - "description": "Pilih opsi grup", "title": "Tambahkan Grup" }, - "media_player_options": { - "data": { - "entities": "Anggota grup" - }, - "description": "Pilih opsi grup" - }, "switch": { "data": { "entities": "Anggota", @@ -99,9 +60,6 @@ "title": "Tambahkan Grup" }, "user": { - "data": { - "group_type": "Jenis grup" - }, "description": "Grup memungkinkan Anda membuat entitas baru yang mewakili beberapa entitas dari jenis yang sama.", "menu_options": { "binary_sensor": "Grup sensor biner", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Sembunyikan anggota" } }, - "media_player_options": { - "data": { - "entities": "Anggota" - } - }, "switch": { "data": { "all": "Semua entitas", diff --git a/homeassistant/components/group/translations/it.json b/homeassistant/components/group/translations/it.json index 4cbac9145d8..dac9fd264f2 100644 --- a/homeassistant/components/group/translations/it.json +++ b/homeassistant/components/group/translations/it.json @@ -15,57 +15,26 @@ "data": { "entities": "Membri", "hide_members": "Nascondi membri", - "name": "Nome", - "title": "Aggiungi gruppo" + "name": "Nome" }, - "description": "Seleziona le opzioni di gruppo", "title": "Aggiungi gruppo" }, - "cover_options": { - "data": { - "entities": "Membri del gruppo" - }, - "description": "Seleziona le opzioni di gruppo" - }, "fan": { "data": { "entities": "Membri", "hide_members": "Nascondi membri", - "name": "Nome", - "title": "Aggiungi gruppo" + "name": "Nome" }, - "description": "Seleziona le opzioni di gruppo", "title": "Aggiungi gruppo" }, - "fan_options": { - "data": { - "entities": "Membri del gruppo" - }, - "description": "Seleziona le opzioni di gruppo" - }, - "init": { - "data": { - "group_type": "Tipo di gruppo" - }, - "description": "Seleziona il tipo di gruppo" - }, "light": { "data": { - "all": "Tutte le entit\u00e0", "entities": "Membri", "hide_members": "Nascondi membri", - "name": "Nome", - "title": "Aggiungi gruppo" + "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": "Aggiungi gruppo" }, - "light_options": { - "data": { - "entities": "Membri del gruppo" - }, - "description": "Seleziona le opzioni di gruppo" - }, "lock": { "data": { "entities": "Membri", @@ -78,18 +47,10 @@ "data": { "entities": "Membri", "hide_members": "Nascondi membri", - "name": "Nome", - "title": "Aggiungi gruppo" + "name": "Nome" }, - "description": "Seleziona le opzioni di gruppo", "title": "Aggiungi gruppo" }, - "media_player_options": { - "data": { - "entities": "Membri del gruppo" - }, - "description": "Seleziona le opzioni di gruppo" - }, "switch": { "data": { "entities": "Membri", @@ -99,9 +60,6 @@ "title": "Aggiungi gruppo" }, "user": { - "data": { - "group_type": "Tipo di 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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Nascondi membri" } }, - "media_player_options": { - "data": { - "entities": "Membri" - } - }, "switch": { "data": { "all": "Tutte le entit\u00e0", diff --git a/homeassistant/components/group/translations/ja.json b/homeassistant/components/group/translations/ja.json index 55e414927a4..46fbbc7b22f 100644 --- a/homeassistant/components/group/translations/ja.json +++ b/homeassistant/components/group/translations/ja.json @@ -15,57 +15,26 @@ "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" + "name": "\u30b0\u30eb\u30fc\u30d7\u540d" }, - "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": { - "entities": "\u30b0\u30eb\u30fc\u30d7\u306e\u30e1\u30f3\u30d0\u30fc" - }, - "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e" - }, "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" + "name": "\u30b0\u30eb\u30fc\u30d7\u540d" }, - "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": { - "entities": "\u30b0\u30eb\u30fc\u30d7\u306e\u30e1\u30f3\u30d0\u30fc" - }, - "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e" - }, - "init": { - "data": { - "group_type": "\u30b0\u30eb\u30fc\u30d7\u30bf\u30a4\u30d7" - }, - "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u7a2e\u985e\u3092\u9078\u629e" - }, "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" + "name": "\u30b0\u30eb\u30fc\u30d7\u540d" }, - "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": { - "entities": "\u30b0\u30eb\u30fc\u30d7\u306e\u30e1\u30f3\u30d0\u30fc" - }, - "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e" - }, "lock": { "data": { "entities": "\u30e1\u30f3\u30d0\u30fc", @@ -78,18 +47,10 @@ "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" + "name": "\u30b0\u30eb\u30fc\u30d7\u540d" }, - "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": { - "entities": "\u30b0\u30eb\u30fc\u30d7\u306e\u30e1\u30f3\u30d0\u30fc" - }, - "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e" - }, "switch": { "data": { "entities": "\u30e1\u30f3\u30d0\u30fc", @@ -99,9 +60,6 @@ "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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "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", diff --git a/homeassistant/components/group/translations/ko.json b/homeassistant/components/group/translations/ko.json index c2adb88c7ca..3b41a27075b 100644 --- a/homeassistant/components/group/translations/ko.json +++ b/homeassistant/components/group/translations/ko.json @@ -1,4 +1,11 @@ { + "config": { + "step": { + "user": { + "description": "\uadf8\ub8f9\uc744 \uc0ac\uc6a9\ud558\uba74 \ub3d9\uc77c\ud55c \uc720\ud615\uc758 \uc5ec\ub7ec \uad6c\uc131\uc694\uc18c\ub97c \ub098\ud0c0\ub0b4\ub294 \uc0c8 \uad6c\uc131\uc694\uc18c\ub97c \ub9cc\ub4e4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + } + } + }, "state": { "_": { "closed": "\ub2eb\ud798", diff --git a/homeassistant/components/group/translations/nl.json b/homeassistant/components/group/translations/nl.json index e670e2e853a..2b78d41e201 100644 --- a/homeassistant/components/group/translations/nl.json +++ b/homeassistant/components/group/translations/nl.json @@ -15,57 +15,26 @@ "data": { "entities": "Leden", "hide_members": "Verberg leden", - "name": "Naam", - "title": "Groep toevoegen" + "name": "Naam" }, - "description": "Selecteer groepsopties", "title": "Groep toevoegen" }, - "cover_options": { - "data": { - "entities": "Groepsleden" - }, - "description": "Selecteer groepsopties" - }, "fan": { "data": { "entities": "Leden", "hide_members": "Verberg leden", - "name": "Naam", - "title": "Groep toevoegen" + "name": "Naam" }, - "description": "Selecteer groepsopties", "title": "Groep toevoegen" }, - "fan_options": { - "data": { - "entities": "Groepsleden" - }, - "description": "Selecteer groepsopties" - }, - "init": { - "data": { - "group_type": "Groepstype" - }, - "description": "Selecteer groepstype" - }, "light": { "data": { - "all": "Alle entiteiten", "entities": "Leden", "hide_members": "Verberg leden", - "name": "Naam", - "title": "Groep toevoegen" + "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": "Groep toevoegen" }, - "light_options": { - "data": { - "entities": "Groepsleden" - }, - "description": "Selecteer groepsopties" - }, "lock": { "data": { "entities": "Leden", @@ -78,18 +47,10 @@ "data": { "entities": "Leden", "hide_members": "Verberg leden", - "name": "Naam", - "title": "Groep toevoegen" + "name": "Naam" }, - "description": "Selecteer groepsopties", "title": "Groep toevoegen" }, - "media_player_options": { - "data": { - "entities": "Groepsleden" - }, - "description": "Selecteer groepsopties" - }, "switch": { "data": { "entities": "Leden", @@ -99,18 +60,15 @@ "title": "Nieuwe groep" }, "user": { - "data": { - "group_type": "Groepstype" - }, "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", + "binary_sensor": "Binaire-sensorgroep", + "cover": "Afdekkingengroep", "fan": "Ventilatorgroep", "light": "Lichtgroep", - "lock": "Groep vergrendelen", + "lock": "Slotgroep", "media_player": "Mediaspelergroep", - "switch": "Groep wisselen" + "switch": "Schakelaargroep" }, "title": "Groep toevoegen" } @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Verberg leden" } }, - "media_player_options": { - "data": { - "entities": "Leden" - } - }, "switch": { "data": { "all": "Alle entiteiten", diff --git a/homeassistant/components/group/translations/no.json b/homeassistant/components/group/translations/no.json index 016a9c8e934..a79e374f2a5 100644 --- a/homeassistant/components/group/translations/no.json +++ b/homeassistant/components/group/translations/no.json @@ -15,57 +15,26 @@ "data": { "entities": "Medlemmer", "hide_members": "Skjul medlemmer", - "name": "Navn", - "title": "Legg til gruppe" + "name": "Navn" }, - "description": "Velg gruppealternativer", "title": "Legg til gruppe" }, - "cover_options": { - "data": { - "entities": "Gruppemedlemmer" - }, - "description": "Velg gruppealternativer" - }, "fan": { "data": { "entities": "Medlemmer", "hide_members": "Skjul medlemmer", - "name": "Navn", - "title": "Legg til gruppe" + "name": "Navn" }, - "description": "Velg gruppealternativer", "title": "Legg til gruppe" }, - "fan_options": { - "data": { - "entities": "Gruppemedlemmer" - }, - "description": "Velg gruppealternativer" - }, - "init": { - "data": { - "group_type": "Gruppetype" - }, - "description": "Velg gruppetype" - }, "light": { "data": { - "all": "Alle enheter", "entities": "Medlemmer", "hide_members": "Skjul medlemmer", - "name": "Navn", - "title": "Legg til gruppe" + "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": "Legg til gruppe" }, - "light_options": { - "data": { - "entities": "Gruppemedlemmer" - }, - "description": "Velg gruppealternativer" - }, "lock": { "data": { "entities": "Medlemmer", @@ -78,18 +47,10 @@ "data": { "entities": "Medlemmer", "hide_members": "Skjul medlemmer", - "name": "Navn", - "title": "Legg til gruppe" + "name": "Navn" }, - "description": "Velg gruppealternativer", "title": "Legg til gruppe" }, - "media_player_options": { - "data": { - "entities": "Gruppemedlemmer" - }, - "description": "Velg gruppealternativer" - }, "switch": { "data": { "entities": "Medlemmer", @@ -99,9 +60,6 @@ "title": "Legg til gruppe" }, "user": { - "data": { - "group_type": "Gruppetype" - }, "description": "Grupper lar deg opprette en ny enhet som representerer flere enheter av samme type.", "menu_options": { "binary_sensor": "Bin\u00e6r sensorgruppe", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Skjul medlemmer" } }, - "media_player_options": { - "data": { - "entities": "Medlemmer" - } - }, "switch": { "data": { "all": "Alle enheter", diff --git a/homeassistant/components/group/translations/pl.json b/homeassistant/components/group/translations/pl.json index 84cbd9f7b4d..3b6b6ee40ba 100644 --- a/homeassistant/components/group/translations/pl.json +++ b/homeassistant/components/group/translations/pl.json @@ -15,57 +15,26 @@ "data": { "entities": "Encje", "hide_members": "Ukryj encje", - "name": "Nazwa", - "title": "Dodaj grup\u0119" + "name": "Nazwa" }, - "description": "Wybierz opcje grupy", "title": "Dodaj grup\u0119" }, - "cover_options": { - "data": { - "entities": "Encje w grupie" - }, - "description": "Wybierz opcje grupy" - }, "fan": { "data": { "entities": "Encje", "hide_members": "Ukryj encje", - "name": "Nazwa", - "title": "Dodaj grup\u0119" + "name": "Nazwa" }, - "description": "Wybierz opcje grupy", "title": "Dodaj grup\u0119" }, - "fan_options": { - "data": { - "entities": "Encje w grupie" - }, - "description": "Wybierz opcje grupy" - }, - "init": { - "data": { - "group_type": "Rodzaj grupy" - }, - "description": "Wybierz rodzaj grupy" - }, "light": { "data": { - "all": "Wszystkie encje", "entities": "Encje", "hide_members": "Ukryj encje", - "name": "Nazwa", - "title": "Dodaj grup\u0119" + "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 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": { - "entities": "Encje w grupie" - }, - "description": "Wybierz opcje grupy" - }, "lock": { "data": { "entities": "Encje", @@ -78,18 +47,10 @@ "data": { "entities": "Encje", "hide_members": "Ukryj encje", - "name": "Nazwa", - "title": "Dodaj grup\u0119" + "name": "Nazwa" }, - "description": "Wybierz opcje grupy", "title": "Dodaj grup\u0119" }, - "media_player_options": { - "data": { - "entities": "Encje w grupie" - }, - "description": "Wybierz opcje grupy" - }, "switch": { "data": { "entities": "Encje", @@ -99,9 +60,6 @@ "title": "Dodaj grup\u0119" }, "user": { - "data": { - "group_type": "Rodzaj grupy" - }, "description": "Grupy umo\u017cliwiaj\u0105 tworzenie nowej encji, kt\u00f3ra reprezentuje wiele encji tego samego typu.", "menu_options": { "binary_sensor": "Grupa sensor\u00f3w binarnych", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Ukryj encje" } }, - "media_player_options": { - "data": { - "entities": "Encje" - } - }, "switch": { "data": { "all": "Wszystkie encje", diff --git a/homeassistant/components/group/translations/pt-BR.json b/homeassistant/components/group/translations/pt-BR.json index 0987b41f4ae..1f1c7d09719 100644 --- a/homeassistant/components/group/translations/pt-BR.json +++ b/homeassistant/components/group/translations/pt-BR.json @@ -15,57 +15,26 @@ "data": { "entities": "Membros", "hide_members": "Ocultar membros", - "name": "Nome", - "title": "Adicionar grupo" + "name": "Nome" }, - "description": "Selecione as op\u00e7\u00f5es do grupo", "title": "Adicionar grupo" }, - "cover_options": { - "data": { - "entities": "Membros do grupo" - }, - "description": "Selecione as op\u00e7\u00f5es do grupo" - }, "fan": { "data": { "entities": "Membros", "hide_members": "Ocultar membros", - "name": "Nome", - "title": "Adicionar grupo" + "name": "Nome" }, - "description": "Selecione as op\u00e7\u00f5es do grupo", "title": "Adicionar grupo" }, - "fan_options": { - "data": { - "entities": "Membros do grupo" - }, - "description": "Selecione as op\u00e7\u00f5es do grupo" - }, - "init": { - "data": { - "group_type": "Tipo de grupo" - }, - "description": "Selecione o tipo de grupo" - }, "light": { "data": { - "all": "Todas as entidades", "entities": "Membros", "hide_members": "Ocultar membros", - "name": "Nome", - "title": "Adicionar grupo" + "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": "Adicionar grupo" }, - "light_options": { - "data": { - "entities": "Membros do grupo" - }, - "description": "Selecione as op\u00e7\u00f5es do grupo" - }, "lock": { "data": { "entities": "Membros", @@ -78,18 +47,10 @@ "data": { "entities": "Membros", "hide_members": "Ocultar membros", - "name": "Nome", - "title": "Adicionar grupo" + "name": "Nome" }, - "description": "Selecione as op\u00e7\u00f5es do grupo", "title": "Adicionar grupo" }, - "media_player_options": { - "data": { - "entities": "Membros do grupo" - }, - "description": "Selecione as op\u00e7\u00f5es do grupo" - }, "switch": { "data": { "entities": "Membros", @@ -99,9 +60,6 @@ "title": "Adicionar grupo" }, "user": { - "data": { - "group_type": "Tipo de 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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "Ocultar membros" } }, - "media_player_options": { - "data": { - "entities": "Membros" - } - }, "switch": { "data": { "all": "Todas as entidades", diff --git a/homeassistant/components/group/translations/pt.json b/homeassistant/components/group/translations/pt.json index 9333d19b997..940aa088ced 100644 --- a/homeassistant/components/group/translations/pt.json +++ b/homeassistant/components/group/translations/pt.json @@ -9,25 +9,6 @@ } } }, - "options": { - "step": { - "fan_options": { - "data": { - "entities": "Membros" - } - }, - "light_options": { - "data": { - "entities": "Membros" - } - }, - "media_player_options": { - "data": { - "entities": "Membros" - } - } - } - }, "state": { "_": { "closed": "Fechada", diff --git a/homeassistant/components/group/translations/ru.json b/homeassistant/components/group/translations/ru.json index 0ba6d79900e..319121b69e8 100644 --- a/homeassistant/components/group/translations/ru.json +++ b/homeassistant/components/group/translations/ru.json @@ -15,57 +15,26 @@ "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" + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "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": { - "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438 \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." - }, "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": "\u0413\u0440\u0443\u043f\u043f\u0430" + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "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": { - "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438 \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." - }, - "init": { - "data": { - "group_type": "\u0422\u0438\u043f \u0433\u0440\u0443\u043f\u043f\u044b" - }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u0433\u0440\u0443\u043f\u043f\u044b." - }, "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": "\u0413\u0440\u0443\u043f\u043f\u0430" + "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": "\u0413\u0440\u0443\u043f\u043f\u0430" }, - "light_options": { - "data": { - "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438 \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." - }, "lock": { "data": { "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", @@ -78,18 +47,10 @@ "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" + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "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": { - "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438 \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." - }, "switch": { "data": { "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", @@ -99,9 +60,6 @@ "title": "\u0413\u0440\u0443\u043f\u043f\u0430" }, "user": { - "data": { - "group_type": "\u0422\u0438\u043f \u0433\u0440\u0443\u043f\u043f\u044b" - }, "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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "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", diff --git a/homeassistant/components/group/translations/sk.json b/homeassistant/components/group/translations/sk.json index 151cc1c47b0..51759a9dc86 100644 --- a/homeassistant/components/group/translations/sk.json +++ b/homeassistant/components/group/translations/sk.json @@ -1,4 +1,22 @@ { + "config": { + "step": { + "media_player": { + "data": { + "name": "Meno" + } + } + } + }, + "options": { + "step": { + "light": { + "data": { + "entities": "\u010clenovia" + } + } + } + }, "state": { "_": { "closed": "Zatvoren\u00e1", diff --git a/homeassistant/components/group/translations/sv.json b/homeassistant/components/group/translations/sv.json index 9ee5390db8a..77ae43cb1ee 100644 --- a/homeassistant/components/group/translations/sv.json +++ b/homeassistant/components/group/translations/sv.json @@ -9,64 +9,11 @@ }, "title": "Ny grupp" }, - "cover": { - "data": { - "title": "Ny grupp" - } - }, - "fan": { - "data": { - "title": "Ny grupp" - } - }, - "light": { - "data": { - "title": "Ny grupp" - } - }, - "media_player": { - "data": { - "title": "Ny grupp" - } - }, "user": { - "data": { - "group_type": "Grupptyp" - }, "title": "Ny grupp" } } }, - "options": { - "step": { - "binary_sensor_options": { - "data": { - "all": "Alla entiteter", - "entities": "Medlemmar" - } - }, - "cover_options": { - "data": { - "entities": "Medlemmar" - } - }, - "fan_options": { - "data": { - "entities": "Medlemmar" - } - }, - "light_options": { - "data": { - "entities": "Medlemmar" - } - }, - "media_player_options": { - "data": { - "entities": "Medlemmar" - } - } - } - }, "state": { "_": { "closed": "St\u00e4ngd", diff --git a/homeassistant/components/group/translations/tr.json b/homeassistant/components/group/translations/tr.json index 354ed29214c..49648f55ec4 100644 --- a/homeassistant/components/group/translations/tr.json +++ b/homeassistant/components/group/translations/tr.json @@ -15,57 +15,26 @@ "data": { "entities": "\u00dcyeler", "hide_members": "\u00dcyeleri gizle", - "name": "Ad", - "title": "Grup Ekle" + "name": "Ad" }, - "description": "Grup se\u00e7eneklerini se\u00e7in", "title": "Grup Ekle" }, - "cover_options": { - "data": { - "entities": "Grup \u00fcyeleri" - }, - "description": "Grup se\u00e7eneklerini se\u00e7in" - }, "fan": { "data": { "entities": "\u00dcyeler", "hide_members": "\u00dcyeleri gizle", - "name": "Ad", - "title": "Grup Ekle" + "name": "Ad" }, - "description": "Grup se\u00e7eneklerini se\u00e7in", "title": "Grup Ekle" }, - "fan_options": { - "data": { - "entities": "Grup \u00fcyeleri" - }, - "description": "Grup se\u00e7eneklerini se\u00e7in" - }, - "init": { - "data": { - "group_type": "Grup t\u00fcr\u00fc" - }, - "description": "Grup t\u00fcr\u00fcn\u00fc se\u00e7in" - }, "light": { "data": { - "all": "T\u00fcm varl\u0131klar", "entities": "\u00dcyeler", "hide_members": "\u00dcyeleri gizle", - "name": "Ad", - "title": "Grup Ekle" + "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" }, - "light_options": { - "data": { - "entities": "Grup \u00fcyeleri" - }, - "description": "Grup se\u00e7eneklerini se\u00e7in" - }, "lock": { "data": { "entities": "\u00dcyeler", @@ -78,18 +47,10 @@ "data": { "entities": "\u00dcyeler", "hide_members": "\u00dcyeleri gizle", - "name": "Ad", - "title": "Grup Ekle" + "name": "Ad" }, - "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", @@ -99,9 +60,6 @@ "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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "\u00dcyeleri gizle" } }, - "media_player_options": { - "data": { - "entities": "\u00dcyeler" - } - }, "switch": { "data": { "all": "T\u00fcm varl\u0131klar", diff --git a/homeassistant/components/group/translations/zh-Hant.json b/homeassistant/components/group/translations/zh-Hant.json index 380c2216976..023c76ebbba 100644 --- a/homeassistant/components/group/translations/zh-Hant.json +++ b/homeassistant/components/group/translations/zh-Hant.json @@ -15,57 +15,26 @@ "data": { "entities": "\u6210\u54e1", "hide_members": "\u96b1\u85cf\u6210\u54e1", - "name": "\u540d\u7a31", - "title": "\u65b0\u589e\u7fa4\u7d44" + "name": "\u540d\u7a31" }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805", "title": "\u65b0\u589e\u7fa4\u7d44" }, - "cover_options": { - "data": { - "entities": "\u7fa4\u7d44\u6210\u54e1" - }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805" - }, "fan": { "data": { "entities": "\u6210\u54e1", "hide_members": "\u96b1\u85cf\u6210\u54e1", - "name": "\u540d\u7a31", - "title": "\u65b0\u589e\u7fa4\u7d44" + "name": "\u540d\u7a31" }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805", "title": "\u65b0\u589e\u7fa4\u7d44" }, - "fan_options": { - "data": { - "entities": "\u7fa4\u7d44\u6210\u54e1" - }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805" - }, - "init": { - "data": { - "group_type": "\u7fa4\u7d44\u985e\u5225" - }, - "description": "\u9078\u64c7\u7fa4\u7d44\u985e\u5225" - }, "light": { "data": { - "all": "\u6240\u6709\u5be6\u9ad4", "entities": "\u6210\u54e1", "hide_members": "\u96b1\u85cf\u6210\u54e1", - "name": "\u540d\u7a31", - "title": "\u65b0\u589e\u7fa4\u7d44" + "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", "title": "\u65b0\u589e\u7fa4\u7d44" }, - "light_options": { - "data": { - "entities": "\u7fa4\u7d44\u6210\u54e1" - }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805" - }, "lock": { "data": { "entities": "\u6210\u54e1", @@ -78,18 +47,10 @@ "data": { "entities": "\u6210\u54e1", "hide_members": "\u96b1\u85cf\u6210\u54e1", - "name": "\u540d\u7a31", - "title": "\u65b0\u589e\u7fa4\u7d44" + "name": "\u540d\u7a31" }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805", "title": "\u65b0\u589e\u7fa4\u7d44" }, - "media_player_options": { - "data": { - "entities": "\u7fa4\u7d44\u6210\u54e1" - }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805" - }, "switch": { "data": { "entities": "\u6210\u54e1", @@ -99,9 +60,6 @@ "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", @@ -126,34 +84,18 @@ }, "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", @@ -162,12 +104,6 @@ }, "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", @@ -180,11 +116,6 @@ "hide_members": "\u96b1\u85cf\u6210\u54e1" } }, - "media_player_options": { - "data": { - "entities": "\u6210\u54e1" - } - }, "switch": { "data": { "all": "\u6240\u6709\u5be6\u9ad4", diff --git a/homeassistant/components/growatt_server/translations/ko.json b/homeassistant/components/growatt_server/translations/ko.json new file mode 100644 index 00000000000..676542812e7 --- /dev/null +++ b/homeassistant/components/growatt_server/translations/ko.json @@ -0,0 +1,3 @@ +{ + "title": "Growatt \uc11c\ubc84" +} \ No newline at end of file diff --git a/homeassistant/components/gstreamer/media_player.py b/homeassistant/components/gstreamer/media_player.py index d1b0fb056ff..723be2880ff 100644 --- a/homeassistant/components/gstreamer/media_player.py +++ b/homeassistant/components/gstreamer/media_player.py @@ -6,11 +6,16 @@ import logging from gsp import GstreamerPlayer import voluptuous as vol +from homeassistant.components import media_source from homeassistant.components.media_player import ( PLATFORM_SCHEMA, + 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_MUSIC from homeassistant.const import CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE from homeassistant.core import HomeAssistant @@ -58,6 +63,7 @@ class GstreamerDevice(MediaPlayerEntity): | MediaPlayerEntityFeature.PAUSE | MediaPlayerEntityFeature.PLAY_MEDIA | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.BROWSE_MEDIA ) def __init__(self, player, name): @@ -86,12 +92,22 @@ class GstreamerDevice(MediaPlayerEntity): """Set the volume level.""" self._player.volume = volume - def play_media(self, media_type, media_id, **kwargs): + async def async_play_media(self, media_type, media_id, **kwargs): """Play media.""" - if media_type != MEDIA_TYPE_MUSIC: + # Handle media_source + if media_source.is_media_source_id(media_id): + sourced_media = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) + media_id = sourced_media.url + + elif media_type != MEDIA_TYPE_MUSIC: _LOGGER.error("Invalid media type") return - self._player.queue(media_id) + + media_id = async_process_play_media_url(self.hass, media_id) + + await self.hass.async_add_executor_job(self._player.queue, media_id) def media_play(self): """Play.""" @@ -149,3 +165,13 @@ class GstreamerDevice(MediaPlayerEntity): def media_album_name(self): """Media album.""" return self._album + + 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/"), + ) diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index b971a428a76..25e0df913d8 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -363,7 +363,7 @@ class PairedSensorManager: # Remove the paired sensor device from the device registry (which will # clean up entities and the entity registry): - dev_reg = await self._hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(self._hass) device = dev_reg.async_get_or_create( config_entry_id=self._entry.entry_id, identifiers={(DOMAIN, uid)} ) diff --git a/homeassistant/components/guardian/translations/ca.json b/homeassistant/components/guardian/translations/ca.json index 0831975511e..59d9f6d03b8 100644 --- a/homeassistant/components/guardian/translations/ca.json +++ b/homeassistant/components/guardian/translations/ca.json @@ -15,9 +15,6 @@ "port": "Port" }, "description": "Configura un dispositiu Elexa Guardian local." - }, - "zeroconf_confirm": { - "description": "Vols configurar aquest dispositiu Guardian?" } } } diff --git a/homeassistant/components/guardian/translations/cs.json b/homeassistant/components/guardian/translations/cs.json index cb5ee7ef102..087c4b7ba02 100644 --- a/homeassistant/components/guardian/translations/cs.json +++ b/homeassistant/components/guardian/translations/cs.json @@ -12,9 +12,6 @@ "port": "Port" }, "description": "Nastate m\u00edstn\u00ed za\u0159\u00edzen\u00ed Elexa Guardian." - }, - "zeroconf_confirm": { - "description": "Chcete nastavit toto za\u0159\u00edzen\u00ed Guardian?" } } } diff --git a/homeassistant/components/guardian/translations/de.json b/homeassistant/components/guardian/translations/de.json index 63949b22de6..2078df1cae9 100644 --- a/homeassistant/components/guardian/translations/de.json +++ b/homeassistant/components/guardian/translations/de.json @@ -15,9 +15,6 @@ "port": "Port" }, "description": "Konfiguriere ein lokales Elexa Guardian Ger\u00e4t." - }, - "zeroconf_confirm": { - "description": "M\u00f6chtest du dieses Guardian-Ger\u00e4t einrichten?" } } } diff --git a/homeassistant/components/guardian/translations/el.json b/homeassistant/components/guardian/translations/el.json index 8173f4d7fa0..8f6a77ac2ec 100644 --- a/homeassistant/components/guardian/translations/el.json +++ b/homeassistant/components/guardian/translations/el.json @@ -15,9 +15,6 @@ "port": "\u0398\u03cd\u03c1\u03b1" }, "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Elexa Guardian." - }, - "zeroconf_confirm": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Guardian;" } } } diff --git a/homeassistant/components/guardian/translations/en.json b/homeassistant/components/guardian/translations/en.json index 52932cce02b..310f550bcc1 100644 --- a/homeassistant/components/guardian/translations/en.json +++ b/homeassistant/components/guardian/translations/en.json @@ -15,9 +15,6 @@ "port": "Port" }, "description": "Configure a local Elexa Guardian device." - }, - "zeroconf_confirm": { - "description": "Do you want to set up this Guardian device?" } } } diff --git a/homeassistant/components/guardian/translations/es.json b/homeassistant/components/guardian/translations/es.json index 981534cca9b..fe2367232a4 100644 --- a/homeassistant/components/guardian/translations/es.json +++ b/homeassistant/components/guardian/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Este dispositivo Guardian ya ha sido configurado", + "already_configured": "El dispositivo ya est\u00e1 configurado", "already_in_progress": "La configuraci\u00f3n del dispositivo Guardian ya est\u00e1 en proceso.", "cannot_connect": "No se pudo conectar" }, @@ -15,9 +15,6 @@ "port": "Puerto" }, "description": "Configurar un dispositivo local Elexa Guardian." - }, - "zeroconf_confirm": { - "description": "\u00bfQuieres configurar este dispositivo Guardian?" } } } diff --git a/homeassistant/components/guardian/translations/et.json b/homeassistant/components/guardian/translations/et.json index 56aec0e00c7..22ee0bf1300 100644 --- a/homeassistant/components/guardian/translations/et.json +++ b/homeassistant/components/guardian/translations/et.json @@ -15,9 +15,6 @@ "port": "" }, "description": "Seadista kohalik Elexa Guardiani seade." - }, - "zeroconf_confirm": { - "description": "Kas soovid seadistada seda Guardian'i seadet?" } } } diff --git a/homeassistant/components/guardian/translations/fr.json b/homeassistant/components/guardian/translations/fr.json index e1e1bcb4fcf..d76ec4886e3 100644 --- a/homeassistant/components/guardian/translations/fr.json +++ b/homeassistant/components/guardian/translations/fr.json @@ -15,9 +15,6 @@ "port": "Port" }, "description": "Configurez un appareil Elexa Guardian local." - }, - "zeroconf_confirm": { - "description": "Voulez-vous configurer cet appareil Guardian?" } } } diff --git a/homeassistant/components/guardian/translations/hu.json b/homeassistant/components/guardian/translations/hu.json index 82fd422abf7..1f8f9039707 100644 --- a/homeassistant/components/guardian/translations/hu.json +++ b/homeassistant/components/guardian/translations/hu.json @@ -15,9 +15,6 @@ "port": "Port" }, "description": "Konfigur\u00e1lja a helyi Elexa Guardian eszk\u00f6zt." - }, - "zeroconf_confirm": { - "description": "Szeretn\u00e9 be\u00e1ll\u00edtani ezt a Guardian eszk\u00f6zt?" } } } diff --git a/homeassistant/components/guardian/translations/id.json b/homeassistant/components/guardian/translations/id.json index 8193386fb62..77c46e95afc 100644 --- a/homeassistant/components/guardian/translations/id.json +++ b/homeassistant/components/guardian/translations/id.json @@ -15,9 +15,6 @@ "port": "Port" }, "description": "Konfigurasikan perangkat Elexa Guardian lokal." - }, - "zeroconf_confirm": { - "description": "Ingin menyiapkan perangkat Guardian ini?" } } } diff --git a/homeassistant/components/guardian/translations/it.json b/homeassistant/components/guardian/translations/it.json index 5db0956d80f..e6450dae21d 100644 --- a/homeassistant/components/guardian/translations/it.json +++ b/homeassistant/components/guardian/translations/it.json @@ -15,9 +15,6 @@ "port": "Porta" }, "description": "Configura un dispositivo Elexa Guardian locale." - }, - "zeroconf_confirm": { - "description": "Vuoi configurare questo dispositivo Guardian?" } } } diff --git a/homeassistant/components/guardian/translations/ja.json b/homeassistant/components/guardian/translations/ja.json index 0c282a324bd..337223c5736 100644 --- a/homeassistant/components/guardian/translations/ja.json +++ b/homeassistant/components/guardian/translations/ja.json @@ -15,9 +15,6 @@ "port": "\u30dd\u30fc\u30c8" }, "description": "\u30ed\u30fc\u30ab\u30eb\u306eElexa Guardian\u30c7\u30d0\u30a4\u30b9\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u3002" - }, - "zeroconf_confirm": { - "description": "\u3053\u306eGuardian\u30c7\u30d0\u30a4\u30b9\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" } } } diff --git a/homeassistant/components/guardian/translations/ko.json b/homeassistant/components/guardian/translations/ko.json index d9f70ad2d33..3faa244b246 100644 --- a/homeassistant/components/guardian/translations/ko.json +++ b/homeassistant/components/guardian/translations/ko.json @@ -6,15 +6,15 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "discovery_confirm": { + "description": "Guardian \uae30\uae30\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + }, "user": { "data": { "ip_address": "IP \uc8fc\uc18c", "port": "\ud3ec\ud2b8" }, "description": "\ub85c\uceec Elexa Guardian \uae30\uae30\ub97c \uad6c\uc131\ud574\uc8fc\uc138\uc694." - }, - "zeroconf_confirm": { - "description": "\uc774 Guardian \uae30\uae30\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/guardian/translations/lb.json b/homeassistant/components/guardian/translations/lb.json index 8d062eb8cf7..bc3fdc1eed3 100644 --- a/homeassistant/components/guardian/translations/lb.json +++ b/homeassistant/components/guardian/translations/lb.json @@ -12,9 +12,6 @@ "port": "Port" }, "description": "Ee lokalen Elexa Guardian Apparat ariichten." - }, - "zeroconf_confirm": { - "description": "Soll d\u00ebsen Guardian Apparat konfigur\u00e9iert ginn?" } } } diff --git a/homeassistant/components/guardian/translations/nl.json b/homeassistant/components/guardian/translations/nl.json index 409c3db9bed..d8ed1242e92 100644 --- a/homeassistant/components/guardian/translations/nl.json +++ b/homeassistant/components/guardian/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken" }, "step": { @@ -15,9 +15,6 @@ "port": "Poort" }, "description": "Configureer een lokaal Elexa Guardian-apparaat." - }, - "zeroconf_confirm": { - "description": "Wilt u dit Guardian-apparaat instellen?" } } } diff --git a/homeassistant/components/guardian/translations/no.json b/homeassistant/components/guardian/translations/no.json index 28313fa8520..a61b43dcfea 100644 --- a/homeassistant/components/guardian/translations/no.json +++ b/homeassistant/components/guardian/translations/no.json @@ -15,9 +15,6 @@ "port": "Port" }, "description": "Konfigurer en lokal Elexa Guardian-enhet." - }, - "zeroconf_confirm": { - "description": "Vil du konfigurere denne Guardian-enheten?" } } } diff --git a/homeassistant/components/guardian/translations/pl.json b/homeassistant/components/guardian/translations/pl.json index 663265a7a11..5e5b2d143e2 100644 --- a/homeassistant/components/guardian/translations/pl.json +++ b/homeassistant/components/guardian/translations/pl.json @@ -15,9 +15,6 @@ "port": "Port" }, "description": "Skonfiguruj lokalne urz\u0105dzenie Elexa Guardian." - }, - "zeroconf_confirm": { - "description": "Czy chcesz skonfigurowa\u0107 to urz\u0105dzenie Guardian?" } } } diff --git a/homeassistant/components/guardian/translations/pt-BR.json b/homeassistant/components/guardian/translations/pt-BR.json index f0996e6b84e..f4d4273b4ab 100644 --- a/homeassistant/components/guardian/translations/pt-BR.json +++ b/homeassistant/components/guardian/translations/pt-BR.json @@ -15,9 +15,6 @@ "port": "Porta" }, "description": "Configure um dispositivo local Elexa Guardian." - }, - "zeroconf_confirm": { - "description": "Deseja configurar este dispositivo Guardian?" } } } diff --git a/homeassistant/components/guardian/translations/ru.json b/homeassistant/components/guardian/translations/ru.json index ca904d980af..fe93ccb6369 100644 --- a/homeassistant/components/guardian/translations/ru.json +++ b/homeassistant/components/guardian/translations/ru.json @@ -15,9 +15,6 @@ "port": "\u041f\u043e\u0440\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 Elexa Guardian." - }, - "zeroconf_confirm": { - "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Elexa Guardian?" } } } diff --git a/homeassistant/components/guardian/translations/tr.json b/homeassistant/components/guardian/translations/tr.json index 9d3e903a2d6..fe4dffad3aa 100644 --- a/homeassistant/components/guardian/translations/tr.json +++ b/homeassistant/components/guardian/translations/tr.json @@ -15,9 +15,6 @@ "port": "Port" }, "description": "Yerel bir Elexa Guardian cihaz\u0131 yap\u0131land\u0131r\u0131n." - }, - "zeroconf_confirm": { - "description": "Bu Guardian cihaz\u0131n\u0131 kurmak istiyor musunuz?" } } } diff --git a/homeassistant/components/guardian/translations/uk.json b/homeassistant/components/guardian/translations/uk.json index 439a225895e..fd6ecc8d6d1 100644 --- a/homeassistant/components/guardian/translations/uk.json +++ b/homeassistant/components/guardian/translations/uk.json @@ -12,9 +12,6 @@ "port": "\u041f\u043e\u0440\u0442" }, "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Elexa Guardian." - }, - "zeroconf_confirm": { - "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0446\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Elexa Guardian?" } } } diff --git a/homeassistant/components/guardian/translations/zh-Hant.json b/homeassistant/components/guardian/translations/zh-Hant.json index dc3bde8ec1c..40a7de81170 100644 --- a/homeassistant/components/guardian/translations/zh-Hant.json +++ b/homeassistant/components/guardian/translations/zh-Hant.json @@ -15,9 +15,6 @@ "port": "\u901a\u8a0a\u57e0" }, "description": "\u8a2d\u5b9a\u5340\u57df Elexa Guardian \u88dd\u7f6e\u3002" - }, - "zeroconf_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Guardian \u88dd\u7f6e\uff1f" } } } diff --git a/homeassistant/components/habitica/translations/ca.json b/homeassistant/components/habitica/translations/ca.json index 675fc33db8c..729d03834b5 100644 --- a/homeassistant/components/habitica/translations/ca.json +++ b/homeassistant/components/habitica/translations/ca.json @@ -15,6 +15,5 @@ "description": "Connecta el perfil d'Habitica per permetre el seguiment del teu perfil i tasques d'usuari. Tingues en compte que l'api_id i l'api_key els has d'obtenir des de https://habitica.com/user/settings/api" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/de.json b/homeassistant/components/habitica/translations/de.json index 694bbdd65d4..6eac60a55d3 100644 --- a/homeassistant/components/habitica/translations/de.json +++ b/homeassistant/components/habitica/translations/de.json @@ -15,6 +15,5 @@ "description": "Verbinde dein Habitica-Profil, um die \u00dcberwachung des Profils und der Aufgaben deines Benutzers zu erm\u00f6glichen. Beachte, dass api_id und api_key von https://habitica.com/user/settings/api bezogen werden m\u00fcssen." } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/el.json b/homeassistant/components/habitica/translations/el.json index 43257c239a9..ddacd192d6e 100644 --- a/homeassistant/components/habitica/translations/el.json +++ b/homeassistant/components/habitica/translations/el.json @@ -15,6 +15,5 @@ "description": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Habitica \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb \u03ba\u03b1\u03b9 \u03c4\u03c9\u03bd \u03b5\u03c1\u03b3\u03b1\u03c3\u03b9\u03ce\u03bd \u03c4\u03bf\u03c5 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c3\u03b1\u03c2. \u03a3\u03b7\u03bc\u03b5\u03b9\u03ce\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03c4\u03bf api_id \u03ba\u03b1\u03b9 \u03c4\u03bf api_key \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd \u03b1\u03c0\u03cc \u03c4\u03bf https://habitica.com/user/settings/api." } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/en.json b/homeassistant/components/habitica/translations/en.json index ffbbd2de840..377e3129ee8 100644 --- a/homeassistant/components/habitica/translations/en.json +++ b/homeassistant/components/habitica/translations/en.json @@ -15,6 +15,5 @@ "description": "Connect your Habitica profile to allow monitoring of your user's profile and tasks. Note that api_id and api_key must be gotten from https://habitica.com/user/settings/api" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/es.json b/homeassistant/components/habitica/translations/es.json index 6850c903b99..55cf8eb7642 100644 --- a/homeassistant/components/habitica/translations/es.json +++ b/homeassistant/components/habitica/translations/es.json @@ -15,6 +15,5 @@ "description": "Conecta tu perfil de Habitica para permitir la supervisi\u00f3n del perfil y las tareas de tu usuario. Ten en cuenta que api_id y api_key deben obtenerse de https://habitica.com/user/settings/api" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/et.json b/homeassistant/components/habitica/translations/et.json index cfc2bcf898c..f2ff048c1c4 100644 --- a/homeassistant/components/habitica/translations/et.json +++ b/homeassistant/components/habitica/translations/et.json @@ -15,6 +15,5 @@ "description": "\u00dchenda oma Habitica profiil, et saaksid j\u00e4lgida oma kasutaja profiili ja \u00fclesandeid. Pane t\u00e4hele, et api_id ja api_key tuleb hankida aadressilt https://habitica.com/user/settings/api" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/fr.json b/homeassistant/components/habitica/translations/fr.json index 9f04d9ed588..343f28c0a3e 100644 --- a/homeassistant/components/habitica/translations/fr.json +++ b/homeassistant/components/habitica/translations/fr.json @@ -15,6 +15,5 @@ "description": "Connectez votre profil Habitica pour permettre la surveillance du profil et des t\u00e2ches de votre utilisateur. Notez que api_id et api_key doivent \u00eatre obtenus de https://habitica.com/user/settings/api" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/hu.json b/homeassistant/components/habitica/translations/hu.json index 589f53e852c..a26871e3cbf 100644 --- a/homeassistant/components/habitica/translations/hu.json +++ b/homeassistant/components/habitica/translations/hu.json @@ -15,6 +15,5 @@ "description": "Csatlakoztassa Habitica-profilj\u00e1t, hogy figyelemmel k\u00eds\u00e9rhesse felhaszn\u00e1l\u00f3i profilj\u00e1t \u00e9s feladatait. Ne feledje, hogy az api_id \u00e9s api_key c\u00edmeket a https://habitica.com/user/settings/api webhelyr\u0151l kell beszerezni" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/id.json b/homeassistant/components/habitica/translations/id.json index c7e4c549206..9130a320b6d 100644 --- a/homeassistant/components/habitica/translations/id.json +++ b/homeassistant/components/habitica/translations/id.json @@ -15,6 +15,5 @@ "description": "Hubungkan profil Habitica Anda untuk memungkinkan pemantauan profil dan tugas pengguna Anda. Perhatikan bahwa api_id dan api_key harus diperoleh dari https://habitica.com/user/settings/api" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/it.json b/homeassistant/components/habitica/translations/it.json index 2bef21519b6..9467c5af335 100644 --- a/homeassistant/components/habitica/translations/it.json +++ b/homeassistant/components/habitica/translations/it.json @@ -15,6 +15,5 @@ "description": "Collega il tuo profilo Habitica per consentire il monitoraggio del profilo e delle attivit\u00e0 dell'utente. Nota che api_id e api_key devono essere ottenuti da https://habitica.com/user/settings/api" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/ja.json b/homeassistant/components/habitica/translations/ja.json index 28d877a4788..b478f5f083d 100644 --- a/homeassistant/components/habitica/translations/ja.json +++ b/homeassistant/components/habitica/translations/ja.json @@ -15,6 +15,5 @@ "description": "Habitica profile\u306b\u63a5\u7d9a\u3057\u3066\u3001\u3042\u306a\u305f\u306e\u30e6\u30fc\u30b6\u30fc\u306e\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u3068\u30bf\u30b9\u30af\u3092\u76e3\u8996\u3067\u304d\u308b\u3088\u3046\u306b\u3057\u307e\u3059\u3002 \u6ce8\u610f: api_id\u3068api_key\u306f\u3001https://habitica.com/user/settings/api \u304b\u3089\u53d6\u5f97\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/ko.json b/homeassistant/components/habitica/translations/ko.json index 6b890a320df..7640f163cc0 100644 --- a/homeassistant/components/habitica/translations/ko.json +++ b/homeassistant/components/habitica/translations/ko.json @@ -15,6 +15,5 @@ "description": "\uc0ac\uc6a9\uc790\uc758 \ud504\ub85c\ud544 \ubc0f \uc791\uc5c5\uc744 \ubaa8\ub2c8\ud130\ub9c1\ud560 \uc218 \uc788\ub3c4\ub85d \ud558\ub824\uba74 Habitica \ud504\ub85c\ud544\uc744 \uc5f0\uacb0\ud574\uc8fc\uc138\uc694.\n\ucc38\uace0\ub85c api_id \ubc0f api_key\ub294 https://habitica.com/user/settings/api \uc5d0\uc11c \uac00\uc838\uc640\uc57c \ud569\ub2c8\ub2e4." } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/nl.json b/homeassistant/components/habitica/translations/nl.json index 817ffd8c616..91461372e59 100644 --- a/homeassistant/components/habitica/translations/nl.json +++ b/homeassistant/components/habitica/translations/nl.json @@ -15,6 +15,5 @@ "description": "Verbind uw Habitica-profiel om het profiel en de taken van uw gebruiker te bewaken. Houd er rekening mee dat api_id en api_key van https://habitica.com/user/settings/api moeten worden gehaald" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/no.json b/homeassistant/components/habitica/translations/no.json index cdb72d3c3d6..218b861e513 100644 --- a/homeassistant/components/habitica/translations/no.json +++ b/homeassistant/components/habitica/translations/no.json @@ -15,6 +15,5 @@ "description": "Koble til Habitica-profilen din for \u00e5 tillate overv\u00e5king av brukerens profil og oppgaver. Merk at api_id og api_key m\u00e5 hentes fra https://habitica.com/user/settings/api" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/pl.json b/homeassistant/components/habitica/translations/pl.json index f06f1a0e1aa..7a8c6a088bc 100644 --- a/homeassistant/components/habitica/translations/pl.json +++ b/homeassistant/components/habitica/translations/pl.json @@ -15,6 +15,5 @@ "description": "Po\u0142\u0105cz sw\u00f3j profil Habitica, aby umo\u017cliwi\u0107 monitorowanie profilu i zada\u0144 u\u017cytkownika. Pami\u0119taj, \u017ce api_id i api_key musz\u0105 zosta\u0107 pobrane z https://habitica.com/user/settings/api" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/pt-BR.json b/homeassistant/components/habitica/translations/pt-BR.json index cbdb6e3453f..b5aafdea88b 100644 --- a/homeassistant/components/habitica/translations/pt-BR.json +++ b/homeassistant/components/habitica/translations/pt-BR.json @@ -15,6 +15,5 @@ "description": "Conecte seu perfil do Habitica para permitir o monitoramento do perfil e das tarefas do seu usu\u00e1rio. Observe que api_id e api_key devem ser obtidos em https://habitica.com/user/settings/api" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/ru.json b/homeassistant/components/habitica/translations/ru.json index 4899cd1e43b..23576d2a6d6 100644 --- a/homeassistant/components/habitica/translations/ru.json +++ b/homeassistant/components/habitica/translations/ru.json @@ -15,6 +15,5 @@ "description": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 \u043f\u0440\u043e\u0444\u0438\u043b\u044c Habitica, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u0412\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e api_id \u0438 api_key \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u044b \u0441 https://habitica.com/user/settings/api" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/tr.json b/homeassistant/components/habitica/translations/tr.json index 32ad5aa4957..dfb29c8d17c 100644 --- a/homeassistant/components/habitica/translations/tr.json +++ b/homeassistant/components/habitica/translations/tr.json @@ -15,6 +15,5 @@ "description": "Kullan\u0131c\u0131n\u0131z\u0131n profilinin ve g\u00f6revlerinin izlenmesine izin vermek i\u00e7in Habitica profilinizi ba\u011flay\u0131n. api_id ve api_key'in https://habitica.com/user/settings/api adresinden al\u0131nmas\u0131 gerekti\u011fini unutmay\u0131n." } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/zh-Hant.json b/homeassistant/components/habitica/translations/zh-Hant.json index 65918685161..d5aeda2c359 100644 --- a/homeassistant/components/habitica/translations/zh-Hant.json +++ b/homeassistant/components/habitica/translations/zh-Hant.json @@ -15,6 +15,5 @@ "description": "\u9023\u7dda\u81f3 Habitica \u8a2d\u5b9a\u6a94\u4ee5\u4f9b\u76e3\u63a7\u500b\u4eba\u8a2d\u5b9a\u8207\u4efb\u52d9\u3002\u6ce8\u610f\uff1a\u5fc5\u9808\u7531 https://habitica.com/user/settings/api \u53d6\u5f97 api_id \u8207 api_key" } } - }, - "title": "Habitica" + } } \ No newline at end of file diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py index 11181c5b0ff..c3c363ef55e 100644 --- a/homeassistant/components/hangouts/hangouts_bot.py +++ b/homeassistant/components/hangouts/hangouts_bot.py @@ -187,11 +187,16 @@ class HangoutsBot: if not (match := matcher.match(text)): continue if intent_type == INTENT_HELP: - return await self.hass.helpers.intent.async_handle( - DOMAIN, intent_type, {"conv_id": {"value": conv_id}}, text + return await intent.async_handle( + self.hass, + DOMAIN, + intent_type, + {"conv_id": {"value": conv_id}}, + text, ) - return await self.hass.helpers.intent.async_handle( + return await intent.async_handle( + self.hass, DOMAIN, intent_type, {"conv_id": {"value": conv_id}} diff --git a/homeassistant/components/hangouts/translations/es.json b/homeassistant/components/hangouts/translations/es.json index 692df44c5bc..a2aba99c24c 100644 --- a/homeassistant/components/hangouts/translations/es.json +++ b/homeassistant/components/hangouts/translations/es.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Google Hangouts ya est\u00e1 configurado", + "already_configured": "El servicio ya est\u00e1 configurado", "unknown": "Error desconocido" }, "error": { "invalid_2fa": "Autenticaci\u00f3n de 2 factores no v\u00e1lida, por favor, int\u00e9ntelo de nuevo.", - "invalid_2fa_method": "M\u00e9todo 2FA no v\u00e1lido (verificar en el tel\u00e9fono).", + "invalid_2fa_method": "M\u00e9todo 2FA inv\u00e1lido (verificar en el tel\u00e9fono).", "invalid_login": "Inicio de sesi\u00f3n no v\u00e1lido, por favor, int\u00e9ntalo de nuevo." }, "step": { @@ -24,7 +24,7 @@ "password": "Contrase\u00f1a" }, "description": "Vac\u00edo", - "title": "Iniciar sesi\u00f3n en Google Hangouts" + "title": "Inicio de sesi\u00f3n de Google Chat" } } } diff --git a/homeassistant/components/hangouts/translations/nl.json b/homeassistant/components/hangouts/translations/nl.json index 276c310da5a..826bc560045 100644 --- a/homeassistant/components/hangouts/translations/nl.json +++ b/homeassistant/components/hangouts/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", + "already_configured": "Dienst is al geconfigureerd", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/hardkernel/__init__.py b/homeassistant/components/hardkernel/__init__.py new file mode 100644 index 00000000000..6dfe30b9e75 --- /dev/null +++ b/homeassistant/components/hardkernel/__init__.py @@ -0,0 +1,22 @@ +"""The Hardkernel integration.""" +from __future__ import annotations + +from homeassistant.components.hassio import get_os_info +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up a Hardkernel config entry.""" + if (os_info := get_os_info(hass)) is None: + # The hassio integration has not yet fetched data from the supervisor + raise ConfigEntryNotReady + + board: str + if (board := os_info.get("board")) is None or not board.startswith("odroid"): + # Not running on a Hardkernel board, Home Assistant may have been migrated + hass.async_create_task(hass.config_entries.async_remove(entry.entry_id)) + return False + + return True diff --git a/homeassistant/components/hardkernel/config_flow.py b/homeassistant/components/hardkernel/config_flow.py new file mode 100644 index 00000000000..b0445fae231 --- /dev/null +++ b/homeassistant/components/hardkernel/config_flow.py @@ -0,0 +1,22 @@ +"""Config flow for the Hardkernel 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 DOMAIN + + +class HardkernelConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Hardkernel.""" + + VERSION = 1 + + async def async_step_system(self, data: dict[str, Any] | None = None) -> FlowResult: + """Handle the initial step.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + return self.async_create_entry(title="Hardkernel", data={}) diff --git a/homeassistant/components/hardkernel/const.py b/homeassistant/components/hardkernel/const.py new file mode 100644 index 00000000000..2850f3d4ebb --- /dev/null +++ b/homeassistant/components/hardkernel/const.py @@ -0,0 +1,3 @@ +"""Constants for the Hardkernel integration.""" + +DOMAIN = "hardkernel" diff --git a/homeassistant/components/hardkernel/hardware.py b/homeassistant/components/hardkernel/hardware.py new file mode 100644 index 00000000000..804f105f2ed --- /dev/null +++ b/homeassistant/components/hardkernel/hardware.py @@ -0,0 +1,39 @@ +"""The Hardkernel hardware platform.""" +from __future__ import annotations + +from homeassistant.components.hardware.models import BoardInfo, HardwareInfo +from homeassistant.components.hassio import get_os_info +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError + +from .const import DOMAIN + +BOARD_NAMES = { + "odroid-c2": "Hardkernel Odroid-C2", + "odroid-c4": "Hardkernel Odroid-C4", + "odroid-n2": "Home Assistant Blue / Hardkernel Odroid-N2", + "odroid-xu4": "Hardkernel Odroid-XU4", +} + + +@callback +def async_info(hass: HomeAssistant) -> HardwareInfo: + """Return board info.""" + if (os_info := get_os_info(hass)) is None: + raise HomeAssistantError + board: str + if (board := os_info.get("board")) is None: + raise HomeAssistantError + if not board.startswith("odroid"): + raise HomeAssistantError + + return HardwareInfo( + board=BoardInfo( + hassio_board_id=board, + manufacturer=DOMAIN, + model=board, + revision=None, + ), + name=BOARD_NAMES.get(board, f"Unknown hardkernel Odroid model '{board}'"), + url=None, + ) diff --git a/homeassistant/components/hardkernel/manifest.json b/homeassistant/components/hardkernel/manifest.json new file mode 100644 index 00000000000..366ca245191 --- /dev/null +++ b/homeassistant/components/hardkernel/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "hardkernel", + "name": "Hardkernel", + "config_flow": false, + "documentation": "https://www.home-assistant.io/integrations/hardkernel", + "dependencies": ["hardware", "hassio"], + "codeowners": ["@home-assistant/core"], + "integration_type": "hardware" +} diff --git a/homeassistant/components/hardware/__init__.py b/homeassistant/components/hardware/__init__.py new file mode 100644 index 00000000000..b3f342d4e32 --- /dev/null +++ b/homeassistant/components/hardware/__init__.py @@ -0,0 +1,17 @@ +"""The Hardware integration.""" +from __future__ import annotations + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType + +from . import websocket_api +from .const import DOMAIN + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up Hardware.""" + hass.data[DOMAIN] = {} + + websocket_api.async_setup(hass) + + return True diff --git a/homeassistant/components/hardware/const.py b/homeassistant/components/hardware/const.py new file mode 100644 index 00000000000..7fd64d5d968 --- /dev/null +++ b/homeassistant/components/hardware/const.py @@ -0,0 +1,3 @@ +"""Constants for the Hardware integration.""" + +DOMAIN = "hardware" diff --git a/homeassistant/components/hardware/hardware.py b/homeassistant/components/hardware/hardware.py new file mode 100644 index 00000000000..e07f70022f4 --- /dev/null +++ b/homeassistant/components/hardware/hardware.py @@ -0,0 +1,31 @@ +"""The Hardware integration.""" +from __future__ import annotations + +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.integration_platform import ( + async_process_integration_platforms, +) + +from .const import DOMAIN +from .models import HardwareProtocol + + +async def async_process_hardware_platforms(hass: HomeAssistant): + """Start processing hardware platforms.""" + hass.data[DOMAIN]["hardware_platform"] = {} + + await async_process_integration_platforms(hass, DOMAIN, _register_hardware_platform) + + return True + + +async def _register_hardware_platform( + hass: HomeAssistant, integration_domain: str, platform: HardwareProtocol +): + """Register a hardware platform.""" + if integration_domain == DOMAIN: + return + if not hasattr(platform, "async_info"): + raise HomeAssistantError(f"Invalid hardware platform {platform}") + hass.data[DOMAIN]["hardware_platform"][integration_domain] = platform diff --git a/homeassistant/components/hardware/manifest.json b/homeassistant/components/hardware/manifest.json new file mode 100644 index 00000000000..e7e156b6065 --- /dev/null +++ b/homeassistant/components/hardware/manifest.json @@ -0,0 +1,7 @@ +{ + "domain": "hardware", + "name": "Hardware", + "config_flow": false, + "documentation": "https://www.home-assistant.io/integrations/hardware", + "codeowners": ["@home-assistant/core"] +} diff --git a/homeassistant/components/hardware/models.py b/homeassistant/components/hardware/models.py new file mode 100644 index 00000000000..067c2d955df --- /dev/null +++ b/homeassistant/components/hardware/models.py @@ -0,0 +1,34 @@ +"""Models for Hardware.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Protocol + +from homeassistant.core import HomeAssistant, callback + + +@dataclass +class BoardInfo: + """Board info type.""" + + hassio_board_id: str | None + manufacturer: str + model: str | None + revision: str | None + + +@dataclass +class HardwareInfo: + """Hardware info type.""" + + name: str | None + board: BoardInfo | None + url: str | None + + +class HardwareProtocol(Protocol): + """Define the format of hardware platforms.""" + + @callback + def async_info(self, hass: HomeAssistant) -> HardwareInfo: + """Return info.""" diff --git a/homeassistant/components/hardware/websocket_api.py b/homeassistant/components/hardware/websocket_api.py new file mode 100644 index 00000000000..388b9597481 --- /dev/null +++ b/homeassistant/components/hardware/websocket_api.py @@ -0,0 +1,47 @@ +"""The Hardware websocket API.""" +from __future__ import annotations + +import contextlib +from dataclasses import asdict + +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError + +from .const import DOMAIN +from .hardware import async_process_hardware_platforms +from .models import HardwareProtocol + + +@callback +def async_setup(hass: HomeAssistant) -> None: + """Set up the hardware websocket API.""" + websocket_api.async_register_command(hass, ws_info) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "hardware/info", + } +) +@websocket_api.async_response +async def ws_info( + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict +) -> None: + """Return hardware info.""" + hardware_info = [] + + if "hardware_platform" not in hass.data[DOMAIN]: + await async_process_hardware_platforms(hass) + + hardware_platform: dict[str, HardwareProtocol] = hass.data[DOMAIN][ + "hardware_platform" + ] + for platform in hardware_platform.values(): + if hasattr(platform, "async_info"): + with contextlib.suppress(HomeAssistantError): + hardware_info.append(asdict(platform.async_info(hass))) + + connection.send_result(msg["id"], {"hardware": hardware_info}) diff --git a/homeassistant/components/harmony/translations/es.json b/homeassistant/components/harmony/translations/es.json index 39305d30680..527cd8433e8 100644 --- a/homeassistant/components/harmony/translations/es.json +++ b/homeassistant/components/harmony/translations/es.json @@ -4,10 +4,10 @@ "already_configured": "El dispositivo ya est\u00e1 configurado" }, "error": { - "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo", + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", "unknown": "Error inesperado" }, - "flow_title": "Logitech Harmony Hub {name}", + "flow_title": "{name}", "step": { "link": { "description": "\u00bfQuieres configurar {name} ({host})?", diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index a3689f61746..93d902e4bae 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -33,14 +33,15 @@ from homeassistant.core import ( callback, ) from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv, recorder -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.device_registry import ( - DeviceEntryType, - DeviceRegistry, - async_get_registry, +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + recorder, ) +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.event import async_track_point_in_utc_time +from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.loader import bind_hass @@ -204,6 +205,10 @@ MAP_SERVICE_API = { ), } +HARDWARE_INTEGRATIONS = { + "rpi": "raspberry_pi", +} + @bind_hass async def async_get_addon_info(hass: HomeAssistant, slug: str) -> dict: @@ -519,7 +524,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: if not await hassio.is_connected(): _LOGGER.warning("Not connected with the supervisor / system too busy!") - store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + store = Store(hass, STORAGE_VERSION, STORAGE_KEY) if (data := await store.async_load()) is None: data = {} @@ -637,8 +642,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: except HassioAPIError as err: _LOGGER.warning("Can't read Supervisor data: %s", err) - hass.helpers.event.async_track_point_in_utc_time( - update_info_data, utcnow() + HASSIO_UPDATE_INTERVAL + async_track_point_in_utc_time( + hass, update_info_data, utcnow() + HASSIO_UPDATE_INTERVAL ) # Fetch data @@ -704,6 +709,29 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: # Init add-on ingress panels await async_setup_addon_panel(hass, hassio) + # Setup hardware integration for the detected board type + async def _async_setup_hardware_integration(hass): + """Set up hardaware integration for the detected board type.""" + if (os_info := get_os_info(hass)) is None: + # os info not yet fetched from supervisor, retry later + async_track_point_in_utc_time( + hass, + _async_setup_hardware_integration, + utcnow() + HASSIO_UPDATE_INTERVAL, + ) + return + if (board := os_info.get("board")) is None: + return + if (hw_integration := HARDWARE_INTEGRATIONS.get(board)) is None: + return + hass.async_create_task( + hass.config_entries.flow.async_init( + hw_integration, context={"source": "system"} + ) + ) + + await _async_setup_hardware_integration(hass) + hass.async_create_task( hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"}) ) @@ -713,7 +741,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up a config entry.""" - dev_reg = await async_get_registry(hass) + dev_reg = dr.async_get(hass) coordinator = HassioDataUpdateCoordinator(hass, entry, dev_reg) hass.data[ADDONS_COORDINATOR] = coordinator await coordinator.async_config_entry_first_refresh() @@ -735,7 +763,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @callback def async_register_addons_in_dev_reg( - entry_id: str, dev_reg: DeviceRegistry, addons: list[dict[str, Any]] + entry_id: str, dev_reg: dr.DeviceRegistry, addons: list[dict[str, Any]] ) -> None: """Register addons in the device registry.""" for addon in addons: @@ -744,7 +772,7 @@ def async_register_addons_in_dev_reg( model=SupervisorEntityModel.ADDON, sw_version=addon[ATTR_VERSION], name=addon[ATTR_NAME], - entry_type=DeviceEntryType.SERVICE, + entry_type=dr.DeviceEntryType.SERVICE, configuration_url=f"homeassistant://hassio/addon/{addon[ATTR_SLUG]}", ) if manufacturer := addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL): @@ -754,7 +782,7 @@ def async_register_addons_in_dev_reg( @callback def async_register_os_in_dev_reg( - entry_id: str, dev_reg: DeviceRegistry, os_dict: dict[str, Any] + entry_id: str, dev_reg: dr.DeviceRegistry, os_dict: dict[str, Any] ) -> None: """Register OS in the device registry.""" params = DeviceInfo( @@ -763,7 +791,7 @@ def async_register_os_in_dev_reg( model=SupervisorEntityModel.OS, sw_version=os_dict[ATTR_VERSION], name="Home Assistant Operating System", - entry_type=DeviceEntryType.SERVICE, + entry_type=dr.DeviceEntryType.SERVICE, ) dev_reg.async_get_or_create(config_entry_id=entry_id, **params) @@ -771,7 +799,7 @@ def async_register_os_in_dev_reg( @callback def async_register_core_in_dev_reg( entry_id: str, - dev_reg: DeviceRegistry, + dev_reg: dr.DeviceRegistry, core_dict: dict[str, Any], ) -> None: """Register OS in the device registry.""" @@ -781,7 +809,7 @@ def async_register_core_in_dev_reg( model=SupervisorEntityModel.CORE, sw_version=core_dict[ATTR_VERSION], name="Home Assistant Core", - entry_type=DeviceEntryType.SERVICE, + entry_type=dr.DeviceEntryType.SERVICE, ) dev_reg.async_get_or_create(config_entry_id=entry_id, **params) @@ -789,7 +817,7 @@ def async_register_core_in_dev_reg( @callback def async_register_supervisor_in_dev_reg( entry_id: str, - dev_reg: DeviceRegistry, + dev_reg: dr.DeviceRegistry, supervisor_dict: dict[str, Any], ) -> None: """Register OS in the device registry.""" @@ -799,13 +827,15 @@ def async_register_supervisor_in_dev_reg( model=SupervisorEntityModel.SUPERVIOSR, sw_version=supervisor_dict[ATTR_VERSION], name="Home Assistant Supervisor", - entry_type=DeviceEntryType.SERVICE, + entry_type=dr.DeviceEntryType.SERVICE, ) dev_reg.async_get_or_create(config_entry_id=entry_id, **params) @callback -def async_remove_addons_from_dev_reg(dev_reg: DeviceRegistry, addons: set[str]) -> None: +def async_remove_addons_from_dev_reg( + dev_reg: dr.DeviceRegistry, addons: set[str] +) -> None: """Remove addons from the device registry.""" for addon_slug in addons: if dev := dev_reg.async_get_device({(DOMAIN, addon_slug)}): @@ -816,7 +846,7 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator): """Class to retrieve Hass.io status.""" def __init__( - self, hass: HomeAssistant, config_entry: ConfigEntry, dev_reg: DeviceRegistry + self, hass: HomeAssistant, config_entry: ConfigEntry, dev_reg: dr.DeviceRegistry ) -> None: """Initialize coordinator.""" super().__init__( diff --git a/homeassistant/components/hassio/strings.json b/homeassistant/components/hassio/strings.json index 875a79a60d7..90142bd453f 100644 --- a/homeassistant/components/hassio/strings.json +++ b/homeassistant/components/hassio/strings.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Agent Version", "board": "Board", "disk_total": "Disk Total", "disk_used": "Disk Used", diff --git a/homeassistant/components/hassio/system_health.py b/homeassistant/components/hassio/system_health.py index 5ce38b0e121..1039a0237a8 100644 --- a/homeassistant/components/hassio/system_health.py +++ b/homeassistant/components/hassio/system_health.py @@ -44,6 +44,7 @@ async def system_health_info(hass: HomeAssistant): "host_os": host_info.get("operating_system"), "update_channel": info.get("channel"), "supervisor_version": f"supervisor-{info.get('supervisor')}", + "agent_version": host_info.get("agent_version"), "docker_version": info.get("docker"), "disk_total": f"{host_info.get('disk_total')} GB", "disk_used": f"{host_info.get('disk_used')} GB", diff --git a/homeassistant/components/hassio/translations/ca.json b/homeassistant/components/hassio/translations/ca.json index 7813d970d0e..2c4285d4908 100644 --- a/homeassistant/components/hassio/translations/ca.json +++ b/homeassistant/components/hassio/translations/ca.json @@ -1,8 +1,9 @@ { "system_health": { "info": { + "agent_version": "Versi\u00f3 de l'agent", "board": "Placa", - "disk_total": "Total disc", + "disk_total": "Total del disc", "disk_used": "Emmagatzematge utilitzat", "docker_version": "Versi\u00f3 de Docker", "healthy": "Saludable", @@ -11,7 +12,7 @@ "supervisor_api": "API del Supervisor", "supervisor_version": "Versi\u00f3 del Supervisor", "supported": "Compatible", - "update_channel": "Canal d'actualitzaci\u00f3", + "update_channel": "Canal d'actualitzacions", "version_api": "Versi\u00f3 d'APIs" } } diff --git a/homeassistant/components/hassio/translations/de.json b/homeassistant/components/hassio/translations/de.json index 99747512e97..f25ae73b423 100644 --- a/homeassistant/components/hassio/translations/de.json +++ b/homeassistant/components/hassio/translations/de.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Agent-Version", "board": "Board", "disk_total": "Speicherplatz gesamt", "disk_used": "Speicherplatz genutzt", diff --git a/homeassistant/components/hassio/translations/en.json b/homeassistant/components/hassio/translations/en.json index bb5f8e6f01a..14d79f0d8d6 100644 --- a/homeassistant/components/hassio/translations/en.json +++ b/homeassistant/components/hassio/translations/en.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Agent Version", "board": "Board", "disk_total": "Disk Total", "disk_used": "Disk Used", diff --git a/homeassistant/components/hassio/translations/et.json b/homeassistant/components/hassio/translations/et.json index 4449c058498..b86eef353b9 100644 --- a/homeassistant/components/hassio/translations/et.json +++ b/homeassistant/components/hassio/translations/et.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Agendi versioon", "board": "Seade", "disk_total": "Kettaruum kokku", "disk_used": "Kasutatud kettaruum", diff --git a/homeassistant/components/hassio/translations/fr.json b/homeassistant/components/hassio/translations/fr.json index 6e20e37a2b9..9a042b97e57 100644 --- a/homeassistant/components/hassio/translations/fr.json +++ b/homeassistant/components/hassio/translations/fr.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Version de l'agent", "board": "Tableau de bord", "disk_total": "Taille total du disque", "disk_used": "Taille du disque utilis\u00e9", diff --git a/homeassistant/components/hassio/translations/hu.json b/homeassistant/components/hassio/translations/hu.json index 8d2bdfd75ad..4c83b94935d 100644 --- a/homeassistant/components/hassio/translations/hu.json +++ b/homeassistant/components/hassio/translations/hu.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "\u00dcgyn\u00f6k verzi\u00f3", "board": "Alaplap", "disk_total": "\u00d6sszes hely", "disk_used": "Felhaszn\u00e1lt hely", diff --git a/homeassistant/components/hassio/translations/id.json b/homeassistant/components/hassio/translations/id.json index b87e1b47c44..250e6e7d4ad 100644 --- a/homeassistant/components/hassio/translations/id.json +++ b/homeassistant/components/hassio/translations/id.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Versi Agen", "board": "Board", "disk_total": "Total Disk", "disk_used": "Disk Digunakan", diff --git a/homeassistant/components/hassio/translations/it.json b/homeassistant/components/hassio/translations/it.json index b601a11241d..3dc55d0f525 100644 --- a/homeassistant/components/hassio/translations/it.json +++ b/homeassistant/components/hassio/translations/it.json @@ -1,15 +1,16 @@ { "system_health": { "info": { + "agent_version": "Versione agente", "board": "Scheda di base", "disk_total": "Disco totale", "disk_used": "Disco utilizzato", "docker_version": "Versione Docker", "healthy": "Integrit\u00e0", - "host_os": "Sistema operativo dell'host", + "host_os": "Sistema Operativo Host", "installed_addons": "Componenti aggiuntivi installati", - "supervisor_api": "API supervisore", - "supervisor_version": "Versione supervisore", + "supervisor_api": "API Supervisor", + "supervisor_version": "Versione Supervisor", "supported": "Supportato", "update_channel": "Canale di aggiornamento", "version_api": "Versione API" diff --git a/homeassistant/components/hassio/translations/ja.json b/homeassistant/components/hassio/translations/ja.json index b7489852bdb..2561cf75310 100644 --- a/homeassistant/components/hassio/translations/ja.json +++ b/homeassistant/components/hassio/translations/ja.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "\u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u306e\u30d0\u30fc\u30b8\u30e7\u30f3", "board": "\u30dc\u30fc\u30c9", "disk_total": "\u30c7\u30a3\u30b9\u30af\u5408\u8a08", "disk_used": "\u4f7f\u7528\u6e08\u307f\u30c7\u30a3\u30b9\u30af", diff --git a/homeassistant/components/hassio/translations/nl.json b/homeassistant/components/hassio/translations/nl.json index e5541ff1d00..3a31897fd67 100644 --- a/homeassistant/components/hassio/translations/nl.json +++ b/homeassistant/components/hassio/translations/nl.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Agent-versie", "board": "Bord", "disk_total": "Totale schijfruimte", "disk_used": "Gebruikte schijfruimte", diff --git a/homeassistant/components/hassio/translations/no.json b/homeassistant/components/hassio/translations/no.json index 30ff8b903a9..1fa10a98921 100644 --- a/homeassistant/components/hassio/translations/no.json +++ b/homeassistant/components/hassio/translations/no.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Agentversjon", "board": "Styret", "disk_total": "Disk totalt", "disk_used": "Disk brukt", diff --git a/homeassistant/components/hassio/translations/pl.json b/homeassistant/components/hassio/translations/pl.json index 2f6b5cab1dc..8850b7066fd 100644 --- a/homeassistant/components/hassio/translations/pl.json +++ b/homeassistant/components/hassio/translations/pl.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Wersja agenta", "board": "Uk\u0142ad", "disk_total": "Pojemno\u015b\u0107 dysku", "disk_used": "Pojemno\u015b\u0107 u\u017cyta", diff --git a/homeassistant/components/hassio/translations/pt-BR.json b/homeassistant/components/hassio/translations/pt-BR.json index b157725600d..4f3e5d84ec1 100644 --- a/homeassistant/components/hassio/translations/pt-BR.json +++ b/homeassistant/components/hassio/translations/pt-BR.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Vers\u00e3o do Agent", "board": "Borda", "disk_total": "Total do disco", "disk_used": "Disco usado", diff --git a/homeassistant/components/hassio/translations/tr.json b/homeassistant/components/hassio/translations/tr.json index 16504c32372..cf92d597e23 100644 --- a/homeassistant/components/hassio/translations/tr.json +++ b/homeassistant/components/hassio/translations/tr.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Arac\u0131 S\u00fcr\u00fcm\u00fc", "board": "Panel", "disk_total": "Disk Toplam\u0131", "disk_used": "Kullan\u0131lan Disk", diff --git a/homeassistant/components/hassio/translations/zh-Hant.json b/homeassistant/components/hassio/translations/zh-Hant.json index 91c7f64e39c..5a503e54937 100644 --- a/homeassistant/components/hassio/translations/zh-Hant.json +++ b/homeassistant/components/hassio/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "agent_version": "Agent \u7248\u672c", "board": "\u677f", "disk_total": "\u7e3d\u78c1\u789f\u7a7a\u9593", "disk_used": "\u5df2\u4f7f\u7528\u7a7a\u9593", diff --git a/homeassistant/components/hdmi_cec/switch.py b/homeassistant/components/hdmi_cec/switch.py index a5d64b2a7fa..076bedde0b2 100644 --- a/homeassistant/components/hdmi_cec/switch.py +++ b/homeassistant/components/hdmi_cec/switch.py @@ -52,15 +52,6 @@ class CecSwitchEntity(CecEntity, SwitchEntity): self._state = STATE_OFF self.schedule_update_ha_state(force_refresh=False) - def toggle(self, **kwargs): - """Toggle the entity.""" - self._device.toggle() - if self._state == STATE_ON: - self._state = STATE_OFF - else: - self._state = STATE_ON - self.schedule_update_ha_state(force_refresh=False) - @property def is_on(self) -> bool: """Return True if entity is on.""" diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index dbd66e28307..a7f56e91368 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -12,6 +12,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady, 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_connect, @@ -167,10 +168,9 @@ class ControllerManager: async def connect_listeners(self): """Subscribe to events of interest.""" - self._device_registry, self._entity_registry = await asyncio.gather( - self._hass.helpers.device_registry.async_get_registry(), - self._hass.helpers.entity_registry.async_get_registry(), - ) + self._device_registry = dr.async_get(self._hass) + self._entity_registry = er.async_get(self._hass) + # Handle controller events self._signals.append( self.controller.dispatcher.connect( diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 2a68ecdc7df..ad9225d9b21 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -1,14 +1,18 @@ """Denon HEOS Media Player.""" from __future__ import annotations +from collections.abc import Awaitable, Callable, Coroutine from functools import reduce, wraps import logging from operator import ior +from typing import Any from pyheos import HeosError, const as heos_const +from typing_extensions import ParamSpec from homeassistant.components import media_source from homeassistant.components.media_player import ( + MediaPlayerEnqueue, MediaPlayerEntity, MediaPlayerEntityFeature, ) @@ -42,6 +46,8 @@ from .const import ( SIGNAL_HEOS_UPDATED, ) +_P = ParamSpec("_P") + BASE_SUPPORTED_FEATURES = ( MediaPlayerEntityFeature.VOLUME_MUTE | MediaPlayerEntityFeature.VOLUME_SET @@ -68,6 +74,14 @@ CONTROL_TO_SUPPORT = { heos_const.CONTROL_PLAY_NEXT: MediaPlayerEntityFeature.NEXT_TRACK, } +HA_HEOS_ENQUEUE_MAP = { + None: heos_const.ADD_QUEUE_REPLACE_AND_PLAY, + MediaPlayerEnqueue.ADD: heos_const.ADD_QUEUE_ADD_TO_END, + MediaPlayerEnqueue.REPLACE: heos_const.ADD_QUEUE_REPLACE_AND_PLAY, + MediaPlayerEnqueue.NEXT: heos_const.ADD_QUEUE_PLAY_NEXT, + MediaPlayerEnqueue.PLAY: heos_const.ADD_QUEUE_PLAY_NOW, +} + _LOGGER = logging.getLogger(__name__) @@ -80,12 +94,16 @@ async def async_setup_entry( async_add_entities(devices, True) -def log_command_error(command: str): +def log_command_error( + command: str, +) -> Callable[[Callable[_P, Awaitable[Any]]], Callable[_P, Coroutine[Any, Any, None]]]: """Return decorator that logs command failure.""" - def decorator(func): + def decorator( + func: Callable[_P, Awaitable[Any]] + ) -> Callable[_P, Coroutine[Any, Any, None]]: @wraps(func) - async def wrapper(*args, **kwargs): + async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> None: try: await func(*args, **kwargs) except (HeosError, ValueError) as ex: @@ -183,7 +201,9 @@ class HeosMediaPlayer(MediaPlayerEntity): """Play a piece of media.""" if media_source.is_media_source_id(media_id): media_type = MEDIA_TYPE_URL - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = play_item.url if media_type in (MEDIA_TYPE_URL, MEDIA_TYPE_MUSIC): @@ -213,11 +233,8 @@ class HeosMediaPlayer(MediaPlayerEntity): playlist = next((p for p in playlists if p.name == media_id), None) if not playlist: raise ValueError(f"Invalid playlist '{media_id}'") - add_queue_option = ( - heos_const.ADD_QUEUE_ADD_TO_END - if kwargs.get(ATTR_MEDIA_ENQUEUE) - else heos_const.ADD_QUEUE_REPLACE_AND_PLAY - ) + add_queue_option = HA_HEOS_ENQUEUE_MAP.get(kwargs.get(ATTR_MEDIA_ENQUEUE)) + await self._player.add_to_queue(playlist, add_queue_option) return diff --git a/homeassistant/components/heos/translations/ja.json b/homeassistant/components/heos/translations/ja.json index 0464bf98979..55e075a548a 100644 --- a/homeassistant/components/heos/translations/ja.json +++ b/homeassistant/components/heos/translations/ja.json @@ -11,7 +11,7 @@ "data": { "host": "\u30db\u30b9\u30c8" }, - "description": "Heos\u30c7\u30d0\u30a4\u30b9(\u3067\u304d\u308c\u3070\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u306b\u6709\u7dda\u3067\u63a5\u7d9a\u3055\u308c\u3066\u3044\u308b\u30c7\u30d0\u30a4\u30b9)\u306e\u30db\u30b9\u30c8\u540d\u307e\u305f\u306fIP\u30a2\u30c9\u30ec\u30b9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "description": "Heos\u30c7\u30d0\u30a4\u30b9(\u53ef\u80fd\u306a\u306e\u3067\u3042\u308c\u3070\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u306b\u6709\u7dda\u3067\u63a5\u7d9a\u3055\u308c\u3066\u3044\u308b\u30c7\u30d0\u30a4\u30b9)\u306e\u30db\u30b9\u30c8\u540d\u307e\u305f\u306fIP\u30a2\u30c9\u30ec\u30b9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "title": "Heos\u306b\u63a5\u7d9a" } } diff --git a/homeassistant/components/here_travel_time/__init__.py b/homeassistant/components/here_travel_time/__init__.py index 0ad344e3fdf..2b9853c3b10 100644 --- a/homeassistant/components/here_travel_time/__init__.py +++ b/homeassistant/components/here_travel_time/__init__.py @@ -8,12 +8,22 @@ 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.config_entries import ConfigEntry +from homeassistant.const import ( + ATTR_ATTRIBUTION, + CONF_API_KEY, + CONF_MODE, + CONF_UNIT_SYSTEM, + CONF_UNIT_SYSTEM_IMPERIAL, + LENGTH_METERS, + 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 +from homeassistant.util.unit_system import IMPERIAL_SYSTEM from .const import ( ATTR_DESTINATION, @@ -24,6 +34,15 @@ from .const import ( ATTR_ORIGIN, ATTR_ORIGIN_NAME, ATTR_ROUTE, + CONF_ARRIVAL_TIME, + CONF_DEPARTURE_TIME, + CONF_DESTINATION_ENTITY_ID, + CONF_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE, + CONF_ORIGIN_ENTITY_ID, + CONF_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE, + CONF_ROUTE_MODE, DEFAULT_SCAN_INTERVAL, DOMAIN, NO_ROUTE_ERROR_MESSAGE, @@ -37,6 +56,58 @@ PLATFORMS = [Platform.SENSOR] _LOGGER = logging.getLogger(__name__) +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Set up HERE Travel Time from a config entry.""" + api_key = config_entry.data[CONF_API_KEY] + here_client = RoutingApi(api_key) + + arrival = ( + dt.parse_time(config_entry.options[CONF_ARRIVAL_TIME]) + if config_entry.options[CONF_ARRIVAL_TIME] is not None + else None + ) + departure = ( + dt.parse_time(config_entry.options[CONF_DEPARTURE_TIME]) + if config_entry.options[CONF_DEPARTURE_TIME] is not None + else None + ) + + here_travel_time_config = HERETravelTimeConfig( + destination_latitude=config_entry.data.get(CONF_DESTINATION_LATITUDE), + destination_longitude=config_entry.data.get(CONF_DESTINATION_LONGITUDE), + destination_entity_id=config_entry.data.get(CONF_DESTINATION_ENTITY_ID), + origin_latitude=config_entry.data.get(CONF_ORIGIN_LATITUDE), + origin_longitude=config_entry.data.get(CONF_ORIGIN_LONGITUDE), + origin_entity_id=config_entry.data.get(CONF_ORIGIN_ENTITY_ID), + travel_mode=config_entry.data[CONF_MODE], + route_mode=config_entry.options[CONF_ROUTE_MODE], + units=config_entry.options[CONF_UNIT_SYSTEM], + arrival=arrival, + departure=departure, + ) + + coordinator = HereTravelTimeDataUpdateCoordinator( + hass, + here_client, + here_travel_time_config, + ) + hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = coordinator + hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Unload a config entry.""" + unload_ok = await hass.config_entries.async_unload_platforms( + config_entry, PLATFORMS + ) + if unload_ok: + hass.data[DOMAIN].pop(config_entry.entry_id) + + return unload_ok + + class HereTravelTimeDataUpdateCoordinator(DataUpdateCoordinator): """HERETravelTime DataUpdateCoordinator.""" @@ -109,7 +180,7 @@ class HereTravelTimeDataUpdateCoordinator(DataUpdateCoordinator): traffic_time = summary["trafficTime"] if self.config.units == CONF_UNIT_SYSTEM_IMPERIAL: # Convert to miles. - distance = distance / 1609.344 + distance = IMPERIAL_SYSTEM.length(distance, LENGTH_METERS) else: # Convert to kilometers distance = distance / 1000 @@ -135,33 +206,40 @@ class HereTravelTimeDataUpdateCoordinator(DataUpdateCoordinator): ) -> 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 + def _from_entity_id(entity_id: str) -> list[str]: + coordinates = find_coordinates(self.hass, entity_id) + if coordinates is None: + raise InvalidCoordinatesException( + f"No coordinatnes found for {entity_id}" + ) + try: + here_formatted_coordinates = coordinates.split(",") + vol.Schema(cv.gps(here_formatted_coordinates)) + except (AttributeError, vol.Invalid) as ex: + raise InvalidCoordinatesException( + f"{coordinates} are not valid coordinates" + ) from ex + return here_formatted_coordinates + # Destination if self.config.destination_entity_id is not None: - destination = find_coordinates(self.hass, self.config.destination_entity_id) + destination = _from_entity_id(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 + destination = [ + str(self.config.destination_latitude), + str(self.config.destination_longitude), + ] + + # Origin + if self.config.origin_entity_id is not None: + origin = _from_entity_id(self.config.origin_entity_id) + else: + origin = [ + str(self.config.origin_latitude), + str(self.config.origin_longitude), + ] + + # Arrival/Departure arrival: str | None = None departure: str | None = None if self.config.arrival is not None: @@ -172,7 +250,7 @@ class HereTravelTimeDataUpdateCoordinator(DataUpdateCoordinator): if arrival is None and departure is None: departure = "now" - return (here_formatted_origin, here_formatted_destination, arrival, departure) + return (origin, destination, arrival, departure) def build_hass_attribution(source_attribution: dict) -> str | None: diff --git a/homeassistant/components/here_travel_time/config_flow.py b/homeassistant/components/here_travel_time/config_flow.py new file mode 100644 index 00000000000..e8a05796b66 --- /dev/null +++ b/homeassistant/components/here_travel_time/config_flow.py @@ -0,0 +1,397 @@ +"""Config flow for HERE Travel Time integration.""" +from __future__ import annotations + +import logging +from typing import Any + +from herepy import HEREError, InvalidCredentialsError, RouteMode, RoutingApi +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import ( + CONF_API_KEY, + CONF_ENTITY_NAMESPACE, + CONF_MODE, + CONF_NAME, + CONF_UNIT_SYSTEM, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.data_entry_flow import FlowResult +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.selector import ( + EntitySelector, + LocationSelector, + TimeSelector, + selector, +) + +from .const import ( + CONF_ARRIVAL, + CONF_ARRIVAL_TIME, + CONF_DEPARTURE, + CONF_DEPARTURE_TIME, + CONF_ROUTE_MODE, + CONF_TRAFFIC_MODE, + DEFAULT_NAME, + DOMAIN, + ROUTE_MODE_FASTEST, + ROUTE_MODES, + TRAFFIC_MODE_DISABLED, + TRAFFIC_MODE_ENABLED, + TRAFFIC_MODES, + TRAVEL_MODE_CAR, + TRAVEL_MODE_PUBLIC_TIME_TABLE, + TRAVEL_MODES, + UNITS, +) +from .sensor import ( + CONF_DESTINATION_ENTITY_ID, + CONF_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE, + CONF_ORIGIN_ENTITY_ID, + CONF_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE, +) + +_LOGGER = logging.getLogger(__name__) + + +def is_dupe_import( + entry: config_entries.ConfigEntry, + user_input: dict[str, Any], + options: dict[str, Any], +) -> bool: + """Return whether imported config already exists.""" + # Check the main data keys + if any( + user_input[key] != entry.data[key] + for key in (CONF_API_KEY, CONF_MODE, CONF_NAME) + ): + return False + + # Check origin/destination + for key in ( + CONF_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE, + CONF_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE, + CONF_DESTINATION_ENTITY_ID, + CONF_ORIGIN_ENTITY_ID, + ): + if user_input.get(key) != entry.data.get(key): + return False + + # We have to check for options that don't have defaults + for key in ( + CONF_TRAFFIC_MODE, + CONF_UNIT_SYSTEM, + CONF_ROUTE_MODE, + CONF_ARRIVAL_TIME, + CONF_DEPARTURE_TIME, + ): + if options.get(key) != entry.options.get(key): + return False + + return True + + +def validate_api_key(api_key: str) -> None: + """Validate the user input allows us to connect.""" + known_working_origin = [38.9, -77.04833] + known_working_destination = [39.0, -77.1] + RoutingApi(api_key).public_transport_timetable( + known_working_origin, + known_working_destination, + True, + [ + RouteMode[ROUTE_MODE_FASTEST], + RouteMode[TRAVEL_MODE_CAR], + RouteMode[TRAFFIC_MODE_ENABLED], + ], + arrival=None, + departure="now", + ) + + +def get_user_step_schema(data: dict[str, Any]) -> vol.Schema: + """Get a populated schema or default.""" + return vol.Schema( + { + vol.Optional( + CONF_NAME, default=data.get(CONF_NAME, DEFAULT_NAME) + ): cv.string, + vol.Required(CONF_API_KEY, default=data.get(CONF_API_KEY)): cv.string, + vol.Optional( + CONF_MODE, default=data.get(CONF_MODE, TRAVEL_MODE_CAR) + ): vol.In(TRAVEL_MODES), + } + ) + + +def default_options(hass: HomeAssistant) -> dict[str, str | None]: + """Get the default options.""" + return { + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_ARRIVAL_TIME: None, + CONF_DEPARTURE_TIME: None, + CONF_UNIT_SYSTEM: hass.config.units.name, + } + + +class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for HERE Travel Time.""" + + VERSION = 1 + + def __init__(self) -> None: + """Init Config Flow.""" + self._config: dict[str, Any] = {} + + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> HERETravelTimeOptionsFlow: + """Get the options flow.""" + return HERETravelTimeOptionsFlow(config_entry) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + errors = {} + user_input = user_input or {} + if user_input: + try: + await self.hass.async_add_executor_job( + validate_api_key, user_input[CONF_API_KEY] + ) + except InvalidCredentialsError: + errors["base"] = "invalid_auth" + except HEREError as error: + _LOGGER.exception("Unexpected exception: %s", error) + errors["base"] = "unknown" + if not errors: + self._config = user_input + return self.async_show_menu( + step_id="origin_menu", + menu_options=["origin_coordinates", "origin_entity"], + ) + return self.async_show_form( + step_id="user", data_schema=get_user_step_schema(user_input), errors=errors + ) + + async def async_step_origin_coordinates( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Configure origin by using gps coordinates.""" + if user_input is not None: + self._config[CONF_ORIGIN_LATITUDE] = user_input["origin"]["latitude"] + self._config[CONF_ORIGIN_LONGITUDE] = user_input["origin"]["longitude"] + return self.async_show_menu( + step_id="destination_menu", + menu_options=["destination_coordinates", "destination_entity"], + ) + schema = vol.Schema({"origin": selector({LocationSelector.selector_type: {}})}) + return self.async_show_form(step_id="origin_coordinates", data_schema=schema) + + async def async_step_origin_entity( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Configure origin by using an entity.""" + if user_input is not None: + self._config[CONF_ORIGIN_ENTITY_ID] = user_input[CONF_ORIGIN_ENTITY_ID] + return self.async_show_menu( + step_id="destination_menu", + menu_options=["destination_coordinates", "destination_entity"], + ) + schema = vol.Schema( + {CONF_ORIGIN_ENTITY_ID: selector({EntitySelector.selector_type: {}})} + ) + return self.async_show_form(step_id="origin_entity", data_schema=schema) + + async def async_step_destination_coordinates( + self, + user_input: dict[str, Any] | None = None, + ) -> FlowResult: + """Configure destination by using gps coordinates.""" + if user_input is not None: + self._config[CONF_DESTINATION_LATITUDE] = user_input["destination"][ + "latitude" + ] + self._config[CONF_DESTINATION_LONGITUDE] = user_input["destination"][ + "longitude" + ] + return self.async_create_entry( + title=self._config[CONF_NAME], + data=self._config, + options=default_options(self.hass), + ) + schema = vol.Schema( + {"destination": selector({LocationSelector.selector_type: {}})} + ) + return self.async_show_form( + step_id="destination_coordinates", data_schema=schema + ) + + async def async_step_destination_entity( + self, + user_input: dict[str, Any] | None = None, + ) -> FlowResult: + """Configure destination by using an entity.""" + if user_input is not None: + self._config[CONF_DESTINATION_ENTITY_ID] = user_input[ + CONF_DESTINATION_ENTITY_ID + ] + return self.async_create_entry( + title=self._config[CONF_NAME], + data=self._config, + options=default_options(self.hass), + ) + schema = vol.Schema( + {CONF_DESTINATION_ENTITY_ID: selector({EntitySelector.selector_type: {}})} + ) + return self.async_show_form(step_id="destination_entity", data_schema=schema) + + async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: + """Import from configuration.yaml.""" + options: dict[str, Any] = {} + user_input, options = self._transform_import_input(user_input) + # We need to prevent duplicate imports + if any( + is_dupe_import(entry, user_input, options) + for entry in self.hass.config_entries.async_entries(DOMAIN) + if entry.source == config_entries.SOURCE_IMPORT + ): + return self.async_abort(reason="already_configured") + return self.async_create_entry( + title=user_input[CONF_NAME], data=user_input, options=options + ) + + def _transform_import_input( + self, import_input: dict[str, Any] + ) -> tuple[dict[str, Any], dict[str, Any]]: + """Transform platform schema input to new model.""" + options: dict[str, Any] = {} + user_input: dict[str, Any] = {} + + if import_input.get(CONF_ORIGIN_LATITUDE) is not None: + user_input[CONF_ORIGIN_LATITUDE] = import_input[CONF_ORIGIN_LATITUDE] + user_input[CONF_ORIGIN_LONGITUDE] = import_input[CONF_ORIGIN_LONGITUDE] + else: + user_input[CONF_ORIGIN_ENTITY_ID] = import_input[CONF_ORIGIN_ENTITY_ID] + + if import_input.get(CONF_DESTINATION_LATITUDE) is not None: + user_input[CONF_DESTINATION_LATITUDE] = import_input[ + CONF_DESTINATION_LATITUDE + ] + user_input[CONF_DESTINATION_LONGITUDE] = import_input[ + CONF_DESTINATION_LONGITUDE + ] + else: + user_input[CONF_DESTINATION_ENTITY_ID] = import_input[ + CONF_DESTINATION_ENTITY_ID + ] + + user_input[CONF_API_KEY] = import_input[CONF_API_KEY] + user_input[CONF_MODE] = import_input[CONF_MODE] + user_input[CONF_NAME] = import_input[CONF_NAME] + if (namespace := import_input.get(CONF_ENTITY_NAMESPACE)) is not None: + user_input[CONF_NAME] = f"{namespace} {user_input[CONF_NAME]}" + + options[CONF_TRAFFIC_MODE] = ( + TRAFFIC_MODE_ENABLED + if import_input.get(CONF_TRAFFIC_MODE, False) + else TRAFFIC_MODE_DISABLED + ) + options[CONF_ROUTE_MODE] = import_input.get(CONF_ROUTE_MODE) + options[CONF_UNIT_SYSTEM] = import_input.get( + CONF_UNIT_SYSTEM, self.hass.config.units.name + ) + options[CONF_ARRIVAL_TIME] = import_input.get(CONF_ARRIVAL, None) + options[CONF_DEPARTURE_TIME] = import_input.get(CONF_DEPARTURE, None) + + return user_input, options + + +class HERETravelTimeOptionsFlow(config_entries.OptionsFlow): + """Handle HERE Travel Time options.""" + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Initialize HERE Travel Time options flow.""" + self.config_entry = config_entry + self._config: dict[str, Any] = {} + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage the HERE Travel Time options.""" + if user_input is not None: + self._config = user_input + if self.config_entry.data[CONF_MODE] == TRAVEL_MODE_PUBLIC_TIME_TABLE: + return self.async_show_menu( + step_id="time_menu", + menu_options=["departure_time", "arrival_time", "no_time"], + ) + return self.async_show_menu( + step_id="time_menu", + menu_options=["departure_time", "no_time"], + ) + + options = { + vol.Optional( + CONF_TRAFFIC_MODE, + default=self.config_entry.options.get( + CONF_TRAFFIC_MODE, TRAFFIC_MODE_ENABLED + ), + ): vol.In(TRAFFIC_MODES), + vol.Optional( + CONF_ROUTE_MODE, + default=self.config_entry.options.get( + CONF_ROUTE_MODE, ROUTE_MODE_FASTEST + ), + ): vol.In(ROUTE_MODES), + vol.Optional( + CONF_UNIT_SYSTEM, + default=self.config_entry.options.get( + CONF_UNIT_SYSTEM, self.hass.config.units.name + ), + ): vol.In(UNITS), + } + + return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) + + async def async_step_no_time( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Create Options Entry.""" + return self.async_create_entry(title="", data=self._config) + + async def async_step_arrival_time( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Configure arrival time.""" + if user_input is not None: + self._config[CONF_ARRIVAL_TIME] = user_input[CONF_ARRIVAL_TIME] + return self.async_create_entry(title="", data=self._config) + + options = {"arrival_time": selector({TimeSelector.selector_type: {}})} + + return self.async_show_form( + step_id="arrival_time", data_schema=vol.Schema(options) + ) + + async def async_step_departure_time( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Configure departure time.""" + if user_input is not None: + self._config[CONF_DEPARTURE_TIME] = user_input[CONF_DEPARTURE_TIME] + return self.async_create_entry(title="", data=self._config) + + options = {"departure_time": selector({TimeSelector.selector_type: {}})} + + return self.async_show_form( + step_id="departure_time", data_schema=vol.Schema(options) + ) diff --git a/homeassistant/components/here_travel_time/const.py b/homeassistant/components/here_travel_time/const.py index a6b958ebf5e..bde17f5c306 100644 --- a/homeassistant/components/here_travel_time/const.py +++ b/homeassistant/components/here_travel_time/const.py @@ -8,20 +8,19 @@ from homeassistant.const import ( DOMAIN = "here_travel_time" DEFAULT_SCAN_INTERVAL = 300 -CONF_DESTINATION = "destination" -CONF_ORIGIN = "origin" + +CONF_DESTINATION_LATITUDE = "destination_latitude" +CONF_DESTINATION_LONGITUDE = "destination_longitude" +CONF_DESTINATION_ENTITY_ID = "destination_entity_id" +CONF_ORIGIN_LATITUDE = "origin_latitude" +CONF_ORIGIN_LONGITUDE = "origin_longitude" +CONF_ORIGIN_ENTITY_ID = "origin_entity_id" CONF_TRAFFIC_MODE = "traffic_mode" CONF_ROUTE_MODE = "route_mode" CONF_ARRIVAL = "arrival" CONF_DEPARTURE = "departure" CONF_ARRIVAL_TIME = "arrival_time" CONF_DEPARTURE_TIME = "departure_time" -CONF_TIME_TYPE = "time_type" -CONF_TIME = "time" - -ARRIVAL_TIME = "Arrival Time" -DEPARTURE_TIME = "Departure Time" -TIME_TYPES = [ARRIVAL_TIME, DEPARTURE_TIME] DEFAULT_NAME = "HERE Travel Time" diff --git a/homeassistant/components/here_travel_time/manifest.json b/homeassistant/components/here_travel_time/manifest.json index b620153bba7..68370311254 100644 --- a/homeassistant/components/here_travel_time/manifest.json +++ b/homeassistant/components/here_travel_time/manifest.json @@ -1,6 +1,7 @@ { "domain": "here_travel_time", "name": "HERE Travel Time", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/here_travel_time", "requirements": ["herepy==2.0.0"], "codeowners": ["@eifinger"], diff --git a/homeassistant/components/here_travel_time/model.py b/homeassistant/components/here_travel_time/model.py index eb85e966edf..65673a1e8b6 100644 --- a/homeassistant/components/here_travel_time/model.py +++ b/homeassistant/components/here_travel_time/model.py @@ -24,10 +24,12 @@ class HERERoutingData(TypedDict): class HERETravelTimeConfig: """Configuration for HereTravelTimeDataUpdateCoordinator.""" - origin: str | None - destination: str | None - origin_entity_id: str | None + destination_latitude: float | None + destination_longitude: float | None destination_entity_id: str | None + origin_latitude: float | None + origin_longitude: float | None + origin_entity_id: str | None travel_mode: str route_mode: str units: str diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index 304c49b6bed..75c9fd2ea3b 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -4,11 +4,10 @@ from __future__ import annotations from datetime import timedelta import logging -import herepy -from herepy.here_enum import RouteMode import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_MODE, @@ -16,86 +15,59 @@ from homeassistant.const import ( CONF_MODE, CONF_NAME, CONF_UNIT_SYSTEM, - CONF_UNIT_SYSTEM_IMPERIAL, - CONF_UNIT_SYSTEM_METRIC, TIME_MINUTES, ) from homeassistant.core import HomeAssistant 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.start import async_at_start from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import HereTravelTimeDataUpdateCoordinator -from .model import HERETravelTimeConfig - -_LOGGER = logging.getLogger(__name__) - -CONF_DESTINATION_LATITUDE = "destination_latitude" -CONF_DESTINATION_LONGITUDE = "destination_longitude" -CONF_DESTINATION_ENTITY_ID = "destination_entity_id" -CONF_ORIGIN_LATITUDE = "origin_latitude" -CONF_ORIGIN_LONGITUDE = "origin_longitude" -CONF_ORIGIN_ENTITY_ID = "origin_entity_id" -CONF_TRAFFIC_MODE = "traffic_mode" -CONF_ROUTE_MODE = "route_mode" -CONF_ARRIVAL = "arrival" -CONF_DEPARTURE = "departure" - -DEFAULT_NAME = "HERE Travel Time" - -TRAVEL_MODE_BICYCLE = "bicycle" -TRAVEL_MODE_CAR = "car" -TRAVEL_MODE_PEDESTRIAN = "pedestrian" -TRAVEL_MODE_PUBLIC = "publicTransport" -TRAVEL_MODE_PUBLIC_TIME_TABLE = "publicTransportTimeTable" -TRAVEL_MODE_TRUCK = "truck" -TRAVEL_MODE = [ +from .const import ( + ATTR_DURATION, + ATTR_DURATION_IN_TRAFFIC, + ATTR_TRAFFIC_MODE, + ATTR_UNIT_SYSTEM, + CONF_ARRIVAL, + CONF_DEPARTURE, + CONF_DESTINATION_ENTITY_ID, + CONF_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE, + CONF_ORIGIN_ENTITY_ID, + CONF_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE, + CONF_ROUTE_MODE, + CONF_TRAFFIC_MODE, + DEFAULT_NAME, + DOMAIN, + ICON_BICYCLE, + ICON_CAR, + ICON_PEDESTRIAN, + ICON_PUBLIC, + ICON_TRUCK, + ROUTE_MODE_FASTEST, + ROUTE_MODES, + TRAFFIC_MODE_ENABLED, TRAVEL_MODE_BICYCLE, TRAVEL_MODE_CAR, TRAVEL_MODE_PEDESTRIAN, TRAVEL_MODE_PUBLIC, TRAVEL_MODE_PUBLIC_TIME_TABLE, TRAVEL_MODE_TRUCK, -] + TRAVEL_MODES, + TRAVEL_MODES_PUBLIC, + UNITS, +) -TRAVEL_MODES_PUBLIC = [TRAVEL_MODE_PUBLIC, TRAVEL_MODE_PUBLIC_TIME_TABLE] -TRAVEL_MODES_VEHICLE = [TRAVEL_MODE_CAR, TRAVEL_MODE_TRUCK] -TRAVEL_MODES_NON_VEHICLE = [TRAVEL_MODE_BICYCLE, TRAVEL_MODE_PEDESTRIAN] +_LOGGER = logging.getLogger(__name__) -TRAFFIC_MODE_ENABLED = "traffic_enabled" -TRAFFIC_MODE_DISABLED = "traffic_disabled" - -ROUTE_MODE_FASTEST = "fastest" -ROUTE_MODE_SHORTEST = "shortest" -ROUTE_MODE = [ROUTE_MODE_FASTEST, ROUTE_MODE_SHORTEST] - -ICON_BICYCLE = "mdi:bike" -ICON_CAR = "mdi:car" -ICON_PEDESTRIAN = "mdi:walk" -ICON_PUBLIC = "mdi:bus" -ICON_TRUCK = "mdi:truck" - -UNITS = [CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL] - -ATTR_DURATION = "duration" -ATTR_DISTANCE = "distance" -ATTR_ROUTE = "route" -ATTR_ORIGIN = "origin" -ATTR_DESTINATION = "destination" - -ATTR_UNIT_SYSTEM = CONF_UNIT_SYSTEM -ATTR_TRAFFIC_MODE = CONF_TRAFFIC_MODE - -ATTR_DURATION_IN_TRAFFIC = "duration_in_traffic" -ATTR_ORIGIN_NAME = "origin_name" -ATTR_DESTINATION_NAME = "destination_name" SCAN_INTERVAL = timedelta(minutes=5) -NO_ROUTE_ERROR_MESSAGE = "HERE could not find a route based on the input" - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_API_KEY): cv.string, @@ -113,8 +85,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Exclusive(CONF_ORIGIN_ENTITY_ID, "origin"): cv.entity_id, vol.Optional(CONF_DEPARTURE): cv.time, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MODE, default=TRAVEL_MODE_CAR): vol.In(TRAVEL_MODE), - vol.Optional(CONF_ROUTE_MODE, default=ROUTE_MODE_FASTEST): vol.In(ROUTE_MODE), + vol.Optional(CONF_MODE, default=TRAVEL_MODE_CAR): vol.In(TRAVEL_MODES), + vol.Optional(CONF_ROUTE_MODE, default=ROUTE_MODE_FASTEST): vol.In(ROUTE_MODES), vol.Optional(CONF_TRAFFIC_MODE, default=False): cv.boolean, vol.Optional(CONF_UNIT_SYSTEM): vol.In(UNITS), } @@ -150,79 +122,37 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the HERE travel time platform.""" - api_key = config[CONF_API_KEY] - here_client = herepy.RoutingApi(api_key) - - if not await hass.async_add_executor_job( - _are_valid_client_credentials, here_client - ): - _LOGGER.error( - "Invalid credentials. This error is returned if the specified token was invalid or no contract could be found for this token" + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, ) - return - - if config.get(CONF_ORIGIN_LATITUDE) is not None: - origin = f"{config[CONF_ORIGIN_LATITUDE]},{config[CONF_ORIGIN_LONGITUDE]}" - origin_entity_id = None - else: - origin = None - origin_entity_id = config[CONF_ORIGIN_ENTITY_ID] - - if config.get(CONF_DESTINATION_LATITUDE) is not None: - destination = ( - f"{config[CONF_DESTINATION_LATITUDE]},{config[CONF_DESTINATION_LONGITUDE]}" - ) - destination_entity_id = None - else: - destination = None - destination_entity_id = config[CONF_DESTINATION_ENTITY_ID] - - traffic_mode = config[CONF_TRAFFIC_MODE] - name = config[CONF_NAME] - - here_travel_time_config = HERETravelTimeConfig( - origin=origin, - destination=destination, - origin_entity_id=origin_entity_id, - destination_entity_id=destination_entity_id, - travel_mode=config[CONF_MODE], - route_mode=config[CONF_ROUTE_MODE], - units=config.get(CONF_UNIT_SYSTEM, hass.config.units.name), - arrival=config.get(CONF_ARRIVAL), - departure=config.get(CONF_DEPARTURE), ) - coordinator = HereTravelTimeDataUpdateCoordinator( - hass, - here_client, - here_travel_time_config, + _LOGGER.warning( + "Your HERE travel time configuration has been imported into the UI; " + "please remove it from configuration.yaml as support for it will be " + "removed in a future release" ) - sensor = HERETravelTimeSensor(name, traffic_mode, coordinator) - async_add_entities([sensor]) - - -def _are_valid_client_credentials(here_client: herepy.RoutingApi) -> bool: - """Check if the provided credentials are correct using defaults.""" - known_working_origin = [38.9, -77.04833] - known_working_destination = [39.0, -77.1] - try: - here_client.public_transport_timetable( - known_working_origin, - known_working_destination, - True, - [ - RouteMode[ROUTE_MODE_FASTEST], - RouteMode[TRAVEL_MODE_CAR], - RouteMode[TRAFFIC_MODE_ENABLED], - ], - arrival=None, - departure="now", - ) - except herepy.InvalidCredentialsError: - return False - return True +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Add HERE travel time entities from a config_entry.""" + async_add_entities( + [ + HERETravelTimeSensor( + config_entry.entry_id, + config_entry.data[CONF_NAME], + config_entry.options[CONF_TRAFFIC_MODE], + hass.data[DOMAIN][config_entry.entry_id], + ) + ], + ) class HERETravelTimeSensor(SensorEntity, CoordinatorEntity): @@ -230,15 +160,23 @@ class HERETravelTimeSensor(SensorEntity, CoordinatorEntity): def __init__( self, + unique_id_prefix: str, name: str, - traffic_mode: bool, + traffic_mode: str, coordinator: HereTravelTimeDataUpdateCoordinator, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) - self._traffic_mode = traffic_mode + self._traffic_mode = traffic_mode == TRAFFIC_MODE_ENABLED self._attr_native_unit_of_measurement = TIME_MINUTES self._attr_name = name + self._attr_unique_id = unique_id_prefix + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, unique_id_prefix)}, + entry_type=DeviceEntryType.SERVICE, + name=name, + manufacturer="HERE Technologies", + ) async def async_added_to_hass(self) -> None: """Wait for start so origin and destination entities can be resolved.""" diff --git a/homeassistant/components/here_travel_time/strings.json b/homeassistant/components/here_travel_time/strings.json new file mode 100644 index 00000000000..e4a20a38d6b --- /dev/null +++ b/homeassistant/components/here_travel_time/strings.json @@ -0,0 +1,82 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "[%key:common::config_flow::data::name%]", + "api_key": "[%key:common::config_flow::data::api_key%]", + "mode": "Travel Mode" + } + }, + "origin_coordinates": { + "title": "Choose Origin", + "data": { + "origin": "Origin as GPS coordinates" + } + }, + "origin_entity_id": { + "title": "Choose Origin", + "data": { + "origin_entity_id": "Origin using an entity" + } + }, + "destination_menu": { + "title": "Choose Destination", + "menu_options": { + "destination_coordinates": "Using a map location", + "destination_entity": "Using an entity" + } + }, + "destination_coordinates": { + "title": "Choose Destination", + "data": { + "destination": "Destination as GPS coordinates" + } + }, + "destination_entity_id": { + "title": "Choose Destination", + "data": { + "destination_entity_id": "Destination using an entity" + } + } + }, + "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "traffic_mode": "Traffic Mode", + "route_mode": "Route Mode", + "unit_system": "Unit system" + } + }, + "time_menu": { + "title": "Choose Time Type", + "menu_options": { + "departure_time": "Configure a departure time", + "arrival_time": "Configure an arrival time", + "no_time": "Do not configure a time" + } + }, + "departure_time": { + "title": "Choose Departure Time", + "data": { + "departure_time": "Departure Time" + } + }, + "arrival_time": { + "title": "Choose Arrival Time", + "data": { + "arrival_time": "Arrival Time" + } + } + } + } +} diff --git a/homeassistant/components/here_travel_time/translations/ca.json b/homeassistant/components/here_travel_time/translations/ca.json new file mode 100644 index 00000000000..08dfb0d970d --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/ca.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Destinaci\u00f3 com a coordenades GPS" + }, + "title": "Tria destinaci\u00f3" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Destinaci\u00f3 utilitzant entitat" + }, + "title": "Tria destinaci\u00f3" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Utilitzant una ubicaci\u00f3 de mapa", + "destination_entity": "Utilitzant una entitat" + }, + "title": "Tria destinaci\u00f3" + }, + "origin_coordinates": { + "data": { + "origin": "Origen com a coordenades GPS" + }, + "title": "Tria origen" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Origen utilitzant entitat" + }, + "title": "Tria origen" + }, + "user": { + "data": { + "api_key": "Clau API", + "mode": "Mode de viatge", + "name": "Nom" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Hora d'arribada" + }, + "title": "Tria l'hora d'arribada" + }, + "departure_time": { + "data": { + "departure_time": "Hora de sortida" + }, + "title": "Tria l'hora de sortida" + }, + "init": { + "data": { + "route_mode": "Mode ruta", + "traffic_mode": "Mode tr\u00e0nsit", + "unit_system": "Sistema d'unitats" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Configura hora d'arribada", + "departure_time": "Configura hora de sortida", + "no_time": "No configurar hora" + }, + "title": "Tria tipus d'hora" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/de.json b/homeassistant/components/here_travel_time/translations/de.json new file mode 100644 index 00000000000..b50ef028089 --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/de.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Ziel als GPS-Koordinaten" + }, + "title": "Ziel w\u00e4hlen" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Ziel mit einer Entit\u00e4t" + }, + "title": "Ziel w\u00e4hlen" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Verwendung einer Kartenposition", + "destination_entity": "Verwenden einer Entit\u00e4t" + }, + "title": "Ziel w\u00e4hlen" + }, + "origin_coordinates": { + "data": { + "origin": "Herkunft als GPS-Koordinaten" + }, + "title": "Herkunft w\u00e4hlen" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Herkunft \u00fcber eine Entit\u00e4t" + }, + "title": "Herkunft w\u00e4hlen" + }, + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "mode": "Reisemodus", + "name": "Name" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Ankunftszeit" + }, + "title": "Ankunftszeit w\u00e4hlen" + }, + "departure_time": { + "data": { + "departure_time": "Abfahrtszeit" + }, + "title": "Abfahrtszeit w\u00e4hlen" + }, + "init": { + "data": { + "route_mode": "Routenmodus", + "traffic_mode": "Verkehrsmodus", + "unit_system": "Einheitssystem" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Ankunftszeit konfigurieren", + "departure_time": "Abfahrtszeit konfigurieren", + "no_time": "Keine Zeit konfigurieren" + }, + "title": "Zeitart w\u00e4hlen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/el.json b/homeassistant/components/here_travel_time/translations/el.json new file mode 100644 index 00000000000..cd4d986ed3f --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/el.json @@ -0,0 +1,82 @@ +{ + "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": { + "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": { + "destination_coordinates": { + "data": { + "destination": "\u03a0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c9\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 GPS" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "\u03a0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b8\u03ad\u03c3\u03b7\u03c2 \u03c7\u03ac\u03c1\u03c4\u03b7", + "destination_entity": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd" + }, + "origin_coordinates": { + "data": { + "origin": "\u03a0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7 \u03c9\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 GPS" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7\u03c2" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "\u03a0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7\u03c2" + }, + "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c4\u03b1\u03be\u03b9\u03b4\u03b9\u03bf\u03cd", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "\u038f\u03c1\u03b1 \u03ac\u03c6\u03b9\u03be\u03b7\u03c2" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ce\u03c1\u03b1 \u03ac\u03c6\u03b9\u03be\u03b7\u03c2" + }, + "departure_time": { + "data": { + "departure_time": "\u038f\u03c1\u03b1 \u03b1\u03bd\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ce\u03c1\u03b1 \u03b1\u03bd\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2" + }, + "init": { + "data": { + "route_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2", + "traffic_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03ba\u03c5\u03ba\u03bb\u03bf\u03c6\u03bf\u03c1\u03af\u03b1\u03c2", + "unit_system": "\u03a3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 \u03bc\u03bf\u03bd\u03ac\u03b4\u03c9\u03bd" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03ce\u03c1\u03b1\u03c2 \u03ac\u03c6\u03b9\u03be\u03b7\u03c2", + "departure_time": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03ce\u03c1\u03b1\u03c2 \u03b1\u03bd\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2", + "no_time": "\u039c\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03ce\u03c1\u03b1\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/en.json b/homeassistant/components/here_travel_time/translations/en.json new file mode 100644 index 00000000000..d4f9984d945 --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/en.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Destination as GPS coordinates" + }, + "title": "Choose Destination" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Destination using an entity" + }, + "title": "Choose Destination" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Using a map location", + "destination_entity": "Using an entity" + }, + "title": "Choose Destination" + }, + "origin_coordinates": { + "data": { + "origin": "Origin as GPS coordinates" + }, + "title": "Choose Origin" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Origin using an entity" + }, + "title": "Choose Origin" + }, + "user": { + "data": { + "api_key": "API Key", + "mode": "Travel Mode", + "name": "Name" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Arrival Time" + }, + "title": "Choose Arrival Time" + }, + "departure_time": { + "data": { + "departure_time": "Departure Time" + }, + "title": "Choose Departure Time" + }, + "init": { + "data": { + "route_mode": "Route Mode", + "traffic_mode": "Traffic Mode", + "unit_system": "Unit system" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Configure an arrival time", + "departure_time": "Configure a departure time", + "no_time": "Do not configure a time" + }, + "title": "Choose Time Type" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/et.json b/homeassistant/components/here_travel_time/translations/et.json new file mode 100644 index 00000000000..c9646af7544 --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/et.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "invalid_auth": "Tuvastamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Sihtkoha GPS koordinaadid" + }, + "title": "Vali sihtkoht" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Sihtkoha valik olemist" + }, + "title": "Vali sihtkoht" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Kasuta asukohta kaardil", + "destination_entity": "Kasuta olemit" + }, + "title": "Vali sihtkoht" + }, + "origin_coordinates": { + "data": { + "origin": "L\u00e4htekoha GPS asukoht" + }, + "title": "Vali l\u00e4htekoht" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "L\u00e4htekoha valik olemist" + }, + "title": "Vali l\u00e4htekoht" + }, + "user": { + "data": { + "api_key": "API v\u00f5ti", + "mode": "Vali reisimise viis", + "name": "Nimi" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Saabumisaeg" + }, + "title": "Vali saabumisaeg" + }, + "departure_time": { + "data": { + "departure_time": "V\u00e4ljumisaeg" + }, + "title": "Vali v\u00e4ljumisaeg" + }, + "init": { + "data": { + "route_mode": "Teekonna valik", + "traffic_mode": "S\u00f5iduvahend", + "unit_system": "\u00dchikute s\u00fcsteem" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Vali saabumisaeg", + "departure_time": "Vali v\u00e4ljumisaeg", + "no_time": "\u00c4ra seadista aega" + }, + "title": "Vali aja vorming" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/fr.json b/homeassistant/components/here_travel_time/translations/fr.json new file mode 100644 index 00000000000..063a4008779 --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/fr.json @@ -0,0 +1,81 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "invalid_auth": "Authentification non valide", + "unknown": "Erreur inattendue" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Destination sous forme de coordonn\u00e9es GPS" + }, + "title": "Choix de la destination" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Destination avec utilisation d'une entit\u00e9" + }, + "title": "Choix de la destination" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Utilisation d'un emplacement sur la carte", + "destination_entity": "Utilisation d'une entit\u00e9" + }, + "title": "Choix de la destination" + }, + "origin_coordinates": { + "data": { + "origin": "Origine sous forme de coordonn\u00e9es GPS" + }, + "title": "Choix de l'origine" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Origine avec utilisation d'une entit\u00e9" + }, + "title": "Choix de l'origine" + }, + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "mode": "Mode de d\u00e9placement", + "name": "Nom" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Heure d'arriv\u00e9e" + }, + "title": "Choix de l'heure d'arriv\u00e9e" + }, + "departure_time": { + "data": { + "departure_time": "Heure de d\u00e9part" + }, + "title": "Choix de l'heure de d\u00e9part" + }, + "init": { + "data": { + "route_mode": "Mode itin\u00e9raire", + "traffic_mode": "Mode circulation", + "unit_system": "Syst\u00e8me d'unit\u00e9s" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Configurer une heure d'arriv\u00e9e", + "departure_time": "Configurer une heure de d\u00e9part", + "no_time": "Ne configurez pas d'heure" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/hu.json b/homeassistant/components/here_travel_time/translations/hu.json new file mode 100644 index 00000000000..cdd40f139d7 --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/hu.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "C\u00e9l GPS koordin\u00e1tak\u00e9nt" + }, + "title": "C\u00e9l kiv\u00e1laszt\u00e1sa" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "C\u00e9l egy entit\u00e1s haszn\u00e1lat\u00e1val" + }, + "title": "C\u00e9l kiv\u00e1laszt\u00e1sa" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "T\u00e9rk\u00e9pes hely haszn\u00e1lata", + "destination_entity": "Entit\u00e1s haszn\u00e1lata" + }, + "title": "C\u00e9l kiv\u00e1laszt\u00e1sa" + }, + "origin_coordinates": { + "data": { + "origin": "Eredet GPS koordin\u00e1t\u00e1kk\u00e9nt" + }, + "title": "Eredet kiv\u00e1laszt\u00e1sa" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Eredet egy entit\u00e1s haszn\u00e1lat\u00e1val" + }, + "title": "Eredet kiv\u00e1laszt\u00e1sa" + }, + "user": { + "data": { + "api_key": "API kulcs", + "mode": "Utaz\u00e1si m\u00f3d", + "name": "Elnevez\u00e9s" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "\u00c9rkez\u00e9s ideje" + }, + "title": "\u00c9rkez\u00e9si id\u0151 kiv\u00e1laszt\u00e1sa" + }, + "departure_time": { + "data": { + "departure_time": "Indul\u00e1si id\u0151" + }, + "title": "Indul\u00e1si id\u0151 kiv\u00e1laszt\u00e1sa" + }, + "init": { + "data": { + "route_mode": "\u00datvonaltervez\u00e9si m\u00f3d", + "traffic_mode": "Forgalmi m\u00f3d", + "unit_system": "Egys\u00e9grendszer" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "\u00c9rkez\u00e9si id\u0151 konfigur\u00e1l\u00e1sa", + "departure_time": "Indul\u00e1si id\u0151 be\u00e1ll\u00edt\u00e1sa", + "no_time": "Ne konfigur\u00e1ljon id\u0151pontot" + }, + "title": "Id\u0151t\u00edpus kiv\u00e1laszt\u00e1sa" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/id.json b/homeassistant/components/here_travel_time/translations/id.json new file mode 100644 index 00000000000..f03910d9adf --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/id.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Tujuan dalam koordinat GPS" + }, + "title": "Pilih Tujuan" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Tujuan menggunakan entitas" + }, + "title": "Pilih Tujuan" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Menggunakan lokasi pada peta", + "destination_entity": "Menggunakan entitas" + }, + "title": "Pilih Tujuan" + }, + "origin_coordinates": { + "data": { + "origin": "Asal dalam koordinat GPS" + }, + "title": "Pilih Asal" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Asal menggunakan entitas" + }, + "title": "Pilih Asal" + }, + "user": { + "data": { + "api_key": "Kunci API", + "mode": "Mode Perjalanan", + "name": "Nama" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Waktu Kedatangan" + }, + "title": "Pilih Waktu Kedatangan" + }, + "departure_time": { + "data": { + "departure_time": "Waktu Keberangkatan" + }, + "title": "Pilih Waktu Keberangkatan" + }, + "init": { + "data": { + "route_mode": "Mode Rute", + "traffic_mode": "Modus Lalu Lintas", + "unit_system": "Sistem unit" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Konfigurasikan waktu kedatangan", + "departure_time": "Konfigurasikan waktu keberangkatan", + "no_time": "Jangan mengonfigurasi waktu" + }, + "title": "Pilih Jenis Waktu" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/it.json b/homeassistant/components/here_travel_time/translations/it.json new file mode 100644 index 00000000000..e9716318adb --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/it.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Destinazione come coordinate GPS" + }, + "title": "Scegli la destinazione" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Destinazione utilizzando un'entit\u00e0" + }, + "title": "Scegli la destinazione" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Utilizzando una posizione sulla mappa", + "destination_entity": "Utilizzando un'entit\u00e0" + }, + "title": "Scegli la destinazione" + }, + "origin_coordinates": { + "data": { + "origin": "Partenza come coordinate GPS" + }, + "title": "Scegli la partenza" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Partenza utilizzando un'entit\u00e0" + }, + "title": "Scegli la partenza" + }, + "user": { + "data": { + "api_key": "Chiave API", + "mode": "Modalit\u00e0 di viaggio", + "name": "Nome" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Orario di arrivo" + }, + "title": "Scegli l'orario di arrivo" + }, + "departure_time": { + "data": { + "departure_time": "Orario di partenza" + }, + "title": "Scegli l'orario di partenza" + }, + "init": { + "data": { + "route_mode": "Modalit\u00e0 percorso", + "traffic_mode": "Modalit\u00e0 traffico", + "unit_system": "Sistema di unit\u00e0" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Configura un orario di arrivo", + "departure_time": "Configura un orario di partenza", + "no_time": "Non configurare un orario" + }, + "title": "Scegli il tipo di orario" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/ja.json b/homeassistant/components/here_travel_time/translations/ja.json new file mode 100644 index 00000000000..6db262d829a --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/ja.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "error": { + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "GPS\u5ea7\u6a19\u3068\u3057\u3066\u306e\u76ee\u7684\u5730" + }, + "title": "\u76ee\u7684\u5730\u3092\u9078\u629e" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u4f7f\u7528\u3057\u305f\u76ee\u7684\u5730" + }, + "title": "\u76ee\u7684\u5730\u3092\u9078\u629e" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "\u5730\u56f3\u4e0a\u306e\u5834\u6240\u3092\u4f7f\u7528\u3059\u308b", + "destination_entity": "\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u4f7f\u7528\u3059\u308b" + }, + "title": "\u76ee\u7684\u5730\u3092\u9078\u629e" + }, + "origin_coordinates": { + "data": { + "origin": "GPS\u5ea7\u6a19\u3068\u3057\u3066\u306e\u539f\u70b9(Origin)" + }, + "title": "\u539f\u70b9(Origin)\u3092\u9078\u629e" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u4f7f\u7528\u3057\u305f\u539f\u70b9(Origin)" + }, + "title": "\u539f\u70b9(Origin)\u3092\u9078\u629e" + }, + "user": { + "data": { + "api_key": "API\u30ad\u30fc", + "mode": "\u30c8\u30e9\u30d9\u30eb\u30e2\u30fc\u30c9", + "name": "\u540d\u524d" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "\u5230\u7740\u6642\u523b" + }, + "title": "\u5230\u7740\u6642\u9593\u3092\u9078\u629e" + }, + "departure_time": { + "data": { + "departure_time": "\u51fa\u767a\u6642\u523b" + }, + "title": "\u51fa\u767a\u6642\u523b\u3092\u9078\u629e" + }, + "init": { + "data": { + "route_mode": "\u30eb\u30fc\u30c8\u30e2\u30fc\u30c9", + "traffic_mode": "\u30c8\u30e9\u30d5\u30a3\u30c3\u30af\u30e2\u30fc\u30c9", + "unit_system": "\u5358\u4f4d\u30b7\u30b9\u30c6\u30e0" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "\u5230\u7740\u6642\u523b\u3092\u8a2d\u5b9a\u3059\u308b", + "departure_time": "\u51fa\u767a\u6642\u523b\u3092\u8a2d\u5b9a\u3059\u308b", + "no_time": "\u6642\u523b\u3092\u8a2d\u5b9a\u3057\u306a\u3044" + }, + "title": "\u6642\u9593\u306e\u7a2e\u985e\u3092\u9078\u629e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/nl.json b/homeassistant/components/here_travel_time/translations/nl.json new file mode 100644 index 00000000000..cbec21776e5 --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/nl.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Bestemming als GPS-co\u00f6rdinaten" + }, + "title": "Bestemming kiezen" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Bestemming van een entiteit" + }, + "title": "Bestemming kiezen" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Een kaartlocatie gebruiken", + "destination_entity": "Een entiteit gebruiken" + }, + "title": "Bestemming kiezen" + }, + "origin_coordinates": { + "data": { + "origin": "Herkomst als GPS-co\u00f6rdinaten" + }, + "title": "Herkomst kiezen" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Herkomst van een entiteit" + }, + "title": "Herkomst kiezen" + }, + "user": { + "data": { + "api_key": "API-sleutel", + "mode": "Reismodus", + "name": "Naam" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Aankomsttijd" + }, + "title": "Aankomsttijd kiezen" + }, + "departure_time": { + "data": { + "departure_time": "Vertrektijd" + }, + "title": "Vertrektijd kiezen" + }, + "init": { + "data": { + "route_mode": "Routemodus", + "traffic_mode": "Verkeersmodus", + "unit_system": "Eenheidssysteem" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Een aankomsttijd configureren", + "departure_time": "Een vertrektijd configureren", + "no_time": "Geen tijd configureren" + }, + "title": "Kies tijdtype" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/no.json b/homeassistant/components/here_travel_time/translations/no.json new file mode 100644 index 00000000000..52d4477f379 --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/no.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Destinasjon som GPS-koordinater" + }, + "title": "Velg Destinasjon" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Destinasjon ved hjelp av en enhet" + }, + "title": "Velg Destinasjon" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Bruke en kartplassering", + "destination_entity": "Bruke en enhet" + }, + "title": "Velg Destinasjon" + }, + "origin_coordinates": { + "data": { + "origin": "Opprinnelse som GPS-koordinater" + }, + "title": "Velg Opprinnelse" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Opprinnelse ved hjelp av en enhet" + }, + "title": "Velg Opprinnelse" + }, + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "mode": "Reisemodus", + "name": "Navn" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Ankomsttid" + }, + "title": "Velg Ankomsttid" + }, + "departure_time": { + "data": { + "departure_time": "Avgangstid" + }, + "title": "Velg Avreisetid" + }, + "init": { + "data": { + "route_mode": "Rutemodus", + "traffic_mode": "Trafikkmodus", + "unit_system": "Enhetssystem" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Konfigurer en ankomsttid", + "departure_time": "Konfigurer en avgangstid", + "no_time": "Ikke konfigurer en tid" + }, + "title": "Velg Tidstype" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/pl.json b/homeassistant/components/here_travel_time/translations/pl.json new file mode 100644 index 00000000000..3e4f41212a2 --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/pl.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Punkt docelowy jako wsp\u00f3\u0142rz\u0119dne GPS" + }, + "title": "Wybierz punkt docelowy" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Punkt docelowy przy u\u017cyciu encji" + }, + "title": "Wybierz punkt docelowy" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Lokalizacja na mapie", + "destination_entity": "Encja" + }, + "title": "Wybierz punkt docelowy" + }, + "origin_coordinates": { + "data": { + "origin": "Punkt pocz\u0105tkowy jako wsp\u00f3\u0142rz\u0119dne GPS" + }, + "title": "Wybierz punkt pocz\u0105tkowy" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Punkt pocz\u0105tkowy przy u\u017cyciu encji" + }, + "title": "Wybierz punkt pocz\u0105tkowy" + }, + "user": { + "data": { + "api_key": "Klucz API", + "mode": "Tryb podr\u00f3\u017cy", + "name": "Nazwa" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Czas przyjazdu" + }, + "title": "Wybierz czas przyjazdu" + }, + "departure_time": { + "data": { + "departure_time": "Czas wyjazdu" + }, + "title": "Wybierz czas wyjazdu" + }, + "init": { + "data": { + "route_mode": "Tryb trasy", + "traffic_mode": "Tryb ruchu", + "unit_system": "System metryczny" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Skonfiguruj czas przyjazdu", + "departure_time": "Skonfiguruj czas wyjazdu", + "no_time": "Nie konfiguruj czasu" + }, + "title": "Wybierz typ czasu" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/pt-BR.json b/homeassistant/components/here_travel_time/translations/pt-BR.json new file mode 100644 index 00000000000..78996561564 --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/pt-BR.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "Destino como coordenadas GPS" + }, + "title": "Escolha o destino" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Destino usando uma entidade" + }, + "title": "Escolha o destino" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Usando uma localiza\u00e7\u00e3o no mapa", + "destination_entity": "Usando uma entidade" + }, + "title": "Escolha o destino" + }, + "origin_coordinates": { + "data": { + "origin": "Origem como coordenadas GPS" + }, + "title": "Escolha a Origem" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Origem usando uma entidade" + }, + "title": "Escolha a Origem" + }, + "user": { + "data": { + "api_key": "Chave API", + "mode": "Modo de viagem", + "name": "Nome" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Hora da chegada" + }, + "title": "Escolha a hora de chegada" + }, + "departure_time": { + "data": { + "departure_time": "Hora de partida" + }, + "title": "Escolha o hor\u00e1rio de partida" + }, + "init": { + "data": { + "route_mode": "Modo de rota", + "traffic_mode": "Modo de tr\u00e2nsito", + "unit_system": "Sistema de unidades" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Configurar um hor\u00e1rio de chegada", + "departure_time": "Configurar um hor\u00e1rio de partida", + "no_time": "N\u00e3o configure um hor\u00e1rio" + }, + "title": "Escolha o tipo de hor\u00e1rio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/ru.json b/homeassistant/components/here_travel_time/translations/ru.json new file mode 100644 index 00000000000..fc649d920df --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/ru.json @@ -0,0 +1,82 @@ +{ + "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": { + "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": { + "destination_coordinates": { + "data": { + "destination": "\u041f\u0443\u043d\u043a\u0442 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432 \u0432\u0438\u0434\u0435 GPS-\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0443\u043d\u043a\u0442 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "\u041e\u0431\u044a\u0435\u043a\u0442 \u043f\u0443\u043d\u043a\u0442\u0430 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0443\u043d\u043a\u0442 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "\u0423\u043a\u0430\u0437\u0430\u0442\u044c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u043a\u0430\u0440\u0442\u0435", + "destination_entity": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0443\u043d\u043a\u0442 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f" + }, + "origin_coordinates": { + "data": { + "origin": "\u041f\u0443\u043d\u043a\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432 \u0432\u0438\u0434\u0435 GPS-\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0443\u043d\u043a\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "\u041e\u0431\u044a\u0435\u043a\u0442 \u043f\u0443\u043d\u043a\u0442\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0443\u043d\u043a\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f" + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "mode": "\u0421\u043f\u043e\u0441\u043e\u0431 \u043f\u0435\u0440\u0435\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "\u0412\u0440\u0435\u043c\u044f \u043f\u0440\u0438\u0431\u044b\u0442\u0438\u044f" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0438\u0431\u044b\u0442\u0438\u044f" + }, + "departure_time": { + "data": { + "departure_time": "\u0412\u0440\u0435\u043c\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f" + }, + "init": { + "data": { + "route_mode": "\u0420\u0435\u0436\u0438\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430", + "traffic_mode": "\u0420\u0435\u0436\u0438\u043c \u0442\u0440\u0430\u0444\u0438\u043a\u0430", + "unit_system": "\u0415\u0434\u0438\u043d\u0438\u0446\u044b \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0438\u0431\u044b\u0442\u0438\u044f", + "departure_time": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f", + "no_time": "\u041d\u0435 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0432\u0440\u0435\u043c\u044f" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u0432\u0440\u0435\u043c\u0435\u043d\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/tr.json b/homeassistant/components/here_travel_time/translations/tr.json new file mode 100644 index 00000000000..181588ba54a --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/tr.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "GPS koordinatlar\u0131 olarak hedef" + }, + "title": "Hedef Se\u00e7" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "Bir varl\u0131k kullanarak hedef" + }, + "title": "Hedef Se\u00e7" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "Bir harita konumu kullan\u0131n", + "destination_entity": "Bir varl\u0131\u011f\u0131 kullan\u0131n" + }, + "title": "Hedef Se\u00e7" + }, + "origin_coordinates": { + "data": { + "origin": "GPS koordinatlar\u0131 olarak kalk\u0131\u015f" + }, + "title": "Kalk\u0131\u015f Se\u00e7in" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "Bir varl\u0131k kullanarak kaynak" + }, + "title": "Kalk\u0131\u015f Se\u00e7in" + }, + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "mode": "Seyahat Modu", + "name": "Ad" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "Var\u0131\u015f Zaman\u0131" + }, + "title": "Var\u0131\u015f Saatini Se\u00e7in" + }, + "departure_time": { + "data": { + "departure_time": "Hareket Saati" + }, + "title": "Kalk\u0131\u015f Saatini Se\u00e7in" + }, + "init": { + "data": { + "route_mode": "Rota Modu", + "traffic_mode": "Trafik Modu", + "unit_system": "\u00d6l\u00e7\u00fc sistemi" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "Var\u0131\u015f saatini yap\u0131land\u0131r\u0131n", + "departure_time": "Kalk\u0131\u015f saati yap\u0131land\u0131r\u0131n", + "no_time": "Bir zaman yap\u0131land\u0131rmay\u0131n" + }, + "title": "Zaman T\u00fcr\u00fcn\u00fc Se\u00e7in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/here_travel_time/translations/zh-Hant.json b/homeassistant/components/here_travel_time/translations/zh-Hant.json new file mode 100644 index 00000000000..53d5eae18fd --- /dev/null +++ b/homeassistant/components/here_travel_time/translations/zh-Hant.json @@ -0,0 +1,82 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "destination_coordinates": { + "data": { + "destination": "\u76ee\u7684\u5730\u70ba GPS \u5ea7\u6a19" + }, + "title": "\u9078\u64c7\u76ee\u7684\u5730" + }, + "destination_entity_id": { + "data": { + "destination_entity_id": "\u4f7f\u7528\u76ee\u7684\u5730\u70ba\u5be6\u9ad4" + }, + "title": "\u9078\u64c7\u76ee\u7684\u5730" + }, + "destination_menu": { + "menu_options": { + "destination_coordinates": "\u4f7f\u7528\u5730\u5716\u5ea7\u6a19", + "destination_entity": "\u4f7f\u7528\u70ba\u5be6\u9ad4" + }, + "title": "\u9078\u64c7\u76ee\u7684\u5730" + }, + "origin_coordinates": { + "data": { + "origin": "\u51fa\u767c\u5730\u70ba GPS \u5ea7\u6a19" + }, + "title": "\u9078\u64c7\u51fa\u767c\u5730" + }, + "origin_entity_id": { + "data": { + "origin_entity_id": "\u4f7f\u7528\u51fa\u767c\u5730\u70ba\u5be6\u9ad4" + }, + "title": "\u9078\u64c7\u51fa\u767c\u5730" + }, + "user": { + "data": { + "api_key": "API \u91d1\u9470", + "mode": "\u65c5\u884c\u6a21\u5f0f", + "name": "\u540d\u7a31" + } + } + } + }, + "options": { + "step": { + "arrival_time": { + "data": { + "arrival_time": "\u62b5\u9054\u6642\u9593" + }, + "title": "\u9078\u64c7\u62b5\u9054\u6642\u9593" + }, + "departure_time": { + "data": { + "departure_time": "\u51fa\u767c\u6642\u9593" + }, + "title": "\u9078\u64c7\u51fa\u767c\u6642\u9593" + }, + "init": { + "data": { + "route_mode": "\u8def\u5f91\u6a21\u5f0f", + "traffic_mode": "\u4ea4\u901a\u6a21\u5f0f", + "unit_system": "\u55ae\u4f4d\u7cfb\u7d71" + } + }, + "time_menu": { + "menu_options": { + "arrival_time": "\u8a2d\u5b9a\u62b5\u9054\u6642\u9593", + "departure_time": "\u8a2d\u5b9a\u51fa\u767c\u6642\u9593", + "no_time": "\u4e0d\u8981\u8a2d\u5b9a\u6642\u9593" + }, + "title": "\u9078\u64c7\u6642\u9593\u985e\u578b" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/es.json b/homeassistant/components/hisense_aehw4a1/translations/es.json index 1a3a8ca221d..8eb81391ec4 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/es.json +++ b/homeassistant/components/hisense_aehw4a1/translations/es.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No se encontraron dispositivos Hisense AEH-W4A1 en la red.", - "single_instance_allowed": "Solo es posible una \u00fanica configuraci\u00f3n de Hisense AEH-W4A1." + "no_devices_found": "No se encontraron dispositivos en la red", + "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/nl.json b/homeassistant/components/hisense_aehw4a1/translations/nl.json index c1f353558b6..0d7661a07b6 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/nl.json +++ b/homeassistant/components/hisense_aehw4a1/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 8740f352dee..27acff54f99 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -6,46 +6,39 @@ from datetime import datetime as dt, timedelta from http import HTTPStatus import logging import time -from typing import cast +from typing import Literal, cast from aiohttp import web -from sqlalchemy import not_, or_ import voluptuous as vol from homeassistant.components import frontend, websocket_api from homeassistant.components.http import HomeAssistantView -from homeassistant.components.recorder import ( - get_instance, - history, - models as history_models, +from homeassistant.components.recorder import get_instance, history +from homeassistant.components.recorder.filters import ( + Filters, + sqlalchemy_filter_from_include_exclude_conf, ) from homeassistant.components.recorder.statistics import ( list_statistic_ids, statistics_during_period, ) from homeassistant.components.recorder.util import session_scope -from homeassistant.const import CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE +from homeassistant.components.websocket_api import messages +from homeassistant.components.websocket_api.const import JSON_DUMP from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.deprecation import deprecated_class, deprecated_function -from homeassistant.helpers.entityfilter import ( - CONF_ENTITY_GLOBS, - INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA, -) +from homeassistant.helpers.entityfilter import INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA from homeassistant.helpers.typing import ConfigType import homeassistant.util.dt as dt_util -# mypy: allow-untyped-defs, no-check-untyped-defs - _LOGGER = logging.getLogger(__name__) DOMAIN = "history" +HISTORY_FILTERS = "history_filters" +HISTORY_USE_INCLUDE_ORDER = "history_use_include_order" + CONF_ORDER = "use_include_order" -GLOB_TO_SQL_CHARS = { - 42: "%", # * - 46: "_", # . -} CONFIG_SCHEMA = vol.Schema( { @@ -57,47 +50,39 @@ CONFIG_SCHEMA = vol.Schema( ) -@deprecated_function("homeassistant.components.recorder.history.get_significant_states") -def get_significant_states(hass, *args, **kwargs): - """Wrap get_significant_states_with_session with an sql session.""" - return history.get_significant_states(hass, *args, **kwargs) - - -@deprecated_function( - "homeassistant.components.recorder.history.state_changes_during_period" -) -def state_changes_during_period(hass, start_time, end_time=None, entity_id=None): - """Return states changes during UTC period start_time - end_time.""" - return history.state_changes_during_period( - hass, start_time, end_time=None, entity_id=None - ) - - -@deprecated_function("homeassistant.components.recorder.history.get_last_state_changes") -def get_last_state_changes(hass, number_of_states, entity_id): - """Return the last number_of_states.""" - return history.get_last_state_changes(hass, number_of_states, entity_id) - - async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the history hooks.""" conf = config.get(DOMAIN, {}) - filters = sqlalchemy_filter_from_include_exclude_conf(conf) - - use_include_order = conf.get(CONF_ORDER) + hass.data[HISTORY_FILTERS] = filters = sqlalchemy_filter_from_include_exclude_conf( + conf + ) + hass.data[HISTORY_USE_INCLUDE_ORDER] = use_include_order = conf.get(CONF_ORDER) hass.http.register_view(HistoryPeriodView(filters, use_include_order)) frontend.async_register_built_in_panel(hass, "history", "history", "hass:chart-box") websocket_api.async_register_command(hass, ws_get_statistics_during_period) websocket_api.async_register_command(hass, ws_get_list_statistic_ids) + websocket_api.async_register_command(hass, ws_get_history_during_period) return True -@deprecated_class("homeassistant.components.recorder.models.LazyState") -class LazyState(history_models.LazyState): - """A lazy version of core State.""" +def _ws_get_statistics_during_period( + hass: HomeAssistant, + msg_id: int, + start_time: dt, + end_time: dt | None = None, + statistic_ids: list[str] | None = None, + period: Literal["5minute", "day", "hour", "month"] = "hour", +) -> str: + """Fetch statistics and convert them to json in the executor.""" + return JSON_DUMP( + messages.result_message( + msg_id, + statistics_during_period(hass, start_time, end_time, statistic_ids, period), + ) + ) @websocket_api.websocket_command( @@ -132,15 +117,28 @@ async def ws_get_statistics_during_period( else: end_time = None - statistics = await get_instance(hass).async_add_executor_job( - statistics_during_period, - hass, - start_time, - end_time, - msg.get("statistic_ids"), - msg.get("period"), + connection.send_message( + await get_instance(hass).async_add_executor_job( + _ws_get_statistics_during_period, + hass, + msg["id"], + start_time, + end_time, + msg.get("statistic_ids"), + msg.get("period"), + ) + ) + + +def _ws_get_list_statistic_ids( + hass: HomeAssistant, + msg_id: int, + statistic_type: Literal["mean"] | Literal["sum"] | None = None, +) -> str: + """Fetch a list of available statistic_id and convert them to json in the executor.""" + return JSON_DUMP( + messages.result_message(msg_id, list_statistic_ids(hass, None, statistic_type)) ) - connection.send_result(msg["id"], statistics) @websocket_api.websocket_command( @@ -154,13 +152,129 @@ async def ws_get_list_statistic_ids( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict ) -> None: """Fetch a list of available statistic_id.""" - statistic_ids = await get_instance(hass).async_add_executor_job( - list_statistic_ids, - hass, - None, - msg.get("statistic_type"), + connection.send_message( + await get_instance(hass).async_add_executor_job( + _ws_get_list_statistic_ids, + hass, + msg["id"], + msg.get("statistic_type"), + ) + ) + + +def _ws_get_significant_states( + hass: HomeAssistant, + msg_id: int, + start_time: dt, + end_time: dt | None, + entity_ids: list[str] | None, + filters: Filters | None, + use_include_order: bool | None, + include_start_time_state: bool, + significant_changes_only: bool, + minimal_response: bool, + no_attributes: bool, +) -> str: + """Fetch history significant_states and convert them to json in the executor.""" + states = history.get_significant_states( + hass, + start_time, + end_time, + entity_ids, + filters, + include_start_time_state, + significant_changes_only, + minimal_response, + no_attributes, + True, + ) + + if not use_include_order or not filters: + return JSON_DUMP(messages.result_message(msg_id, states)) + + return JSON_DUMP( + messages.result_message( + msg_id, + { + order_entity: states.pop(order_entity) + for order_entity in filters.included_entities + if order_entity in states + } + | states, + ) + ) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "history/history_during_period", + vol.Required("start_time"): str, + vol.Optional("end_time"): str, + vol.Optional("entity_ids"): [str], + vol.Optional("include_start_time_state", default=True): bool, + vol.Optional("significant_changes_only", default=True): bool, + vol.Optional("minimal_response", default=False): bool, + vol.Optional("no_attributes", default=False): bool, + } +) +@websocket_api.async_response +async def ws_get_history_during_period( + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict +) -> None: + """Handle history during period websocket command.""" + start_time_str = msg["start_time"] + end_time_str = msg.get("end_time") + + if start_time := dt_util.parse_datetime(start_time_str): + start_time = dt_util.as_utc(start_time) + else: + connection.send_error(msg["id"], "invalid_start_time", "Invalid start_time") + return + + if end_time_str: + if end_time := dt_util.parse_datetime(end_time_str): + end_time = dt_util.as_utc(end_time) + else: + connection.send_error(msg["id"], "invalid_end_time", "Invalid end_time") + return + else: + end_time = None + + if start_time > dt_util.utcnow(): + connection.send_result(msg["id"], {}) + return + + entity_ids = msg.get("entity_ids") + include_start_time_state = msg["include_start_time_state"] + + if ( + not include_start_time_state + and entity_ids + and not _entities_may_have_state_changes_after(hass, entity_ids, start_time) + ): + connection.send_result(msg["id"], {}) + return + + significant_changes_only = msg["significant_changes_only"] + no_attributes = msg["no_attributes"] + minimal_response = msg["minimal_response"] + + connection.send_message( + await get_instance(hass).async_add_executor_job( + _ws_get_significant_states, + hass, + msg["id"], + start_time, + end_time, + entity_ids, + hass.data[HISTORY_FILTERS], + hass.data[HISTORY_USE_INCLUDE_ORDER], + include_start_time_state, + significant_changes_only, + minimal_response, + no_attributes, + ) ) - connection.send_result(msg["id"], statistic_ids) class HistoryPeriodView(HomeAssistantView): @@ -239,20 +353,20 @@ class HistoryPeriodView(HomeAssistantView): def _sorted_significant_states_json( self, - hass, - start_time, - end_time, - entity_ids, - include_start_time_state, - significant_changes_only, - minimal_response, - no_attributes, - ): + hass: HomeAssistant, + start_time: dt, + end_time: dt, + entity_ids: list[str] | None, + include_start_time_state: bool, + significant_changes_only: bool, + minimal_response: bool, + no_attributes: bool, + ) -> web.Response: """Fetch significant stats from the database as json.""" timer_start = time.perf_counter() with session_scope(hass=hass) as session: - result = history.get_significant_states_with_session( + states = history.get_significant_states_with_session( hass, session, start_time, @@ -265,134 +379,24 @@ class HistoryPeriodView(HomeAssistantView): no_attributes, ) - result = list(result.values()) if _LOGGER.isEnabledFor(logging.DEBUG): elapsed = time.perf_counter() - timer_start - _LOGGER.debug("Extracted %d states in %fs", sum(map(len, result)), elapsed) + _LOGGER.debug( + "Extracted %d states in %fs", sum(map(len, states.values())), elapsed + ) # Optionally reorder the result to respect the ordering given # by any entities explicitly included in the configuration. - if self.filters and self.use_include_order: - sorted_result = [] - for order_entity in self.filters.included_entities: - for state_list in result: - if state_list[0].entity_id == order_entity: - sorted_result.append(state_list) - result.remove(state_list) - break - sorted_result.extend(result) - result = sorted_result + if not self.filters or not self.use_include_order: + return self.json(list(states.values())) - return self.json(result) - - -def sqlalchemy_filter_from_include_exclude_conf(conf: ConfigType) -> Filters | None: - """Build a sql filter from config.""" - filters = Filters() - if exclude := conf.get(CONF_EXCLUDE): - filters.excluded_entities = exclude.get(CONF_ENTITIES, []) - filters.excluded_domains = exclude.get(CONF_DOMAINS, []) - filters.excluded_entity_globs = exclude.get(CONF_ENTITY_GLOBS, []) - if include := conf.get(CONF_INCLUDE): - filters.included_entities = include.get(CONF_ENTITIES, []) - filters.included_domains = include.get(CONF_DOMAINS, []) - filters.included_entity_globs = include.get(CONF_ENTITY_GLOBS, []) - - return filters if filters.has_config else None - - -class Filters: - """Container for the configured include and exclude filters.""" - - def __init__(self) -> None: - """Initialise the include and exclude filters.""" - self.excluded_entities: list[str] = [] - self.excluded_domains: list[str] = [] - self.excluded_entity_globs: list[str] = [] - - self.included_entities: list[str] = [] - self.included_domains: list[str] = [] - self.included_entity_globs: list[str] = [] - - def apply(self, query): - """Apply the entity filter.""" - if not self.has_config: - return query - - return query.filter(self.entity_filter()) - - @property - def has_config(self): - """Determine if there is any filter configuration.""" - if ( - self.excluded_entities - or self.excluded_domains - or self.excluded_entity_globs - or self.included_entities - or self.included_domains - or self.included_entity_globs - ): - return True - - return False - - def bake(self, baked_query): - """Update a baked query. - - Works the same as apply on a baked_query. - """ - if not self.has_config: - return - - baked_query += lambda q: q.filter(self.entity_filter()) - - def entity_filter(self): - """Generate the entity filter query.""" - includes = [] - if self.included_domains: - includes.append( - or_( - *[ - history_models.States.entity_id.like(f"{domain}.%") - for domain in self.included_domains - ] - ).self_group() - ) - if self.included_entities: - includes.append(history_models.States.entity_id.in_(self.included_entities)) - for glob in self.included_entity_globs: - includes.append(_glob_to_like(glob)) - - excludes = [] - if self.excluded_domains: - excludes.append( - or_( - *[ - history_models.States.entity_id.like(f"{domain}.%") - for domain in self.excluded_domains - ] - ).self_group() - ) - if self.excluded_entities: - excludes.append(history_models.States.entity_id.in_(self.excluded_entities)) - for glob in self.excluded_entity_globs: - excludes.append(_glob_to_like(glob)) - - if not includes and not excludes: - return None - - if includes and not excludes: - return or_(*includes) - - if not includes and excludes: - return not_(or_(*excludes)) - - return or_(*includes) & not_(or_(*excludes)) - - -def _glob_to_like(glob_str): - """Translate glob to sql.""" - return history_models.States.entity_id.like(glob_str.translate(GLOB_TO_SQL_CHARS)) + sorted_result = [ + states.pop(order_entity) + for order_entity in self.filters.included_entities + if order_entity in states + ] + sorted_result.extend(list(states.values())) + return self.json(sorted_result) def _entities_may_have_state_changes_after( diff --git a/homeassistant/components/history_stats/coordinator.py b/homeassistant/components/history_stats/coordinator.py index 2a2cc392bb5..7d44da9f5f6 100644 --- a/homeassistant/components/history_stats/coordinator.py +++ b/homeassistant/components/history_stats/coordinator.py @@ -19,11 +19,9 @@ _LOGGER = logging.getLogger(__name__) UPDATE_INTERVAL = timedelta(minutes=1) -class HistoryStatsUpdateCoordinator(DataUpdateCoordinator): +class HistoryStatsUpdateCoordinator(DataUpdateCoordinator[HistoryStatsState]): """DataUpdateCoordinator to gather data for a specific TPLink device.""" - data: HistoryStatsState - def __init__( self, hass: HomeAssistant, diff --git a/homeassistant/components/hive/__init__.py b/homeassistant/components/hive/__init__.py index fab89bd5b19..292bbe62ae1 100644 --- a/homeassistant/components/hive/__init__.py +++ b/homeassistant/components/hive/__init__.py @@ -1,10 +1,15 @@ """Support for the Hive devices and services.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable, Coroutine from functools import wraps import logging +from typing import Any, TypeVar from aiohttp.web_exceptions import HTTPException from apyhiveapi import Hive from apyhiveapi.helper.hive_exceptions import HiveReauthRequired +from typing_extensions import Concatenate, ParamSpec import voluptuous as vol from homeassistant import config_entries @@ -22,6 +27,9 @@ from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, PLATFORM_LOOKUP, PLATFORMS +_HiveEntityT = TypeVar("_HiveEntityT", bound="HiveEntity") +_P = ParamSpec("_P") + _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( @@ -68,8 +76,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Hive from a config entry.""" websession = aiohttp_client.async_get_clientsession(hass) - hive = Hive(websession) hive_config = dict(entry.data) + hive = Hive( + websession, + deviceGroupKey=hive_config["device_data"][0], + deviceKey=hive_config["device_data"][1], + devicePassword=hive_config["device_data"][2], + ) hive_config["options"] = {} hive_config["options"].update( @@ -104,11 +117,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -def refresh_system(func): +def refresh_system( + func: Callable[Concatenate[_HiveEntityT, _P], Awaitable[Any]] +) -> Callable[Concatenate[_HiveEntityT, _P], Coroutine[Any, Any, None]]: """Force update all entities after state change.""" @wraps(func) - async def wrapper(self, *args, **kwargs): + async def wrapper(self: _HiveEntityT, *args: _P.args, **kwargs: _P.kwargs) -> None: await func(self, *args, **kwargs) async_dispatcher_send(self.hass, DOMAIN) diff --git a/homeassistant/components/hive/config_flow.py b/homeassistant/components/hive/config_flow.py index 2632a24e360..9c391f13294 100644 --- a/homeassistant/components/hive/config_flow.py +++ b/homeassistant/components/hive/config_flow.py @@ -103,6 +103,7 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Setup the config entry self.data["tokens"] = self.tokens + self.data["device_data"] = await self.hive_auth.getDeviceData() if self.context["source"] == config_entries.SOURCE_REAUTH: self.hass.config_entries.async_update_entry( self.entry, title=self.data["username"], data=self.data diff --git a/homeassistant/components/hive/manifest.json b/homeassistant/components/hive/manifest.json index 19958b51bd7..472adc137ba 100644 --- a/homeassistant/components/hive/manifest.json +++ b/homeassistant/components/hive/manifest.json @@ -3,7 +3,7 @@ "name": "Hive", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hive", - "requirements": ["pyhiveapi==0.4.2"], + "requirements": ["pyhiveapi==0.5.4"], "codeowners": ["@Rendili", "@KJonline"], "iot_class": "cloud_polling", "loggers": ["apyhiveapi"] diff --git a/homeassistant/components/hive/translations/es.json b/homeassistant/components/hive/translations/es.json index 727a33ec66e..09acc273536 100644 --- a/homeassistant/components/hive/translations/es.json +++ b/homeassistant/components/hive/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente", "unknown_entry": "No se puede encontrar una entrada existente." }, diff --git a/homeassistant/components/hive/translations/nl.json b/homeassistant/components/hive/translations/nl.json index 206aa661735..2cbc7c94fc6 100644 --- a/homeassistant/components/hive/translations/nl.json +++ b/homeassistant/components/hive/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol", + "reauth_successful": "Herauthenticatie geslaagd", "unknown_entry": "Kan bestaand item niet vinden." }, "error": { diff --git a/homeassistant/components/home_connect/__init__.py b/homeassistant/components/home_connect/__init__.py index 26448893438..345eeeddaaa 100644 --- a/homeassistant/components/home_connect/__init__.py +++ b/homeassistant/components/home_connect/__init__.py @@ -6,6 +6,10 @@ import logging from requests import HTTPError import voluptuous as vol +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, Platform from homeassistant.core import HomeAssistant @@ -13,22 +17,25 @@ from homeassistant.helpers import config_entry_oauth2_flow, config_validation as from homeassistant.helpers.typing import ConfigType from homeassistant.util import Throttle -from . import api, config_flow -from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN +from . import api +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(minutes=1) CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_CLIENT_SECRET): cv.string, - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) @@ -42,17 +49,21 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if DOMAIN not in config: return True - config_flow.OAuth2FlowHandler.async_register_implementation( + await async_import_client_credential( hass, - config_entry_oauth2_flow.LocalOAuth2Implementation( - hass, - DOMAIN, + DOMAIN, + ClientCredential( config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET], - OAUTH2_AUTHORIZE, - OAUTH2_TOKEN, ), ) + _LOGGER.warning( + "Configuration of Home Connect integration in YAML is deprecated and " + "will be removed in a future release; Your existing OAuth " + "Application Credentials have been imported into the UI " + "automatically and can be safely removed from your " + "configuration.yaml file" + ) return True diff --git a/homeassistant/components/home_connect/application_credentials.py b/homeassistant/components/home_connect/application_credentials.py new file mode 100644 index 00000000000..3d5a407b487 --- /dev/null +++ b/homeassistant/components/home_connect/application_credentials.py @@ -0,0 +1,14 @@ +"""Application credentials platform for Home Connect.""" + +from homeassistant.components.application_credentials import AuthorizationServer +from homeassistant.core import HomeAssistant + +from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN + + +async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer: + """Return authorization server.""" + return AuthorizationServer( + authorize_url=OAUTH2_AUTHORIZE, + token_url=OAUTH2_TOKEN, + ) diff --git a/homeassistant/components/home_connect/manifest.json b/homeassistant/components/home_connect/manifest.json index e50053d2d1b..0a055c971c5 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": ["auth"], + "dependencies": ["application_credentials"], "codeowners": ["@DavidMStraub"], "requirements": ["homeconnect==0.7.0"], "config_flow": true, diff --git a/homeassistant/components/home_connect/translations/es.json b/homeassistant/components/home_connect/translations/es.json index 10b49c96926..9ee5769583b 100644 --- a/homeassistant/components/home_connect/translations/es.json +++ b/homeassistant/components/home_connect/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "missing_configuration": "El componente Home Connect no est\u00e1 configurado. Por favor, sigue la documentaci\u00f3n.", + "missing_configuration": "El componente no est\u00e1 configurado. Mira su documentaci\u00f3n.", "no_url_available": "No hay URL disponible. Para obtener informaci\u00f3n sobre este error, [consulta la secci\u00f3n de ayuda]({docs_url})" }, "create_entry": { diff --git a/homeassistant/components/home_connect/translations/nl.json b/homeassistant/components/home_connect/translations/nl.json index 25a81209607..72bbb5a0e91 100644 --- a/homeassistant/components/home_connect/translations/nl.json +++ b/homeassistant/components/home_connect/translations/nl.json @@ -1,15 +1,15 @@ { "config": { "abort": { - "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", - "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})" }, "create_entry": { - "default": "Succesvol geverifieerd" + "default": "Authenticatie geslaagd" }, "step": { "pick_implementation": { - "title": "Kies de verificatiemethode" + "title": "Kies een authenticatie methode" } } } diff --git a/homeassistant/components/home_plus_control/translations/ca.json b/homeassistant/components/home_plus_control/translations/ca.json index 6e6dc1e0577..1fe3adb2d6c 100644 --- a/homeassistant/components/home_plus_control/translations/ca.json +++ b/homeassistant/components/home_plus_control/translations/ca.json @@ -16,6 +16,5 @@ "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/de.json b/homeassistant/components/home_plus_control/translations/de.json index 8cb47ae3fec..5b927e03007 100644 --- a/homeassistant/components/home_plus_control/translations/de.json +++ b/homeassistant/components/home_plus_control/translations/de.json @@ -16,6 +16,5 @@ "title": "W\u00e4hle die Authentifizierungsmethode" } } - }, - "title": "Legrand Home+ Steuerung" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/el.json b/homeassistant/components/home_plus_control/translations/el.json index 724dafff28f..f2fbb7f4bb3 100644 --- a/homeassistant/components/home_plus_control/translations/el.json +++ b/homeassistant/components/home_plus_control/translations/el.json @@ -16,6 +16,5 @@ "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" } } - }, - "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 Legrand Home+" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/en.json b/homeassistant/components/home_plus_control/translations/en.json index f5f8afe73d1..250ca9d56c2 100644 --- a/homeassistant/components/home_plus_control/translations/en.json +++ b/homeassistant/components/home_plus_control/translations/en.json @@ -16,6 +16,5 @@ "title": "Pick Authentication Method" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/es-419.json b/homeassistant/components/home_plus_control/translations/es-419.json index e35bb529a85..9596f3c9d2f 100644 --- a/homeassistant/components/home_plus_control/translations/es-419.json +++ b/homeassistant/components/home_plus_control/translations/es-419.json @@ -16,6 +16,5 @@ "title": "Escoja el m\u00e9todo de autenticaci\u00f3n" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/es.json b/homeassistant/components/home_plus_control/translations/es.json index 3c471ffc75e..194eff4bb8c 100644 --- a/homeassistant/components/home_plus_control/translations/es.json +++ b/homeassistant/components/home_plus_control/translations/es.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n.", "no_url_available": "No hay URL disponible. Para obtener informaci\u00f3n sobre este error, [consulta la secci\u00f3n de ayuda]({docs_url})", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." @@ -16,6 +16,5 @@ "title": "Selecciona el m\u00e9todo de autenticaci\u00f3n" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/et.json b/homeassistant/components/home_plus_control/translations/et.json index cfe40d86bcd..d03d44a9901 100644 --- a/homeassistant/components/home_plus_control/translations/et.json +++ b/homeassistant/components/home_plus_control/translations/et.json @@ -16,6 +16,5 @@ "title": "Vali tuvastusmeetod" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/fr.json b/homeassistant/components/home_plus_control/translations/fr.json index 4e250b743b7..0eaeafc19d4 100644 --- a/homeassistant/components/home_plus_control/translations/fr.json +++ b/homeassistant/components/home_plus_control/translations/fr.json @@ -16,6 +16,5 @@ "title": "S\u00e9lectionner une m\u00e9thode d'authentification" } } - }, - "title": "Legrand Home + Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/hu.json b/homeassistant/components/home_plus_control/translations/hu.json index 3dc4b451332..5e64b3e3880 100644 --- a/homeassistant/components/home_plus_control/translations/hu.json +++ b/homeassistant/components/home_plus_control/translations/hu.json @@ -16,6 +16,5 @@ "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" } } - }, - "title": "Legrand Home+ vez\u00e9rl\u00e9s" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/id.json b/homeassistant/components/home_plus_control/translations/id.json index 2ef7efe3d87..5d66b185d9a 100644 --- a/homeassistant/components/home_plus_control/translations/id.json +++ b/homeassistant/components/home_plus_control/translations/id.json @@ -16,6 +16,5 @@ "title": "Pilih Metode Autentikasi" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/it.json b/homeassistant/components/home_plus_control/translations/it.json index e79d22275f7..67c3ea2b92a 100644 --- a/homeassistant/components/home_plus_control/translations/it.json +++ b/homeassistant/components/home_plus_control/translations/it.json @@ -16,6 +16,5 @@ "title": "Scegli il metodo di autenticazione" } } - }, - "title": "Legrand Home + Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/ja.json b/homeassistant/components/home_plus_control/translations/ja.json index df5165fc3fa..3cef1cd5fa1 100644 --- a/homeassistant/components/home_plus_control/translations/ja.json +++ b/homeassistant/components/home_plus_control/translations/ja.json @@ -16,6 +16,5 @@ "title": "\u8a8d\u8a3c\u65b9\u6cd5\u306e\u9078\u629e" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/ko.json b/homeassistant/components/home_plus_control/translations/ko.json index 94c8bb91648..44667e6a324 100644 --- a/homeassistant/components/home_plus_control/translations/ko.json +++ b/homeassistant/components/home_plus_control/translations/ko.json @@ -16,6 +16,5 @@ "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/nl.json b/homeassistant/components/home_plus_control/translations/nl.json index 8f6df2fdad0..f1471f36273 100644 --- a/homeassistant/components/home_plus_control/translations/nl.json +++ b/homeassistant/components/home_plus_control/translations/nl.json @@ -2,20 +2,19 @@ "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})", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "already_in_progress": "De configuratie is momenteel al bezig", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "step": { "pick_implementation": { "title": "Kies een authenticatie methode" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/no.json b/homeassistant/components/home_plus_control/translations/no.json index 363f5cf54f7..314cfe84c28 100644 --- a/homeassistant/components/home_plus_control/translations/no.json +++ b/homeassistant/components/home_plus_control/translations/no.json @@ -16,6 +16,5 @@ "title": "Velg godkjenningsmetode" } } - }, - "title": "Legrand Home + Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/pl.json b/homeassistant/components/home_plus_control/translations/pl.json index b684c874a7d..27448ab2398 100644 --- a/homeassistant/components/home_plus_control/translations/pl.json +++ b/homeassistant/components/home_plus_control/translations/pl.json @@ -16,6 +16,5 @@ "title": "Wybierz metod\u0119 uwierzytelniania" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/pt-BR.json b/homeassistant/components/home_plus_control/translations/pt-BR.json index 3b7340be7c7..12ca9127cff 100644 --- a/homeassistant/components/home_plus_control/translations/pt-BR.json +++ b/homeassistant/components/home_plus_control/translations/pt-BR.json @@ -16,6 +16,5 @@ "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/ru.json b/homeassistant/components/home_plus_control/translations/ru.json index c968cc261e2..b67c2eb9ce7 100644 --- a/homeassistant/components/home_plus_control/translations/ru.json +++ b/homeassistant/components/home_plus_control/translations/ru.json @@ -16,6 +16,5 @@ "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" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/tr.json b/homeassistant/components/home_plus_control/translations/tr.json index 0138716d548..105fe1ebbed 100644 --- a/homeassistant/components/home_plus_control/translations/tr.json +++ b/homeassistant/components/home_plus_control/translations/tr.json @@ -16,6 +16,5 @@ "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7" } } - }, - "title": "Legrand Home+ Kontrol" + } } \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/zh-Hant.json b/homeassistant/components/home_plus_control/translations/zh-Hant.json index da55be65f04..572edf837d1 100644 --- a/homeassistant/components/home_plus_control/translations/zh-Hant.json +++ b/homeassistant/components/home_plus_control/translations/zh-Hant.json @@ -16,6 +16,5 @@ "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" } } - }, - "title": "Legrand Home+ Control" + } } \ No newline at end of file diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index f79213e2484..81d79b03c10 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -27,6 +27,7 @@ from homeassistant.helpers.entity_component import async_update_entity from homeassistant.helpers.service import ( async_extract_config_entry_ids, async_extract_referenced_entity_ids, + async_register_admin_service, ) from homeassistant.helpers.typing import ConfigType @@ -206,14 +207,14 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no if tasks: await asyncio.wait(tasks) - hass.helpers.service.async_register_admin_service( - ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service + async_register_admin_service( + hass, ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service ) - hass.helpers.service.async_register_admin_service( - ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service + async_register_admin_service( + hass, ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service ) - hass.helpers.service.async_register_admin_service( - ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service + async_register_admin_service( + hass, ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service ) hass.services.async_register( ha.DOMAIN, @@ -233,8 +234,8 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no # auth only processed during startup await conf_util.async_process_ha_core_config(hass, conf.get(ha.DOMAIN) or {}) - hass.helpers.service.async_register_admin_service( - ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config + async_register_admin_service( + hass, ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config ) async def async_set_location(call: ha.ServiceCall) -> None: @@ -243,7 +244,8 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no latitude=call.data[ATTR_LATITUDE], longitude=call.data[ATTR_LONGITUDE] ) - hass.helpers.service.async_register_admin_service( + async_register_admin_service( + hass, ha.DOMAIN, SERVICE_SET_LOCATION, async_set_location, @@ -265,7 +267,8 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no ) ) - hass.helpers.service.async_register_admin_service( + async_register_admin_service( + hass, ha.DOMAIN, SERVICE_RELOAD_CONFIG_ENTRY, async_handle_reload_config_entry, diff --git a/homeassistant/components/homeassistant/logbook.py b/homeassistant/components/homeassistant/logbook.py new file mode 100644 index 00000000000..548753982a8 --- /dev/null +++ b/homeassistant/components/homeassistant/logbook.py @@ -0,0 +1,39 @@ +"""Describe homeassistant logbook events.""" +from __future__ import annotations + +from collections.abc import Callable + +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_ICON, + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP +from homeassistant.core import Event, HomeAssistant, callback + +from . import DOMAIN + +EVENT_TO_NAME = { + EVENT_HOMEASSISTANT_STOP: "stopped", + EVENT_HOMEASSISTANT_START: "started", +} + + +@callback +def async_describe_events( + hass: HomeAssistant, + async_describe_event: Callable[[str, str, Callable[[Event], dict[str, str]]], None], +) -> None: + """Describe logbook events.""" + + @callback + def async_describe_hass_event(event: Event) -> dict[str, str]: + """Describe homeassisant logbook event.""" + return { + LOGBOOK_ENTRY_NAME: "Home Assistant", + LOGBOOK_ENTRY_MESSAGE: EVENT_TO_NAME[event.event_type], + LOGBOOK_ENTRY_ICON: "mdi:home-assistant", + } + + async_describe_event(DOMAIN, EVENT_HOMEASSISTANT_STOP, async_describe_hass_event) + async_describe_event(DOMAIN, EVENT_HOMEASSISTANT_START, async_describe_hass_event) diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index 505dc0027d6..21c364ba65b 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -35,6 +35,7 @@ from homeassistant.helpers import ( entity_platform, ) from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.state import async_reproduce_state from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.loader import async_get_integration @@ -100,6 +101,7 @@ PLATFORM_SCHEMA = vol.Schema( vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_ICON): cv.icon, vol.Required(CONF_ENTITIES): STATES_SCHEMA, + vol.Optional("metadata"): dict, } ) ], @@ -206,9 +208,7 @@ async def async_setup_platform( hass.bus.async_fire(EVENT_SCENE_RELOADED, context=call.context) - hass.helpers.service.async_register_admin_service( - SCENE_DOMAIN, SERVICE_RELOAD, reload_config - ) + async_register_admin_service(hass, SCENE_DOMAIN, SERVICE_RELOAD, reload_config) async def apply_service(call: ServiceCall) -> None: """Apply a scene.""" diff --git a/homeassistant/components/homeassistant/translations/es.json b/homeassistant/components/homeassistant/translations/es.json index 0a9342afa69..6b00a1b3b51 100644 --- a/homeassistant/components/homeassistant/translations/es.json +++ b/homeassistant/components/homeassistant/translations/es.json @@ -6,7 +6,7 @@ "docker": "Docker", "hassio": "Supervisor", "installation_type": "Tipo de instalaci\u00f3n", - "os_name": "Nombre del Sistema Operativo", + "os_name": "Familia del sistema operativo", "os_version": "Versi\u00f3n del Sistema Operativo", "python_version": "Versi\u00f3n de Python", "timezone": "Zona horaria", diff --git a/homeassistant/components/homeassistant/translations/it.json b/homeassistant/components/homeassistant/translations/it.json index b85f3072620..3052a536338 100644 --- a/homeassistant/components/homeassistant/translations/it.json +++ b/homeassistant/components/homeassistant/translations/it.json @@ -4,10 +4,10 @@ "arch": "Architettura della CPU", "dev": "Sviluppo", "docker": "Docker", - "hassio": "Supervisore", + "hassio": "Supervisor", "installation_type": "Tipo di installazione", - "os_name": "Famiglia del sistema operativo", - "os_version": "Versione del sistema operativo", + "os_name": "Famiglia del Sistema Operativo", + "os_version": "Versione del Sistema Operativo", "python_version": "Versione Python", "timezone": "Fuso orario", "user": "Utente", diff --git a/homeassistant/components/homeassistant/trigger.py b/homeassistant/components/homeassistant/trigger.py index ca77747cd96..42b0e30af1d 100644 --- a/homeassistant/components/homeassistant/trigger.py +++ b/homeassistant/components/homeassistant/trigger.py @@ -1,14 +1,25 @@ """Home Assistant trigger dispatcher.""" import importlib +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) +from homeassistant.components.device_automation.trigger import ( + DeviceAutomationTriggerProtocol, +) from homeassistant.const import CONF_PLATFORM +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers.typing import ConfigType -def _get_trigger_platform(config): +def _get_trigger_platform(config: ConfigType) -> DeviceAutomationTriggerProtocol: return importlib.import_module(f"..triggers.{config[CONF_PLATFORM]}", __name__) -async def async_validate_trigger_config(hass, config): +async def async_validate_trigger_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: """Validate config.""" platform = _get_trigger_platform(config) if hasattr(platform, "async_validate_trigger_config"): @@ -17,7 +28,12 @@ async def async_validate_trigger_config(hass, config): return platform.TRIGGER_SCHEMA(config) -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Attach trigger of specified platform.""" platform = _get_trigger_platform(config) return await platform.async_attach_trigger(hass, config, action, automation_info) diff --git a/homeassistant/components/homeassistant/triggers/homeassistant.py b/homeassistant/components/homeassistant/triggers/homeassistant.py index 6f2ec75e313..c9a5a780e88 100644 --- a/homeassistant/components/homeassistant/triggers/homeassistant.py +++ b/homeassistant/components/homeassistant/triggers/homeassistant.py @@ -1,9 +1,14 @@ """Offer Home Assistant core automation rules.""" import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import CONF_EVENT, CONF_PLATFORM, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import HassJob, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.typing import ConfigType # mypy: allow-untyped-defs @@ -18,7 +23,12 @@ TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( ) -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for events based on configuration.""" trigger_data = automation_info["trigger_data"] event = config.get(CONF_EVENT) diff --git a/homeassistant/components/homeassistant/triggers/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py index 2d73f38d110..934cc99993a 100644 --- a/homeassistant/components/homeassistant/triggers/numeric_state.py +++ b/homeassistant/components/homeassistant/triggers/numeric_state.py @@ -4,6 +4,10 @@ import logging import voluptuous as vol from homeassistant import exceptions +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import ( CONF_ABOVE, CONF_ATTRIBUTE, @@ -81,10 +85,15 @@ async def async_validate_trigger_config( async def async_attach_trigger( - hass, config, action, automation_info, *, platform_type="numeric_state" + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, + *, + platform_type: str = "numeric_state", ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" - entity_ids = config.get(CONF_ENTITY_ID) + entity_ids: list[str] = config[CONF_ENTITY_ID] below = config.get(CONF_BELOW) above = config.get(CONF_ABOVE) time_delta = config.get(CONF_FOR) diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index e6a4b90dbe8..4f1e823c90f 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -7,6 +7,10 @@ import logging import voluptuous as vol from homeassistant import exceptions +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import CONF_ATTRIBUTE, CONF_FOR, CONF_PLATFORM, MATCH_ALL from homeassistant.core import ( CALLBACK_TYPE, @@ -92,9 +96,9 @@ async def async_validate_trigger_config( async def async_attach_trigger( hass: HomeAssistant, - config, - action, - automation_info, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, *, platform_type: str = "state", ) -> CALLBACK_TYPE: diff --git a/homeassistant/components/homeassistant/triggers/time.py b/homeassistant/components/homeassistant/triggers/time.py index 49a42d3843d..619ef0e207c 100644 --- a/homeassistant/components/homeassistant/triggers/time.py +++ b/homeassistant/components/homeassistant/triggers/time.py @@ -5,6 +5,10 @@ from functools import partial import voluptuous as vol from homeassistant.components import sensor +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import ( ATTR_DEVICE_CLASS, CONF_AT, @@ -12,13 +16,14 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HassJob, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import ( async_track_point_in_time, async_track_state_change_event, async_track_time_change, ) +from homeassistant.helpers.typing import ConfigType import homeassistant.util.dt as dt_util # mypy: allow-untyped-defs, no-check-untyped-defs @@ -37,10 +42,15 @@ TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( ) -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" trigger_data = automation_info["trigger_data"] - entities = {} + entities: dict[str, CALLBACK_TYPE] = {} removes = [] job = HassJob(action) diff --git a/homeassistant/components/homeassistant/triggers/time_pattern.py b/homeassistant/components/homeassistant/triggers/time_pattern.py index 000d73b6cd1..7ee1d218171 100644 --- a/homeassistant/components/homeassistant/triggers/time_pattern.py +++ b/homeassistant/components/homeassistant/triggers/time_pattern.py @@ -1,10 +1,15 @@ """Offer time listening automation rules.""" import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import CONF_PLATFORM -from homeassistant.core import HassJob, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import async_track_time_change +from homeassistant.helpers.typing import ConfigType # mypy: allow-untyped-defs, no-check-untyped-defs @@ -55,7 +60,12 @@ TRIGGER_SCHEMA = vol.All( ) -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" trigger_data = automation_info["trigger_data"] hours = config.get(CONF_HOURS) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 48f8f336fe7..5d7e647e466 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -46,7 +46,7 @@ from homeassistant.const import ( ) from homeassistant.core import CoreState, HomeAssistant, ServiceCall, State, callback from homeassistant.exceptions import HomeAssistantError, Unauthorized -from homeassistant.helpers import device_registry, entity_registry +from homeassistant.helpers import device_registry, entity_registry, instance_id import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import ( BASE_FILTER_SCHEMA, @@ -54,7 +54,10 @@ from homeassistant.helpers.entityfilter import ( EntityFilter, ) from homeassistant.helpers.reload import async_integration_yaml_config -from homeassistant.helpers.service import async_extract_referenced_entity_ids +from homeassistant.helpers.service import ( + async_extract_referenced_entity_ids, + async_register_admin_service, +) from homeassistant.helpers.typing import ConfigType from homeassistant.loader import IntegrationNotFound, async_get_integration @@ -461,7 +464,8 @@ def _async_register_events_and_services(hass: HomeAssistant) -> None: await asyncio.gather(*reload_tasks) - hass.helpers.service.async_register_admin_service( + async_register_admin_service( + hass, DOMAIN, SERVICE_RELOAD, _handle_homekit_reload, @@ -722,7 +726,7 @@ class HomeKit: return self.status = STATUS_WAIT async_zc_instance = await zeroconf.async_get_async_instance(self.hass) - uuid = await self.hass.helpers.instance_id.async_get() + uuid = await instance_id.async_get(self.hass) await self.hass.async_add_executor_job(self.setup, async_zc_instance, uuid) self.aid_storage = AccessoryAidStorage(self.hass, self._entry_id) await self.aid_storage.async_initialize() diff --git a/homeassistant/components/homekit/aidmanager.py b/homeassistant/components/homekit/aidmanager.py index ddba9d02bcd..27bd6234d46 100644 --- a/homeassistant/components/homekit/aidmanager.py +++ b/homeassistant/components/homekit/aidmanager.py @@ -17,7 +17,7 @@ import random from fnvhash import fnv1a_32 from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.entity_registry import EntityRegistry, RegistryEntry +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.storage import Store from .util import get_aid_storage_filename_for_entry_id @@ -34,7 +34,7 @@ AID_MIN = 2 AID_MAX = 18446744073709551615 -def get_system_unique_id(entity: RegistryEntry) -> str: +def get_system_unique_id(entity: er.RegistryEntry) -> str: """Determine the system wide unique_id for an entity.""" return f"{entity.platform}.{entity.domain}.{entity.unique_id}" @@ -74,13 +74,11 @@ class AccessoryAidStorage: self.allocated_aids: set[int] = set() self._entry_id = entry_id self.store: Store | None = None - self._entity_registry: EntityRegistry | None = None + self._entity_registry: er.EntityRegistry | None = None async def async_initialize(self) -> None: """Load the latest AID data.""" - self._entity_registry = ( - await self.hass.helpers.entity_registry.async_get_registry() - ) + self._entity_registry = er.async_get(self.hass) aidstore = get_aid_storage_filename_for_entry_id(self._entry_id) self.store = Store(self.hass, AID_MANAGER_STORAGE_VERSION, aidstore) diff --git a/homeassistant/components/homekit/logbook.py b/homeassistant/components/homekit/logbook.py index b6805f8cf6c..a513b31b232 100644 --- a/homeassistant/components/homekit/logbook.py +++ b/homeassistant/components/homekit/logbook.py @@ -2,6 +2,11 @@ from collections.abc import Callable from typing import Any +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_ENTITY_ID, + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) from homeassistant.const import ATTR_ENTITY_ID, ATTR_SERVICE from homeassistant.core import Event, HomeAssistant, callback @@ -26,9 +31,9 @@ def async_describe_events( message = f"send command {data[ATTR_SERVICE]}{value_msg} for {data[ATTR_DISPLAY_NAME]}" return { - "name": "HomeKit", - "message": message, - "entity_id": entity_id, + LOGBOOK_ENTRY_NAME: "HomeKit", + LOGBOOK_ENTRY_MESSAGE: message, + LOGBOOK_ENTRY_ENTITY_ID: entity_id, } async_describe_event(DOMAIN, EVENT_HOMEKIT_CHANGED, async_describe_logbook_event) diff --git a/homeassistant/components/homekit/strings.json b/homeassistant/components/homekit/strings.json index d2b6951a698..2c0fad0290e 100644 --- a/homeassistant/components/homekit/strings.json +++ b/homeassistant/components/homekit/strings.json @@ -44,8 +44,7 @@ }, "advanced": { "data": { - "devices": "Devices (Triggers)", - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)" + "devices": "Devices (Triggers)" }, "description": "Programmable switches are created for each selected device. When a device trigger fires, HomeKit can be configured to run an automation or scene.", "title": "Advanced Configuration" diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json index f1036cceb25..16843fa1741 100644 --- a/homeassistant/components/homekit/translations/ca.json +++ b/homeassistant/components/homekit/translations/ca.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Inici autom\u00e0tic (desactiva-ho si crides el servei homekit.start manualment)", "devices": "Dispositius (disparadors)" }, "description": "Els interruptors programables es creen per cada dispositiu seleccionat. HomeKit pot ser programat per a que executi una automatitzaci\u00f3 o escena quan un dispositiu es dispari.", diff --git a/homeassistant/components/homekit/translations/de.json b/homeassistant/components/homekit/translations/de.json index d9c07e4cf21..d145d826337 100644 --- a/homeassistant/components/homekit/translations/de.json +++ b/homeassistant/components/homekit/translations/de.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Autostart (deaktivieren, wenn du den homekit.start-Dienst manuell aufrufst)", "devices": "Ger\u00e4te (Trigger)" }, "description": "F\u00fcr jedes ausgew\u00e4hlte Ger\u00e4t werden programmierbare Schalter erstellt. Wenn ein Ger\u00e4teausl\u00f6ser ausgel\u00f6st wird, kann HomeKit so konfiguriert werden, dass eine Automatisierung oder Szene ausgef\u00fchrt wird.", diff --git a/homeassistant/components/homekit/translations/el.json b/homeassistant/components/homekit/translations/el.json index 370617bd5fd..1031fcca85d 100644 --- a/homeassistant/components/homekit/translations/el.json +++ b/homeassistant/components/homekit/translations/el.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 (\u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03bd \u03ba\u03b1\u03bb\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 homekit.start \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b1)", "devices": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 (\u0395\u03bd\u03b1\u03cd\u03c3\u03bc\u03b1\u03c4\u03b1)" }, "description": "\u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03b9 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u038c\u03c4\u03b1\u03bd \u03c0\u03c5\u03c1\u03bf\u03b4\u03bf\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03b9\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2, \u03c4\u03bf HomeKit \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03ba\u03c4\u03ad\u03bb\u03b5\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03b1\u03c5\u03c4\u03bf\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03bf\u03cd \u03ae \u03bc\u03b9\u03b1\u03c2 \u03c3\u03ba\u03b7\u03bd\u03ae\u03c2.", diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index 14585b60f1e..f0c26868e7d 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", "devices": "Devices (Triggers)" }, "description": "Programmable switches are created for each selected device. When a device trigger fires, HomeKit can be configured to run an automation or scene.", diff --git a/homeassistant/components/homekit/translations/es-419.json b/homeassistant/components/homekit/translations/es-419.json index 459832cac80..55cbafcb647 100644 --- a/homeassistant/components/homekit/translations/es-419.json +++ b/homeassistant/components/homekit/translations/es-419.json @@ -20,9 +20,6 @@ "options": { "step": { "advanced": { - "data": { - "auto_start": "Inicio autom\u00e1tico (deshabilitar si se usa Z-Wave u otro sistema de inicio diferido)" - }, "description": "Esta configuraci\u00f3n solo necesita ser ajustada si el puente HomeKit no es funcional.", "title": "Configuraci\u00f3n avanzada" }, diff --git a/homeassistant/components/homekit/translations/es.json b/homeassistant/components/homekit/translations/es.json index ef60610a8a1..9aa71faef12 100644 --- a/homeassistant/components/homekit/translations/es.json +++ b/homeassistant/components/homekit/translations/es.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "port_name_in_use": "Ya est\u00e1 configurada una pasarela con el mismo nombre o puerto." + "port_name_in_use": "Ya existe un enlace o accesorio configurado con ese nombre o puerto." }, "step": { "pairing": { "description": "Para completar el emparejamiento, sigue las instrucciones en \"Notificaciones\" en \"Emparejamiento HomeKit\".", - "title": "Vincular pasarela Homekit" + "title": "Vinculaci\u00f3n HomeKit" }, "user": { "data": { @@ -19,9 +19,13 @@ }, "options": { "step": { + "accessory": { + "data": { + "entities": "Entidad" + } + }, "advanced": { "data": { - "auto_start": "Arranque autom\u00e1tico (desactivado si se utiliza Z-Wave u otro sistema de arranque retardado)", "devices": "Dispositivos (disparadores)" }, "description": "Esta configuraci\u00f3n solo necesita ser ajustada si el puente HomeKit no es funcional.", @@ -36,16 +40,23 @@ "title": "Seleccione el c\u00f3dec de video de la c\u00e1mara." }, "exclude": { + "data": { + "entities": "Entidades" + }, + "title": "Selecciona las entidades a excluir" + }, + "include": { "data": { "entities": "Entidades" } }, "init": { "data": { - "mode": "Modo" + "domains": "Dominios a incluir", + "mode": "Mode de HomeKit" }, "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.", - "title": "Seleccione los dominios que desea establecer un puente." + "title": "Selecciona el modo y dominios." }, "yaml": { "description": "Esta entrada se controla a trav\u00e9s de YAML", diff --git a/homeassistant/components/homekit/translations/et.json b/homeassistant/components/homekit/translations/et.json index 671dede46e0..350812600e7 100644 --- a/homeassistant/components/homekit/translations/et.json +++ b/homeassistant/components/homekit/translations/et.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Autostart (keela kui kasutad homekit.start teenust k\u00e4sitsi)", "devices": "Seadmed (p\u00e4\u00e4stikud)" }, "description": "Iga valitud seadme jaoks luuakse programmeeritavad l\u00fclitid. Seadme p\u00e4\u00e4stiku k\u00e4ivitamisel saab HomeKiti seadistada automaatiseeringu v\u00f5i stseeni k\u00e4ivitamiseks.", diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json index e4850893bd3..57294ad4315 100644 --- a/homeassistant/components/homekit/translations/fr.json +++ b/homeassistant/components/homekit/translations/fr.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "D\u00e9marrage automatique (d\u00e9sactiver si vous utilisez Z-Wave ou un autre syst\u00e8me de d\u00e9marrage diff\u00e9r\u00e9)", "devices": "Appareils (d\u00e9clencheurs)" }, "description": "Ces param\u00e8tres ne doivent \u00eatre ajust\u00e9s que si le pont HomeKit n'est pas fonctionnel.", diff --git a/homeassistant/components/homekit/translations/hu.json b/homeassistant/components/homekit/translations/hu.json index a1c64f6fac3..eefbf2ad381 100644 --- a/homeassistant/components/homekit/translations/hu.json +++ b/homeassistant/components/homekit/translations/hu.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Automatikus ind\u00edt\u00e1s (tiltsa le, ha manu\u00e1lisan h\u00edvja a homekit.start szolg\u00e1ltat\u00e1st)", "devices": "Eszk\u00f6z\u00f6k (triggerek)" }, "description": "Programozhat\u00f3 kapcsol\u00f3k j\u00f6nnek l\u00e9tre minden kiv\u00e1lasztott eszk\u00f6zh\u00f6z. Amikor egy eszk\u00f6z esem\u00e9nyt ind\u00edt el, a HomeKit be\u00e1ll\u00edthat\u00f3 \u00fagy, hogy egy automatizmus vagy egy jelenet induljon el.", diff --git a/homeassistant/components/homekit/translations/id.json b/homeassistant/components/homekit/translations/id.json index 8293ad9d72c..e3889ce031f 100644 --- a/homeassistant/components/homekit/translations/id.json +++ b/homeassistant/components/homekit/translations/id.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Mulai otomatis (nonaktifkan jika Anda memanggil layanan homekit.start secara manual)", "devices": "Perangkat (Pemicu)" }, "description": "Sakelar yang dapat diprogram dibuat untuk setiap perangkat yang dipilih. Saat pemicu perangkat aktif, HomeKit dapat dikonfigurasi untuk menjalankan otomatisasi atau skenario.", diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json index 7acec1af5c3..4086db071c2 100644 --- a/homeassistant/components/homekit/translations/it.json +++ b/homeassistant/components/homekit/translations/it.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Avvio automatico (disabilita se stai chiamando manualmente il servizio homekit.start)", "devices": "Dispositivi (Attivatori)" }, "description": "Gli interruttori programmabili vengono creati per ogni dispositivo selezionato. Quando si attiva un trigger del dispositivo, HomeKit pu\u00f2 essere configurato per eseguire un'automazione o una scena.", diff --git a/homeassistant/components/homekit/translations/ja.json b/homeassistant/components/homekit/translations/ja.json index 6bcad37ad61..a2364e44c78 100644 --- a/homeassistant/components/homekit/translations/ja.json +++ b/homeassistant/components/homekit/translations/ja.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "\u81ea\u52d5\u8d77\u52d5(homekit.start\u30b5\u30fc\u30d3\u30b9\u3092\u624b\u52d5\u3067\u547c\u3073\u51fa\u3059\u5834\u5408\u306f\u7121\u52b9\u306b\u3059\u308b)", "devices": "\u30c7\u30d0\u30a4\u30b9(\u30c8\u30ea\u30ac\u30fc)" }, "description": "\u9078\u629e\u3057\u305f\u30c7\u30d0\u30a4\u30b9\u3054\u3068\u306b\u3001\u30d7\u30ed\u30b0\u30e9\u30e0\u53ef\u80fd\u306a\u30b9\u30a4\u30c3\u30c1\u304c\u4f5c\u6210\u3055\u308c\u307e\u3059\u3002\u30c7\u30d0\u30a4\u30b9\u306e\u30c8\u30ea\u30ac\u30fc\u304c\u767a\u751f\u3059\u308b\u3068\u3001HomeKit\u306f\u30aa\u30fc\u30c8\u30e1\u30fc\u30b7\u30e7\u30f3\u3084\u30b7\u30fc\u30f3\u3092\u5b9f\u884c\u3059\u308b\u3088\u3046\u306b\u69cb\u6210\u3067\u304d\u307e\u3059\u3002", diff --git a/homeassistant/components/homekit/translations/ko.json b/homeassistant/components/homekit/translations/ko.json index cd8cbb81943..7db067c5803 100644 --- a/homeassistant/components/homekit/translations/ko.json +++ b/homeassistant/components/homekit/translations/ko.json @@ -20,9 +20,6 @@ "options": { "step": { "advanced": { - "data": { - "auto_start": "\uc790\ub3d9 \uc2dc\uc791 (homekit.start \uc11c\ube44\uc2a4\ub97c \uc218\ub3d9\uc73c\ub85c \ud638\ucd9c\ud558\ub824\uba74 \ube44\ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)" - }, "description": "\uc774 \uc124\uc815\uc740 HomeKit\uac00 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uc5d0\ub9cc \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "\uace0\uae09 \uad6c\uc131" }, diff --git a/homeassistant/components/homekit/translations/lb.json b/homeassistant/components/homekit/translations/lb.json index 409d860940d..367472630bc 100644 --- a/homeassistant/components/homekit/translations/lb.json +++ b/homeassistant/components/homekit/translations/lb.json @@ -20,9 +20,6 @@ "options": { "step": { "advanced": { - "data": { - "auto_start": "Autostart (d\u00e9aktiv\u00e9ier falls Z-Wave oder een aanere verz\u00f6gerte Start System benotzt g\u00ebtt)" - }, "description": "D\u00ebs Astellungen brauche n\u00ebmmen ajust\u00e9iert ze ginn falls HomeKit net funktion\u00e9iert.", "title": "Erweidert Konfiguratioun" }, diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index 7f8ab1e2ff8..6ad97c5ecf0 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Autostart (deactiveer als je de homekit.start service handmatig aanroept)", "devices": "Apparaten (triggers)" }, "description": "Voor elk geselecteerd apparaat worden programmeerbare schakelaars gemaakt. Wanneer een apparaattrigger wordt geactiveerd, kan HomeKit worden geconfigureerd om een automatisering of sc\u00e8ne uit te voeren.", diff --git a/homeassistant/components/homekit/translations/no.json b/homeassistant/components/homekit/translations/no.json index 29f775853fd..463b52bc27b 100644 --- a/homeassistant/components/homekit/translations/no.json +++ b/homeassistant/components/homekit/translations/no.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Autostart (deaktiver hvis du ringer til homekit.start-tjenesten manuelt)", "devices": "Enheter (utl\u00f8sere)" }, "description": "Programmerbare brytere opprettes for hver valgt enhet. N\u00e5r en enhetstrigger utl\u00f8ses, kan HomeKit konfigureres til \u00e5 kj\u00f8re en automatisering eller scene.", diff --git a/homeassistant/components/homekit/translations/pl.json b/homeassistant/components/homekit/translations/pl.json index 73ed271d636..9080a03ecc0 100644 --- a/homeassistant/components/homekit/translations/pl.json +++ b/homeassistant/components/homekit/translations/pl.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Automatyczne uruchomienie (wy\u0142\u0105cz, je\u015bli r\u0119cznie uruchamiasz us\u0142ug\u0119 homekit.start)", "devices": "Urz\u0105dzenia (Wyzwalacze)" }, "description": "Dla ka\u017cdego wybranego urz\u0105dzenia stworzony zostanie programowalny prze\u0142\u0105cznik. Po uruchomieniu wyzwalacza urz\u0105dzenia, HomeKit mo\u017cna skonfigurowa\u0107 do uruchamiania automatyzacji lub sceny.", diff --git a/homeassistant/components/homekit/translations/pt-BR.json b/homeassistant/components/homekit/translations/pt-BR.json index 5fcc2748d0c..5296facd831 100644 --- a/homeassistant/components/homekit/translations/pt-BR.json +++ b/homeassistant/components/homekit/translations/pt-BR.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Autostart (desabilite se voc\u00ea estiver chamando o servi\u00e7o homekit.start manualmente)", "devices": "Dispositivos (gatilhos)" }, "description": "Os interruptores program\u00e1veis s\u00e3o criados para cada dispositivo selecionado. Quando um dispositivo dispara, o HomeKit pode ser configurado para executar uma automa\u00e7\u00e3o ou cena.", diff --git a/homeassistant/components/homekit/translations/pt.json b/homeassistant/components/homekit/translations/pt.json index 3df84b70866..f122a97b19c 100644 --- a/homeassistant/components/homekit/translations/pt.json +++ b/homeassistant/components/homekit/translations/pt.json @@ -15,9 +15,6 @@ "options": { "step": { "advanced": { - "data": { - "auto_start": "[%key:component::homekit::config::step::user::data::auto_start%]" - }, "title": "Configura\u00e7\u00e3o avan\u00e7ada" }, "cameras": { diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index 81dd9afa302..1f10b632fba 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u0435, \u0435\u0441\u043b\u0438 \u0412\u044b \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0435 \u0441\u043b\u0443\u0436\u0431\u0443 homekit.start)", "devices": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 (\u0442\u0440\u0438\u0433\u0433\u0435\u0440\u044b)" }, "description": "\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. HomeKit \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u043b\u0438 \u0441\u0446\u0435\u043d\u044b, \u043a\u043e\u0433\u0434\u0430 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0442\u0440\u0438\u0433\u0433\u0435\u0440 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", diff --git a/homeassistant/components/homekit/translations/sl.json b/homeassistant/components/homekit/translations/sl.json index 333ec699253..0428bebc85f 100644 --- a/homeassistant/components/homekit/translations/sl.json +++ b/homeassistant/components/homekit/translations/sl.json @@ -20,9 +20,6 @@ "options": { "step": { "advanced": { - "data": { - "auto_start": "Samodejni zagon (onemogo\u010dite, \u010de uporabljate Z-wave ali kakteri drug sistem z zakasnjenim zagonom)" - }, "description": "Te nastavitve je treba prilagoditi le, \u010de most HomeKit ni funkcionalen.", "title": "Napredna konfiguracija" }, diff --git a/homeassistant/components/homekit/translations/tr.json b/homeassistant/components/homekit/translations/tr.json index 52683302a34..f6ac036ff84 100644 --- a/homeassistant/components/homekit/translations/tr.json +++ b/homeassistant/components/homekit/translations/tr.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "Otomatik ba\u015flatma (homekit.start hizmetini manuel olarak ar\u0131yorsan\u0131z devre d\u0131\u015f\u0131 b\u0131rak\u0131n)", "devices": "Cihazlar (Tetikleyiciler)" }, "description": "Se\u00e7ilen her cihaz i\u00e7in programlanabilir anahtarlar olu\u015fturulur. Bir cihaz tetikleyicisi tetiklendi\u011finde, HomeKit bir otomasyon veya sahne \u00e7al\u0131\u015ft\u0131racak \u015fekilde yap\u0131land\u0131r\u0131labilir.", diff --git a/homeassistant/components/homekit/translations/uk.json b/homeassistant/components/homekit/translations/uk.json index 52da83cca73..43fe9b25a9d 100644 --- a/homeassistant/components/homekit/translations/uk.json +++ b/homeassistant/components/homekit/translations/uk.json @@ -20,9 +20,6 @@ "options": { "step": { "advanced": { - "data": { - "auto_start": "[%key:component::homekit::config::step::user::data::auto_start%]" - }, "description": "\u0426\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0456, \u043b\u0438\u0448\u0435 \u044f\u043a\u0449\u043e HomeKit \u043d\u0435 \u043f\u0440\u0430\u0446\u044e\u0454.", "title": "\u0420\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" }, diff --git a/homeassistant/components/homekit/translations/zh-Hans.json b/homeassistant/components/homekit/translations/zh-Hans.json index 852979fb12a..4415fcdcd38 100644 --- a/homeassistant/components/homekit/translations/zh-Hans.json +++ b/homeassistant/components/homekit/translations/zh-Hans.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "\u81ea\u52a8\u542f\u52a8\uff08\u5982\u679c\u60a8\u624b\u52a8\u8c03\u7528 homekit.start \u670d\u52a1\uff0c\u8bf7\u7981\u7528\u6b64\u9879\uff09", "devices": "\u8bbe\u5907 (\u89e6\u53d1\u5668)" }, "description": "\u5c06\u4e3a\u6bcf\u4e2a\u9009\u62e9\u7684\u8bbe\u5907\u521b\u5efa\u4e00\u4e2a\u53ef\u7f16\u7a0b\u5f00\u5173\u914d\u4ef6\u3002\u53ef\u4ee5\u5728 HomeKit \u4e2d\u914d\u7f6e\u8fd9\u4e9b\u914d\u4ef6\uff0c\u5f53\u8bbe\u5907\u89e6\u53d1\u65f6\uff0c\u6267\u884c\u6307\u5b9a\u7684\u81ea\u52a8\u5316\u6216\u573a\u666f\u3002", diff --git a/homeassistant/components/homekit/translations/zh-Hant.json b/homeassistant/components/homekit/translations/zh-Hant.json index 3ad28a22f45..54c0bb29450 100644 --- a/homeassistant/components/homekit/translations/zh-Hant.json +++ b/homeassistant/components/homekit/translations/zh-Hant.json @@ -27,7 +27,6 @@ }, "advanced": { "data": { - "auto_start": "\u81ea\u52d5\u555f\u52d5\uff08\u5047\u5982\u624b\u52d5\u4f7f\u7528 homekit.start \u670d\u52d9\u6642\u3001\u8acb\u95dc\u9589\uff09", "devices": "\u88dd\u7f6e\uff08\u89f8\u767c\u5668\uff09" }, "description": "\u70ba\u6240\u9078\u64c7\u7684\u88dd\u7f6e\u65b0\u589e\u53ef\u7a0b\u5f0f\u958b\u95dc\u3002\u7576\u88dd\u7f6e\u89f8\u767c\u5668\u89f8\u767c\u6642\u3001Homekit \u53ef\u8a2d\u5b9a\u70ba\u57f7\u884c\u81ea\u52d5\u5316\u6216\u5834\u666f\u3002", diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index c3bdfcc42ae..b4bd66aa626 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -14,10 +14,7 @@ from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.core import callback from homeassistant.data_entry_flow import AbortFlow, FlowResult -from homeassistant.helpers.device_registry import ( - CONNECTION_NETWORK_MAC, - async_get_registry as async_get_device_registry, -) +from homeassistant.helpers import device_registry as dr from .const import DOMAIN, KNOWN_DEVICES from .utils import async_get_controller @@ -163,9 +160,9 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def _hkid_is_homekit(self, hkid): """Determine if the device is a homekit bridge or accessory.""" - dev_reg = await async_get_device_registry(self.hass) + dev_reg = dr.async_get(self.hass) device = dev_reg.async_get_device( - identifiers=set(), connections={(CONNECTION_NETWORK_MAC, hkid)} + identifiers=set(), connections={(dr.CONNECTION_NETWORK_MAC, hkid)} ) if device is None: diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 9642d5d3bc5..7a85e234807 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -19,6 +19,7 @@ from aiohomekit.model.services import Service from homeassistant.const import ATTR_VIA_DEVICE from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.event import async_track_time_interval @@ -162,7 +163,7 @@ class HKDevice: if self.available == available: return self.available = available - self.hass.helpers.dispatcher.async_dispatcher_send(self.signal_state_updated) + async_dispatcher_send(self.hass, self.signal_state_updated) async def async_setup(self) -> bool: """Prepare to use a paired HomeKit device in Home Assistant.""" @@ -568,7 +569,7 @@ class HKDevice: # For now we update both self.entity_map.process_changes(new_values_dict) - self.hass.helpers.dispatcher.async_dispatcher_send(self.signal_state_updated) + async_dispatcher_send(self.hass, self.signal_state_updated) async def get_characteristics(self, *args, **kwargs) -> dict[str, Any]: """Read latest state from homekit accessory.""" diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index aa2765d9be5..dcac7238c8e 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -1,6 +1,7 @@ """Provides device automations for homekit devices.""" from __future__ import annotations +from collections.abc import Generator from typing import TYPE_CHECKING, Any from aiohomekit.model.characteristics import CharacteristicsTypes @@ -63,7 +64,7 @@ class TriggerSource: self._hass = connection.hass self._connection = connection self._aid = aid - self._triggers = {} + self._triggers: dict[tuple[str, str], dict[str, Any]] = {} for trigger in triggers: self._triggers[(trigger["type"], trigger["subtype"])] = trigger self._callbacks = {} @@ -73,7 +74,7 @@ class TriggerSource: for event_handler in self._callbacks.get(iid, []): event_handler(value) - def async_get_triggers(self): + def async_get_triggers(self) -> Generator[tuple[str, str], None, None]: """List device triggers for homekit devices.""" yield from self._triggers @@ -240,13 +241,13 @@ def async_fire_triggers(conn: HKDevice, events: dict[tuple[int, int], Any]): async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for homekit devices.""" if device_id not in hass.data.get(TRIGGERS, {}): return [] - device = hass.data[TRIGGERS][device_id] + device: TriggerSource = hass.data[TRIGGERS][device_id] return [ { diff --git a/homeassistant/components/homekit_controller/translations/es.json b/homeassistant/components/homekit_controller/translations/es.json index 52b295ecf21..a528d66ea06 100644 --- a/homeassistant/components/homekit_controller/translations/es.json +++ b/homeassistant/components/homekit_controller/translations/es.json @@ -18,7 +18,7 @@ "unable_to_pair": "No se ha podido emparejar, por favor int\u00e9ntelo de nuevo.", "unknown_error": "El dispositivo report\u00f3 un error desconocido. La vinculaci\u00f3n ha fallado." }, - "flow_title": "Accesorio HomeKit: {name}", + "flow_title": "{name}", "step": { "busy_error": { "description": "Interrumpe el emparejamiento en todos los controladores o intenta reiniciar el dispositivo y luego contin\u00faa con el emparejamiento.", @@ -69,5 +69,5 @@ "single_press": "\"{subtype}\" pulsado" } }, - "title": "Accesorio HomeKit" + "title": "Controlador HomeKit" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/ko.json b/homeassistant/components/homekit_controller/translations/ko.json index c28573ee6ac..fd40288a7e3 100644 --- a/homeassistant/components/homekit_controller/translations/ko.json +++ b/homeassistant/components/homekit_controller/translations/ko.json @@ -12,6 +12,7 @@ }, "error": { "authentication_error": "HomeKit \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud655\uc778 \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "insecure_setup_code": "\uc694\uccad\ud55c \uc124\uc815 \ucf54\ub4dc\ub294 \uc548\uc804\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc774 \uc561\uc138\uc11c\ub9ac\ub294 \uae30\ubcf8 \ubcf4\uc548 \uc694\uad6c \uc0ac\ud56d\uc744 \ucda9\uc871\ud558\uc9c0 \ubabb\ud569\ub2c8\ub2e4.", "max_peers_error": "\uae30\uae30\uc5d0 \ube44\uc5b4\uc788\ub294 \ud398\uc5b4\ub9c1 \uc7a5\uc18c\uac00 \uc5c6\uc5b4 \ud398\uc5b4\ub9c1\uc744 \ucd94\uac00\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "pairing_failed": "\uc774 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1\uc744 \uc2dc\ub3c4\ud558\ub294 \uc911 \ucc98\ub9ac\ub418\uc9c0 \uc54a\uc740 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. \uc77c\uc2dc\uc801\uc778 \uc624\ub958\uc774\uac70\ub098 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uae30\uae30 \uc77c \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unable_to_pair": "\ud398\uc5b4\ub9c1 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", @@ -29,6 +30,7 @@ }, "pair": { "data": { + "allow_insecure_setup_codes": "\uc548\uc804\ud558\uc9c0 \uc54a\uc740 \uc124\uc815 \ucf54\ub4dc\uc640\uc758 \ud398\uc5b4\ub9c1\uc744 \ud5c8\uc6a9\ud569\ub2c8\ub2e4.", "pairing_code": "\ud398\uc5b4\ub9c1 \ucf54\ub4dc" }, "description": "HomeKit \ucee8\ud2b8\ub864\ub7ec\ub294 \ubcc4\ub3c4\uc758 HomeKit \ucee8\ud2b8\ub864\ub7ec \ub610\ub294 iCloud \uc5c6\uc774 \uc554\ud638\ud654\ub41c \ubcf4\uc548 \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uc5ec \ub85c\uceec \uc601\uc5ed \ub124\ud2b8\uc6cc\ud06c \uc0c1\uc5d0\uc11c {name}\uacfc(\uc640) \ud1b5\uc2e0\ud569\ub2c8\ub2e4. \uc774 \uc561\uc138\uc11c\ub9ac\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 HomeKit \ud398\uc5b4\ub9c1 \ucf54\ub4dc(XX-XX-XXX \ud615\uc2dd)\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc774 \ucf54\ub4dc\ub294 \uc77c\ubc18\uc801\uc73c\ub85c \uae30\uae30\ub098 \ud3ec\uc7a5 \ubc15\uc2a4\uc5d0 \ud45c\uc2dc\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.", diff --git a/homeassistant/components/homekit_controller/translations/nl.json b/homeassistant/components/homekit_controller/translations/nl.json index 4312fdc033c..fd966ad43fa 100644 --- a/homeassistant/components/homekit_controller/translations/nl.json +++ b/homeassistant/components/homekit_controller/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "accessory_not_found_error": "Kan geen koppeling toevoegen omdat het apparaat niet langer kan worden gevonden.", "already_configured": "Accessoire is al geconfigureerd met deze controller.", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "already_paired": "Dit accessoire is al gekoppeld aan een ander apparaat. Reset het accessoire en probeer het opnieuw.", "ignored_model": "HomeKit-ondersteuning voor dit model is geblokkeerd omdat er een meer functie volledige native integratie beschikbaar is.", "invalid_config_entry": "Dit apparaat geeft aan dat het gereed is om te koppelen, maar er is al een conflicterend configuratie-item voor in de Home Assistant dat eerst moet worden verwijderd.", diff --git a/homeassistant/components/homekit_controller/translations/select.es.json b/homeassistant/components/homekit_controller/translations/select.es.json new file mode 100644 index 00000000000..0cbfbc71373 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/select.es.json @@ -0,0 +1,8 @@ +{ + "state": { + "homekit_controller__ecobee_mode": { + "away": "Afuera", + "sleep": "Durmiendo" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/sk.json b/homeassistant/components/homekit_controller/translations/sk.json index bee0999420f..8ebd0e1e08e 100644 --- a/homeassistant/components/homekit_controller/translations/sk.json +++ b/homeassistant/components/homekit_controller/translations/sk.json @@ -2,6 +2,11 @@ "config": { "abort": { "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" + }, + "step": { + "user": { + "title": "V\u00fdber zariadenia" + } } } } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/sv.json b/homeassistant/components/homekit_controller/translations/sv.json index 71425e0f208..348c0305e03 100644 --- a/homeassistant/components/homekit_controller/translations/sv.json +++ b/homeassistant/components/homekit_controller/translations/sv.json @@ -22,7 +22,7 @@ "data": { "pairing_code": "Parningskod" }, - "description": "Ange din HomeKit-parningskod (i formatet XXX-XX-XXX) f\u00f6r att anv\u00e4nda det h\u00e4r tillbeh\u00f6ret", + "description": "HomeKit Controller kommunicerar med {name} \u00f6ver det lokala n\u00e4tverket med hj\u00e4lp av en s\u00e4ker krypterad anslutning utan en separat HomeKit-kontroller eller iCloud. Ange din HomeKit-kopplingskod (i formatet XXX-XX-XXX) f\u00f6r att anv\u00e4nda detta tillbeh\u00f6r. Denna kod finns vanligtvis p\u00e5 sj\u00e4lva enheten eller i f\u00f6rpackningen.", "title": "Para HomeKit-tillbeh\u00f6r" }, "user": { diff --git a/homeassistant/components/homematic/entity.py b/homeassistant/components/homematic/entity.py index 1fe3799bbd9..fee68caf7ed 100644 --- a/homeassistant/components/homematic/entity.py +++ b/homeassistant/components/homematic/entity.py @@ -11,6 +11,7 @@ from pyhomematic.devicetypes.generic import HMGeneric from homeassistant.const import ATTR_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity, EntityDescription +from homeassistant.helpers.event import track_time_interval from .const import ( ATTR_ADDRESS, @@ -222,12 +223,10 @@ class HMHub(Entity): self._state = None # Load data - self.hass.helpers.event.track_time_interval(self._update_hub, SCAN_INTERVAL_HUB) + track_time_interval(self.hass, self._update_hub, SCAN_INTERVAL_HUB) self.hass.add_job(self._update_hub, None) - self.hass.helpers.event.track_time_interval( - self._update_variables, SCAN_INTERVAL_VARIABLES - ) + track_time_interval(self.hass, self._update_variables, SCAN_INTERVAL_VARIABLES) self.hass.add_job(self._update_variables, None) @property diff --git a/homeassistant/components/homematicip_cloud/translations/es.json b/homeassistant/components/homematicip_cloud/translations/es.json index 28a084a7ee9..454fa8f9f2a 100644 --- a/homeassistant/components/homematicip_cloud/translations/es.json +++ b/homeassistant/components/homematicip_cloud/translations/es.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El punto de acceso ya est\u00e1 configurado", - "connection_aborted": "No se pudo conectar al servidor HMIP", + "connection_aborted": "Fall\u00f3 la conexi\u00f3n", "unknown": "Se ha producido un error desconocido." }, "error": { diff --git a/homeassistant/components/homematicip_cloud/translations/nl.json b/homeassistant/components/homematicip_cloud/translations/nl.json index cb65dee7bd1..d5945293089 100644 --- a/homeassistant/components/homematicip_cloud/translations/nl.json +++ b/homeassistant/components/homematicip_cloud/translations/nl.json @@ -6,7 +6,7 @@ "unknown": "Onverwachte fout" }, "error": { - "invalid_sgtin_or_pin": "Ongeldige SGTIN of PIN-code, probeer het opnieuw.", + "invalid_sgtin_or_pin": "Ongeldige SGTIN of Pincode, probeer het opnieuw.", "press_the_button": "Druk op de blauwe knop.", "register_failed": "Kan niet registreren, gelieve opnieuw te proberen.", "timeout_button": "Blauwe knop druk op timeout, probeer het opnieuw." @@ -16,7 +16,7 @@ "data": { "hapid": "Accesspoint ID (SGTIN)", "name": "Naam (optioneel, gebruikt als naamvoorvoegsel voor alle apparaten)", - "pin": "PIN-code" + "pin": "Pincode" }, "title": "Kies HomematicIP accesspoint" }, diff --git a/homeassistant/components/homewizard/__init__.py b/homeassistant/components/homewizard/__init__.py index b50d87a940d..4a087988b61 100644 --- a/homeassistant/components/homewizard/__init__.py +++ b/homeassistant/components/homewizard/__init__.py @@ -1,14 +1,11 @@ """The Homewizard integration.""" import logging -from aiohwenergy import DisabledError - from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_IP_ADDRESS from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry as er -from homeassistant.helpers.update_coordinator import UpdateFailed from .const import DOMAIN, PLATFORMS from .coordinator import HWEnergyDeviceUpdateCoordinator as Coordinator @@ -69,16 +66,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Create coordinator coordinator = Coordinator(hass, entry.data[CONF_IP_ADDRESS]) try: - await coordinator.initialize_api() - - except DisabledError: - _LOGGER.error("API is disabled, enable API in HomeWizard Energy app") - return False - - except UpdateFailed as ex: - raise ConfigEntryNotReady from ex - - await coordinator.async_config_entry_first_refresh() + await coordinator.async_config_entry_first_refresh() + except ConfigEntryNotReady: + await coordinator.api.close() + raise # Finalize hass.data.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/homewizard/config_flow.py b/homeassistant/components/homewizard/config_flow.py index 6ab485f534f..df883baf3b1 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -4,9 +4,8 @@ from __future__ import annotations import logging from typing import Any -import aiohwenergy -from aiohwenergy.hwenergy import SUPPORTED_DEVICES -import async_timeout +from homewizard_energy import HomeWizardEnergy +from homewizard_energy.errors import DisabledError, UnsupportedError from voluptuous import Required, Schema from homeassistant import config_entries @@ -175,16 +174,19 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # Make connection with device # This is to test the connection and to get info for unique_id - energy_api = aiohwenergy.HomeWizardEnergy(ip_address) + energy_api = HomeWizardEnergy(ip_address) try: - with async_timeout.timeout(10): - await energy_api.initialize() + device = await energy_api.device() - except aiohwenergy.DisabledError as ex: + except DisabledError as ex: _LOGGER.error("API disabled, API must be enabled in the app") raise AbortFlow("api_not_enabled") from ex + except UnsupportedError as ex: + _LOGGER.error("API version unsuppored") + raise AbortFlow("unsupported_api_version") from ex + except Exception as ex: _LOGGER.exception( "Error connecting with Energy Device at %s", @@ -195,25 +197,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): finally: await energy_api.close() - if energy_api.device is None: - _LOGGER.error("Initialization failed") - raise AbortFlow("unknown_error") - - # Validate metadata - if energy_api.device.api_version != "v1": - raise AbortFlow("unsupported_api_version") - - if energy_api.device.product_type not in SUPPORTED_DEVICES: - _LOGGER.error( - "Device (%s) not supported by integration", - energy_api.device.product_type, - ) - raise AbortFlow("device_not_supported") - return { - CONF_PRODUCT_NAME: energy_api.device.product_name, - CONF_PRODUCT_TYPE: energy_api.device.product_type, - CONF_SERIAL: energy_api.device.serial, + CONF_PRODUCT_NAME: device.product_name, + CONF_PRODUCT_TYPE: device.product_type, + CONF_SERIAL: device.serial, } async def _async_set_and_check_unique_id(self, entry_info: dict[str, Any]) -> None: diff --git a/homeassistant/components/homewizard/const.py b/homeassistant/components/homewizard/const.py index 75c522a211e..c1c788d371b 100644 --- a/homeassistant/components/homewizard/const.py +++ b/homeassistant/components/homewizard/const.py @@ -5,10 +5,9 @@ from datetime import timedelta from typing import TypedDict # Set up. -from aiohwenergy.device import Device +from homewizard_energy.models import Data, Device, State from homeassistant.const import Platform -from homeassistant.helpers.typing import StateType DOMAIN = "homewizard" PLATFORMS = [Platform.SENSOR, Platform.SWITCH] @@ -29,4 +28,5 @@ class DeviceResponseEntry(TypedDict): """Dict describing a single response entry.""" device: Device - data: dict[str, StateType] + data: Data + state: State diff --git a/homeassistant/components/homewizard/coordinator.py b/homeassistant/components/homewizard/coordinator.py index 2cce88cbe36..e12edda63ae 100644 --- a/homeassistant/components/homewizard/coordinator.py +++ b/homeassistant/components/homewizard/coordinator.py @@ -1,11 +1,10 @@ """Update coordinator for HomeWizard.""" from __future__ import annotations -import asyncio import logging -import aiohwenergy -import async_timeout +from homewizard_energy import HomeWizardEnergy +from homewizard_energy.errors import DisabledError from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -19,7 +18,7 @@ _LOGGER = logging.getLogger(__name__) class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]): """Gather data for the energy device.""" - api: aiohwenergy.HomeWizardEnergy + api: HomeWizardEnergy def __init__( self, @@ -29,56 +28,20 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry] """Initialize Update Coordinator.""" super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) - - session = async_get_clientsession(hass) - self.api = aiohwenergy.HomeWizardEnergy(host, clientsession=session) + self.api = HomeWizardEnergy(host, clientsession=async_get_clientsession(hass)) async def _async_update_data(self) -> DeviceResponseEntry: """Fetch all device and sensor data from api.""" - async with async_timeout.timeout(10): - - if self.api.device is None: - await self.initialize_api() - - # Update all properties - try: - if not await self.api.update(): - raise UpdateFailed("Failed to communicate with device") - - except aiohwenergy.DisabledError as ex: - raise UpdateFailed( - "API disabled, API must be enabled in the app" - ) from ex - + # Update all properties + try: data: DeviceResponseEntry = { - "device": self.api.device, - "data": {}, + "device": await self.api.device(), + "data": await self.api.data(), + "state": await self.api.state(), } - for datapoint in self.api.data.available_datapoints: - data["data"][datapoint] = getattr(self.api.data, datapoint) + except DisabledError as ex: + raise UpdateFailed("API disabled, API must be enabled in the app") from ex return data - - async def initialize_api(self) -> aiohwenergy: - """Initialize API and validate connection.""" - - try: - await self.api.initialize() - - except (asyncio.TimeoutError, aiohwenergy.RequestError) as ex: - raise UpdateFailed( - f"Error connecting to the Energy device at {self.api.host}" - ) from ex - - except aiohwenergy.DisabledError as ex: - raise ex - - except aiohwenergy.AiohwenergyException as ex: - raise UpdateFailed("Unknown Energy API error occurred") from ex - - except Exception as ex: - raise UpdateFailed( - f"Unknown error connecting with Energy Device at {self.api.host}" - ) from ex diff --git a/homeassistant/components/homewizard/diagnostics.py b/homeassistant/components/homewizard/diagnostics.py index 3dd55933291..a97d2507098 100644 --- a/homeassistant/components/homewizard/diagnostics.py +++ b/homeassistant/components/homewizard/diagnostics.py @@ -1,6 +1,7 @@ """Diagnostics support for P1 Monitor.""" from __future__ import annotations +from dataclasses import asdict from typing import Any from homeassistant.components.diagnostics import async_redact_data @@ -21,10 +22,10 @@ async def async_get_config_entry_diagnostics( coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] meter_data = { - "device": coordinator.api.device.todict(), - "data": coordinator.api.data.todict(), - "state": coordinator.api.state.todict() - if coordinator.api.state is not None + "device": asdict(coordinator.data["device"]), + "data": asdict(coordinator.data["data"]), + "state": asdict(coordinator.data["state"]) + if coordinator.data["state"] is not None else None, } diff --git a/homeassistant/components/homewizard/manifest.json b/homeassistant/components/homewizard/manifest.json index 1bd856334b7..e426234b449 100644 --- a/homeassistant/components/homewizard/manifest.json +++ b/homeassistant/components/homewizard/manifest.json @@ -4,9 +4,9 @@ "documentation": "https://www.home-assistant.io/integrations/homewizard", "codeowners": ["@DCSBL"], "dependencies": [], - "requirements": ["aiohwenergy==0.8.0"], + "requirements": ["python-homewizard-energy==1.0.3"], "zeroconf": ["_hwenergy._tcp.local."], "config_flow": true, "iot_class": "local_polling", - "loggers": ["aiohwenergy"] + "loggers": ["homewizard_energy"] } diff --git a/homeassistant/components/homewizard/sensor.py b/homeassistant/components/homewizard/sensor.py index 34479dd6b0a..b7f759f6a0e 100644 --- a/homeassistant/components/homewizard/sensor.py +++ b/homeassistant/components/homewizard/sensor.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Final +from typing import Final, cast from homeassistant.components.sensor import ( SensorDeviceClass, @@ -129,12 +129,9 @@ async def async_setup_entry( coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] entities = [] - if coordinator.api.data is not None: + if coordinator.data["data"] is not None: for description in SENSORS: - if ( - description.key in coordinator.api.data.available_datapoints - and getattr(coordinator.api.data, description.key) is not None - ): + if getattr(coordinator.data["data"], description.key) is not None: entities.append(HWEnergySensor(coordinator, entry, description)) async_add_entities(entities) @@ -165,7 +162,7 @@ class HWEnergySensor(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SensorE "total_power_export_t1_kwh", "total_power_export_t2_kwh", ]: - if self.data["data"][self.data_type] == 0: + if self.native_value == 0: self._attr_entity_registry_enabled_default = False @property @@ -187,9 +184,9 @@ class HWEnergySensor(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SensorE @property def native_value(self) -> StateType: """Return state of meter.""" - return self.data["data"][self.data_type] + return cast(StateType, getattr(self.data["data"], self.data_type)) @property def available(self) -> bool: """Return availability of meter.""" - return super().available and self.data_type in self.data["data"] + return super().available and self.native_value is not None diff --git a/homeassistant/components/homewizard/switch.py b/homeassistant/components/homewizard/switch.py index 3c6b1a1c5dc..eb2e9c49afe 100644 --- a/homeassistant/components/homewizard/switch.py +++ b/homeassistant/components/homewizard/switch.py @@ -22,7 +22,7 @@ async def async_setup_entry( """Set up switches.""" coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - if coordinator.api.state: + if coordinator.data["state"]: async_add_entities( [ HWEnergyMainSwitchEntity(coordinator, entry), @@ -70,12 +70,12 @@ class HWEnergyMainSwitchEntity(HWEnergySwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" - await self.coordinator.api.state.set(power_on=True) + await self.coordinator.api.state_set(power_on=True) await self.coordinator.async_refresh() async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" - await self.coordinator.api.state.set(power_on=False) + await self.coordinator.api.state_set(power_on=False) await self.coordinator.async_refresh() @property @@ -85,12 +85,12 @@ class HWEnergyMainSwitchEntity(HWEnergySwitchEntity): This switch becomes unavailable when switch_lock is enabled. """ - return super().available and not self.coordinator.api.state.switch_lock + return super().available and not self.coordinator.data["state"].switch_lock @property def is_on(self) -> bool: """Return true if switch is on.""" - return bool(self.coordinator.api.state.power_on) + return bool(self.coordinator.data["state"].power_on) class HWEnergySwitchLockEntity(HWEnergySwitchEntity): @@ -115,15 +115,15 @@ class HWEnergySwitchLockEntity(HWEnergySwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn switch-lock on.""" - await self.coordinator.api.state.set(switch_lock=True) + await self.coordinator.api.state_set(switch_lock=True) await self.coordinator.async_refresh() async def async_turn_off(self, **kwargs: Any) -> None: """Turn switch-lock off.""" - await self.coordinator.api.state.set(switch_lock=False) + await self.coordinator.api.state_set(switch_lock=False) await self.coordinator.async_refresh() @property def is_on(self) -> bool: """Return true if switch is on.""" - return bool(self.coordinator.api.state.switch_lock) + return bool(self.coordinator.data["state"].switch_lock) diff --git a/homeassistant/components/homewizard/translations/es.json b/homeassistant/components/homewizard/translations/es.json index 92faffd15c8..898d37fed09 100644 --- a/homeassistant/components/homewizard/translations/es.json +++ b/homeassistant/components/homewizard/translations/es.json @@ -4,7 +4,7 @@ "already_configured": "El dispositivo ya est\u00e1 configurado", "api_not_enabled": "La API no est\u00e1 habilitada. Habilite la API en la aplicaci\u00f3n HomeWizard Energy en configuraci\u00f3n", "device_not_supported": "Este dispositivo no es compatible", - "invalid_discovery_parameters": "unsupported_api_version", + "invalid_discovery_parameters": "Versi\u00f3n de API no compatible detectada", "unknown_error": "Error inesperado" }, "step": { diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index 5c238d73e0b..11b899dc0f5 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -21,7 +21,10 @@ from homeassistant.components.climate.const import ( HVACAction, HVACMode, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( _LOGGER, @@ -70,12 +73,14 @@ HW_FAN_MODE_TO_HA = { PARALLEL_UPDATES = 1 -async def async_setup_entry(hass, config, async_add_entities, discovery_info=None): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up the Honeywell thermostat.""" - cool_away_temp = config.options.get(CONF_COOL_AWAY_TEMPERATURE) - heat_away_temp = config.options.get(CONF_HEAT_AWAY_TEMPERATURE) + cool_away_temp = entry.options.get(CONF_COOL_AWAY_TEMPERATURE) + heat_away_temp = entry.options.get(CONF_HEAT_AWAY_TEMPERATURE) - data = hass.data[DOMAIN][config.entry_id] + data = hass.data[DOMAIN][entry.entry_id] async_add_entities( [ diff --git a/homeassistant/components/honeywell/translations/es.json b/homeassistant/components/honeywell/translations/es.json index bdae26b8794..f30e9606a4d 100644 --- a/homeassistant/components/honeywell/translations/es.json +++ b/homeassistant/components/honeywell/translations/es.json @@ -12,5 +12,14 @@ "description": "Por favor, introduzca las credenciales utilizadas para iniciar sesi\u00f3n en mytotalconnectcomfort.com." } } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Temperatura fria, modo fuera" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/honeywell/translations/sk.json b/homeassistant/components/honeywell/translations/sk.json index 5ada995aa6e..1b1e671c054 100644 --- a/homeassistant/components/honeywell/translations/sk.json +++ b/homeassistant/components/honeywell/translations/sk.json @@ -2,6 +2,13 @@ "config": { "error": { "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "password": "Heslo" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index 117d1b2d92e..dab6abede4c 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -17,6 +17,7 @@ from homeassistant.auth.const import GROUP_ID_READ_ONLY from homeassistant.auth.models import User from homeassistant.components import websocket_api from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.storage import Store from homeassistant.util import dt as dt_util from homeassistant.util.network import is_local @@ -96,7 +97,7 @@ def async_user_not_allowed_do_auth( return "User is local only" try: - remote = ip_address(request.remote) + remote = ip_address(request.remote) # type: ignore[arg-type] except ValueError: return "Invalid remote IP" @@ -108,8 +109,8 @@ def async_user_not_allowed_do_auth( async def async_setup_auth(hass: HomeAssistant, app: Application) -> None: """Create auth middleware for the app.""" - store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) - if (data := await store.async_load()) is None: + store = Store(hass, STORAGE_VERSION, STORAGE_KEY) + if (data := await store.async_load()) is None or not isinstance(data, dict): data = {} refresh_token = None diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 292c46e55f9..620bdc7613c 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -67,7 +67,7 @@ async def ban_middleware( return await handler(request) # Verify if IP is not banned - ip_address_ = ip_address(request.remote) + ip_address_ = ip_address(request.remote) # type: ignore[arg-type] is_banned = any( ip_ban.ip_address == ip_address_ for ip_ban in request.app[KEY_BANNED_IPS] ) @@ -107,7 +107,7 @@ async def process_wrong_login(request: Request) -> None: """ hass = request.app["hass"] - remote_addr = ip_address(request.remote) + remote_addr = ip_address(request.remote) # type: ignore[arg-type] remote_host = request.remote with suppress(herror): remote_host, _, _ = await hass.async_add_executor_job( @@ -170,7 +170,7 @@ async def process_success_login(request: Request) -> None: No release IP address from banned list function, it can only be done by manual modify ip bans config file. """ - remote_addr = ip_address(request.remote) + remote_addr = ip_address(request.remote) # type: ignore[arg-type] # Check if ban middleware is loaded if KEY_BANNED_IPS not in request.app or request.app[KEY_LOGIN_THRESHOLD] < 1: diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index ca15731641d..601a3b9af8d 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -10,9 +10,9 @@ import logging import time from typing import Any, NamedTuple, cast -from huawei_lte_api.AuthorizedConnection import AuthorizedConnection from huawei_lte_api.Client import Client from huawei_lte_api.Connection import Connection +from huawei_lte_api.enums.device import ControlModeEnum from huawei_lte_api.exceptions import ( ResponseErrorException, ResponseErrorLoginRequiredException, @@ -47,6 +47,7 @@ from homeassistant.helpers import ( from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ConfigType from .const import ( @@ -186,9 +187,12 @@ class Router: try: self.data[key] = func() except ResponseErrorLoginRequiredException: - if isinstance(self.connection, AuthorizedConnection): + if not self.config_entry.options.get(CONF_UNAUTHENTICATED_MODE): _LOGGER.debug("Trying to authorize again") - if self.connection.enforce_authorized_connection(): + if self.client.user.login( + self.config_entry.data.get(CONF_USERNAME, ""), + self.config_entry.data.get(CONF_PASSWORD, ""), + ): _LOGGER.debug( "success, %s will be updated by a future periodic run", key, @@ -276,8 +280,6 @@ class Router: def logout(self) -> None: """Log out router session.""" - if not isinstance(self.connection, AuthorizedConnection): - return try: self.client.user.logout() except ResponseErrorNotSupportedException: @@ -293,6 +295,7 @@ class Router: self.subscriptions.clear() self.logout() + self.connection.requests_session.close() class HuaweiLteData(NamedTuple): @@ -315,7 +318,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.debug("Connecting in authenticated mode, full feature set") username = entry.data.get(CONF_USERNAME) or "" password = entry.data.get(CONF_PASSWORD) or "" - connection = AuthorizedConnection( + connection = Connection( url, username=username, password=password, timeout=CONNECTION_TIMEOUT ) return connection @@ -515,7 +518,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if router.suspended: _LOGGER.debug("%s: ignored, integration suspended", service.service) return - result = router.client.device.reboot() + result = router.client.device.set_control(ControlModeEnum.REBOOT) _LOGGER.debug("%s: %s", service.service, result) elif service.service == SERVICE_RESUME_INTEGRATION: # Login will be handled automatically on demand @@ -529,7 +532,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _LOGGER.error("%s: unsupported service", service.service) for service in ADMIN_SERVICES: - hass.helpers.service.async_register_admin_service( + async_register_admin_service( + hass, DOMAIN, service, service_handler, diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index f4ea7cd86f7..3dfd38d6304 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -5,9 +5,9 @@ import logging from typing import TYPE_CHECKING, Any from urllib.parse import urlparse -from huawei_lte_api.AuthorizedConnection import AuthorizedConnection from huawei_lte_api.Client import Client -from huawei_lte_api.Connection import GetResponseType +from huawei_lte_api.Connection import Connection +from huawei_lte_api.Session import GetResponseType from huawei_lte_api.exceptions import ( LoginErrorPasswordWrongException, LoginErrorUsernamePasswordOverrunException, @@ -108,19 +108,17 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): user_input=user_input, errors=errors ) - conn: AuthorizedConnection - def logout() -> None: try: - conn.user.logout() + conn.user_session.user.logout() # type: ignore[union-attr] except Exception: # pylint: disable=broad-except _LOGGER.debug("Could not logout", exc_info=True) - def try_connect(user_input: dict[str, Any]) -> AuthorizedConnection: + def try_connect(user_input: dict[str, Any]) -> Connection: """Try connecting with given credentials.""" username = user_input.get(CONF_USERNAME) or "" password = user_input.get(CONF_PASSWORD) or "" - conn = AuthorizedConnection( + conn = Connection( user_input[CONF_URL], username=username, password=password, diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py index c8e9a5d7a0d..ee643c59b12 100644 --- a/homeassistant/components/huawei_lte/device_tracker.py +++ b/homeassistant/components/huawei_lte/device_tracker.py @@ -66,7 +66,7 @@ async def async_setup_entry( # Initialize already tracked entities tracked: set[str] = set() - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) known_entities: list[Entity] = [] track_wired_clients = router.config_entry.options.get( CONF_TRACK_WIRED_CLIENTS, DEFAULT_TRACK_WIRED_CLIENTS diff --git a/homeassistant/components/huawei_lte/manifest.json b/homeassistant/components/huawei_lte/manifest.json index 3e7ebf24b16..dd0382d5f55 100644 --- a/homeassistant/components/huawei_lte/manifest.json +++ b/homeassistant/components/huawei_lte/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/huawei_lte", "requirements": [ - "huawei-lte-api==1.4.18", + "huawei-lte-api==1.6.0", "stringcase==1.2.0", "url-normalize==1.4.1" ], diff --git a/homeassistant/components/huawei_lte/translations/bg.json b/homeassistant/components/huawei_lte/translations/bg.json index 741b8ec7d47..7e67477f000 100644 --- a/homeassistant/components/huawei_lte/translations/bg.json +++ b/homeassistant/components/huawei_lte/translations/bg.json @@ -1,8 +1,6 @@ { "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", - "already_in_progress": "\u0422\u043e\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0432\u0435\u0447\u0435 \u0441\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430", "not_huawei_lte": "\u041d\u0435 \u0435 Huawei LTE \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" }, "error": { @@ -30,8 +28,7 @@ "step": { "init": { "data": { - "recipient": "\u041f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0438 \u043d\u0430 SMS \u0438\u0437\u0432\u0435\u0441\u0442\u0438\u044f", - "track_new_devices": "\u041f\u0440\u043e\u0441\u043b\u0435\u0434\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u043d\u043e\u0432\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" + "recipient": "\u041f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0438 \u043d\u0430 SMS \u0438\u0437\u0432\u0435\u0441\u0442\u0438\u044f" } } } diff --git a/homeassistant/components/huawei_lte/translations/ca.json b/homeassistant/components/huawei_lte/translations/ca.json index 0347398ce8b..903ba233407 100644 --- a/homeassistant/components/huawei_lte/translations/ca.json +++ b/homeassistant/components/huawei_lte/translations/ca.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat", - "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "not_huawei_lte": "No \u00e9s un dispositiu Huawei LTE" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Nom del servei de notificacions (reinici necessari si canvia)", "recipient": "Destinataris de notificacions SMS", - "track_new_devices": "Segueix dispositius nous", "track_wired_clients": "Segueix els clients connectats a la xarxa per cable", "unauthenticated_mode": "Mode no autenticat (canviar requereix tornar a carregar)" } diff --git a/homeassistant/components/huawei_lte/translations/cs.json b/homeassistant/components/huawei_lte/translations/cs.json index 298cf182b08..7782b2fc622 100644 --- a/homeassistant/components/huawei_lte/translations/cs.json +++ b/homeassistant/components/huawei_lte/translations/cs.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", - "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", "not_huawei_lte": "Nejedn\u00e1 se o za\u0159\u00edzen\u00ed Huawei LTE" }, "error": { @@ -33,8 +31,7 @@ "init": { "data": { "name": "Jm\u00e9no slu\u017eby ozn\u00e1men\u00ed (zm\u011bna vy\u017eaduje restart)", - "recipient": "P\u0159\u00edjemci ozn\u00e1men\u00ed SMS", - "track_new_devices": "Sledovat nov\u00e1 za\u0159\u00edzen\u00ed" + "recipient": "P\u0159\u00edjemci ozn\u00e1men\u00ed SMS" } } } diff --git a/homeassistant/components/huawei_lte/translations/da.json b/homeassistant/components/huawei_lte/translations/da.json index 52765ad704a..2b1f937b6be 100644 --- a/homeassistant/components/huawei_lte/translations/da.json +++ b/homeassistant/components/huawei_lte/translations/da.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Denne enhed er allerede konfigureret", - "already_in_progress": "Denne enhed er allerede ved at blive konfigureret", "not_huawei_lte": "Ikke en Huawei LTE-enhed" }, "error": { @@ -29,8 +27,7 @@ "init": { "data": { "name": "Navn p\u00e5 meddelelsestjeneste (\u00e6ndring kr\u00e6ver genstart)", - "recipient": "Modtagere af SMS-meddelelse", - "track_new_devices": "Spor nye enheder" + "recipient": "Modtagere af SMS-meddelelse" } } } diff --git a/homeassistant/components/huawei_lte/translations/de.json b/homeassistant/components/huawei_lte/translations/de.json index a3b40d0c0ae..50e7b7a2e53 100644 --- a/homeassistant/components/huawei_lte/translations/de.json +++ b/homeassistant/components/huawei_lte/translations/de.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "not_huawei_lte": "Kein Huawei LTE-Ger\u00e4t" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Name des Benachrichtigungsdienstes (\u00c4nderung erfordert Neustart)", "recipient": "SMS-Benachrichtigungsempf\u00e4nger", - "track_new_devices": "Neue Ger\u00e4te verfolgen", "track_wired_clients": "Kabelgebundene Netzwerk-Clients verfolgen", "unauthenticated_mode": "Nicht authentifizierter Modus (\u00c4nderung erfordert erneutes Laden)" } diff --git a/homeassistant/components/huawei_lte/translations/el.json b/homeassistant/components/huawei_lte/translations/el.json index 01526400165..8b6def091c9 100644 --- a/homeassistant/components/huawei_lte/translations/el.json +++ b/homeassistant/components/huawei_lte/translations/el.json @@ -1,8 +1,6 @@ { "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_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", "not_huawei_lte": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Huawei LTE" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1\u03c2 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2 (\u03b7 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7)", "recipient": "\u03a0\u03b1\u03c1\u03b1\u03bb\u03ae\u03c0\u03c4\u03b5\u03c2 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c9\u03bd SMS", - "track_new_devices": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03bd\u03ad\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd", "track_wired_clients": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03c0\u03b5\u03bb\u03b1\u03c4\u03ce\u03bd \u03b5\u03bd\u03c3\u03cd\u03c1\u03bc\u03b1\u03c4\u03bf\u03c5 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", "unauthenticated_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c7\u03c9\u03c1\u03af\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 (\u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7)" } diff --git a/homeassistant/components/huawei_lte/translations/en.json b/homeassistant/components/huawei_lte/translations/en.json index ade7beed75c..5636d952b19 100644 --- a/homeassistant/components/huawei_lte/translations/en.json +++ b/homeassistant/components/huawei_lte/translations/en.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Device is already configured", - "already_in_progress": "Configuration flow is already in progress", "not_huawei_lte": "Not a Huawei LTE device" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Notification service name (change requires restart)", "recipient": "SMS notification recipients", - "track_new_devices": "Track new devices", "track_wired_clients": "Track wired network clients", "unauthenticated_mode": "Unauthenticated mode (change requires reload)" } diff --git a/homeassistant/components/huawei_lte/translations/es-419.json b/homeassistant/components/huawei_lte/translations/es-419.json index 29e7658b8e0..056b8dba886 100644 --- a/homeassistant/components/huawei_lte/translations/es-419.json +++ b/homeassistant/components/huawei_lte/translations/es-419.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Este dispositivo ya ha sido configurado", - "already_in_progress": "Este dispositivo ya est\u00e1 siendo configurado", "not_huawei_lte": "No es un dispositivo Huawei LTE" }, "error": { @@ -29,8 +27,7 @@ "init": { "data": { "name": "Nombre del servicio de notificaci\u00f3n (el cambio requiere reiniciar)", - "recipient": "Destinatarios de notificaciones por SMS", - "track_new_devices": "Rastrear nuevos dispositivos" + "recipient": "Destinatarios de notificaciones por SMS" } } } diff --git a/homeassistant/components/huawei_lte/translations/es.json b/homeassistant/components/huawei_lte/translations/es.json index 5d5e72e70c3..485d2e16f69 100644 --- a/homeassistant/components/huawei_lte/translations/es.json +++ b/homeassistant/components/huawei_lte/translations/es.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Este dispositivo ya ha sido configurado", - "already_in_progress": "Este dispositivo ya se est\u00e1 configurando", "not_huawei_lte": "No es un dispositivo Huawei LTE" }, "error": { @@ -15,7 +13,7 @@ "response_error": "Error desconocido del dispositivo", "unknown": "Error inesperado" }, - "flow_title": "Huawei LTE: {name}", + "flow_title": "{name}", "step": { "user": { "data": { @@ -34,7 +32,6 @@ "data": { "name": "Nombre del servicio de notificaci\u00f3n (el cambio requiere reiniciar)", "recipient": "Destinatarios de notificaciones por SMS", - "track_new_devices": "Rastrea nuevos dispositivos", "track_wired_clients": "Seguir clientes de red cableados", "unauthenticated_mode": "Modo no autenticado (el cambio requiere recarga)" } diff --git a/homeassistant/components/huawei_lte/translations/et.json b/homeassistant/components/huawei_lte/translations/et.json index 955d5cc2c5a..08fcca84b23 100644 --- a/homeassistant/components/huawei_lte/translations/et.json +++ b/homeassistant/components/huawei_lte/translations/et.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Seade on juba seadistatud", - "already_in_progress": "Seadistamine on juba k\u00e4imas", "not_huawei_lte": "Pole Huawei LTE seade" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Teavitusteenuse nimi (muudatus n\u00f5uab taask\u00e4ivitamist)", "recipient": "SMS teavituse saajad", - "track_new_devices": "Uute seadmete j\u00e4lgimine", "track_wired_clients": "J\u00e4lgi juhtmega v\u00f5rgukliente", "unauthenticated_mode": "Tuvastuseta re\u017eiim (muutmine n\u00f5uab taaslaadimist)" } diff --git a/homeassistant/components/huawei_lte/translations/fr.json b/homeassistant/components/huawei_lte/translations/fr.json index 1f5042d3aa1..117514985c9 100644 --- a/homeassistant/components/huawei_lte/translations/fr.json +++ b/homeassistant/components/huawei_lte/translations/fr.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", - "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "not_huawei_lte": "Pas un appareil Huawei LTE" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Nom du service de notification (red\u00e9marrage requis)", "recipient": "Destinataires des notifications SMS", - "track_new_devices": "Suivre les nouveaux appareils", "track_wired_clients": "Suivre les clients du r\u00e9seau filaire", "unauthenticated_mode": "Mode non authentifi\u00e9 (le changement n\u00e9cessite un rechargement)" } diff --git a/homeassistant/components/huawei_lte/translations/he.json b/homeassistant/components/huawei_lte/translations/he.json index 1e4333a989a..daad7429a83 100644 --- a/homeassistant/components/huawei_lte/translations/he.json +++ b/homeassistant/components/huawei_lte/translations/he.json @@ -1,9 +1,5 @@ { "config": { - "abort": { - "already_configured": "\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" - }, "error": { "incorrect_password": "\u05e1\u05d9\u05e1\u05de\u05d4 \u05e9\u05d2\u05d5\u05d9\u05d4", "incorrect_username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9 \u05e9\u05d2\u05d5\u05d9", diff --git a/homeassistant/components/huawei_lte/translations/hu.json b/homeassistant/components/huawei_lte/translations/hu.json index b3cc71d5a9d..c01a5cb4bb8 100644 --- a/homeassistant/components/huawei_lte/translations/hu.json +++ b/homeassistant/components/huawei_lte/translations/hu.json @@ -1,8 +1,6 @@ { "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", "not_huawei_lte": "Nem Huawei LTE eszk\u00f6z" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "\u00c9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1s neve (a m\u00f3dos\u00edt\u00e1s \u00fajraind\u00edt\u00e1st ig\u00e9nyel)", "recipient": "SMS-\u00e9rtes\u00edt\u00e9s c\u00edmzettjei", - "track_new_devices": "\u00daj eszk\u00f6z\u00f6k nyomk\u00f6vet\u00e9se", "track_wired_clients": "Vezet\u00e9kes h\u00e1l\u00f3zati \u00fcgyfelek nyomon k\u00f6vet\u00e9se", "unauthenticated_mode": "Nem hiteles\u00edtett m\u00f3d (a v\u00e1ltoztat\u00e1shoz \u00fajrat\u00f6lt\u00e9sre van sz\u00fcks\u00e9g)" } diff --git a/homeassistant/components/huawei_lte/translations/id.json b/homeassistant/components/huawei_lte/translations/id.json index fa586718cac..ac925d0b460 100644 --- a/homeassistant/components/huawei_lte/translations/id.json +++ b/homeassistant/components/huawei_lte/translations/id.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Perangkat sudah dikonfigurasi", - "already_in_progress": "Alur konfigurasi sedang berlangsung", "not_huawei_lte": "Bukan perangkat Huawei LTE" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Nama layanan notifikasi (perubahan harus dimulai ulang)", "recipient": "Penerima notifikasi SMS", - "track_new_devices": "Lacak perangkat baru", "track_wired_clients": "Lacak klien jaringan kabel", "unauthenticated_mode": "Mode tidak diautentikasi (perubahan memerlukan pemuatan ulang)" } diff --git a/homeassistant/components/huawei_lte/translations/it.json b/homeassistant/components/huawei_lte/translations/it.json index ad8d4a82b08..6a90b67d307 100644 --- a/homeassistant/components/huawei_lte/translations/it.json +++ b/homeassistant/components/huawei_lte/translations/it.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "not_huawei_lte": "Non \u00e8 un dispositivo Huawei LTE" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Nome del servizio di notifica (la modifica richiede il riavvio)", "recipient": "Destinatari della notifica SMS", - "track_new_devices": "Traccia nuovi dispositivi", "track_wired_clients": "Tieni traccia dei client di rete cablata", "unauthenticated_mode": "Modalit\u00e0 non autenticata (la modifica richiede il ricaricamento)" } diff --git a/homeassistant/components/huawei_lte/translations/ja.json b/homeassistant/components/huawei_lte/translations/ja.json index 6c74f5a7918..c3b41fbbcfc 100644 --- a/homeassistant/components/huawei_lte/translations/ja.json +++ b/homeassistant/components/huawei_lte/translations/ja.json @@ -1,8 +1,6 @@ { "config": { "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", "not_huawei_lte": "Huawei LTE\u30c7\u30d0\u30a4\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "\u901a\u77e5\u30b5\u30fc\u30d3\u30b9\u540d(\u5909\u66f4\u306b\u306f\u518d\u8d77\u52d5\u304c\u5fc5\u8981)", "recipient": "SMS\u901a\u77e5\u306e\u53d7\u4fe1\u8005", - "track_new_devices": "\u65b0\u3057\u3044\u30c7\u30d0\u30a4\u30b9\u306e\u8ffd\u8de1", "track_wired_clients": "\u6709\u7dda\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u3092\u8ffd\u8de1\u3059\u308b", "unauthenticated_mode": "\u8a8d\u8a3c\u306a\u3057\u306e\u30e2\u30fc\u30c9(\u5909\u66f4\u306b\u306f\u30ea\u30ed\u30fc\u30c9\u304c\u5fc5\u8981)" } diff --git a/homeassistant/components/huawei_lte/translations/ko.json b/homeassistant/components/huawei_lte/translations/ko.json index ab108964ed8..2a29418e67d 100644 --- a/homeassistant/components/huawei_lte/translations/ko.json +++ b/homeassistant/components/huawei_lte/translations/ko.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "not_huawei_lte": "\ud654\uc6e8\uc774 LTE \uae30\uae30\uac00 \uc544\ub2d9\ub2c8\ub2e4" }, "error": { @@ -34,7 +32,7 @@ "data": { "name": "\uc54c\ub9bc \uc11c\ube44\uc2a4 \uc774\ub984 (\ubcc0\uacbd \uc2dc \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud568)", "recipient": "SMS \uc54c\ub9bc \uc218\uc2e0\uc790", - "track_new_devices": "\uc0c8\ub85c\uc6b4 \uae30\uae30 \ucd94\uc801" + "track_wired_clients": "\uc720\uc120 \ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8 \ucd94\uc801" } } } diff --git a/homeassistant/components/huawei_lte/translations/lb.json b/homeassistant/components/huawei_lte/translations/lb.json index d6983bed8fc..2be64393358 100644 --- a/homeassistant/components/huawei_lte/translations/lb.json +++ b/homeassistant/components/huawei_lte/translations/lb.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Apparat ass scho konfigur\u00e9iert", - "already_in_progress": "Konfiguratioun's Oflas ass schon am gaang", "not_huawei_lte": "Keen Huawei LTE Apparat" }, "error": { @@ -33,8 +31,7 @@ "init": { "data": { "name": "Numm vum Notifikatioun's Service (Restart n\u00e9ideg bei \u00c4nnerung)", - "recipient": "Empf\u00e4nger vun SMS Notifikatioune", - "track_new_devices": "Nei Apparater verfollegen" + "recipient": "Empf\u00e4nger vun SMS Notifikatioune" } } } diff --git a/homeassistant/components/huawei_lte/translations/nl.json b/homeassistant/components/huawei_lte/translations/nl.json index e65c261d62b..3e5148170a7 100644 --- a/homeassistant/components/huawei_lte/translations/nl.json +++ b/homeassistant/components/huawei_lte/translations/nl.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", "not_huawei_lte": "Geen Huawei LTE-apparaat" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Naam meldingsservice (wijziging vereist opnieuw opstarten)", "recipient": "Ontvangers van sms-berichten", - "track_new_devices": "Volg nieuwe apparaten", "track_wired_clients": "Volg bekabelde netwerkclients", "unauthenticated_mode": "Niet-geverifieerde modus (wijzigen vereist opnieuw laden)" } diff --git a/homeassistant/components/huawei_lte/translations/no.json b/homeassistant/components/huawei_lte/translations/no.json index 3c8b26ab0cd..d1cd33ce2b0 100644 --- a/homeassistant/components/huawei_lte/translations/no.json +++ b/homeassistant/components/huawei_lte/translations/no.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert", - "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "not_huawei_lte": "Ikke en Huawei LTE-enhet" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Navn p\u00e5 varslingstjeneste (endring krever omstart)", "recipient": "Mottakere av SMS-varsling", - "track_new_devices": "Spor nye enheter", "track_wired_clients": "Spor kablede nettverksklienter", "unauthenticated_mode": "Uautentisert modus (endring krever omlasting)" } diff --git a/homeassistant/components/huawei_lte/translations/pl.json b/homeassistant/components/huawei_lte/translations/pl.json index 38ee2117b9d..76e7c92a010 100644 --- a/homeassistant/components/huawei_lte/translations/pl.json +++ b/homeassistant/components/huawei_lte/translations/pl.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", - "already_in_progress": "Konfiguracja jest ju\u017c w toku", "not_huawei_lte": "To nie jest urz\u0105dzenie Huawei LTE" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Nazwa us\u0142ugi powiadomie\u0144 (zmiana wymaga ponownego uruchomienia)", "recipient": "Odbiorcy powiadomie\u0144 SMS", - "track_new_devices": "\u015aled\u017a nowe urz\u0105dzenia", "track_wired_clients": "\u015aled\u017a klient\u00f3w sieci przewodowej", "unauthenticated_mode": "Tryb nieuwierzytelniony (zmiana wymaga prze\u0142adowania)" } diff --git a/homeassistant/components/huawei_lte/translations/pt-BR.json b/homeassistant/components/huawei_lte/translations/pt-BR.json index 7b69fce212d..a92c2de3f10 100644 --- a/homeassistant/components/huawei_lte/translations/pt-BR.json +++ b/homeassistant/components/huawei_lte/translations/pt-BR.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "not_huawei_lte": "N\u00e3o \u00e9 um dispositivo Huawei LTE" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Nome do servi\u00e7o de notifica\u00e7\u00e3o (a altera\u00e7\u00e3o requer rein\u00edcio)", "recipient": "Destinat\u00e1rios de notifica\u00e7\u00e3o por SMS", - "track_new_devices": "Rastrear novos dispositivos", "track_wired_clients": "Rastrear clientes da rede cabeada", "unauthenticated_mode": "Modo n\u00e3o autenticado (a altera\u00e7\u00e3o requer recarga)" } diff --git a/homeassistant/components/huawei_lte/translations/pt.json b/homeassistant/components/huawei_lte/translations/pt.json index 34d057a9ab0..be10fe829f7 100644 --- a/homeassistant/components/huawei_lte/translations/pt.json +++ b/homeassistant/components/huawei_lte/translations/pt.json @@ -1,9 +1,5 @@ { "config": { - "abort": { - "already_configured": "Este dispositivo j\u00e1 foi configurado", - "already_in_progress": "Este dispositivo j\u00e1 est\u00e1 a ser configurado" - }, "error": { "connection_timeout": "Liga\u00e7\u00e3o expirou", "incorrect_password": "Palavra-passe incorreta", @@ -27,8 +23,7 @@ "step": { "init": { "data": { - "recipient": "Destinat\u00e1rios de notifica\u00e7\u00e3o por SMS", - "track_new_devices": "Seguir novos dispositivos" + "recipient": "Destinat\u00e1rios de notifica\u00e7\u00e3o por SMS" } } } diff --git a/homeassistant/components/huawei_lte/translations/ru.json b/homeassistant/components/huawei_lte/translations/ru.json index e9ddd73191d..6c27673b556 100644 --- a/homeassistant/components/huawei_lte/translations/ru.json +++ b/homeassistant/components/huawei_lte/translations/ru.json @@ -1,8 +1,6 @@ { "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_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.", "not_huawei_lte": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c Huawei LTE" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u043b\u0443\u0436\u0431\u044b \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 (\u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a)", "recipient": "\u041f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0438 SMS-\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439", - "track_new_devices": "\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", "track_wired_clients": "\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043f\u0440\u043e\u0432\u043e\u0434\u043d\u043e\u0439 \u0441\u0435\u0442\u0438", "unauthenticated_mode": "\u0420\u0435\u0436\u0438\u043c \u0431\u0435\u0437 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 (\u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430)" } diff --git a/homeassistant/components/huawei_lte/translations/sk.json b/homeassistant/components/huawei_lte/translations/sk.json index 0b7bf878ea9..5ada995aa6e 100644 --- a/homeassistant/components/huawei_lte/translations/sk.json +++ b/homeassistant/components/huawei_lte/translations/sk.json @@ -1,8 +1,5 @@ { "config": { - "abort": { - "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" - }, "error": { "invalid_auth": "Neplatn\u00e9 overenie" } diff --git a/homeassistant/components/huawei_lte/translations/sl.json b/homeassistant/components/huawei_lte/translations/sl.json index 07a9b269986..0297db82c7b 100644 --- a/homeassistant/components/huawei_lte/translations/sl.json +++ b/homeassistant/components/huawei_lte/translations/sl.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Ta naprava je \u017ee konfigurirana", - "already_in_progress": "Ta naprava se \u017ee nastavlja", "not_huawei_lte": "Ni naprava Huawei LTE" }, "error": { @@ -29,8 +27,7 @@ "init": { "data": { "name": "Ime storitve obve\u0161\u010danja (sprememba zahteva ponovni zagon)", - "recipient": "Prejemniki obvestil SMS", - "track_new_devices": "Sledi novim napravam" + "recipient": "Prejemniki obvestil SMS" } } } diff --git a/homeassistant/components/huawei_lte/translations/sv.json b/homeassistant/components/huawei_lte/translations/sv.json index 3f478c3f826..83cbe335b2d 100644 --- a/homeassistant/components/huawei_lte/translations/sv.json +++ b/homeassistant/components/huawei_lte/translations/sv.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Den h\u00e4r enheten har redan konfigurerats", - "already_in_progress": "Den h\u00e4r enheten har redan konfigurerats", "not_huawei_lte": "Inte en Huawei LTE-enhet" }, "error": { @@ -29,8 +27,7 @@ "init": { "data": { "name": "Namn p\u00e5 meddelandetj\u00e4nsten (\u00e4ndring kr\u00e4ver omstart)", - "recipient": "Mottagare av SMS-meddelanden", - "track_new_devices": "Sp\u00e5ra nya enheter" + "recipient": "Mottagare av SMS-meddelanden" } } } diff --git a/homeassistant/components/huawei_lte/translations/tr.json b/homeassistant/components/huawei_lte/translations/tr.json index 92d40ca810b..6d231efa8ed 100644 --- a/homeassistant/components/huawei_lte/translations/tr.json +++ b/homeassistant/components/huawei_lte/translations/tr.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", - "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "not_huawei_lte": "Huawei LTE cihaz\u0131 de\u011fil" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "Bildirim hizmeti ad\u0131 (de\u011fi\u015fiklik yeniden ba\u015flatmay\u0131 gerektirir)", "recipient": "SMS bildirimi al\u0131c\u0131lar\u0131", - "track_new_devices": "Yeni cihazlar\u0131 izle", "track_wired_clients": "Kablolu a\u011f istemcilerini izleyin", "unauthenticated_mode": "Kimli\u011fi do\u011frulanmam\u0131\u015f mod (de\u011fi\u015fiklik yeniden y\u00fckleme gerektirir)" } diff --git a/homeassistant/components/huawei_lte/translations/uk.json b/homeassistant/components/huawei_lte/translations/uk.json index 17f3d3b71c3..ae98ce59332 100644 --- a/homeassistant/components/huawei_lte/translations/uk.json +++ b/homeassistant/components/huawei_lte/translations/uk.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", - "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0442\u0440\u0438\u0432\u0430\u0454.", "not_huawei_lte": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0454 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c Huawei LTE" }, "error": { @@ -33,8 +31,7 @@ "init": { "data": { "name": "\u041d\u0430\u0437\u0432\u0430 \u0441\u043b\u0443\u0436\u0431\u0438 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u044c (\u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a)", - "recipient": "\u041e\u0434\u0435\u0440\u0436\u0443\u0432\u0430\u0447\u0456 SMS-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c", - "track_new_devices": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u0442\u0438 \u043d\u043e\u0432\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457" + "recipient": "\u041e\u0434\u0435\u0440\u0436\u0443\u0432\u0430\u0447\u0456 SMS-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c" } } } diff --git a/homeassistant/components/huawei_lte/translations/zh-Hans.json b/homeassistant/components/huawei_lte/translations/zh-Hans.json index a63ff964b62..c04409b2783 100644 --- a/homeassistant/components/huawei_lte/translations/zh-Hans.json +++ b/homeassistant/components/huawei_lte/translations/zh-Hans.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e", - "already_in_progress": "\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c", "not_huawei_lte": "\u8be5\u8bbe\u5907\u4e0d\u662f\u534e\u4e3a LTE \u8bbe\u5907" }, "error": { @@ -32,7 +30,6 @@ "data": { "name": "\u63a8\u9001\u670d\u52a1\u540d\u79f0\uff08\u66f4\u6539\u540e\u9700\u8981\u91cd\u8f7d\uff09", "recipient": "\u77ed\u4fe1\u901a\u77e5\u6536\u4ef6\u4eba", - "track_new_devices": "\u8ddf\u8e2a\u65b0\u8bbe\u5907", "track_wired_clients": "\u8ddf\u8e2a\u6709\u7ebf\u7f51\u7edc\u5ba2\u6237\u7aef", "unauthenticated_mode": "\u672a\u7ecf\u8eab\u4efd\u9a8c\u8bc1\u7684\u6a21\u5f0f\uff08\u66f4\u6539\u540e\u9700\u8981\u91cd\u8f7d\uff09" } diff --git a/homeassistant/components/huawei_lte/translations/zh-Hant.json b/homeassistant/components/huawei_lte/translations/zh-Hant.json index 0c1d2baa7a9..85a2d84f6de 100644 --- a/homeassistant/components/huawei_lte/translations/zh-Hant.json +++ b/homeassistant/components/huawei_lte/translations/zh-Hant.json @@ -1,8 +1,6 @@ { "config": { "abort": { - "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "not_huawei_lte": "\u4e26\u975e\u83ef\u70ba LTE \u88dd\u7f6e" }, "error": { @@ -34,7 +32,6 @@ "data": { "name": "\u901a\u77e5\u670d\u52d9\u540d\u7a31\uff08\u8b8a\u66f4\u5f8c\u9700\u91cd\u555f\uff09", "recipient": "\u7c21\u8a0a\u901a\u77e5\u6536\u4ef6\u8005", - "track_new_devices": "\u8ffd\u8e64\u65b0\u88dd\u7f6e", "track_wired_clients": "\u8ffd\u8e64\u6709\u7dda\u7db2\u8def\u5ba2\u6236\u7aef", "unauthenticated_mode": "\u672a\u6388\u6b0a\u6a21\u5f0f\uff08\u8b8a\u66f4\u5f8c\u9700\u91cd\u555f\uff09" } diff --git a/homeassistant/components/huawei_lte/utils.py b/homeassistant/components/huawei_lte/utils.py index 69b346a58f4..37f9f1a5542 100644 --- a/homeassistant/components/huawei_lte/utils.py +++ b/homeassistant/components/huawei_lte/utils.py @@ -1,7 +1,7 @@ """Utilities for the Huawei LTE integration.""" from __future__ import annotations -from huawei_lte_api.Connection import GetResponseType +from huawei_lte_api.Session import GetResponseType from homeassistant.helpers.device_registry import format_mac diff --git a/homeassistant/components/hue/device_trigger.py b/homeassistant/components/hue/device_trigger.py index a4b545aa141..e8eb7695ed9 100644 --- a/homeassistant/components/hue/device_trigger.py +++ b/homeassistant/components/hue/device_trigger.py @@ -1,7 +1,7 @@ """Provides device automations for Philips Hue events.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, @@ -33,7 +33,9 @@ if TYPE_CHECKING: from .bridge import HueBridge -async def async_validate_trigger_config(hass: "HomeAssistant", config: ConfigType): +async def async_validate_trigger_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: """Validate config.""" if DOMAIN not in hass.data: # happens at startup @@ -51,13 +53,14 @@ async def async_validate_trigger_config(hass: "HomeAssistant", config: ConfigTyp if bridge.api_version == 1: return await async_validate_trigger_config_v1(bridge, device_entry, config) return await async_validate_trigger_config_v2(bridge, device_entry, config) + return config async def async_attach_trigger( - hass: "HomeAssistant", + hass: HomeAssistant, config: ConfigType, - action: "AutomationActionType", - automation_info: "AutomationTriggerInfo", + action: AutomationActionType, + automation_info: AutomationTriggerInfo, ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" device_id = config[CONF_DEVICE_ID] @@ -82,7 +85,9 @@ async def async_attach_trigger( ) -async def async_get_triggers(hass: "HomeAssistant", device_id: str): +async def async_get_triggers( + hass: HomeAssistant, device_id: str +) -> list[dict[str, Any]]: """Get device triggers for given (hass) device id.""" if DOMAIN not in hass.data: return [] @@ -99,5 +104,6 @@ async def async_get_triggers(hass: "HomeAssistant", device_id: str): bridge: HueBridge = hass.data[DOMAIN][conf_entry_id] if bridge.api_version == 1: - return await async_get_triggers_v1(bridge, device_entry) - return await async_get_triggers_v2(bridge, device_entry) + return async_get_triggers_v1(bridge, device_entry) + return async_get_triggers_v2(bridge, device_entry) + return [] diff --git a/homeassistant/components/hue/logbook.py b/homeassistant/components/hue/logbook.py new file mode 100644 index 00000000000..e98a99f1861 --- /dev/null +++ b/homeassistant/components/hue/logbook.py @@ -0,0 +1,77 @@ +"""Describe hue logbook events.""" +from __future__ import annotations + +from collections.abc import Callable + +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) +from homeassistant.const import CONF_DEVICE_ID, CONF_EVENT, CONF_ID, CONF_TYPE +from homeassistant.core import Event, HomeAssistant, callback +from homeassistant.helpers import device_registry as dr + +from .const import ATTR_HUE_EVENT, CONF_SUBTYPE, DOMAIN + +TRIGGER_SUBTYPE = { + "button_1": "first button", + "button_2": "second button", + "button_3": "third button", + "button_4": "fourth button", + "double_buttons_1_3": "first and third buttons", + "double_buttons_2_4": "second and fourth buttons", + "dim_down": "dim down", + "dim_up": "dim up", + "turn_off": "turn off", + "turn_on": "turn on", + "1": "first button", + "2": "second button", + "3": "third button", + "4": "fourth button", +} +TRIGGER_TYPE = { + "remote_button_long_release": "{subtype} released after long press", + "remote_button_short_press": "{subtype} pressed", + "remote_button_short_release": "{subtype} released", + "remote_double_button_long_press": "both {subtype} released after long press", + "remote_double_button_short_press": "both {subtype} released", + "initial_press": "{subtype} pressed initially", + "repeat": "{subtype} held down", + "short_release": "{subtype} released after short press", + "long_release": "{subtype} released after long press", + "double_short_release": "both {subtype} released", +} + +UNKNOWN_TYPE = "unknown type" +UNKNOWN_SUB_TYPE = "unknown sub type" + + +@callback +def async_describe_events( + hass: HomeAssistant, + async_describe_event: Callable[[str, str, Callable[[Event], dict[str, str]]], None], +) -> None: + """Describe hue logbook events.""" + + @callback + def async_describe_hue_event(event: Event) -> dict[str, str]: + """Describe hue logbook event.""" + data = event.data + name: str | None = None + if dev_ent := dr.async_get(hass).async_get(data[CONF_DEVICE_ID]): + name = dev_ent.name + if name is None: + name = data[CONF_ID] + if CONF_TYPE in data: # v2 + subtype = TRIGGER_SUBTYPE.get(str(data[CONF_SUBTYPE]), UNKNOWN_SUB_TYPE) + message = TRIGGER_TYPE.get(data[CONF_TYPE], UNKNOWN_TYPE).format( + subtype=subtype + ) + else: + message = f"Event {data[CONF_EVENT]}" # v1 + return { + LOGBOOK_ENTRY_NAME: name, + LOGBOOK_ENTRY_MESSAGE: message, + } + + async_describe_event(DOMAIN, ATTR_HUE_EVENT, async_describe_hue_event) diff --git a/homeassistant/components/hue/translations/bg.json b/homeassistant/components/hue/translations/bg.json index babf0089389..68da5279508 100644 --- a/homeassistant/components/hue/translations/bg.json +++ b/homeassistant/components/hue/translations/bg.json @@ -6,6 +6,7 @@ "already_in_progress": "\u0412 \u043c\u043e\u043c\u0435\u043d\u0442\u0430 \u0442\u0435\u0447\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f.", "cannot_connect": "\u041d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0436\u0435 \u0441 \u0431\u0430\u0437\u043e\u0432\u0430\u0442\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f", "discover_timeout": "\u041d\u0435\u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e \u0435 \u0434\u0430 \u0431\u044a\u0434\u0430\u0442 \u043e\u0442\u043a\u0440\u0438\u0442\u0438 \u0431\u0430\u0437\u043e\u0432\u0438 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 \u043d\u0430 Philips Hue", + "invalid_host": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d \u0445\u043e\u0441\u0442", "no_bridges": "\u041d\u0435 \u0441\u0430 \u043e\u0442\u043a\u0440\u0438\u0442\u0438 \u0431\u0430\u0437\u043e\u0432\u0438 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 \u043d\u0430 Philips Hue", "not_hue_bridge": "\u041d\u0435 \u0435 Hue \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f", "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" diff --git a/homeassistant/components/hue/translations/es.json b/homeassistant/components/hue/translations/es.json index e8611cbe690..7017cca99c1 100644 --- a/homeassistant/components/hue/translations/es.json +++ b/homeassistant/components/hue/translations/es.json @@ -3,11 +3,12 @@ "abort": { "all_configured": "Ya se han configurado todas las pasarelas Philips Hue", "already_configured": "El dispositivo ya est\u00e1 configurado", - "already_in_progress": "El flujo de configuraci\u00f3n para la pasarela ya est\u00e1 en marcha.", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "cannot_connect": "No se pudo conectar", "discover_timeout": "Imposible encontrar pasarelas Philips Hue", + "invalid_host": "Host inv\u00e1lido", "no_bridges": "No se han encontrado pasarelas Philips Hue.", - "not_hue_bridge": "No es una pasarela Hue", + "not_hue_bridge": "No es un enlace Hue", "unknown": "Error inesperado" }, "error": { diff --git a/homeassistant/components/hue/translations/ja.json b/homeassistant/components/hue/translations/ja.json index 7a99db7e498..ec88173adca 100644 --- a/homeassistant/components/hue/translations/ja.json +++ b/homeassistant/components/hue/translations/ja.json @@ -6,6 +6,7 @@ "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", "discover_timeout": "Hue bridge\u3092\u767a\u898b(\u63a2\u308a\u5f53\u3066)\u3067\u304d\u307e\u305b\u3093", + "invalid_host": "\u7121\u52b9\u306a\u30db\u30b9\u30c8", "no_bridges": "Hue bridge\u306f\u767a\u898b\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f", "not_hue_bridge": "Hue bridge\u3067\u306f\u3042\u308a\u307e\u305b\u3093", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index 56bac6b89d8..57c52c4156f 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "all_configured": "Alle Philips Hue bridges zijn al geconfigureerd", "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken", "discover_timeout": "Hue bridges kunnen niet worden gevonden", "invalid_host": "Ongeldige host", @@ -70,7 +70,7 @@ "data": { "allow_hue_groups": "Sta Hue-groepen toe", "allow_hue_scenes": "Sta Hue sc\u00e8nes toe", - "allow_unreachable": "Onbereikbare lampen toestaan hun status correct te melden", + "allow_unreachable": "Onbereikbare lampen toestaan hun status te melden", "ignore_availability": "Verbindingsstatus negeren voor de opgegeven apparaten" } } diff --git a/homeassistant/components/hue/translations/sk.json b/homeassistant/components/hue/translations/sk.json index 424ac0d9252..10605f27ce1 100644 --- a/homeassistant/components/hue/translations/sk.json +++ b/homeassistant/components/hue/translations/sk.json @@ -20,5 +20,10 @@ "description": "Pre registr\u00e1ciu Philips Hue s Home Assistant stla\u010dte tla\u010didlo na Philips Hue bridge.\n\n![Location of button on bridge](/static/images/config_philips_hue.jpg)" } } + }, + "device_automation": { + "trigger_subtype": { + "1": "Prv\u00e9 tla\u010didlo" + } } } \ No newline at end of file diff --git a/homeassistant/components/hue/translations/sv.json b/homeassistant/components/hue/translations/sv.json index 80f7b179692..c4687726357 100644 --- a/homeassistant/components/hue/translations/sv.json +++ b/homeassistant/components/hue/translations/sv.json @@ -6,6 +6,7 @@ "already_in_progress": "Konfigurations fl\u00f6det f\u00f6r bryggan p\u00e5g\u00e5r redan.", "cannot_connect": "Det gick inte att ansluta till bryggan", "discover_timeout": "Det gick inte att uppt\u00e4cka n\u00e5gra Hue-bryggor", + "invalid_host": "Ogiltig v\u00e4rd", "no_bridges": "Inga Philips Hue-bryggor uppt\u00e4cktes", "not_hue_bridge": "Inte en Hue-brygga", "unknown": "Ett ok\u00e4nt fel intr\u00e4ffade" diff --git a/homeassistant/components/hue/v1/device_trigger.py b/homeassistant/components/hue/v1/device_trigger.py index d6b471b7257..579f4b71efb 100644 --- a/homeassistant/components/hue/v1/device_trigger.py +++ b/homeassistant/components/hue/v1/device_trigger.py @@ -3,6 +3,10 @@ from typing import TYPE_CHECKING import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, @@ -16,7 +20,9 @@ from homeassistant.const import ( CONF_TYPE, CONF_UNIQUE_ID, ) +from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers.device_registry import DeviceEntry +from homeassistant.helpers.typing import ConfigType from ..const import ATTR_HUE_EVENT, CONF_SUBTYPE, DOMAIN @@ -94,7 +100,7 @@ HUE_FOHSWITCH_REMOTE = { } -REMOTES = { +REMOTES: dict[str, dict[tuple[str, str], dict[str, int]]] = { HUE_DIMMER_REMOTE_MODEL: HUE_DIMMER_REMOTE, HUE_TAP_REMOTE_MODEL: HUE_TAP_REMOTE, HUE_BUTTON_REMOTE_MODEL: HUE_BUTTON_REMOTE, @@ -113,7 +119,9 @@ def _get_hue_event_from_device_id(hass, device_id): return None -async def async_validate_trigger_config(bridge, device_entry, config): +async def async_validate_trigger_config( + bridge: "HueBridge", device_entry: DeviceEntry, config: ConfigType +) -> ConfigType: """Validate config.""" config = TRIGGER_SCHEMA(config) trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) @@ -136,7 +144,13 @@ async def async_validate_trigger_config(bridge, device_entry, config): return config -async def async_attach_trigger(bridge, device_entry, config, action, automation_info): +async def async_attach_trigger( + bridge: "HueBridge", + device_entry: DeviceEntry, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" hass = bridge.hass @@ -144,9 +158,10 @@ async def async_attach_trigger(bridge, device_entry, config, action, automation_ if hue_event is None: raise InvalidDeviceAutomationConfig - trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) + trigger_key: tuple[str, str] = (config[CONF_TYPE], config[CONF_SUBTYPE]) - trigger = REMOTES[device_entry.model][trigger] + assert device_entry.model + trigger = REMOTES[device_entry.model][trigger_key] event_config = { event_trigger.CONF_PLATFORM: "event", @@ -160,7 +175,10 @@ async def async_attach_trigger(bridge, device_entry, config, action, automation_ ) -async def async_get_triggers(bridge: "HueBridge", device: DeviceEntry): +@callback +def async_get_triggers( + bridge: "HueBridge", device: DeviceEntry +) -> list[dict[str, str]]: """Return device triggers for device on `v1` bridge. Make sure device is a supported remote model. @@ -168,7 +186,7 @@ async def async_get_triggers(bridge: "HueBridge", device: DeviceEntry): Generate device trigger list. """ if device.model not in REMOTES: - return + return [] triggers = [] for trigger, subtype in REMOTES[device.model]: diff --git a/homeassistant/components/hue/v1/helpers.py b/homeassistant/components/hue/v1/helpers.py index d4582f0bb52..b0d774915df 100644 --- a/homeassistant/components/hue/v1/helpers.py +++ b/homeassistant/components/hue/v1/helpers.py @@ -1,7 +1,6 @@ """Helper functions for Philips Hue.""" -from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg -from homeassistant.helpers.entity_registry import async_get_registry as get_ent_reg +from homeassistant.helpers import device_registry as dr, entity_registry as er from ..const import DOMAIN @@ -18,10 +17,10 @@ async def remove_devices(bridge, api_ids, current): entity = current[item_id] removed_items.append(item_id) await entity.async_remove(force_remove=True) - ent_registry = await get_ent_reg(bridge.hass) + ent_registry = er.async_get(bridge.hass) if entity.entity_id in ent_registry.entities: ent_registry.async_remove(entity.entity_id) - dev_registry = await get_dev_reg(bridge.hass) + dev_registry = dr.async_get(bridge.hass) device = dev_registry.async_get_device(identifiers={(DOMAIN, entity.device_id)}) if device is not None: dev_registry.async_update_device( diff --git a/homeassistant/components/hue/v1/hue_event.py b/homeassistant/components/hue/v1/hue_event.py index 9074baaaa88..b3faf88c2d9 100644 --- a/homeassistant/components/hue/v1/hue_event.py +++ b/homeassistant/components/hue/v1/hue_event.py @@ -10,6 +10,7 @@ from aiohue.v1.sensors import ( from homeassistant.const import CONF_DEVICE_ID, CONF_EVENT, CONF_ID, CONF_UNIQUE_ID from homeassistant.core import callback +from homeassistant.helpers import device_registry as dr from homeassistant.util import dt as dt_util, slugify from ..const import ATTR_HUE_EVENT @@ -89,9 +90,7 @@ class HueEvent(GenericHueDevice): async def async_update_device_registry(self): """Update device registry.""" - device_registry = ( - await self.bridge.hass.helpers.device_registry.async_get_registry() - ) + device_registry = dr.async_get(self.bridge.hass) entry = device_registry.async_get_or_create( config_entry_id=self.bridge.config_entry.entry_id, **self.device_info diff --git a/homeassistant/components/hue/v1/light.py b/homeassistant/components/hue/v1/light.py index 9cddb665006..74fe25dafcf 100644 --- a/homeassistant/components/hue/v1/light.py +++ b/homeassistant/components/hue/v1/light.py @@ -20,13 +20,10 @@ from homeassistant.components.light import ( EFFECT_RANDOM, FLASH_LONG, FLASH_SHORT, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, + filter_supported_color_modes, ) from homeassistant.core import callback from homeassistant.exceptions import PlatformNotReady @@ -60,10 +57,24 @@ SCAN_INTERVAL = timedelta(seconds=5) LOGGER = logging.getLogger(__name__) -SUPPORT_HUE_ON_OFF = SUPPORT_FLASH | SUPPORT_TRANSITION -SUPPORT_HUE_DIMMABLE = SUPPORT_HUE_ON_OFF | SUPPORT_BRIGHTNESS -SUPPORT_HUE_COLOR_TEMP = SUPPORT_HUE_DIMMABLE | SUPPORT_COLOR_TEMP -SUPPORT_HUE_COLOR = SUPPORT_HUE_DIMMABLE | SUPPORT_EFFECT | SUPPORT_COLOR +COLOR_MODES_HUE_ON_OFF = {ColorMode.ONOFF} +COLOR_MODES_HUE_DIMMABLE = {ColorMode.BRIGHTNESS} +COLOR_MODES_HUE_COLOR_TEMP = {ColorMode.COLOR_TEMP} +COLOR_MODES_HUE_COLOR = {ColorMode.HS} +COLOR_MODES_HUE_EXTENDED = {ColorMode.COLOR_TEMP, ColorMode.HS} + +COLOR_MODES_HUE = { + "Extended color light": COLOR_MODES_HUE_EXTENDED, + "Color light": COLOR_MODES_HUE_COLOR, + "Dimmable light": COLOR_MODES_HUE_DIMMABLE, + "On/Off plug-in unit": COLOR_MODES_HUE_ON_OFF, + "Color temperature light": COLOR_MODES_HUE_COLOR_TEMP, +} + +SUPPORT_HUE_ON_OFF = LightEntityFeature.FLASH | LightEntityFeature.TRANSITION +SUPPORT_HUE_DIMMABLE = SUPPORT_HUE_ON_OFF +SUPPORT_HUE_COLOR_TEMP = SUPPORT_HUE_DIMMABLE +SUPPORT_HUE_COLOR = SUPPORT_HUE_DIMMABLE | LightEntityFeature.EFFECT SUPPORT_HUE_EXTENDED = SUPPORT_HUE_COLOR_TEMP | SUPPORT_HUE_COLOR SUPPORT_HUE = { @@ -96,17 +107,32 @@ def create_light(item_class, coordinator, bridge, is_group, rooms, api, item_id) api_item = api[item_id] if is_group: + supported_color_modes = set() supported_features = 0 for light_id in api_item.lights: if light_id not in bridge.api.lights: continue light = bridge.api.lights[light_id] supported_features |= SUPPORT_HUE.get(light.type, SUPPORT_HUE_EXTENDED) + supported_color_modes.update( + COLOR_MODES_HUE.get(light.type, COLOR_MODES_HUE_EXTENDED) + ) supported_features = supported_features or SUPPORT_HUE_EXTENDED + supported_color_modes = supported_color_modes or COLOR_MODES_HUE_EXTENDED + supported_color_modes = filter_supported_color_modes(supported_color_modes) else: + supported_color_modes = COLOR_MODES_HUE.get( + api_item.type, COLOR_MODES_HUE_EXTENDED + ) supported_features = SUPPORT_HUE.get(api_item.type, SUPPORT_HUE_EXTENDED) return item_class( - coordinator, bridge, is_group, api_item, supported_features, rooms + coordinator, + bridge, + is_group, + api_item, + supported_color_modes, + supported_features, + rooms, ) @@ -281,24 +307,41 @@ def hass_to_hue_brightness(value): class HueLight(CoordinatorEntity, LightEntity): """Representation of a Hue light.""" - def __init__(self, coordinator, bridge, is_group, light, supported_features, rooms): + def __init__( + self, + coordinator, + bridge, + is_group, + light, + supported_color_modes, + supported_features, + rooms, + ): """Initialize the light.""" super().__init__(coordinator) + self._attr_supported_color_modes = supported_color_modes + self._attr_supported_features = supported_features self.light = light self.bridge = bridge self.is_group = is_group - self._supported_features = supported_features self._rooms = rooms self.allow_unreachable = self.bridge.config_entry.options.get( CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_UNREACHABLE ) + self._fixed_color_mode = None + if len(supported_color_modes) == 1: + self._fixed_color_mode = next(iter(supported_color_modes)) + else: + assert supported_color_modes == {ColorMode.COLOR_TEMP, ColorMode.HS} + if is_group: self.is_osram = False self.is_philips = False self.is_innr = False self.is_ewelink = False self.is_livarno = False + self.is_s31litezb = False self.gamut_typ = GAMUT_TYPE_UNAVAILABLE self.gamut = None else: @@ -307,6 +350,7 @@ class HueLight(CoordinatorEntity, LightEntity): self.is_innr = light.manufacturername == "innr" self.is_ewelink = light.manufacturername == "eWeLink" self.is_livarno = light.manufacturername.startswith("_TZ3000_") + self.is_s31litezb = light.modelid == "S31 Lite zb" self.gamut_typ = self.light.colorgamuttype self.gamut = self.light.colorgamut LOGGER.debug("Color gamut of %s: %s", self.name, str(self.gamut)) @@ -354,6 +398,19 @@ class HueLight(CoordinatorEntity, LightEntity): return hue_brightness_to_hass(bri) + @property + def color_mode(self) -> str: + """Return the color mode of the light.""" + if self._fixed_color_mode: + return self._fixed_color_mode + + # The light supports both hs/xy and white with adjustabe color_temperature + mode = self._color_mode + if mode in ("xy", "hs"): + return ColorMode.HS + + return ColorMode.COLOR_TEMP + @property def _color_mode(self): """Return the hue color mode.""" @@ -426,11 +483,6 @@ class HueLight(CoordinatorEntity, LightEntity): self.is_group or self.allow_unreachable or self.light.state["reachable"] ) - @property - def supported_features(self): - """Flag supported features.""" - return self._supported_features - @property def effect(self): """Return the current effect.""" @@ -504,7 +556,12 @@ class HueLight(CoordinatorEntity, LightEntity): elif flash == FLASH_SHORT: command["alert"] = "select" del command["on"] - elif not self.is_innr and not self.is_ewelink and not self.is_livarno: + elif ( + not self.is_innr + and not self.is_ewelink + and not self.is_livarno + and not self.is_s31litezb + ): command["alert"] = "none" if ATTR_EFFECT in kwargs: diff --git a/homeassistant/components/hue/v2/device_trigger.py b/homeassistant/components/hue/v2/device_trigger.py index cab21b63d6d..2b868a4685c 100644 --- a/homeassistant/components/hue/v2/device_trigger.py +++ b/homeassistant/components/hue/v2/device_trigger.py @@ -1,7 +1,7 @@ """Provides device automations for Philips Hue events.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from aiohue.v2.models.button import ButtonEvent from aiohue.v2.models.resource import ResourceTypes @@ -86,7 +86,7 @@ async def async_validate_trigger_config( bridge: "HueBridge", device_entry: DeviceEntry, config: ConfigType, -): +) -> ConfigType: """Validate config.""" config = TRIGGER_SCHEMA(config) check_invalid_device_trigger(bridge, config, device_entry) @@ -97,8 +97,8 @@ async def async_attach_trigger( bridge: "HueBridge", device_entry: DeviceEntry, config: ConfigType, - action: "AutomationActionType", - automation_info: "AutomationTriggerInfo", + action: AutomationActionType, + automation_info: AutomationTriggerInfo, ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" hass = bridge.hass @@ -119,7 +119,10 @@ async def async_attach_trigger( ) -async def async_get_triggers(bridge: "HueBridge", device_entry: DeviceEntry): +@callback +def async_get_triggers( + bridge: HueBridge, device_entry: DeviceEntry +) -> list[dict[str, Any]]: """Return device triggers for device on `v2` bridge.""" api: HueBridgeV2 = bridge.api diff --git a/homeassistant/components/hue/v2/group.py b/homeassistant/components/hue/v2/group.py index 8a6752307cc..cbf974325e0 100644 --- a/homeassistant/components/hue/v2/group.py +++ b/homeassistant/components/hue/v2/group.py @@ -15,10 +15,9 @@ from homeassistant.components.light import ( ATTR_TRANSITION, ATTR_XY_COLOR, FLASH_SHORT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -81,8 +80,8 @@ class GroupedHueLight(HueBaseEntity, LightEntity): self.group = group self.controller = controller self.api: HueBridgeV2 = bridge.api - self._attr_supported_features |= SUPPORT_FLASH - self._attr_supported_features |= SUPPORT_TRANSITION + self._attr_supported_features |= LightEntityFeature.FLASH + self._attr_supported_features |= LightEntityFeature.TRANSITION self._dynamic_mode_active = False self._update_values() diff --git a/homeassistant/components/hue/v2/light.py b/homeassistant/components/hue/v2/light.py index aaf96a3ca17..0f7cc6cdbab 100644 --- a/homeassistant/components/hue/v2/light.py +++ b/homeassistant/components/hue/v2/light.py @@ -17,11 +17,9 @@ from homeassistant.components.light import ( ATTR_TRANSITION, ATTR_XY_COLOR, FLASH_SHORT, - SUPPORT_EFFECT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -74,7 +72,7 @@ class HueLight(HueBaseEntity, LightEntity): """Initialize the light.""" super().__init__(bridge, controller, resource) if self.resource.alert and self.resource.alert.action_values: - self._attr_supported_features |= SUPPORT_FLASH + self._attr_supported_features |= LightEntityFeature.FLASH self.resource = resource self.controller = controller self._supported_color_modes: set[ColorMode | str] = set() @@ -87,7 +85,7 @@ class HueLight(HueBaseEntity, LightEntity): # only add color mode brightness if no color variants self._supported_color_modes.add(ColorMode.BRIGHTNESS) # support transition if brightness control - self._attr_supported_features |= SUPPORT_TRANSITION + self._attr_supported_features |= LightEntityFeature.TRANSITION # get list of supported effects (combine effects and timed_effects) self._attr_effect_list = [] if effects := resource.effects: @@ -102,7 +100,7 @@ class HueLight(HueBaseEntity, LightEntity): ] if len(self._attr_effect_list) > 0: self._attr_effect_list.insert(0, EFFECT_NONE) - self._attr_supported_features |= SUPPORT_EFFECT + self._attr_supported_features |= LightEntityFeature.EFFECT @property def brightness(self) -> int | None: diff --git a/homeassistant/components/humidifier/device_action.py b/homeassistant/components/humidifier/device_action.py index db51ab34baa..773caa72f95 100644 --- a/homeassistant/components/humidifier/device_action.py +++ b/homeassistant/components/humidifier/device_action.py @@ -1,8 +1,6 @@ """Provides device actions for Humidifier.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.device_automation import toggle_entity @@ -19,6 +17,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import get_capability, get_supported_features +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from . import DOMAIN, const @@ -49,7 +48,7 @@ async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Humidifier devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) actions = await toggle_entity.async_get_actions(hass, device_id, DOMAIN) # Get all the integrations entities for this device @@ -74,8 +73,8 @@ async def async_get_actions( async def async_call_action_from_config( hass: HomeAssistant, - config: dict[str, Any], - variables: dict[str, Any], + config: ConfigType, + variables: TemplateVarsType, context: Context | None, ) -> None: """Execute a device action.""" @@ -97,7 +96,9 @@ async def async_call_action_from_config( ) -async def async_get_action_capabilities(hass, config): +async def async_get_action_capabilities( + hass: HomeAssistant, config: ConfigType +) -> dict[str, vol.Schema]: """List action capabilities.""" action_type = config[CONF_TYPE] diff --git a/homeassistant/components/humidifier/device_condition.py b/homeassistant/components/humidifier/device_condition.py index c58c247d569..949b25fdd15 100644 --- a/homeassistant/components/humidifier/device_condition.py +++ b/homeassistant/components/humidifier/device_condition.py @@ -41,7 +41,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Humidifier devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) conditions = await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) # Get all the integrations entities for this device @@ -85,7 +85,9 @@ def async_condition_from_config( return test_is_state -async def async_get_condition_capabilities(hass, config): +async def async_get_condition_capabilities( + hass: HomeAssistant, config: ConfigType +) -> dict[str, vol.Schema]: """List condition capabilities.""" condition_type = config[CONF_TYPE] diff --git a/homeassistant/components/humidifier/device_trigger.py b/homeassistant/components/humidifier/device_trigger.py index e4b0440e869..e8f3a0ac446 100644 --- a/homeassistant/components/humidifier/device_trigger.py +++ b/homeassistant/components/humidifier/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for Climate.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -59,9 +57,9 @@ TRIGGER_SCHEMA = vol.All( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Humidifier devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers = await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) # Get all the integrations entities for this device diff --git a/homeassistant/components/humidifier/intent.py b/homeassistant/components/humidifier/intent.py index aeeb18cceec..57f42f58fe0 100644 --- a/homeassistant/components/humidifier/intent.py +++ b/homeassistant/components/humidifier/intent.py @@ -22,8 +22,8 @@ INTENT_MODE = "HassHumidifierMode" async def async_setup_intents(hass: HomeAssistant) -> None: """Set up the humidifier intents.""" - hass.helpers.intent.async_register(HumidityHandler()) - hass.helpers.intent.async_register(SetModeHandler()) + intent.async_register(hass, HumidityHandler()) + intent.async_register(hass, SetModeHandler()) class HumidityHandler(intent.IntentHandler): @@ -39,8 +39,8 @@ class HumidityHandler(intent.IntentHandler): """Handle the hass intent.""" hass = intent_obj.hass slots = self.async_validate_slots(intent_obj.slots) - state = hass.helpers.intent.async_match_state( - slots["name"]["value"], hass.states.async_all(DOMAIN) + state = intent.async_match_state( + hass, slots["name"]["value"], hass.states.async_all(DOMAIN) ) service_data = {ATTR_ENTITY_ID: state.entity_id} @@ -83,7 +83,8 @@ class SetModeHandler(intent.IntentHandler): """Handle the hass intent.""" hass = intent_obj.hass slots = self.async_validate_slots(intent_obj.slots) - state = hass.helpers.intent.async_match_state( + state = intent.async_match_state( + hass, slots["name"]["value"], hass.states.async_all(DOMAIN), ) diff --git a/homeassistant/components/humidifier/translations/ca.json b/homeassistant/components/humidifier/translations/ca.json index ebfda0d3722..cd980a39591 100644 --- a/homeassistant/components/humidifier/translations/ca.json +++ b/homeassistant/components/humidifier/translations/ca.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} s'enc\u00e9n o s'apaga", "target_humidity_changed": "Ha canviat la humitat desitjada de {entity_name}", - "toggled": "{entity_name} s'activa o es desactiva", "turned_off": "{entity_name} s'ha apagat", "turned_on": "{entity_name} s'ha engegat" } diff --git a/homeassistant/components/humidifier/translations/cs.json b/homeassistant/components/humidifier/translations/cs.json index d47de36b72f..2e373587afc 100644 --- a/homeassistant/components/humidifier/translations/cs.json +++ b/homeassistant/components/humidifier/translations/cs.json @@ -14,7 +14,6 @@ }, "trigger_type": { "target_humidity_changed": "C\u00edlov\u00e1 vlhkost {entity_name} zm\u011bn\u011bna", - "toggled": "{entity_name} zapnuto nebo vypnuto", "turned_off": "{entity_name} vypnuto", "turned_on": "{entity_name} zapnuto" } diff --git a/homeassistant/components/humidifier/translations/de.json b/homeassistant/components/humidifier/translations/de.json index f55117ab05f..16ad95f9980 100644 --- a/homeassistant/components/humidifier/translations/de.json +++ b/homeassistant/components/humidifier/translations/de.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} ein- oder ausgeschaltet", "target_humidity_changed": "{entity_name} Soll-Luftfeuchtigkeit ge\u00e4ndert", - "toggled": "{entity_name} ein- oder ausgeschaltet", "turned_off": "{entity_name} ausgeschaltet", "turned_on": "{entity_name} eingeschaltet" } diff --git a/homeassistant/components/humidifier/translations/el.json b/homeassistant/components/humidifier/translations/el.json index 85efa779f01..d52b5c0ba43 100644 --- a/homeassistant/components/humidifier/translations/el.json +++ b/homeassistant/components/humidifier/translations/el.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "\u03a4\u03bf {entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "target_humidity_changed": "\u0397 \u03c3\u03c4\u03bf\u03c7\u03b5\u03c5\u03bc\u03ad\u03bd\u03b7 \u03c5\u03b3\u03c1\u03b1\u03c3\u03af\u03b1 \u03c4\u03bf\u03c5 {entity_name} \u03ac\u03bb\u03bb\u03b1\u03be\u03b5", - "toggled": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_off": "{entity_name} \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5" } diff --git a/homeassistant/components/humidifier/translations/en.json b/homeassistant/components/humidifier/translations/en.json index 6c62ec89842..a2fc80fe7da 100644 --- a/homeassistant/components/humidifier/translations/en.json +++ b/homeassistant/components/humidifier/translations/en.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} turned on or off", "target_humidity_changed": "{entity_name} target humidity changed", - "toggled": "{entity_name} turned on or off", "turned_off": "{entity_name} turned off", "turned_on": "{entity_name} turned on" } diff --git a/homeassistant/components/humidifier/translations/es.json b/homeassistant/components/humidifier/translations/es.json index e9c8bb02df9..8506445b25c 100644 --- a/homeassistant/components/humidifier/translations/es.json +++ b/homeassistant/components/humidifier/translations/es.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} activado o desactivado", "target_humidity_changed": "La humedad objetivo ha cambiado en {entity_name}", - "toggled": "{entity_name} activado o desactivado", "turned_off": "{entity_name} desactivado", "turned_on": "{entity_name} activado" } diff --git a/homeassistant/components/humidifier/translations/et.json b/homeassistant/components/humidifier/translations/et.json index dcf1f8488a1..7a4ad019e2a 100644 --- a/homeassistant/components/humidifier/translations/et.json +++ b/homeassistant/components/humidifier/translations/et.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} l\u00fclitus sisse v\u00f5i v\u00e4lja", "target_humidity_changed": "{entity_name} eelseatud niiskus muutus", - "toggled": "{entity_name} l\u00fclitus sisse v\u00f5i v\u00e4lja", "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", "turned_on": "{entity_name} l\u00fclitus sisse" } diff --git a/homeassistant/components/humidifier/translations/fr.json b/homeassistant/components/humidifier/translations/fr.json index 84b24b96904..3229b595881 100644 --- a/homeassistant/components/humidifier/translations/fr.json +++ b/homeassistant/components/humidifier/translations/fr.json @@ -3,7 +3,7 @@ "action_type": { "set_humidity": "R\u00e9gler l'humidit\u00e9 pour {nom_entit\u00e9}", "set_mode": "Changer le mode sur {nom_entit\u00e9}.", - "toggle": "Inverser {nom_entit\u00e9}", + "toggle": "Basculer {entity_name}", "turn_off": "\u00c9teindre {entity_name}", "turn_on": "Allumer {entity_name}" }, @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "target_humidity_changed": "{nom_de_l'entit\u00e9} changement de l'humidit\u00e9 cible", - "toggled": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "turned_off": "{entity_name} s'est \u00e9teint", "turned_on": "{entity_name} s'est allum\u00e9" } diff --git a/homeassistant/components/humidifier/translations/he.json b/homeassistant/components/humidifier/translations/he.json index 152de4c49ca..5a4c58c7934 100644 --- a/homeassistant/components/humidifier/translations/he.json +++ b/homeassistant/components/humidifier/translations/he.json @@ -13,7 +13,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05db\u05d5\u05d1\u05d4", - "toggled": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05db\u05d5\u05d1\u05d4", "turned_off": "{entity_name} \u05db\u05d5\u05d1\u05d4", "turned_on": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc" } diff --git a/homeassistant/components/humidifier/translations/hu.json b/homeassistant/components/humidifier/translations/hu.json index 7979334429d..6572f0e4955 100644 --- a/homeassistant/components/humidifier/translations/hu.json +++ b/homeassistant/components/humidifier/translations/hu.json @@ -15,7 +15,6 @@ "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", "turned_on": "{entity_name} be lett kapcsolva" } diff --git a/homeassistant/components/humidifier/translations/id.json b/homeassistant/components/humidifier/translations/id.json index 1996fd23786..8fd2ccaa9b0 100644 --- a/homeassistant/components/humidifier/translations/id.json +++ b/homeassistant/components/humidifier/translations/id.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} diaktifkan atau dinonaktifkan", "target_humidity_changed": "Kelembapan target {entity_name} berubah", - "toggled": "{entity_name} diaktifkan atau dinonaktifkan", "turned_off": "{entity_name} dimatikan", "turned_on": "{entity_name} dinyalakan" } diff --git a/homeassistant/components/humidifier/translations/it.json b/homeassistant/components/humidifier/translations/it.json index cc0220eca17..7a887c7f0c7 100644 --- a/homeassistant/components/humidifier/translations/it.json +++ b/homeassistant/components/humidifier/translations/it.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} attivata o disattivata", "target_humidity_changed": "{entity_name} umidit\u00e0 target modificata", - "toggled": "{entity_name} attiva o disattiva", "turned_off": "{entity_name} disattivato", "turned_on": "{entity_name} attivato" } diff --git a/homeassistant/components/humidifier/translations/ja.json b/homeassistant/components/humidifier/translations/ja.json index 713049acb52..5c51b58cbfa 100644 --- a/homeassistant/components/humidifier/translations/ja.json +++ b/homeassistant/components/humidifier/translations/ja.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} \u304c\u30aa\u30f3\u307e\u305f\u306f\u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "target_humidity_changed": "{entity_name} \u30bf\u30fc\u30b2\u30c3\u30c8\u6e7f\u5ea6\u304c\u5909\u5316\u3057\u307e\u3057\u305f", - "toggled": "{entity_name} \u304c\u30aa\u30f3\u307e\u305f\u306f\u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "turned_off": "{entity_name} \u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "turned_on": "{entity_name} \u30aa\u30f3\u306b\u306a\u3063\u3066\u3044\u307e\u3059" } diff --git a/homeassistant/components/humidifier/translations/nl.json b/homeassistant/components/humidifier/translations/nl.json index 8d96d698209..4ede3b9cb98 100644 --- a/homeassistant/components/humidifier/translations/nl.json +++ b/homeassistant/components/humidifier/translations/nl.json @@ -10,12 +10,11 @@ "condition_type": { "is_mode": "{entity_name} is ingesteld op een specifieke modus", "is_off": "{entity_name} is uitgeschakeld", - "is_on": "{entity_name} staat aan" + "is_on": "{entity_name} is ingeschakeld" }, "trigger_type": { "changed_states": "{entity_name} in- of uitgeschakeld", "target_humidity_changed": "{entity_name} doel luchtvochtigheid gewijzigd", - "toggled": "{entity_name} in- of uitgeschakeld", "turned_off": "{entity_name} is uitgeschakeld", "turned_on": "{entity_name} is ingeschakeld" } diff --git a/homeassistant/components/humidifier/translations/no.json b/homeassistant/components/humidifier/translations/no.json index dbbe95493f0..276577b81c3 100644 --- a/homeassistant/components/humidifier/translations/no.json +++ b/homeassistant/components/humidifier/translations/no.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} sl\u00e5tt p\u00e5 eller av", "target_humidity_changed": "{entity_name} m\u00e5let fuktighet endret", - "toggled": "{entity_name} sl\u00e5tt p\u00e5 eller av", "turned_off": "{entity_name} sl\u00e5tt av", "turned_on": "{entity_name} sl\u00e5tt p\u00e5" } diff --git a/homeassistant/components/humidifier/translations/pl.json b/homeassistant/components/humidifier/translations/pl.json index 5c82a1e944b..d31db39da8c 100644 --- a/homeassistant/components/humidifier/translations/pl.json +++ b/homeassistant/components/humidifier/translations/pl.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "target_humidity_changed": "zmieni si\u0119 wilgotno\u015b\u0107 docelowa {entity_name}", - "toggled": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "turned_off": "nast\u0105pi wy\u0142\u0105czenie {entity_name}", "turned_on": "nast\u0105pi w\u0142\u0105czenie {entity_name}" } diff --git a/homeassistant/components/humidifier/translations/pt-BR.json b/homeassistant/components/humidifier/translations/pt-BR.json index bb4b6c00177..20b4e663f72 100644 --- a/homeassistant/components/humidifier/translations/pt-BR.json +++ b/homeassistant/components/humidifier/translations/pt-BR.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} for ligado ou desligado", "target_humidity_changed": "{entity_name} tiver a umidade alvo alterada", - "toggled": "{entity_name} for ligado ou desligado", "turned_off": "{entity_name} for desligado", "turned_on": "{entity_name} for ligado" } diff --git a/homeassistant/components/humidifier/translations/ru.json b/homeassistant/components/humidifier/translations/ru.json index 5cbaf6e8e54..fe8026c68b6 100644 --- a/homeassistant/components/humidifier/translations/ru.json +++ b/homeassistant/components/humidifier/translations/ru.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "target_humidity_changed": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u0432\u043b\u0430\u0436\u043d\u043e\u0441\u0442\u0438", - "toggled": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "turned_off": "{entity_name} \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "turned_on": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f" } diff --git a/homeassistant/components/humidifier/translations/sv.json b/homeassistant/components/humidifier/translations/sv.json index 489d8a095a3..325e9f2e6a0 100644 --- a/homeassistant/components/humidifier/translations/sv.json +++ b/homeassistant/components/humidifier/translations/sv.json @@ -1,7 +1,6 @@ { "device_automation": { "trigger_type": { - "toggled": "{entity_name} slogs p\u00e5 eller av", "turned_off": "{entity_name} st\u00e4ngdes av" } } diff --git a/homeassistant/components/humidifier/translations/tr.json b/homeassistant/components/humidifier/translations/tr.json index 41b870d446a..2f2680dce9b 100644 --- a/homeassistant/components/humidifier/translations/tr.json +++ b/homeassistant/components/humidifier/translations/tr.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name} a\u00e7\u0131ld\u0131 veya kapat\u0131ld\u0131", "target_humidity_changed": "{entity_name} hedef nem de\u011fi\u015fti", - "toggled": "{entity_name} a\u00e7\u0131ld\u0131 veya kapat\u0131ld\u0131", "turned_off": "{entity_name} kapat\u0131ld\u0131", "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" } diff --git a/homeassistant/components/humidifier/translations/zh-Hans.json b/homeassistant/components/humidifier/translations/zh-Hans.json index 7a3185728fc..d21c7bf61f7 100644 --- a/homeassistant/components/humidifier/translations/zh-Hans.json +++ b/homeassistant/components/humidifier/translations/zh-Hans.json @@ -14,7 +14,6 @@ }, "trigger_type": { "target_humidity_changed": "{entity_name} \u7684\u8bbe\u5b9a\u6e7f\u5ea6\u53d8\u5316", - "toggled": "{entity_name} \u88ab\u6253\u5f00\u6216\u5173\u95ed", "turned_off": "{entity_name} \u88ab\u5173\u95ed", "turned_on": "{entity_name} \u88ab\u6253\u5f00" } diff --git a/homeassistant/components/humidifier/translations/zh-Hant.json b/homeassistant/components/humidifier/translations/zh-Hant.json index ed851c65795..eaeba2ab8a4 100644 --- a/homeassistant/components/humidifier/translations/zh-Hant.json +++ b/homeassistant/components/humidifier/translations/zh-Hant.json @@ -15,7 +15,6 @@ "trigger_type": { "changed_states": "{entity_name}\u5df2\u958b\u555f\u6216\u95dc\u9589", "target_humidity_changed": "{entity_name}\u8a2d\u5b9a\u6fd5\u5ea6\u5df2\u8b8a\u66f4", - "toggled": "{entity_name}\u5df2\u958b\u555f\u6216\u95dc\u9589", "turned_off": "{entity_name}\u5df2\u95dc\u9589", "turned_on": "{entity_name}\u5df2\u958b\u555f" } diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 2ef1cc46adf..493b5d53639 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -1,4 +1,5 @@ """Support for hunter douglas shades.""" +from abc import abstractmethod import asyncio from contextlib import suppress import logging @@ -8,12 +9,14 @@ from aiopvapi.resources.shade import ( ATTR_POSKIND1, MAX_POSITION, MIN_POSITION, + Silhouette, factory as PvShade, ) import async_timeout from homeassistant.components.cover import ( ATTR_POSITION, + ATTR_TILT_POSITION, CoverDeviceClass, CoverEntity, CoverEntityFeature, @@ -49,6 +52,12 @@ PARALLEL_UPDATES = 1 RESYNC_DELAY = 60 +POSKIND_NONE = 0 +POSKIND_PRIMARY = 1 +POSKIND_SECONDARY = 2 +POSKIND_VANE = 3 +POSKIND_ERROR = 4 + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -82,24 +91,39 @@ async def async_setup_entry( room_id = shade.raw_data.get(ROOM_ID_IN_SHADE) room_name = room_data.get(room_id, {}).get(ROOM_NAME_UNICODE, "") entities.append( - PowerViewShade( + create_powerview_shade_entity( coordinator, device_info, room_name, shade, name_before_refresh ) ) async_add_entities(entities) -def hd_position_to_hass(hd_position): +def create_powerview_shade_entity( + coordinator, device_info, room_name, shade, name_before_refresh +): + """Create a PowerViewShade entity.""" + + if isinstance(shade, Silhouette): + return PowerViewShadeSilhouette( + coordinator, device_info, room_name, shade, name_before_refresh + ) + + return PowerViewShade( + coordinator, device_info, room_name, shade, name_before_refresh + ) + + +def hd_position_to_hass(hd_position, max_val): """Convert hunter douglas position to hass position.""" - return round((hd_position / MAX_POSITION) * 100) + return round((hd_position / max_val) * 100) -def hass_position_to_hd(hass_position): +def hass_position_to_hd(hass_position, max_val): """Convert hass position to hunter douglas position.""" - return int(hass_position / 100 * MAX_POSITION) + return int(hass_position / 100 * max_val) -class PowerViewShade(ShadeEntity, CoverEntity): +class PowerViewShadeBase(ShadeEntity, CoverEntity): """Representation of a powerview shade.""" # The hub frequently reports stale states @@ -113,12 +137,7 @@ class PowerViewShade(ShadeEntity, CoverEntity): self._is_closing = False 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 - ) + self._current_hd_cover_position = MIN_POSITION if self._device_info[DEVICE_MODEL] != LEGACY_DEVICE_MODEL: self._attr_supported_features |= CoverEntityFeature.STOP self._forced_resync = None @@ -131,7 +150,7 @@ class PowerViewShade(ShadeEntity, CoverEntity): @property def is_closed(self): """Return if the cover is closed.""" - return self._current_cover_position == MIN_POSITION + return self._current_hd_cover_position == MIN_POSITION @property def is_opening(self): @@ -146,7 +165,7 @@ class PowerViewShade(ShadeEntity, CoverEntity): @property def current_cover_position(self): """Return the current position of cover.""" - return hd_position_to_hass(self._current_cover_position) + return hd_position_to_hass(self._current_hd_cover_position, MAX_POSITION) @property def device_class(self): @@ -181,14 +200,18 @@ class PowerViewShade(ShadeEntity, CoverEntity): async def _async_move(self, target_hass_position): """Move the shade to a position.""" - current_hass_position = hd_position_to_hass(self._current_cover_position) + current_hass_position = hd_position_to_hass( + self._current_hd_cover_position, MAX_POSITION + ) steps_to_move = abs(current_hass_position - target_hass_position) self._async_schedule_update_for_transition(steps_to_move) self._async_update_from_command( await self._shade.move( { - ATTR_POSITION1: hass_position_to_hd(target_hass_position), - ATTR_POSKIND1: 1, + ATTR_POSITION1: hass_position_to_hd( + target_hass_position, MAX_POSITION + ), + ATTR_POSKIND1: POSKIND_PRIMARY, } ) ) @@ -218,11 +241,15 @@ class PowerViewShade(ShadeEntity, CoverEntity): """Update the current cover position from the data.""" _LOGGER.debug("Raw data update: %s", self._shade.raw_data) position_data = self._shade.raw_data.get(ATTR_POSITION_DATA, {}) - if ATTR_POSITION1 in position_data: - self._current_cover_position = int(position_data[ATTR_POSITION1]) + self._async_process_updated_position_data(position_data) self._is_opening = False self._is_closing = False + @callback + @abstractmethod + def _async_process_updated_position_data(self, position_data): + """Process position data.""" + @callback def _async_cancel_scheduled_transition_update(self): """Cancel any previous updates.""" @@ -299,3 +326,108 @@ class PowerViewShade(ShadeEntity, CoverEntity): return self._async_process_new_shade_data(self.coordinator.data[self._shade.id]) self.async_write_ha_state() + + +class PowerViewShade(PowerViewShadeBase): + """Represent a standard shade.""" + + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) + + @callback + def _async_process_updated_position_data(self, position_data): + """Process position data.""" + if ATTR_POSITION1 in position_data: + self._current_hd_cover_position = int(position_data[ATTR_POSITION1]) + + +class PowerViewShadeWithTilt(PowerViewShade): + """Representation of a PowerView shade with tilt capabilities.""" + + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION + ) + + _max_tilt = MAX_POSITION + _tilt_steps = 10 + + def __init__(self, coordinator, device_info, room_name, shade, name): + """Initialize the shade.""" + super().__init__(coordinator, device_info, room_name, shade, name) + self._attr_current_cover_tilt_position = 0 + + async def async_open_cover_tilt(self, **kwargs): + """Open the cover tilt.""" + current_hass_position = hd_position_to_hass( + self._current_hd_cover_position, MAX_POSITION + ) + steps_to_move = current_hass_position + self._tilt_steps + self._async_schedule_update_for_transition(steps_to_move) + self._async_update_from_command(await self._shade.tilt_open()) + + async def async_close_cover_tilt(self, **kwargs): + """Close the cover tilt.""" + current_hass_position = hd_position_to_hass( + self._current_hd_cover_position, MAX_POSITION + ) + steps_to_move = current_hass_position + self._tilt_steps + self._async_schedule_update_for_transition(steps_to_move) + self._async_update_from_command(await self._shade.tilt_close()) + + async def async_set_cover_tilt_position(self, **kwargs): + """Move the cover tilt to a specific position.""" + target_hass_tilt_position = kwargs[ATTR_TILT_POSITION] + current_hass_position = hd_position_to_hass( + self._current_hd_cover_position, MAX_POSITION + ) + steps_to_move = current_hass_position + self._tilt_steps + + self._async_schedule_update_for_transition(steps_to_move) + self._async_update_from_command( + await self._shade.move( + { + ATTR_POSITION1: hass_position_to_hd( + target_hass_tilt_position, self._max_tilt + ), + ATTR_POSKIND1: POSKIND_VANE, + } + ) + ) + + async def async_stop_cover_tilt(self, **kwargs): + """Stop the cover tilting.""" + # Cancel any previous updates + await self.async_stop_cover() + + @callback + def _async_process_updated_position_data(self, position_data): + """Process position data.""" + if ATTR_POSKIND1 not in position_data: + return + if int(position_data[ATTR_POSKIND1]) == POSKIND_PRIMARY: + self._current_hd_cover_position = int(position_data[ATTR_POSITION1]) + self._attr_current_cover_tilt_position = 0 + if int(position_data[ATTR_POSKIND1]) == POSKIND_VANE: + self._current_hd_cover_position = MIN_POSITION + self._attr_current_cover_tilt_position = hd_position_to_hass( + int(position_data[ATTR_POSITION1]), self._max_tilt + ) + + +class PowerViewShadeSilhouette(PowerViewShadeWithTilt): + """Representation of a Silhouette PowerView shade.""" + + def __init__(self, coordinator, device_info, room_name, shade, name): + """Initialize the shade.""" + super().__init__(coordinator, device_info, room_name, shade, name) + self._max_tilt = 32767 + self._tilt_steps = 4 diff --git a/homeassistant/components/hunterdouglas_powerview/manifest.json b/homeassistant/components/hunterdouglas_powerview/manifest.json index c34f53f47b4..af6aea17de3 100644 --- a/homeassistant/components/hunterdouglas_powerview/manifest.json +++ b/homeassistant/components/hunterdouglas_powerview/manifest.json @@ -3,7 +3,7 @@ "name": "Hunter Douglas PowerView", "documentation": "https://www.home-assistant.io/integrations/hunterdouglas_powerview", "requirements": ["aiopvapi==1.6.19"], - "codeowners": ["@bdraco"], + "codeowners": ["@bdraco", "@trullock"], "config_flow": true, "homekit": { "models": ["PowerView"] diff --git a/homeassistant/components/hunterdouglas_powerview/translations/nl.json b/homeassistant/components/hunterdouglas_powerview/translations/nl.json index 588fa21c813..d8d305383f8 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/nl.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "flow_title": "{name} ({host})", diff --git a/homeassistant/components/hvv_departures/translations/nl.json b/homeassistant/components/hvv_departures/translations/nl.json index 8782499ee05..aab03a0e576 100644 --- a/homeassistant/components/hvv_departures/translations/nl.json +++ b/homeassistant/components/hvv_departures/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "no_results": "Geen resultaten. Probeer het met een ander station/adres" }, diff --git a/homeassistant/components/hyperion/translations/es.json b/homeassistant/components/hyperion/translations/es.json index 5b4534069dd..496064b1543 100644 --- a/homeassistant/components/hyperion/translations/es.json +++ b/homeassistant/components/hyperion/translations/es.json @@ -23,7 +23,7 @@ "description": "Configurar autorizaci\u00f3n a tu servidor Hyperion Ambilight" }, "confirm": { - "description": "\u00bfQuieres a\u00f1adir este Hyperion Ambilight a Home Assistant?\n\n**Host:** {host}\n**Puerto:** {port}\n**Identificaci\u00f3n**: {id}", + "description": "\u00bfQuieres a\u00f1adir el siguiente Hyperion Ambilight en Home Assistant?\n\n**Host:** {host}\n**Puerto:** {port}\n**Identificaci\u00f3n**: {id}", "title": "Confirmar la adici\u00f3n del servicio Hyperion Ambilight" }, "create_token": { diff --git a/homeassistant/components/hyperion/translations/nl.json b/homeassistant/components/hyperion/translations/nl.json index 992d705a533..c3d1684c250 100644 --- a/homeassistant/components/hyperion/translations/nl.json +++ b/homeassistant/components/hyperion/translations/nl.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_configured": "Dienst is al geconfigureerd", + "already_in_progress": "De configuratie is momenteel al bezig", "auth_new_token_not_granted_error": "Nieuw aangemaakte token is niet goedgekeurd in Hyperion UI", "auth_new_token_not_work_error": "Verificatie met nieuw aangemaakt token mislukt", "auth_required_error": "Kan niet bepalen of autorisatie vereist is", "cannot_connect": "Kan geen verbinding maken", "no_id": "De Hyperion Ambilight instantie heeft zijn id niet gerapporteerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/ialarm_xr/__init__.py b/homeassistant/components/ialarm_xr/__init__.py new file mode 100644 index 00000000000..193bbe4fffc --- /dev/null +++ b/homeassistant/components/ialarm_xr/__init__.py @@ -0,0 +1,101 @@ +"""iAlarmXR integration.""" +from __future__ import annotations + +import asyncio +import logging + +from async_timeout import timeout +from pyialarmxr import ( + IAlarmXR, + IAlarmXRGenericException, + IAlarmXRSocketTimeoutException, +) + +from homeassistant.components.alarm_control_panel import SCAN_INTERVAL +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, + Platform, +) +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN +from .utils import async_get_ialarmxr_mac + +PLATFORMS = [Platform.ALARM_CONTROL_PANEL] +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up iAlarmXR config.""" + host = entry.data[CONF_HOST] + port = entry.data[CONF_PORT] + username = entry.data[CONF_USERNAME] + password = entry.data[CONF_PASSWORD] + + ialarmxr = IAlarmXR(username, password, host, port) + + try: + async with timeout(10): + ialarmxr_mac = await async_get_ialarmxr_mac(hass, ialarmxr) + except ( + asyncio.TimeoutError, + ConnectionError, + IAlarmXRGenericException, + IAlarmXRSocketTimeoutException, + ) as ex: + raise ConfigEntryNotReady from ex + + coordinator = IAlarmXRDataUpdateCoordinator(hass, ialarmxr, ialarmxr_mac) + 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 iAlarmXR config.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok + + +class IAlarmXRDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching iAlarmXR data.""" + + def __init__(self, hass: HomeAssistant, ialarmxr: IAlarmXR, mac: str) -> None: + """Initialize global iAlarm data updater.""" + self.ialarmxr: IAlarmXR = ialarmxr + self.state: int | None = None + self.host: str = ialarmxr.host + self.mac: str = mac + + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=SCAN_INTERVAL, + ) + + def _update_data(self) -> None: + """Fetch data from iAlarmXR via sync functions.""" + status: int = self.ialarmxr.get_status() + _LOGGER.debug("iAlarmXR status: %s", status) + + self.state = status + + async def _async_update_data(self) -> None: + """Fetch data from iAlarmXR.""" + try: + async with timeout(10): + await self.hass.async_add_executor_job(self._update_data) + except ConnectionError as error: + raise UpdateFailed(error) from error diff --git a/homeassistant/components/ialarm_xr/alarm_control_panel.py b/homeassistant/components/ialarm_xr/alarm_control_panel.py new file mode 100644 index 00000000000..b64edb74391 --- /dev/null +++ b/homeassistant/components/ialarm_xr/alarm_control_panel.py @@ -0,0 +1,79 @@ +"""Interfaces with iAlarmXR control panels.""" +from __future__ import annotations + +from pyialarmxr import IAlarmXR + +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import IAlarmXRDataUpdateCoordinator +from .const import DOMAIN + +IALARMXR_TO_HASS = { + IAlarmXR.ARMED_AWAY: STATE_ALARM_ARMED_AWAY, + IAlarmXR.ARMED_STAY: STATE_ALARM_ARMED_HOME, + IAlarmXR.DISARMED: STATE_ALARM_DISARMED, + IAlarmXR.TRIGGERED: STATE_ALARM_TRIGGERED, +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up a iAlarmXR alarm control panel based on a config entry.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities([IAlarmXRPanel(coordinator)]) + + +class IAlarmXRPanel( + CoordinatorEntity[IAlarmXRDataUpdateCoordinator], AlarmControlPanelEntity +): + """Representation of an iAlarmXR device.""" + + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) + _attr_name = "iAlarm_XR" + _attr_icon = "mdi:security" + + def __init__(self, coordinator: IAlarmXRDataUpdateCoordinator) -> None: + """Initialize the alarm panel.""" + super().__init__(coordinator) + self._attr_unique_id = coordinator.mac + self._attr_device_info = DeviceInfo( + manufacturer="Antifurto365 - Meian", + name=self.name, + connections={(device_registry.CONNECTION_NETWORK_MAC, coordinator.mac)}, + ) + + @property + def state(self) -> str | None: + """Return the state of the device.""" + return IALARMXR_TO_HASS.get(self.coordinator.state) + + def alarm_disarm(self, code: str | None = None) -> None: + """Send disarm command.""" + self.coordinator.ialarmxr.disarm() + + def alarm_arm_home(self, code: str | None = None) -> None: + """Send arm home command.""" + self.coordinator.ialarmxr.arm_stay() + + def alarm_arm_away(self, code: str | None = None) -> None: + """Send arm away command.""" + self.coordinator.ialarmxr.arm_away() diff --git a/homeassistant/components/ialarm_xr/config_flow.py b/homeassistant/components/ialarm_xr/config_flow.py new file mode 100644 index 00000000000..2a9cc406733 --- /dev/null +++ b/homeassistant/components/ialarm_xr/config_flow.py @@ -0,0 +1,94 @@ +"""Config flow for Antifurto365 iAlarmXR integration.""" +from __future__ import annotations + +import logging +from logging import Logger +from typing import Any + +from pyialarmxr import ( + IAlarmXR, + IAlarmXRGenericException, + IAlarmXRSocketTimeoutException, +) +import voluptuous as vol + +from homeassistant import config_entries, core +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN +from .utils import async_get_ialarmxr_mac + +_LOGGER: Logger = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST, default=IAlarmXR.IALARM_P2P_DEFAULT_HOST): str, + vol.Required(CONF_PORT, default=IAlarmXR.IALARM_P2P_DEFAULT_PORT): int, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } +) + + +async def _async_get_device_formatted_mac( + hass: core.HomeAssistant, username: str, password: str, host: str, port: int +) -> str: + """Return iAlarmXR mac address.""" + + ialarmxr = IAlarmXR(username, password, host, port) + return await async_get_ialarmxr_mac(hass, ialarmxr) + + +class IAlarmConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Antifurto365 iAlarmXR.""" + + VERSION = 1 + + 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: + mac = None + host = user_input[CONF_HOST] + port = user_input[CONF_PORT] + username = user_input[CONF_USERNAME] + password = user_input[CONF_PASSWORD] + + try: + # If we are able to get the MAC address, we are able to establish + # a connection to the device. + mac = await _async_get_device_formatted_mac( + self.hass, username, password, host, port + ) + except ConnectionError: + errors["base"] = "cannot_connect" + except IAlarmXRGenericException as ialarmxr_exception: + _LOGGER.debug( + "IAlarmXRGenericException with message: [ %s ]", + ialarmxr_exception.message, + ) + errors["base"] = "cannot_connect" + except IAlarmXRSocketTimeoutException as ialarmxr_socket_timeout_exception: + _LOGGER.debug( + "IAlarmXRSocketTimeoutException with message: [ %s ]", + ialarmxr_socket_timeout_exception.message, + ) + errors["base"] = "timeout" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + if not errors: + await self.async_set_unique_id(mac) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=user_input[CONF_HOST], data=user_input + ) + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) diff --git a/homeassistant/components/ialarm_xr/const.py b/homeassistant/components/ialarm_xr/const.py new file mode 100644 index 00000000000..12122277340 --- /dev/null +++ b/homeassistant/components/ialarm_xr/const.py @@ -0,0 +1,3 @@ +"""Constants for the iAlarmXR integration.""" + +DOMAIN = "ialarm_xr" diff --git a/homeassistant/components/ialarm_xr/manifest.json b/homeassistant/components/ialarm_xr/manifest.json new file mode 100644 index 00000000000..f863f360242 --- /dev/null +++ b/homeassistant/components/ialarm_xr/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "ialarm_xr", + "name": "Antifurto365 iAlarmXR", + "documentation": "https://www.home-assistant.io/integrations/ialarm_xr", + "requirements": ["pyialarmxr==1.0.18"], + "codeowners": ["@bigmoby"], + "config_flow": true, + "iot_class": "cloud_polling", + "loggers": ["pyialarmxr"] +} diff --git a/homeassistant/components/ialarm_xr/strings.json b/homeassistant/components/ialarm_xr/strings.json new file mode 100644 index 00000000000..ea4f91fdbb9 --- /dev/null +++ b/homeassistant/components/ialarm_xr/strings.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "timeout": "[%key:common::config_flow::error::timeout_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} diff --git a/homeassistant/components/ialarm_xr/translations/en.json b/homeassistant/components/ialarm_xr/translations/en.json new file mode 100644 index 00000000000..be59a5a1dc4 --- /dev/null +++ b/homeassistant/components/ialarm_xr/translations/en.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "timeout": "Timeout establishing connection", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Port", + "username": "Username" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ialarm_xr/utils.py b/homeassistant/components/ialarm_xr/utils.py new file mode 100644 index 00000000000..db82a3fcd44 --- /dev/null +++ b/homeassistant/components/ialarm_xr/utils.py @@ -0,0 +1,18 @@ +"""iAlarmXR utils.""" +import logging + +from pyialarmxr import IAlarmXR + +from homeassistant import core +from homeassistant.helpers.device_registry import format_mac + +_LOGGER = logging.getLogger(__name__) + + +async def async_get_ialarmxr_mac(hass: core.HomeAssistant, ialarmxr: IAlarmXR) -> str: + """Retrieve iAlarmXR MAC address.""" + _LOGGER.debug("Retrieving ialarmxr mac address") + + mac = await hass.async_add_executor_job(ialarmxr.get_mac) + + return format_mac(mac) diff --git a/homeassistant/components/iaqualink/__init__.py b/homeassistant/components/iaqualink/__init__.py index 030bb8cdcc8..02e27d8e82f 100644 --- a/homeassistant/components/iaqualink/__init__.py +++ b/homeassistant/components/iaqualink/__init__.py @@ -2,8 +2,10 @@ from __future__ import annotations import asyncio +from collections.abc import Awaitable, Callable, Coroutine from functools import wraps import logging +from typing import Any, TypeVar import aiohttp.client_exceptions from iaqualink.client import AqualinkClient @@ -16,9 +18,8 @@ from iaqualink.device import ( AqualinkToggle, ) from iaqualink.exception import AqualinkServiceException -import voluptuous as vol +from typing_extensions import Concatenate, ParamSpec -from homeassistant import config_entries from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN @@ -29,59 +30,31 @@ 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 -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, UPDATE_INTERVAL +_AqualinkEntityT = TypeVar("_AqualinkEntityT", bound="AqualinkEntity") +_P = ParamSpec("_P") + _LOGGER = logging.getLogger(__name__) ATTR_CONFIG = "config" PARALLEL_UPDATES = 0 PLATFORMS = [ - BINARY_SENSOR_DOMAIN, - CLIMATE_DOMAIN, - LIGHT_DOMAIN, - SENSOR_DOMAIN, - SWITCH_DOMAIN, + Platform.BINARY_SENSOR, + Platform.CLIMATE, + Platform.LIGHT, + Platform.SENSOR, + Platform.SWITCH, ] -CONFIG_SCHEMA = vol.Schema( - vol.All( - cv.deprecated(DOMAIN), - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - } - ) - }, - ), - extra=vol.ALLOW_EXTRA, -) - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the Aqualink component.""" - if (conf := config.get(DOMAIN)) is not None: - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=conf, - ) - ) - - return True - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Aqualink from a config entry.""" @@ -193,11 +166,15 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return await hass.config_entries.async_unload_platforms(entry, platforms_to_unload) -def refresh_system(func): +def refresh_system( + func: Callable[Concatenate[_AqualinkEntityT, _P], Awaitable[Any]] +) -> Callable[Concatenate[_AqualinkEntityT, _P], Coroutine[Any, Any, None]]: """Force update all entities after state change.""" @wraps(func) - async def wrapper(self, *args, **kwargs): + async def wrapper( + self: _AqualinkEntityT, *args: _P.args, **kwargs: _P.kwargs + ) -> None: """Call decorated function and send update signal to all entities.""" await func(self, *args, **kwargs) async_dispatcher_send(self.hass, DOMAIN) diff --git a/homeassistant/components/iaqualink/config_flow.py b/homeassistant/components/iaqualink/config_flow.py index 921102b85dc..3b3a99cac6e 100644 --- a/homeassistant/components/iaqualink/config_flow.py +++ b/homeassistant/components/iaqualink/config_flow.py @@ -12,6 +12,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -21,7 +22,9 @@ class AqualinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_user(self, user_input: dict[str, Any] | None = None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow start.""" # Supporting a single account. entries = self._async_current_entries() @@ -54,7 +57,3 @@ class AqualinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ), errors=errors, ) - - async def async_step_import(self, user_input: dict[str, Any] | None = None): - """Occurs when an entry is setup through config.""" - return await self.async_step_user(user_input) diff --git a/homeassistant/components/iaqualink/translations/es.json b/homeassistant/components/iaqualink/translations/es.json index 71dc95c82f5..c95f9d51927 100644 --- a/homeassistant/components/iaqualink/translations/es.json +++ b/homeassistant/components/iaqualink/translations/es.json @@ -13,8 +13,8 @@ "password": "Contrase\u00f1a", "username": "Usuario" }, - "description": "Por favor, introduzca el nombre de usuario y la contrase\u00f1a de su cuenta de iAqualink.", - "title": "Con\u00e9ctese a iAqualink" + "description": "Introduce el nombre de usuario y contrase\u00f1a de tu cuenta de iAqualink.", + "title": "Conexi\u00f3n con iAqualink" } } } diff --git a/homeassistant/components/icloud/__init__.py b/homeassistant/components/icloud/__init__.py index 17cc15b195a..8175cf43f27 100644 --- a/homeassistant/components/icloud/__init__.py +++ b/homeassistant/components/icloud/__init__.py @@ -5,6 +5,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant, ServiceCall import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.storage import Store from homeassistant.util import slugify from .account import IcloudAccount @@ -81,7 +82,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if entry.unique_id is None: hass.config_entries.async_update_entry(entry, unique_id=username) - icloud_dir = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + icloud_dir = Store(hass, STORAGE_VERSION, STORAGE_KEY) account = IcloudAccount( hass, diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py index f3630abfdaa..a4c29acff75 100644 --- a/homeassistant/components/icloud/config_flow.py +++ b/homeassistant/components/icloud/config_flow.py @@ -13,6 +13,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.helpers.storage import Store from .const import ( CONF_GPS_ACCURACY_THRESHOLD, @@ -113,7 +114,7 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): PyiCloudService, self._username, self._password, - self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY).path, + Store(self.hass, STORAGE_VERSION, STORAGE_KEY).path, True, None, self._with_family, @@ -162,7 +163,7 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a flow initiated by the user.""" errors = {} - icloud_dir = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + icloud_dir = Store(self.hass, STORAGE_VERSION, STORAGE_KEY) if not os.path.exists(icloud_dir.path): await self.hass.async_add_executor_job(os.makedirs, icloud_dir.path) @@ -276,9 +277,7 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): PyiCloudService, self._username, self._password, - self.hass.helpers.storage.Store( - STORAGE_VERSION, STORAGE_KEY - ).path, + Store(self.hass, STORAGE_VERSION, STORAGE_KEY).path, True, None, self._with_family, diff --git a/homeassistant/components/icloud/translations/es.json b/homeassistant/components/icloud/translations/es.json index 593f2753d99..31db2283f66 100644 --- a/homeassistant/components/icloud/translations/es.json +++ b/homeassistant/components/icloud/translations/es.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "no_device": "Ninguno de tus dispositivos tiene activado \"Buscar mi iPhone\"", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "send_verification_code": "Error al enviar el c\u00f3digo de verificaci\u00f3n", - "validate_verification_code": "No se pudo verificar el c\u00f3digo de verificaci\u00f3n, elegir un dispositivo de confianza e iniciar la verificaci\u00f3n de nuevo" + "validate_verification_code": "No se ha podido verificar el c\u00f3digo de verificaci\u00f3n, vuelve a intentarlo" }, "step": { "reauth": { diff --git a/homeassistant/components/icloud/translations/nl.json b/homeassistant/components/icloud/translations/nl.json index 7260f954c38..522df78a737 100644 --- a/homeassistant/components/icloud/translations/nl.json +++ b/homeassistant/components/icloud/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Account is al geconfigureerd", "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie", @@ -16,7 +16,7 @@ "password": "Wachtwoord" }, "description": "Uw eerder ingevoerde wachtwoord voor {username} werkt niet meer. Update uw wachtwoord om deze integratie te blijven gebruiken.", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "trusted_device": { "data": { diff --git a/homeassistant/components/ifttt/translations/fr.json b/homeassistant/components/ifttt/translations/fr.json index 371bbd65ee0..280c031ea39 100644 --- a/homeassistant/components/ifttt/translations/fr.json +++ b/homeassistant/components/ifttt/translations/fr.json @@ -10,7 +10,7 @@ }, "step": { "user": { - "description": "\u00cates-vous s\u00fbr de vouloir configurer IFTTT?", + "description": "Voulez-vous vraiment configurer IFTTT\u00a0?", "title": "Configurer l'applet IFTTT Webhook" } } diff --git a/homeassistant/components/ifttt/translations/nl.json b/homeassistant/components/ifttt/translations/nl.json index 727b21f43be..93cdd225b98 100644 --- a/homeassistant/components/ifttt/translations/nl.json +++ b/homeassistant/components/ifttt/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." + "webhook_not_internet_accessible": "Je Home Assistant-instantie moet toegankelijk zijn vanaf internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Om evenementen naar de Home Assistant te verzenden, moet u de actie \"Een webverzoek doen\" gebruiken vanuit de [IFTTT Webhook-applet]({applet_url}). \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nZie [the documentation]({docs_url}) voor informatie over het configureren van automatiseringen om inkomende gegevens te verwerken." diff --git a/homeassistant/components/image/manifest.json b/homeassistant/components/image/manifest.json index 4ddba8f31e0..d2e08b77b23 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.1.0"], + "requirements": ["pillow==9.1.1"], "dependencies": ["http"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal" diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index d0d87e0b2d5..a8bd394a159 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -17,12 +17,14 @@ from homeassistant.const import ( CONF_PORT, CONF_USERNAME, CONF_VALUE_TEMPLATE, + CONF_VERIFY_SSL, CONTENT_TYPE_TEXT_PLAIN, ) 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.ssl import client_context _LOGGER = logging.getLogger(__name__) @@ -46,6 +48,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_FOLDER, default="INBOX"): cv.string, + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, } ) @@ -58,11 +61,12 @@ def setup_platform( ) -> None: """Set up the Email sensor platform.""" reader = EmailReader( - config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - config.get(CONF_SERVER), - config.get(CONF_PORT), - config.get(CONF_FOLDER), + config[CONF_USERNAME], + config[CONF_PASSWORD], + config[CONF_SERVER], + config[CONF_PORT], + config[CONF_FOLDER], + config[CONF_VERIFY_SSL], ) if (value_template := config.get(CONF_VALUE_TEMPLATE)) is not None: @@ -70,8 +74,8 @@ def setup_platform( sensor = EmailContentSensor( hass, reader, - config.get(CONF_NAME) or config.get(CONF_USERNAME), - config.get(CONF_SENDERS), + config.get(CONF_NAME) or config[CONF_USERNAME], + config[CONF_SENDERS], value_template, ) @@ -82,21 +86,25 @@ def setup_platform( class EmailReader: """A class to read emails from an IMAP server.""" - def __init__(self, user, password, server, port, folder): + def __init__(self, user, password, server, port, folder, verify_ssl): """Initialize the Email Reader.""" self._user = user self._password = password self._server = server self._port = port self._folder = folder + self._verify_ssl = verify_ssl self._last_id = None self._unread_ids = deque([]) self.connection = None def connect(self): """Login and setup the connection.""" + ssl_context = client_context() if self._verify_ssl else None try: - self.connection = imaplib.IMAP4_SSL(self._server, self._port) + self.connection = imaplib.IMAP4_SSL( + self._server, self._port, ssl_context=ssl_context + ) self.connection.login(self._user, self._password) return True except imaplib.IMAP4.error: diff --git a/homeassistant/components/input_datetime/translations/es.json b/homeassistant/components/input_datetime/translations/es.json index 025943ee4fb..570c7b17592 100644 --- a/homeassistant/components/input_datetime/translations/es.json +++ b/homeassistant/components/input_datetime/translations/es.json @@ -1,3 +1,3 @@ { - "title": "Entrada de fecha" + "title": "Entrada de data i hora" } \ No newline at end of file diff --git a/homeassistant/components/input_number/translations/es.json b/homeassistant/components/input_number/translations/es.json index e05a9130580..e31e0ccc72f 100644 --- a/homeassistant/components/input_number/translations/es.json +++ b/homeassistant/components/input_number/translations/es.json @@ -1,3 +1,3 @@ { - "title": "Entrada de n\u00famero" + "title": "Entrada num\u00e9rica" } \ No newline at end of file diff --git a/homeassistant/components/input_number/translations/tr.json b/homeassistant/components/input_number/translations/tr.json index c1805180884..8d85abf5c15 100644 --- a/homeassistant/components/input_number/translations/tr.json +++ b/homeassistant/components/input_number/translations/tr.json @@ -1,3 +1,3 @@ { - "title": "Numara giriniz" + "title": "Numara giri\u015fi" } \ No newline at end of file diff --git a/homeassistant/components/insteon/api/aldb.py b/homeassistant/components/insteon/api/aldb.py index a3132570ccc..a119707b03d 100644 --- a/homeassistant/components/insteon/api/aldb.py +++ b/homeassistant/components/insteon/api/aldb.py @@ -12,6 +12,7 @@ import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import device_registry as dr from ..const import DEVICE_ADDRESS, ID, INSTEON_DEVICE_NOT_FOUND, TYPE from .device import async_device_name, notify_device_not_found @@ -82,7 +83,7 @@ async def websocket_get_aldb( aldb.update(device.aldb.pending_changes) changed_records = list(device.aldb.pending_changes.keys()) - dev_registry = await hass.helpers.device_registry.async_get_registry() + dev_registry = dr.async_get(hass) records = [ await async_aldb_record_to_dict( diff --git a/homeassistant/components/insteon/api/device.py b/homeassistant/components/insteon/api/device.py index beef78394fa..ea6bade4835 100644 --- a/homeassistant/components/insteon/api/device.py +++ b/homeassistant/components/insteon/api/device.py @@ -6,6 +6,7 @@ import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import device_registry as dr from ..const import ( DEVICE_ADDRESS, @@ -68,7 +69,7 @@ async def websocket_get_device( msg: dict, ) -> None: """Get an Insteon device.""" - dev_registry = await hass.helpers.device_registry.async_get_registry() + dev_registry = dr.async_get(hass) if not (ha_device := dev_registry.async_get(msg[DEVICE_ID])): notify_device_not_found(connection, msg, HA_DEVICE_NOT_FOUND) return diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py index 833180583e2..97e21a02f6f 100644 --- a/homeassistant/components/insteon/climate.py +++ b/homeassistant/components/insteon/climate.py @@ -9,8 +9,7 @@ from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN as CLIMATE_DOMAIN, - HVAC_MODE_AUTO, - HVAC_MODE_FAN_ONLY, + FAN_AUTO, ClimateEntityFeature, HVACAction, HVACMode, @@ -25,6 +24,8 @@ from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity from .utils import async_add_insteon_entities +FAN_ONLY = "fan_only" + COOLING = 1 HEATING = 2 DEHUMIDIFYING = 3 @@ -46,7 +47,7 @@ HVAC_MODES = { 2: HVACMode.COOL, 3: HVACMode.HEAT_COOL, } -FAN_MODES = {4: HVAC_MODE_AUTO, 8: HVAC_MODE_FAN_ONLY} +FAN_MODES = {4: FAN_AUTO, 8: FAN_ONLY} async def async_setup_entry( diff --git a/homeassistant/components/insteon/translations/bg.json b/homeassistant/components/insteon/translations/bg.json index 65bf4bad59a..e1adb8657da 100644 --- a/homeassistant/components/insteon/translations/bg.json +++ b/homeassistant/components/insteon/translations/bg.json @@ -8,7 +8,11 @@ "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", "select_single": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0435\u0434\u043d\u0430 \u043e\u043f\u0446\u0438\u044f." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 {name}?" + }, "hubv1": { "data": { "host": "IP \u0430\u0434\u0440\u0435\u0441", diff --git a/homeassistant/components/insteon/translations/ca.json b/homeassistant/components/insteon/translations/ca.json index 9805d03c685..f8e0e1c853e 100644 --- a/homeassistant/components/insteon/translations/ca.json +++ b/homeassistant/components/insteon/translations/ca.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Tipus de m\u00f2dem." }, - "description": "Selecciona el tipus de m\u00f2dem Insteon.", - "title": "Insteon" + "description": "Selecciona el tipus de m\u00f2dem Insteon." } } }, @@ -61,8 +60,7 @@ "cat": "Categoria del dispositiu (ex: 0x10)", "subcat": "Subcategoria del dispositiu (ex: 0x0a)" }, - "description": "Afegeix una substituci\u00f3 de dispositiu.", - "title": "Insteon" + "description": "Afegeix una substituci\u00f3 de dispositiu." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Passos del regulador d'intensitat (nom\u00e9s per a llums, 22 per defecte)", "unitcode": "Unitcode (1-16)" }, - "description": "Canvia la contrasenya de l'Insteon Hub.", - "title": "Insteon" + "description": "Canvia la contrasenya de l'Insteon Hub." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Port", "username": "Nom d'usuari" }, - "description": "Canvia la informaci\u00f3 de connexi\u00f3 de l'Insteon Hub. Has de reiniciar Home Assistant si fas canvis. Aix\u00f2 no canvia la configuraci\u00f3 del Hub en si. Per canviar la configuraci\u00f3 del Hub, utilitza la seva aplicaci\u00f3.", - "title": "Insteon" + "description": "Canvia la informaci\u00f3 de connexi\u00f3 de l'Insteon Hub. Has de reiniciar Home Assistant si fas canvis. Aix\u00f2 no canvia la configuraci\u00f3 del Hub en si. Per canviar la configuraci\u00f3 del Hub, utilitza la seva aplicaci\u00f3." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Canvia la configuraci\u00f3 del Hub.", "remove_override": "Elimina una substituci\u00f3 de dispositiu.", "remove_x10": "Elimina un dispositiu X10." - }, - "description": "Selecciona una opci\u00f3 a configurar.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Selecciona una adre\u00e7a de dispositiu a eliminar" }, - "description": "Elimina una substituci\u00f3 de dispositiu", - "title": "Insteon" + "description": "Elimina una substituci\u00f3 de dispositiu" }, "remove_x10": { "data": { "address": "Selecciona una adre\u00e7a de dispositiu a eliminar" }, - "description": "Elimina un dispositiu X10", - "title": "Insteon" + "description": "Elimina un dispositiu X10" } } } diff --git a/homeassistant/components/insteon/translations/cs.json b/homeassistant/components/insteon/translations/cs.json index 18d7ff1c999..e10e25e6361 100644 --- a/homeassistant/components/insteon/translations/cs.json +++ b/homeassistant/components/insteon/translations/cs.json @@ -37,8 +37,7 @@ "data": { "modem_type": "Typ modemu." }, - "description": "Vyberte typ modemu Insteon.", - "title": "Insteon" + "description": "Vyberte typ modemu Insteon." } } }, @@ -53,8 +52,7 @@ "data": { "address": "Adresa za\u0159\u00edzen\u00ed (tj. 1a2b3c)", "cat": "Kategorie za\u0159\u00edzen\u00ed (tj. 0x10)" - }, - "title": "Insteon" + } }, "add_x10": { "data": { @@ -62,8 +60,7 @@ "platform": "Platforma", "steps": "Kroky stm\u00edva\u010de (pouze pro sv\u011btla, v\u00fdchoz\u00ed 22)", "unitcode": "K\u00f3d jednotky (1-16)" - }, - "title": "Insteon" + } }, "change_hub_config": { "data": { @@ -72,29 +69,24 @@ "port": "Port", "username": "U\u017eivatelsk\u00e9 jm\u00e9no" }, - "description": "Zm\u011b\u0148te informace o p\u0159ipojen\u00ed rozbo\u010dova\u010de Insteon. Po proveden\u00ed t\u00e9to zm\u011bny mus\u00edte Home Assistant restartovat. T\u00edm se nezm\u011bn\u00ed nastaven\u00ed samotn\u00e9ho rozbo\u010dova\u010de. Chcete-li zm\u011bnit nastaven\u00ed rozbo\u010dova\u010de, pou\u017eijte jeho aplikaci.", - "title": "Insteon" + "description": "Zm\u011b\u0148te informace o p\u0159ipojen\u00ed rozbo\u010dova\u010de Insteon. Po proveden\u00ed t\u00e9to zm\u011bny mus\u00edte Home Assistant restartovat. T\u00edm se nezm\u011bn\u00ed nastaven\u00ed samotn\u00e9ho rozbo\u010dova\u010de. Chcete-li zm\u011bnit nastaven\u00ed rozbo\u010dova\u010de, pou\u017eijte jeho aplikaci." }, "init": { "data": { "add_x10": "P\u0159idejte za\u0159\u00edzen\u00ed X10.", "remove_x10": "Odeberte za\u0159\u00edzen\u00ed X10." - }, - "description": "Vyberte mo\u017enost k nastaven\u00ed.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Vyberte adresu za\u0159\u00edzen\u00ed, kter\u00e9 chcete odebrat" - }, - "title": "Insteon" + } }, "remove_x10": { "data": { "address": "Vyberte adresu za\u0159\u00edzen\u00ed, kter\u00e9 chcete odebrat" }, - "description": "Odeberte za\u0159\u00edzen\u00ed X10", - "title": "Insteon" + "description": "Odeberte za\u0159\u00edzen\u00ed X10" } } } diff --git a/homeassistant/components/insteon/translations/de.json b/homeassistant/components/insteon/translations/de.json index 164fbccceed..d7f853d9a2a 100644 --- a/homeassistant/components/insteon/translations/de.json +++ b/homeassistant/components/insteon/translations/de.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Modemtyp." }, - "description": "W\u00e4hle den Insteon-Modemtyp aus.", - "title": "Insteon" + "description": "W\u00e4hle den Insteon-Modemtyp aus." } } }, @@ -61,8 +60,7 @@ "cat": "Ger\u00e4tekategorie (z. B. 0x10)", "subcat": "Ger\u00e4teunterkategorie (z. B. 0x0a)" }, - "description": "F\u00fcge eine Ger\u00e4te\u00fcberschreibung hinzu.", - "title": "Insteon" + "description": "F\u00fcge eine Ger\u00e4te\u00fcberschreibung hinzu." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Dimmerstufen (nur f\u00fcr Lichtger\u00e4te, Voreinstellung 22)", "unitcode": "Unitcode (1 - 16)" }, - "description": "\u00c4ndere das Insteon Hub-Passwort.", - "title": "Insteon" + "description": "\u00c4ndere das Insteon Hub-Passwort." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Port", "username": "Benutzername" }, - "description": "\u00c4ndere die Verbindungsinformationen des Insteon-Hubs. Du musst Home Assistant neu starten, nachdem du diese \u00c4nderung vorgenommen hast. Dies \u00e4ndert nicht die Konfiguration des Hubs selbst. Um die Konfiguration im Hub zu \u00e4ndern, verwende die Hub-App.", - "title": "Insteon" + "description": "\u00c4ndere die Verbindungsinformationen des Insteon-Hubs. Du musst Home Assistant neu starten, nachdem du diese \u00c4nderung vorgenommen hast. Dies \u00e4ndert nicht die Konfiguration des Hubs selbst. Um die Konfiguration im Hub zu \u00e4ndern, verwende die Hub-App." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "\u00c4ndere die Konfiguration des Hubs.", "remove_override": "Entferne eine Ger\u00e4te\u00fcbersteuerung.", "remove_x10": "Entferne ein X10-Ger\u00e4t." - }, - "description": "W\u00e4hle eine Option zum Konfigurieren aus.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "W\u00e4hle eine Ger\u00e4teadresse zum Entfernen" }, - "description": "Entfernen einer Ger\u00e4te\u00fcbersteuerung", - "title": "Insteon" + "description": "Entfernen einer Ger\u00e4te\u00fcbersteuerung" }, "remove_x10": { "data": { "address": "W\u00e4hle eine Ger\u00e4teadresse zum Entfernen" }, - "description": "Ein X10-Ger\u00e4t entfernen", - "title": "Insteon" + "description": "Ein X10-Ger\u00e4t entfernen" } } } diff --git a/homeassistant/components/insteon/translations/el.json b/homeassistant/components/insteon/translations/el.json index 4a4c402ddc0..9ff9e025f8f 100644 --- a/homeassistant/components/insteon/translations/el.json +++ b/homeassistant/components/insteon/translations/el.json @@ -43,8 +43,7 @@ "data": { "modem_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc." }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c4\u03cd\u03c0\u03bf \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc Insteon.", - "title": "Insteon" + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c4\u03cd\u03c0\u03bf \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc Insteon." } } }, @@ -61,8 +60,7 @@ "cat": "\u039a\u03b1\u03c4\u03b7\u03b3\u03bf\u03c1\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 (\u03c0.\u03c7. 0x10)", "subcat": "\u03a5\u03c0\u03bf\u03ba\u03b1\u03c4\u03b7\u03b3\u03bf\u03c1\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 (\u03c0.\u03c7. 0x0a)" }, - "description": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", - "title": "Insteon" + "description": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "\u0392\u03ae\u03bc\u03b1\u03c4\u03b1 \u03c1\u03bf\u03bf\u03c3\u03c4\u03ac\u03c4\u03b7 (\u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c6\u03c9\u03c4\u03b9\u03c3\u03bc\u03bf\u03cd, \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae 22)", "unitcode": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1\u03c2 (1 - 16)" }, - "description": "\u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Insteon Hub.", - "title": "Insteon" + "description": "\u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Insteon Hub." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "\u0398\u03cd\u03c1\u03b1", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, - "description": "\u0391\u03bb\u03bb\u03ac\u03be\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\u03bf\u03c5 Insteon Hub. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2. \u0391\u03c5\u03c4\u03cc \u03b4\u03b5\u03bd \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03af\u03b4\u03b9\u03bf\u03c5 \u03c4\u03bf\u03c5 Hub. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c4\u03bf Hub \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 Hub.", - "title": "Insteon" + "description": "\u0391\u03bb\u03bb\u03ac\u03be\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\u03bf\u03c5 Insteon Hub. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03b1\u03b3\u03bc\u03b1\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2. \u0391\u03c5\u03c4\u03cc \u03b4\u03b5\u03bd \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03af\u03b4\u03b9\u03bf\u03c5 \u03c4\u03bf\u03c5 Hub. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c4\u03bf Hub \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 Hub." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "\u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Hub.", "remove_override": "\u039a\u03b1\u03c4\u03ac\u03c1\u03b3\u03b7\u03c3\u03b7 \u03bc\u03b9\u03b1\u03c2 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", "remove_x10": "\u0391\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae X10." - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03ba\u03b1\u03c4\u03ac\u03c1\u03b3\u03b7\u03c3\u03b7" }, - "description": "\u039a\u03b1\u03c4\u03ac\u03c1\u03b3\u03b7\u03c3\u03b7 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", - "title": "Insteon" + "description": "\u039a\u03b1\u03c4\u03ac\u03c1\u03b3\u03b7\u03c3\u03b7 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" }, "remove_x10": { "data": { "address": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03ba\u03b1\u03c4\u03ac\u03c1\u03b3\u03b7\u03c3\u03b7" }, - "description": "\u039a\u03b1\u03c4\u03ac\u03c1\u03b3\u03b7\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 X10", - "title": "Insteon" + "description": "\u039a\u03b1\u03c4\u03ac\u03c1\u03b3\u03b7\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 X10" } } } diff --git a/homeassistant/components/insteon/translations/en.json b/homeassistant/components/insteon/translations/en.json index f9e64dcf3cf..441f5cf576a 100644 --- a/homeassistant/components/insteon/translations/en.json +++ b/homeassistant/components/insteon/translations/en.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Modem type." }, - "description": "Select the Insteon modem type.", - "title": "Insteon" + "description": "Select the Insteon modem type." } } }, @@ -61,8 +60,7 @@ "cat": "Device category (i.e. 0x10)", "subcat": "Device subcategory (i.e. 0x0a)" }, - "description": "Add a device override.", - "title": "Insteon" + "description": "Add a device override." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Dimmer steps (for light devices only, default 22)", "unitcode": "Unitcode (1 - 16)" }, - "description": "Change the Insteon Hub password.", - "title": "Insteon" + "description": "Change the Insteon Hub password." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Port", "username": "Username" }, - "description": "Change the Insteon Hub connection information. You must restart Home Assistant after making this change. This does not change the configuration of the Hub itself. To change the configuration in the Hub use the Hub app.", - "title": "Insteon" + "description": "Change the Insteon Hub connection information. You must restart Home Assistant after making this change. This does not change the configuration of the Hub itself. To change the configuration in the Hub use the Hub app." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Change the Hub configuration.", "remove_override": "Remove a device override.", "remove_x10": "Remove an X10 device." - }, - "description": "Select an option to configure.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Select a device address to remove" }, - "description": "Remove a device override", - "title": "Insteon" + "description": "Remove a device override" }, "remove_x10": { "data": { "address": "Select a device address to remove" }, - "description": "Remove an X10 device", - "title": "Insteon" + "description": "Remove an X10 device" } } } diff --git a/homeassistant/components/insteon/translations/es.json b/homeassistant/components/insteon/translations/es.json index 31088b3bf60..5434bc77a8a 100644 --- a/homeassistant/components/insteon/translations/es.json +++ b/homeassistant/components/insteon/translations/es.json @@ -1,14 +1,19 @@ { "config": { "abort": { - "cannot_connect": "No se puede conectar al m\u00f3dem Insteon", + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", + "not_insteon_device": "El dispositivo descubierto no es un dispositivo Insteon", "single_instance_allowed": "Ya esta configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "error": { "cannot_connect": "No se conect\u00f3 al m\u00f3dem Insteon, por favor, int\u00e9ntelo de nuevo.", "select_single": "Seleccione una opci\u00f3n." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "\u00bfQuieres configurar {name}?" + }, "hubv1": { "data": { "host": "Direcci\u00f3n IP", @@ -38,8 +43,7 @@ "data": { "modem_type": "Tipo de m\u00f3dem." }, - "description": "Seleccione el tipo de m\u00f3dem Insteon.", - "title": "Insteon" + "description": "Seleccione el tipo de m\u00f3dem Insteon." } } }, @@ -56,8 +60,7 @@ "cat": "Categor\u00eda del dispositivo (es decir, 0x10)", "subcat": "Subcategor\u00eda del dispositivo (es decir, 0x0a)" }, - "description": "Agregue una anulaci\u00f3n del dispositivo.", - "title": "Insteon" + "description": "Agregue una anulaci\u00f3n del dispositivo." }, "add_x10": { "data": { @@ -66,8 +69,7 @@ "steps": "Pasos de atenuaci\u00f3n (s\u00f3lo para dispositivos de luz, por defecto 22)", "unitcode": "Unitcode (1 - 16)" }, - "description": "Cambie la contrase\u00f1a del Hub Insteon.", - "title": "Insteon" + "description": "Cambie la contrase\u00f1a del Hub Insteon." }, "change_hub_config": { "data": { @@ -76,8 +78,7 @@ "port": "Puerto", "username": "Usuario" }, - "description": "Cambiar la informaci\u00f3n de la conexi\u00f3n del Hub Insteon. Debes reiniciar el Home Assistant despu\u00e9s de hacer este cambio. Esto no cambia la configuraci\u00f3n del Hub en s\u00ed. Para cambiar la configuraci\u00f3n del Hub usa la aplicaci\u00f3n Hub.", - "title": "Insteon" + "description": "Cambiar la informaci\u00f3n de la conexi\u00f3n del Hub Insteon. Debes reiniciar el Home Assistant despu\u00e9s de hacer este cambio. Esto no cambia la configuraci\u00f3n del Hub en s\u00ed. Para cambiar la configuraci\u00f3n del Hub usa la aplicaci\u00f3n Hub." }, "init": { "data": { @@ -86,23 +87,19 @@ "change_hub_config": "Cambie la configuraci\u00f3n del Hub.", "remove_override": "Eliminar una anulaci\u00f3n del dispositivo.", "remove_x10": "Eliminar un dispositivo X10" - }, - "description": "Seleccione una opci\u00f3n para configurar.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Seleccione una direcci\u00f3n del dispositivo para eliminar" }, - "description": "Eliminar una anulaci\u00f3n del dispositivo", - "title": "Insteon" + "description": "Eliminar una anulaci\u00f3n del dispositivo" }, "remove_x10": { "data": { "address": "Seleccione la direcci\u00f3n del dispositivo para eliminar" }, - "description": "Eliminar un dispositivo X10", - "title": "Insteon" + "description": "Eliminar un dispositivo X10" } } } diff --git a/homeassistant/components/insteon/translations/et.json b/homeassistant/components/insteon/translations/et.json index eff2fd9b9b2..72f2a9ac5c4 100644 --- a/homeassistant/components/insteon/translations/et.json +++ b/homeassistant/components/insteon/translations/et.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Modemi mudel." }, - "description": "Vali Insteoni modemi mudel.", - "title": "" + "description": "Vali Insteoni modemi mudel." } } }, @@ -61,8 +60,7 @@ "cat": "Seadme kategooria (n\u00e4iteks 0x10)", "subcat": "Seadme alamkategooria (n\u00e4iteks 0x0a)" }, - "description": "Lisa seadme alistamine.", - "title": "" + "description": "Lisa seadme alistamine." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "H\u00e4mardamise samm (ainult valgustite jaoks, vaikimisi 22)", "unitcode": "" }, - "description": "Muuda Insteon Hubi parooli.", - "title": "" + "description": "Muuda Insteon Hubi parooli." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "", "username": "Kasutajanimi" }, - "description": "Muutda Insteon Hubi \u00fchenduse teavet. P\u00e4rast selle muudatuse tegemist pead Home Assistanti taask\u00e4ivitama. See ei muuda jaoturi enda konfiguratsiooni. Hubis muudatuste tegemiseks kasuta rakendust Hub.", - "title": "" + "description": "Muutda Insteon Hubi \u00fchenduse teavet. P\u00e4rast selle muudatuse tegemist pead Home Assistanti taask\u00e4ivitama. See ei muuda jaoturi enda konfiguratsiooni. Hubis muudatuste tegemiseks kasuta rakendust Hub." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Muuda jaoturi konfiguratsiooni.", "remove_override": "Seadme alistamise eemaldamine.", "remove_x10": "Eemalda X10 seade." - }, - "description": "Vali seadistussuvand.", - "title": "" + } }, "remove_override": { "data": { "address": "Vali eemaldatava seadme aadress" }, - "description": "Seadme alistamise eemaldamine", - "title": "" + "description": "Seadme alistamise eemaldamine" }, "remove_x10": { "data": { "address": "Vali eemaldatava seadme aadress" }, - "description": "Eemalda X10 seade", - "title": "" + "description": "Eemalda X10 seade" } } } diff --git a/homeassistant/components/insteon/translations/fr.json b/homeassistant/components/insteon/translations/fr.json index 2bb0e4d2e33..e9570aaf7e2 100644 --- a/homeassistant/components/insteon/translations/fr.json +++ b/homeassistant/components/insteon/translations/fr.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Type de modem." }, - "description": "S\u00e9lectionnez le type de modem Insteon.", - "title": "Insteon" + "description": "S\u00e9lectionnez le type de modem Insteon." } } }, @@ -61,8 +60,7 @@ "cat": "Cat\u00e9gorie d'appareil (c.-\u00e0-d. 0x10)", "subcat": "Sous-cat\u00e9gorie d'appareil (par exemple 0x0a)" }, - "description": "Ajouter un remplacement d'appareil.", - "title": "Insteon" + "description": "Ajouter un remplacement d'appareil." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Pas de gradateur (pour les appareils d'\u00e9clairage uniquement, par d\u00e9faut 22)", "unitcode": "Code de l'unit\u00e9 (1-16)" }, - "description": "Modifiez le mot de passe Insteon Hub.", - "title": "Insteon" + "description": "Modifiez le mot de passe Insteon Hub." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Port", "username": "Nom d'utilisateur" }, - "description": "Modifiez les informations de connexion Insteon Hub. Vous devez red\u00e9marrer Home Assistant apr\u00e8s avoir effectu\u00e9 cette modification. Cela ne change pas la configuration du Hub lui-m\u00eame. Pour modifier la configuration dans le Hub, utilisez l'application Hub.", - "title": "Insteon" + "description": "Modifiez les informations de connexion Insteon Hub. Vous devez red\u00e9marrer Home Assistant apr\u00e8s avoir effectu\u00e9 cette modification. Cela ne change pas la configuration du Hub lui-m\u00eame. Pour modifier la configuration dans le Hub, utilisez l'application Hub." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Modifier la configuration du Hub.", "remove_override": "Supprimer un remplacement d'appareil", "remove_x10": "Retirer un appareil X10." - }, - "description": "S\u00e9lectionnez une option \u00e0 configurer.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "S\u00e9lectionnez l'adresse d'un appareil \u00e0 retirer" }, - "description": "Supprimer un remplacement d'appareil", - "title": "Insteon" + "description": "Supprimer un remplacement d'appareil" }, "remove_x10": { "data": { "address": "S\u00e9lectionnez l'adresse d'un appareil \u00e0 retirer" }, - "description": "Retirer un appareil X10", - "title": "Insteon" + "description": "Retirer un appareil X10" } } } diff --git a/homeassistant/components/insteon/translations/hu.json b/homeassistant/components/insteon/translations/hu.json index 560c1fc118e..8ba8e3223fa 100644 --- a/homeassistant/components/insteon/translations/hu.json +++ b/homeassistant/components/insteon/translations/hu.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Modem t\u00edpusa." }, - "description": "V\u00e1lassza ki az Insteon modem t\u00edpus\u00e1t.", - "title": "Insteon" + "description": "V\u00e1lassza ki az Insteon modem t\u00edpus\u00e1t." } } }, @@ -61,8 +60,7 @@ "cat": "Eszk\u00f6zkateg\u00f3ria (pl. 0x10)", "subcat": "Eszk\u00f6z alkateg\u00f3ria (pl. 0x0a)" }, - "description": "Eszk\u00f6z-fel\u00fclb\u00edr\u00e1l\u00e1s hozz\u00e1ad\u00e1sa.", - "title": "Insteon" + "description": "Eszk\u00f6z-fel\u00fclb\u00edr\u00e1l\u00e1s hozz\u00e1ad\u00e1sa." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "F\u00e9nyer\u0151-szab\u00e1lyoz\u00e1si l\u00e9p\u00e9sek (csak k\u00f6nny\u0171 eszk\u00f6z\u00f6k eset\u00e9n, alap\u00e9rtelmezett 22)", "unitcode": "Egys\u00e9gk\u00f3d (1 - 16)" }, - "description": "M\u00f3dos\u00edtsa az Insteon Hub jelszav\u00e1t.", - "title": "Insteon" + "description": "M\u00f3dos\u00edtsa az Insteon Hub jelszav\u00e1t." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Port", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, - "description": "M\u00f3dos\u00edtsa az Insteon Hub csatlakoz\u00e1si adatait. A m\u00f3dos\u00edt\u00e1s elv\u00e9gz\u00e9se ut\u00e1n \u00fajra kell ind\u00edtania a Home Assistant alkalmaz\u00e1st. Ez nem v\u00e1ltoztatja meg a Hub konfigur\u00e1ci\u00f3j\u00e1t. A Hub konfigur\u00e1ci\u00f3j\u00e1nak m\u00f3dos\u00edt\u00e1s\u00e1hoz haszn\u00e1lja a Hub alkalmaz\u00e1st.", - "title": "Insteon" + "description": "M\u00f3dos\u00edtsa az Insteon Hub csatlakoz\u00e1si adatait. A m\u00f3dos\u00edt\u00e1s elv\u00e9gz\u00e9se ut\u00e1n \u00fajra kell ind\u00edtania a Home Assistant alkalmaz\u00e1st. Ez nem v\u00e1ltoztatja meg a Hub konfigur\u00e1ci\u00f3j\u00e1t. A Hub konfigur\u00e1ci\u00f3j\u00e1nak m\u00f3dos\u00edt\u00e1s\u00e1hoz haszn\u00e1lja a Hub alkalmaz\u00e1st." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "M\u00f3dos\u00edtsa a Hub konfigur\u00e1ci\u00f3j\u00e1t.", "remove_override": "Egy eszk\u00f6z fel\u00fclb\u00edr\u00e1lat\u00e1nak elt\u00e1vol\u00edt\u00e1sa.", "remove_x10": "T\u00e1vol\u00edtson el egy X10 eszk\u00f6zt." - }, - "description": "V\u00e1lasszon egy be\u00e1ll\u00edt\u00e1st.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "V\u00e1lassza ki az elt\u00e1vol\u00edtani k\u00edv\u00e1nt eszk\u00f6z c\u00edm\u00e9t" }, - "description": "T\u00e1vol\u00edtsa el az eszk\u00f6z fel\u00fclb\u00edr\u00e1l\u00e1s\u00e1t", - "title": "Insteon" + "description": "T\u00e1vol\u00edtsa el az eszk\u00f6z fel\u00fclb\u00edr\u00e1l\u00e1s\u00e1t" }, "remove_x10": { "data": { "address": "V\u00e1lassza ki az elt\u00e1vol\u00edtani k\u00edv\u00e1nt eszk\u00f6z c\u00edm\u00e9t" }, - "description": "T\u00e1vol\u00edtson el egy X10 eszk\u00f6zt", - "title": "Insteon" + "description": "T\u00e1vol\u00edtson el egy X10 eszk\u00f6zt" } } } diff --git a/homeassistant/components/insteon/translations/id.json b/homeassistant/components/insteon/translations/id.json index aae7e9f71ac..7520c6cd03d 100644 --- a/homeassistant/components/insteon/translations/id.json +++ b/homeassistant/components/insteon/translations/id.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Jenis modem." }, - "description": "Pilih jenis modem Insteon.", - "title": "Insteon" + "description": "Pilih jenis modem Insteon." } } }, @@ -61,8 +60,7 @@ "cat": "Kategori perangkat (mis. 0x10)", "subcat": "Subkategori perangkat (mis. 0x0a)" }, - "description": "Tambahkan penimpaan nilai perangkat.", - "title": "Insteon" + "description": "Tambahkan penimpaan nilai perangkat." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Langkah peredup (hanya untuk perangkat ringan, nilai bawaan adalah 22)", "unitcode": "Unitcode (1 - 16)" }, - "description": "Ubah kata sandi Insteon Hub.", - "title": "Insteon" + "description": "Ubah kata sandi Insteon Hub." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Port", "username": "Nama Pengguna" }, - "description": "Ubah informasi koneksi Insteon Hub. Anda harus memulai ulang Home Assistant setelah melakukan perubahan ini. Ini tidak mengubah konfigurasi Hub itu sendiri. Untuk mengubah konfigurasi di Hub, gunakan aplikasi Hub.", - "title": "Insteon" + "description": "Ubah informasi koneksi Insteon Hub. Anda harus memulai ulang Home Assistant setelah melakukan perubahan ini. Ini tidak mengubah konfigurasi Hub itu sendiri. Untuk mengubah konfigurasi di Hub, gunakan aplikasi Hub." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Ubah konfigurasi Hub.", "remove_override": "Hapus penimpaan nilai perangkat.", "remove_x10": "Hapus perangkat X10." - }, - "description": "Pilih opsi untuk dikonfigurasi.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Pilih alamat perangkat untuk dihapus" }, - "description": "Hapus penimpaan nilai perangkat", - "title": "Insteon" + "description": "Hapus penimpaan nilai perangkat" }, "remove_x10": { "data": { "address": "Pilih alamat perangkat untuk dihapus" }, - "description": "Hapus perangkat X10", - "title": "Insteon" + "description": "Hapus perangkat X10" } } } diff --git a/homeassistant/components/insteon/translations/it.json b/homeassistant/components/insteon/translations/it.json index b47a06c9d7a..c5bd0f1af87 100644 --- a/homeassistant/components/insteon/translations/it.json +++ b/homeassistant/components/insteon/translations/it.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Tipo di modem." }, - "description": "Seleziona il tipo di modem Insteon.", - "title": "Insteon" + "description": "Seleziona il tipo di modem Insteon." } } }, @@ -61,8 +60,7 @@ "cat": "Categoria del dispositivo (ad es. 0x10)", "subcat": "Sottocategoria del dispositivo (ad es. 0x0a)" }, - "description": "Aggiungi una sostituzione del dispositivo.", - "title": "Insteon" + "description": "Aggiungi una sostituzione del dispositivo." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Livelli del dimmer (solo per dispositivi luminosi, predefiniti 22)", "unitcode": "Codice unit\u00e0 (1 - 16)" }, - "description": "Cambia la password di Insteon Hub.", - "title": "Insteon" + "description": "Cambia la password di Insteon Hub." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Porta", "username": "Nome utente" }, - "description": "Modifica le informazioni di connessione di Insteon Hub. \u00c8 necessario riavviare Home Assistant dopo aver apportato questa modifica. Ci\u00f2 non modifica la configurazione dell'Hub stesso. Per modificare la configurazione nell'Hub, utilizza l'app Hub.", - "title": "Insteon" + "description": "Modifica le informazioni di connessione di Insteon Hub. \u00c8 necessario riavviare Home Assistant dopo aver apportato questa modifica. Ci\u00f2 non modifica la configurazione dell'Hub stesso. Per modificare la configurazione nell'Hub, utilizza l'app Hub." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Modifica la configurazione dell'hub.", "remove_override": "Rimuovere una sostituzione del dispositivo.", "remove_x10": "Rimuovi un dispositivo X10." - }, - "description": "Seleziona un'opzione da configurare.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Seleziona un indirizzo del dispositivo da rimuovere" }, - "description": "Rimuovere una sostituzione del dispositivo", - "title": "Insteon" + "description": "Rimuovere una sostituzione del dispositivo" }, "remove_x10": { "data": { "address": "Seleziona un indirizzo del dispositivo da rimuovere" }, - "description": "Rimuovi un dispositivo X10", - "title": "Insteon" + "description": "Rimuovi un dispositivo X10" } } } diff --git a/homeassistant/components/insteon/translations/ja.json b/homeassistant/components/insteon/translations/ja.json index 0b8d93518bd..812f9c0bfd6 100644 --- a/homeassistant/components/insteon/translations/ja.json +++ b/homeassistant/components/insteon/translations/ja.json @@ -2,6 +2,7 @@ "config": { "abort": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "not_insteon_device": "\u691c\u51fa\u3055\u308c\u305f\u30c7\u30d0\u30a4\u30b9\u306f\u3001Insteon\u30c7\u30d0\u30a4\u30b9\u3067\u306f\u306a\u3044", "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": { @@ -42,8 +43,7 @@ "data": { "modem_type": "\u30e2\u30c7\u30e0\u306e\u7a2e\u985e\u3002" }, - "description": "Insteon modem type\u3092\u9078\u629e\u3057\u307e\u3059\u3002", - "title": "Insteon" + "description": "Insteon modem type\u3092\u9078\u629e\u3057\u307e\u3059\u3002" } } }, @@ -60,8 +60,7 @@ "cat": "\u30c7\u30d0\u30a4\u30b9\u30ab\u30c6\u30b4\u30ea\u30fc(\u4f8b: 0x10)", "subcat": "\u30c7\u30d0\u30a4\u30b9 \u30b5\u30d6\u30ab\u30c6\u30b4\u30ea\u30fc(\u4f8b: 0x0a)" }, - "description": "\u30c7\u30d0\u30a4\u30b9\u3092\u8ffd\u52a0\u3057\u3066\u4e0a\u66f8\u304d\u3057\u307e\u3059\u3002", - "title": "Insteon" + "description": "\u30c7\u30d0\u30a4\u30b9\u3092\u8ffd\u52a0\u3057\u3066\u4e0a\u66f8\u304d\u3057\u307e\u3059\u3002" }, "add_x10": { "data": { @@ -70,8 +69,7 @@ "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)" }, - "description": "Insteon Hub\u306e\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5909\u66f4\u3059\u308b\u3002", - "title": "Insteon" + "description": "Insteon Hub\u306e\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5909\u66f4\u3059\u308b\u3002" }, "change_hub_config": { "data": { @@ -80,8 +78,7 @@ "port": "\u30dd\u30fc\u30c8", "username": "\u30e6\u30fc\u30b6\u30fc\u540d" }, - "description": "Insteon Hub\u306e\u63a5\u7d9a\u60c5\u5831\u3092\u5909\u66f4\u3057\u307e\u3059\u3002\u3053\u306e\u5909\u66f4\u3092\u884c\u3063\u305f\u5f8c\u3001Home Assistant\u3092\u518d\u8d77\u52d5\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\u3053\u308c\u306f\u3001Hub\u81ea\u4f53\u306e\u8a2d\u5b9a\u3092\u5909\u66f4\u3059\u308b\u3082\u306e\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002Hub\u306e\u8a2d\u5b9a\u3092\u5909\u66f4\u3059\u308b\u306b\u306f\u3001Hub\u30a2\u30d7\u30ea\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002", - "title": "Insteon" + "description": "Insteon Hub\u306e\u63a5\u7d9a\u60c5\u5831\u3092\u5909\u66f4\u3057\u307e\u3059\u3002\u3053\u306e\u5909\u66f4\u3092\u884c\u3063\u305f\u5f8c\u3001Home Assistant\u3092\u518d\u8d77\u52d5\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\u3053\u308c\u306f\u3001Hub\u81ea\u4f53\u306e\u8a2d\u5b9a\u3092\u5909\u66f4\u3059\u308b\u3082\u306e\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002Hub\u306e\u8a2d\u5b9a\u3092\u5909\u66f4\u3059\u308b\u306b\u306f\u3001Hub\u30a2\u30d7\u30ea\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002" }, "init": { "data": { @@ -90,23 +87,19 @@ "change_hub_config": "Hub\u306e\u8a2d\u5b9a\u3092\u5909\u66f4\u3057\u307e\u3059\u3002", "remove_override": "\u30c7\u30d0\u30a4\u30b9\u3092\u524a\u9664\u3057\u3066\u4e0a\u66f8\u304d\u3057\u307e\u3059\u3002", "remove_x10": "X10\u30c7\u30d0\u30a4\u30b9\u524a\u9664\u3092\u524a\u9664\u3057\u307e\u3059\u3002" - }, - "description": "\u8a2d\u5b9a\u3059\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e\u3057\u307e\u3059\u3002", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "\u524a\u9664\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u306e\u30a2\u30c9\u30ec\u30b9\u3092\u9078\u629e" }, - "description": "\u30c7\u30d0\u30a4\u30b9\u3092\u524a\u9664\u3057\u3066\u4e0a\u66f8\u304d\u3057\u307e\u3059", - "title": "Insteon" + "description": "\u30c7\u30d0\u30a4\u30b9\u3092\u524a\u9664\u3057\u3066\u4e0a\u66f8\u304d\u3057\u307e\u3059" }, "remove_x10": { "data": { "address": "\u524a\u9664\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u306e\u30a2\u30c9\u30ec\u30b9\u3092\u9078\u629e" }, - "description": "X10\u30c7\u30d0\u30a4\u30b9\u306e\u524a\u9664", - "title": "Insteon" + "description": "X10\u30c7\u30d0\u30a4\u30b9\u306e\u524a\u9664" } } } diff --git a/homeassistant/components/insteon/translations/ko.json b/homeassistant/components/insteon/translations/ko.json index 559e3351932..927839a6080 100644 --- a/homeassistant/components/insteon/translations/ko.json +++ b/homeassistant/components/insteon/translations/ko.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "not_insteon_device": "\ubc1c\uacac\ub41c \uc7a5\uce58\uac00 Insteon \uc7a5\uce58\uac00 \uc544\ub2d9\ub2c8\ub2e4", "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "select_single": "\ud558\ub098\uc758 \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "{name} \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + }, "hubv1": { "data": { "host": "IP \uc8fc\uc18c", @@ -38,8 +43,7 @@ "data": { "modem_type": "\ubaa8\ub380 \uc720\ud615." }, - "description": "Insteon \ubaa8\ub380 \uc720\ud615\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", - "title": "Insteon" + "description": "Insteon \ubaa8\ub380 \uc720\ud615\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694." } } }, @@ -56,8 +60,7 @@ "cat": "\uae30\uae30 \ubc94\uc8fc (\uc608: 0x10)", "subcat": "\uae30\uae30 \ud558\uc704 \ubc94\uc8fc (\uc608: 0x0a)" }, - "description": "\uae30\uae30 \uc7ac\uc815\uc758\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.", - "title": "Insteon" + "description": "\uae30\uae30 \uc7ac\uc815\uc758\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4." }, "add_x10": { "data": { @@ -66,8 +69,7 @@ "steps": "\ubc1d\uae30 \uc870\uc808 \ub2e8\uacc4 (\uc870\uba85 \uae30\uae30 \uc804\uc6a9, \uae30\ubcf8\uac12 22)", "unitcode": "\uc720\ub2db \ucf54\ub4dc (1-16)" }, - "description": "Insteon Hub \ube44\ubc00\ubc88\ud638\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4.", - "title": "Insteon" + "description": "Insteon Hub \ube44\ubc00\ubc88\ud638\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4." }, "change_hub_config": { "data": { @@ -76,8 +78,7 @@ "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "Insteon \ud5c8\ube0c\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4. \ubcc0\uacbd\ud55c \ud6c4\uc5d0\ub294 Home Assistant\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4. \ud5c8\ube0c \uc790\uccb4\uc758 \uad6c\uc131\uc740 \ubcc0\uacbd\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ud5c8\ube0c\uc758 \uad6c\uc131\uc744 \ubcc0\uacbd\ud558\ub824\uba74 \ud5c8\ube0c \uc571\uc744 \uc0ac\uc6a9\ud574\uc8fc\uc138\uc694.", - "title": "Insteon" + "description": "Insteon \ud5c8\ube0c\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4. \ubcc0\uacbd\ud55c \ud6c4\uc5d0\ub294 Home Assistant\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4. \ud5c8\ube0c \uc790\uccb4\uc758 \uad6c\uc131\uc740 \ubcc0\uacbd\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ud5c8\ube0c\uc758 \uad6c\uc131\uc744 \ubcc0\uacbd\ud558\ub824\uba74 \ud5c8\ube0c \uc571\uc744 \uc0ac\uc6a9\ud574\uc8fc\uc138\uc694." }, "init": { "data": { @@ -86,23 +87,19 @@ "change_hub_config": "\ud5c8\ube0c \uad6c\uc131\uc744 \ubcc0\uacbd\ud569\ub2c8\ub2e4.", "remove_override": "\uae30\uae30 \uc7ac\uc815\uc758\ub97c \uc81c\uac70\ud569\ub2c8\ub2e4.", "remove_x10": "X10 \uae30\uae30\ub97c \uc81c\uac70\ud569\ub2c8\ub2e4." - }, - "description": "\uad6c\uc131\ud560 \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "\uc81c\uac70\ud560 \uae30\uae30\uc758 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694" }, - "description": "\uae30\uae30 \uc7ac\uc815\uc758 \uc81c\uac70\ud558\uae30", - "title": "Insteon" + "description": "\uae30\uae30 \uc7ac\uc815\uc758 \uc81c\uac70\ud558\uae30" }, "remove_x10": { "data": { "address": "\uc81c\uac70\ud560 \uae30\uae30\uc758 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694" }, - "description": "X10 \uae30\uae30 \uc81c\uac70\ud558\uae30", - "title": "Insteon" + "description": "X10 \uae30\uae30 \uc81c\uac70\ud558\uae30" } } } diff --git a/homeassistant/components/insteon/translations/lb.json b/homeassistant/components/insteon/translations/lb.json index ea8bd295900..f5ebf3ea19f 100644 --- a/homeassistant/components/insteon/translations/lb.json +++ b/homeassistant/components/insteon/translations/lb.json @@ -38,8 +38,7 @@ "data": { "modem_type": "Typ vu Modem." }, - "description": "Insteon Modem Typ auswielen.", - "title": "Insteon" + "description": "Insteon Modem Typ auswielen." } } }, @@ -56,8 +55,7 @@ "cat": "Apparat Kategorie (Beispill 0x10)", "subcat": "Apparat \u00cbnnerkategorie (Beispill 0x0a)" }, - "description": "Apparat iwwerschr\u00e9iwen dob\u00e4isetzen", - "title": "Insteon" + "description": "Apparat iwwerschr\u00e9iwen dob\u00e4isetzen" }, "add_x10": { "data": { @@ -66,8 +64,7 @@ "steps": "Dimmer Schr\u00ebtt (n\u00ebmme fir Luuchten, standard 22)", "unitcode": "Unitcode (1-16)" }, - "description": "Insteon Hub passwuert \u00e4nneren", - "title": "Insteon" + "description": "Insteon Hub passwuert \u00e4nneren" }, "change_hub_config": { "data": { @@ -76,8 +73,7 @@ "port": "Port", "username": "Benotzernumm" }, - "description": "Insteon Hub Verbindungs Informatiounen \u00e4nneren. Du muss Home Assistant no der \u00c4nnerung fr\u00ebsch starten. D\u00ebst \u00e4nnert net d'Konfiguratioun vum Hub selwer. Fir d'Konfiguratioun vum Hub ze \u00e4nnere benotz d'Hub App.", - "title": "Insteon" + "description": "Insteon Hub Verbindungs Informatiounen \u00e4nneren. Du muss Home Assistant no der \u00c4nnerung fr\u00ebsch starten. D\u00ebst \u00e4nnert net d'Konfiguratioun vum Hub selwer. Fir d'Konfiguratioun vum Hub ze \u00e4nnere benotz d'Hub App." }, "init": { "data": { @@ -86,23 +82,19 @@ "change_hub_config": "Hub Konfiguratioun \u00e4nneren.", "remove_override": "Apparat iwwerschr\u00e9iwen l\u00e4schen", "remove_x10": "Een X10 Apparat l\u00e4schen." - }, - "description": "Eng Optioun auswielen fir ze konfigur\u00e9ieren", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Eng Apparat Adress auswielen fir ze l\u00e4schen" }, - "description": "Apparat iwwerschr\u00e9iwen l\u00e4schen", - "title": "Insteon" + "description": "Apparat iwwerschr\u00e9iwen l\u00e4schen" }, "remove_x10": { "data": { "address": "Wiel eng Adress vun egem Apparat aus fir ze l\u00e4schen" }, - "description": "Een X10 Apparat l\u00e4schen", - "title": "Insteon" + "description": "Een X10 Apparat l\u00e4schen" } } } diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index 207e370e245..3d05a8ed011 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "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", + "cannot_connect": "Kan geen verbinding maken", "select_single": "Selecteer een optie." }, "flow_title": "{name}", "step": { "confirm_usb": { - "description": "Wilt u {name} instellen?" + "description": "Wil je {name} instellen?" }, "hubv1": { "data": { @@ -43,14 +43,13 @@ "data": { "modem_type": "Modemtype." }, - "description": "Selecteer het Insteon-modemtype.", - "title": "Insteon" + "description": "Selecteer het Insteon-modemtype." } } }, "options": { "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "input_error": "Ongeldige invoer, controleer uw waarden.", "select_single": "Selecteer \u00e9\u00e9n optie." }, @@ -61,8 +60,7 @@ "cat": "Apparaatcategorie (bijv. 0x10)", "subcat": "Apparaatsubcategorie (bijv. 0x0a)" }, - "description": "Voeg een apparaat overschrijven toe.", - "title": "Insteon" + "description": "Voeg een apparaat overschrijven toe." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Dimmerstappen (alleen voor verlichtingsapparaten, standaard 22)", "unitcode": "Unitcode (1 - 16)" }, - "description": "Wijzig het wachtwoord van de Insteon Hub.", - "title": "Insteon" + "description": "Wijzig het wachtwoord van de Insteon Hub." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Poort", "username": "Gebruikersnaam" }, - "description": "Wijzig de verbindingsgegevens van de Insteon Hub. Je moet Home Assistant opnieuw opstarten nadat je deze wijziging hebt aangebracht. Dit verandert niets aan de configuratie van de Hub zelf. Gebruik de Hub-app om de configuratie in de Hub te wijzigen.", - "title": "Insteon" + "description": "Wijzig de verbindingsgegevens van de Insteon Hub. Je moet Home Assistant opnieuw opstarten nadat je deze wijziging hebt aangebracht. Dit verandert niets aan de configuratie van de Hub zelf. Gebruik de Hub-app om de configuratie in de Hub te wijzigen." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Wijzig de Hub-configuratie.", "remove_override": "Verwijder een apparaatoverschrijving.", "remove_x10": "Verwijder een X10-apparaat." - }, - "description": "Selecteer een optie om te configureren.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Selecteer een apparaatadres om te verwijderen" }, - "description": "Verwijder een apparaatoverschrijving", - "title": "Insteon" + "description": "Verwijder een apparaatoverschrijving" }, "remove_x10": { "data": { "address": "Selecteer een apparaatadres om te verwijderen" }, - "description": "Verwijder een X10 apparaat", - "title": "Insteon" + "description": "Verwijder een X10 apparaat" } } } diff --git a/homeassistant/components/insteon/translations/no.json b/homeassistant/components/insteon/translations/no.json index 3716312b00f..70ecd17fb21 100644 --- a/homeassistant/components/insteon/translations/no.json +++ b/homeassistant/components/insteon/translations/no.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Modemtype." }, - "description": "Velg Insteon modemtype.", - "title": "" + "description": "Velg Insteon modemtype." } } }, @@ -61,8 +60,7 @@ "cat": "Enhetskategori (dvs. 0x10)", "subcat": "Underkategori for enhet (dvs. 0x0a)" }, - "description": "Legg til en enhetsoverstyring.", - "title": "" + "description": "Legg til en enhetsoverstyring." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Dimmer trinn (kun for lette enheter, standard 22)", "unitcode": "Enhetskode (1 - 16)" }, - "description": "Endre insteon hub-passordet.", - "title": "" + "description": "Endre insteon hub-passordet." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Port", "username": "Brukernavn" }, - "description": "Endre Insteon Hub-tilkoblingsinformasjonen. Du m\u00e5 starte Home Assistant p\u00e5 nytt n\u00e5r du har gjort denne endringen. Dette endrer ikke konfigurasjonen av selve huben. For \u00e5 endre konfigurasjonen i huben bruker du hub-appen.", - "title": "" + "description": "Endre Insteon Hub-tilkoblingsinformasjonen. Du m\u00e5 starte Home Assistant p\u00e5 nytt n\u00e5r du har gjort denne endringen. Dette endrer ikke konfigurasjonen av selve huben. For \u00e5 endre konfigurasjonen i huben bruker du hub-appen." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Endre hub-konfigurasjonen.", "remove_override": "Fjern en enhet overstyring.", "remove_x10": "Fjern en X10-enhet." - }, - "description": "Velg et alternativ for \u00e5 konfigurere.", - "title": "" + } }, "remove_override": { "data": { "address": "Velg en enhetsadresse du vil fjerne" }, - "description": "Fjerne en enhetsoverstyring", - "title": "" + "description": "Fjerne en enhetsoverstyring" }, "remove_x10": { "data": { "address": "Velg en enhetsadresse du vil fjerne" }, - "description": "Fjern en X10-enhet", - "title": "" + "description": "Fjern en X10-enhet" } } } diff --git a/homeassistant/components/insteon/translations/pl.json b/homeassistant/components/insteon/translations/pl.json index 6d3b6c4391d..9cadcfa955c 100644 --- a/homeassistant/components/insteon/translations/pl.json +++ b/homeassistant/components/insteon/translations/pl.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Typ modemu." }, - "description": "Wybierz typ modemu Insteon.", - "title": "Insteon" + "description": "Wybierz typ modemu Insteon." } } }, @@ -61,8 +60,7 @@ "cat": "Kategoria urz\u0105dzenia (np. 0x10)", "subcat": "Podkategoria urz\u0105dzenia (np. 0x0a)" }, - "description": "Dodawanie nadpisanie urz\u0105dzenia.", - "title": "Insteon" + "description": "Dodawanie nadpisanie urz\u0105dzenia." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Kroki \u015bciemniacza (tylko dla urz\u0105dze\u0144 o\u015bwietleniowych, domy\u015blnie 22)", "unitcode": "Unitcode (1\u201316)" }, - "description": "Zmie\u0144 has\u0142o Huba Insteon.", - "title": "Insteon" + "description": "Zmie\u0144 has\u0142o Huba Insteon." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Port", "username": "Nazwa u\u017cytkownika" }, - "description": "Zmie\u0144 informacje o po\u0142\u0105czeniu Huba Insteon. Po wprowadzeniu tej zmiany musisz ponownie uruchomi\u0107 Home Assistanta. Nie zmienia to konfiguracji samego Huba. Aby zmieni\u0107 jego konfiguracj\u0119, u\u017cyj aplikacji Hub.", - "title": "Insteon" + "description": "Zmie\u0144 informacje o po\u0142\u0105czeniu Huba Insteon. Po wprowadzeniu tej zmiany musisz ponownie uruchomi\u0107 Home Assistanta. Nie zmienia to konfiguracji samego Huba. Aby zmieni\u0107 jego konfiguracj\u0119, u\u017cyj aplikacji Hub." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Zmie\u0144 konfiguracj\u0119 Huba.", "remove_override": "Usu\u0144 nadpisanie urz\u0105dzenia.", "remove_x10": "Usu\u0144 urz\u0105dzenie X10." - }, - "description": "Wybierz opcj\u0119 do skonfigurowania.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Wybierz adres urz\u0105dzenia do usuni\u0119cia" }, - "description": "Usu\u0144 nadpisanie urz\u0105dzenia", - "title": "Insteon" + "description": "Usu\u0144 nadpisanie urz\u0105dzenia" }, "remove_x10": { "data": { "address": "Wybierz adres urz\u0105dzenia do usuni\u0119cia" }, - "description": "Usu\u0144 urz\u0105dzenie X10", - "title": "Insteon" + "description": "Usu\u0144 urz\u0105dzenie X10" } } } diff --git a/homeassistant/components/insteon/translations/pt-BR.json b/homeassistant/components/insteon/translations/pt-BR.json index b5487d9260a..e03bd8a55c0 100644 --- a/homeassistant/components/insteon/translations/pt-BR.json +++ b/homeassistant/components/insteon/translations/pt-BR.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Tipo de modem." }, - "description": "Selecione o tipo de modem Insteon.", - "title": "Insteon" + "description": "Selecione o tipo de modem Insteon." } } }, @@ -61,8 +60,7 @@ "cat": "Subcategoria de dispositivo (ou seja, 0x10)", "subcat": "Subcategoria de dispositivo (ou seja, 0x0a)" }, - "description": "Escolha um dispositivo para sobrescrever", - "title": "Insteon" + "description": "Escolha um dispositivo para sobrescrever" }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Etapas de dimmer (apenas para dispositivos de lux, padr\u00e3o 22)", "unitcode": "C\u00f3digo de unidade (1 - 16)" }, - "description": "Altere a senha do Insteon Hub.", - "title": "Insteon" + "description": "Altere a senha do Insteon Hub." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Porta", "username": "Usu\u00e1rio" }, - "description": "Altere as informa\u00e7\u00f5es de conex\u00e3o do Hub Insteon. Voc\u00ea deve reiniciar o Home Assistant depois de fazer essa altera\u00e7\u00e3o. Isso n\u00e3o altera a configura\u00e7\u00e3o do pr\u00f3prio Hub. Para alterar a configura\u00e7\u00e3o no Hub, use o aplicativo Hub.", - "title": "Insteon" + "description": "Altere as informa\u00e7\u00f5es de conex\u00e3o do Hub Insteon. Voc\u00ea deve reiniciar o Home Assistant depois de fazer essa altera\u00e7\u00e3o. Isso n\u00e3o altera a configura\u00e7\u00e3o do pr\u00f3prio Hub. Para alterar a configura\u00e7\u00e3o no Hub, use o aplicativo Hub." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Altere a configura\u00e7\u00e3o do Hub.", "remove_override": "Remova uma substitui\u00e7\u00e3o de dispositivo.", "remove_x10": "Remova um dispositivo X10." - }, - "description": "Selecione uma op\u00e7\u00e3o para configurar.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Selecione um endere\u00e7o de dispositivo para remover" }, - "description": "Remover uma substitui\u00e7\u00e3o de dispositivo", - "title": "Insteon" + "description": "Remover uma substitui\u00e7\u00e3o de dispositivo" }, "remove_x10": { "data": { "address": "Selecione um endere\u00e7o de dispositivo para remover" }, - "description": "Remover um dispositivo X10", - "title": "Insteon" + "description": "Remover um dispositivo X10" } } } diff --git a/homeassistant/components/insteon/translations/pt.json b/homeassistant/components/insteon/translations/pt.json index e25fe7db5bd..1a281774ffe 100644 --- a/homeassistant/components/insteon/translations/pt.json +++ b/homeassistant/components/insteon/translations/pt.json @@ -35,8 +35,7 @@ "data": { "modem_type": "Tipo de modem." }, - "description": "Selecione o tipo de modem Insteon.", - "title": "Insteon" + "description": "Selecione o tipo de modem Insteon." } } }, @@ -62,8 +61,7 @@ "init": { "data": { "remove_x10": "Remova um dispositivo X10." - }, - "description": "Selecione uma op\u00e7\u00e3o para configurar." + } } } } diff --git a/homeassistant/components/insteon/translations/ru.json b/homeassistant/components/insteon/translations/ru.json index 3151fa35252..a95d3a51afc 100644 --- a/homeassistant/components/insteon/translations/ru.json +++ b/homeassistant/components/insteon/translations/ru.json @@ -43,8 +43,7 @@ "data": { "modem_type": "\u0422\u0438\u043f \u043c\u043e\u0434\u0435\u043c\u0430" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043c\u043e\u0434\u0435\u043c\u0430 Insteon.", - "title": "Insteon" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043c\u043e\u0434\u0435\u043c\u0430 Insteon." } } }, @@ -61,8 +60,7 @@ "cat": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: 0x10)", "subcat": "\u041f\u043e\u0434\u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: 0x0a)" }, - "description": "\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", - "title": "Insteon" + "description": "\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "\u0428\u0430\u0433 \u0434\u0438\u043c\u043c\u0435\u0440\u0430 (\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043e\u0441\u0432\u0435\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u0438\u0431\u043e\u0440\u043e\u0432, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e 22)", "unitcode": "\u042e\u043d\u0438\u0442\u043a\u043e\u0434 (1 - 16)" }, - "description": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u043a Insteon Hub", - "title": "Insteon" + "description": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u043a Insteon Hub" }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "\u041f\u043e\u0440\u0442", "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, - "description": "\u0418\u0437\u043c\u0435\u043d\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 Insteon Hub. \u041f\u043e\u0441\u043b\u0435 \u0432\u043d\u0435\u0441\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c Home Assistant. \u042d\u0442\u043e \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0441\u0430\u043c\u043e\u0433\u043e \u0445\u0430\u0431\u0430. \u0427\u0442\u043e\u0431\u044b \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0445\u0430\u0431\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Hub.", - "title": "Insteon" + "description": "\u0418\u0437\u043c\u0435\u043d\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 Insteon Hub. \u041f\u043e\u0441\u043b\u0435 \u0432\u043d\u0435\u0441\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c Home Assistant. \u042d\u0442\u043e \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0441\u0430\u043c\u043e\u0433\u043e \u0445\u0430\u0431\u0430. \u0427\u0442\u043e\u0431\u044b \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0445\u0430\u0431\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Hub." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0445\u0430\u0431\u0430", "remove_override": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", "remove_x10": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e X10" - }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u043f\u0446\u0438\u044e \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f" }, - "description": "\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", - "title": "Insteon" + "description": "\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" }, "remove_x10": { "data": { "address": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f" }, - "description": "\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 X10", - "title": "Insteon" + "description": "\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 X10" } } } diff --git a/homeassistant/components/insteon/translations/sk.json b/homeassistant/components/insteon/translations/sk.json index c563a509f07..b3711644c03 100644 --- a/homeassistant/components/insteon/translations/sk.json +++ b/homeassistant/components/insteon/translations/sk.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "cannot_connect": "Nepodarilo sa pripoji\u0165" + }, "step": { "hubv1": { "data": { diff --git a/homeassistant/components/insteon/translations/sv.json b/homeassistant/components/insteon/translations/sv.json new file mode 100644 index 00000000000..b8b6834022c --- /dev/null +++ b/homeassistant/components/insteon/translations/sv.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "not_insteon_device": "Uppt\u00e4ckt enhet \u00e4r inte en Insteon-enhet" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/tr.json b/homeassistant/components/insteon/translations/tr.json index 2f74d6a98ae..2bf143699f4 100644 --- a/homeassistant/components/insteon/translations/tr.json +++ b/homeassistant/components/insteon/translations/tr.json @@ -43,8 +43,7 @@ "data": { "modem_type": "Modem t\u00fcr\u00fc." }, - "description": "Insteon modem tipini se\u00e7in.", - "title": "Insteon" + "description": "Insteon modem tipini se\u00e7in." } } }, @@ -61,8 +60,7 @@ "cat": "Cihaz alt kategorisi (\u00f6rnek: 0x10)", "subcat": "Cihaz alt kategorisi (yani 0x0a)" }, - "description": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lma ekleyin.", - "title": "Insteon" + "description": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lma ekleyin." }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "Dimmer ad\u0131mlar\u0131 (yaln\u0131zca hafif cihazlar i\u00e7in varsay\u0131lan 22)", "unitcode": "Birim kodu (1 - 16)" }, - "description": "Insteon Hub parolas\u0131n\u0131 de\u011fi\u015ftirin.", - "title": "Insteon" + "description": "Insteon Hub parolas\u0131n\u0131 de\u011fi\u015ftirin." }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "Port", "username": "Kullan\u0131c\u0131 Ad\u0131" }, - "description": "Insteon Hub ba\u011flant\u0131 bilgilerini de\u011fi\u015ftirin. Bu de\u011fi\u015fikli\u011fi yapt\u0131ktan sonra Home Assistant'\u0131 yeniden ba\u015flatman\u0131z gerekir. Bu, Hub'\u0131n yap\u0131land\u0131rmas\u0131n\u0131 de\u011fi\u015ftirmez. Hub'daki yap\u0131land\u0131rmay\u0131 de\u011fi\u015ftirmek i\u00e7in Hub uygulamas\u0131n\u0131 kullan\u0131n.", - "title": "Insteon" + "description": "Insteon Hub ba\u011flant\u0131 bilgilerini de\u011fi\u015ftirin. Bu de\u011fi\u015fikli\u011fi yapt\u0131ktan sonra Home Assistant'\u0131 yeniden ba\u015flatman\u0131z gerekir. Bu, Hub'\u0131n yap\u0131land\u0131rmas\u0131n\u0131 de\u011fi\u015ftirmez. Hub'daki yap\u0131land\u0131rmay\u0131 de\u011fi\u015ftirmek i\u00e7in Hub uygulamas\u0131n\u0131 kullan\u0131n." }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "Hub yap\u0131land\u0131rmas\u0131n\u0131 de\u011fi\u015ftirin.", "remove_override": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lma i\u015flemini kald\u0131r\u0131n.", "remove_x10": "Bir X10 cihaz\u0131n\u0131 \u00e7\u0131kar\u0131n." - }, - "description": "Yap\u0131land\u0131rmak i\u00e7in bir se\u00e7enek se\u00e7in.", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "Kald\u0131r\u0131lacak bir cihaz adresi se\u00e7in" }, - "description": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lmay\u0131 kald\u0131rma", - "title": "Insteon" + "description": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lmay\u0131 kald\u0131rma" }, "remove_x10": { "data": { "address": "Kald\u0131r\u0131lacak bir cihaz adresi se\u00e7in" }, - "description": "Bir X10 cihaz\u0131n\u0131 kald\u0131r\u0131n", - "title": "Insteon" + "description": "Bir X10 cihaz\u0131n\u0131 kald\u0131r\u0131n" } } } diff --git a/homeassistant/components/insteon/translations/uk.json b/homeassistant/components/insteon/translations/uk.json index 747e3a30176..3d450d8d973 100644 --- a/homeassistant/components/insteon/translations/uk.json +++ b/homeassistant/components/insteon/translations/uk.json @@ -38,8 +38,7 @@ "data": { "modem_type": "\u0422\u0438\u043f \u043c\u043e\u0434\u0435\u043c\u0443" }, - "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043c\u043e\u0434\u0435\u043c\u0443 Insteon.", - "title": "Insteon" + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043c\u043e\u0434\u0435\u043c\u0443 Insteon." } } }, @@ -56,8 +55,7 @@ "cat": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0456\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 0x10)", "subcat": "\u041f\u0456\u0434\u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0456\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434, 0x0a)" }, - "description": "\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", - "title": "Insteon" + "description": "\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" }, "add_x10": { "data": { @@ -66,8 +64,7 @@ "steps": "\u041a\u0440\u043e\u043a \u0434\u0456\u043c\u043c\u0435\u0440\u0430 (\u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u043e\u0441\u0432\u0456\u0442\u043b\u044e\u0432\u0430\u043b\u044c\u043d\u0438\u0445 \u043f\u0440\u0438\u043b\u0430\u0434\u0456\u0432, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c 22)", "unitcode": "\u042e\u043d\u0456\u0442\u043a\u043e\u0434 (1 - 16)" }, - "description": "\u0417\u043c\u0456\u043d\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043e Insteon Hub", - "title": "Insteon" + "description": "\u0417\u043c\u0456\u043d\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043e Insteon Hub" }, "change_hub_config": { "data": { @@ -76,8 +73,7 @@ "port": "\u041f\u043e\u0440\u0442", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" }, - "description": "\u0417\u043c\u0456\u043d\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f Insteon Hub. \u041f\u0456\u0441\u043b\u044f \u0432\u043d\u0435\u0441\u0435\u043d\u043d\u044f \u0446\u0438\u0445 \u0437\u043c\u0456\u043d \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 Home Assistant. \u0426\u0435 \u043d\u0435 \u0437\u043c\u0456\u043d\u044e\u0454 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0441\u0430\u043c\u043e\u0433\u043e \u0445\u0430\u0431\u0430. \u0429\u043e\u0431 \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0445\u0430\u0431\u0430, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Hub.", - "title": "Insteon" + "description": "\u0417\u043c\u0456\u043d\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f Insteon Hub. \u041f\u0456\u0441\u043b\u044f \u0432\u043d\u0435\u0441\u0435\u043d\u043d\u044f \u0446\u0438\u0445 \u0437\u043c\u0456\u043d \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 Home Assistant. \u0426\u0435 \u043d\u0435 \u0437\u043c\u0456\u043d\u044e\u0454 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0441\u0430\u043c\u043e\u0433\u043e \u0445\u0430\u0431\u0430. \u0429\u043e\u0431 \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0445\u0430\u0431\u0430, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Hub." }, "init": { "data": { @@ -86,23 +82,19 @@ "change_hub_config": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0445\u0430\u0431\u0430", "remove_override": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", "remove_x10": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 X10" - }, - "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u043f\u0446\u0456\u044e \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e, \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438" }, - "description": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", - "title": "Insteon" + "description": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" }, "remove_x10": { "data": { "address": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e, \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438" }, - "description": "\u0412\u0438\u0434\u0430\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e X10", - "title": "Insteon" + "description": "\u0412\u0438\u0434\u0430\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e X10" } } } diff --git a/homeassistant/components/insteon/translations/zh-Hant.json b/homeassistant/components/insteon/translations/zh-Hant.json index c55a1ea0a5c..608b6929e52 100644 --- a/homeassistant/components/insteon/translations/zh-Hant.json +++ b/homeassistant/components/insteon/translations/zh-Hant.json @@ -43,8 +43,7 @@ "data": { "modem_type": "\u6578\u64da\u6a5f\u985e\u5225\u3002" }, - "description": "\u9078\u64c7 Insteon \u6578\u64da\u6a5f\u985e\u5225\u3002", - "title": "Insteon" + "description": "\u9078\u64c7 Insteon \u6578\u64da\u6a5f\u985e\u5225\u3002" } } }, @@ -61,8 +60,7 @@ "cat": "\u88dd\u7f6e\u5b50\u985e\u5225\uff08\u4f8b\u5982 0x10\uff09", "subcat": "\u88dd\u7f6e\u5b50\u985e\u5225\uff08\u4f8b\u5982 0x0a\uff09" }, - "description": "\u65b0\u589e\u88dd\u7f6e\u8986\u5beb\u3002", - "title": "Insteon" + "description": "\u65b0\u589e\u88dd\u7f6e\u8986\u5beb\u3002" }, "add_x10": { "data": { @@ -71,8 +69,7 @@ "steps": "\u8abf\u5149\u968e\u6bb5\uff08\u50c5\u9069\u7528\u7167\u660e\u88dd\u7f6e\u3001\u9810\u8a2d\u503c\u70ba 22\uff09", "unitcode": "Unitcode (1 - 16)" }, - "description": "\u8b8a\u66f4 Insteon Hub \u5bc6\u78bc\u3002", - "title": "Insteon" + "description": "\u8b8a\u66f4 Insteon Hub \u5bc6\u78bc\u3002" }, "change_hub_config": { "data": { @@ -81,8 +78,7 @@ "port": "\u901a\u8a0a\u57e0", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, - "description": "\u8b8a\u66f4 Insteon Hub \u9023\u7dda\u8cc7\u8a0a\u3002\u65bc\u8b8a\u66f4\u4e4b\u5f8c\u3001\u5fc5\u9808\u91cd\u555f Home Assistant\u3002\u6b64\u4e9b\u8a2d\u5b9a\u4e0d\u6703\u8b8a\u66f4 Hub \u88dd\u7f6e\u672c\u8eab\u7684\u8a2d\u5b9a\uff0c\u5982\u6b32\u8b8a\u66f4 Hub \u8a2d\u5b9a\u3001\u5247\u8acb\u4f7f\u7528 Hub app\u3002", - "title": "Insteon" + "description": "\u8b8a\u66f4 Insteon Hub \u9023\u7dda\u8cc7\u8a0a\u3002\u65bc\u8b8a\u66f4\u4e4b\u5f8c\u3001\u5fc5\u9808\u91cd\u555f Home Assistant\u3002\u6b64\u4e9b\u8a2d\u5b9a\u4e0d\u6703\u8b8a\u66f4 Hub \u88dd\u7f6e\u672c\u8eab\u7684\u8a2d\u5b9a\uff0c\u5982\u6b32\u8b8a\u66f4 Hub \u8a2d\u5b9a\u3001\u5247\u8acb\u4f7f\u7528 Hub app\u3002" }, "init": { "data": { @@ -91,23 +87,19 @@ "change_hub_config": "\u8b8a\u66f4 Hub \u8a2d\u5b9a\u3002", "remove_override": "\u79fb\u9664\u88dd\u7f6e\u8986\u5beb", "remove_x10": "\u79fb\u9664 X10 \u88dd\u7f6e\u3002" - }, - "description": "\u9078\u64c7\u9078\u9805\u4ee5\u8a2d\u5b9a", - "title": "Insteon" + } }, "remove_override": { "data": { "address": "\u9078\u64c7\u88dd\u7f6e\u4f4d\u5740\u4ee5\u79fb\u9664" }, - "description": "\u79fb\u9664\u88dd\u7f6e\u8986\u5beb", - "title": "Insteon" + "description": "\u79fb\u9664\u88dd\u7f6e\u8986\u5beb" }, "remove_x10": { "data": { "address": "\u9078\u64c7\u88dd\u7f6e\u4f4d\u5740\u4ee5\u79fb\u9664" }, - "description": "\u79fb\u9664 X10 \u88dd\u7f6e", - "title": "Insteon" + "description": "\u79fb\u9664 X10 \u88dd\u7f6e" } } } diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index 03647559345..e8e34ee8e62 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -28,6 +28,7 @@ from homeassistant.const import ( ENTITY_MATCH_ALL, ) from homeassistant.core import ServiceCall, callback +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -294,7 +295,7 @@ def async_register_services(hass): """Remove the device and all entities from hass.""" signal = f"{address.id}_{SIGNAL_REMOVE_ENTITY}" async_dispatcher_send(hass, signal) - dev_registry = await hass.helpers.device_registry.async_get_registry() + dev_registry = dr.async_get(hass) device = dev_registry.async_get_device(identifiers={(DOMAIN, str(address))}) if device: dev_registry.async_remove_device(device.id) diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index 8cf5da5bc7c..5d0dde3e4de 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -154,17 +154,26 @@ class IntegrationSensor(RestoreEntity, SensorEntity): self._method = integration_method 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_template = f"{'' if unit_prefix is None else unit_prefix}{{}}" self._unit_of_measurement = None self._unit_prefix = UNIT_PREFIXES[unit_prefix] self._unit_time = UNIT_TIME[unit_time] + self._unit_time_str = 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} + def _unit(self, source_unit: str) -> str: + """Derive unit from the source sensor, SI prefix and time unit.""" + unit_time = self._unit_time_str + if source_unit.endswith(f"/{unit_time}"): + integral_unit = source_unit[0 : (-(1 + len(unit_time)))] + else: + integral_unit = f"{source_unit}{unit_time}" + + return self._unit_template.format(integral_unit) + async def async_added_to_hass(self): """Handle entity which will be added.""" await super().async_added_to_hass() @@ -191,14 +200,19 @@ class IntegrationSensor(RestoreEntity, SensorEntity): old_state = event.data.get("old_state") new_state = event.data.get("new_state") + if new_state is None or new_state.state in ( + STATE_UNKNOWN, + STATE_UNAVAILABLE, + ): + return + # We may want to update our state before an early return, # based on the source sensor's unit_of_measurement # or device_class. update_state = False - unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) if unit is not None: - new_unit_of_measurement = self._unit_template.format(unit) + new_unit_of_measurement = self._unit(unit) if self._unit_of_measurement != new_unit_of_measurement: self._unit_of_measurement = new_unit_of_measurement update_state = True @@ -214,11 +228,9 @@ class IntegrationSensor(RestoreEntity, SensorEntity): if update_state: self.async_write_ha_state() - if ( - old_state is None - or new_state is None - or old_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE) - or new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE) + if old_state is None or old_state.state in ( + STATE_UNKNOWN, + STATE_UNAVAILABLE, ): return diff --git a/homeassistant/components/integration/translations/ca.json b/homeassistant/components/integration/translations/ca.json index bbe5e6e31b4..ad9553ff346 100644 --- a/homeassistant/components/integration/translations/ca.json +++ b/homeassistant/components/integration/translations/ca.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/cs.json b/homeassistant/components/integration/translations/cs.json index 3e7fdfca729..64e92edeb1c 100644 --- a/homeassistant/components/integration/translations/cs.json +++ b/homeassistant/components/integration/translations/cs.json @@ -28,12 +28,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/de.json b/homeassistant/components/integration/translations/de.json index 3013fa9d039..af26ec446a2 100644 --- a/homeassistant/components/integration/translations/de.json +++ b/homeassistant/components/integration/translations/de.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/el.json b/homeassistant/components/integration/translations/el.json index e366137fce1..6893190d9bf 100644 --- a/homeassistant/components/integration/translations/el.json +++ b/homeassistant/components/integration/translations/el.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/en.json b/homeassistant/components/integration/translations/en.json index 3174eab3f69..1ee047b447f 100644 --- a/homeassistant/components/integration/translations/en.json +++ b/homeassistant/components/integration/translations/en.json @@ -29,12 +29,6 @@ "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/es.json b/homeassistant/components/integration/translations/es.json new file mode 100644 index 00000000000..8034e4746fe --- /dev/null +++ b/homeassistant/components/integration/translations/es.json @@ -0,0 +1,33 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "M\u00e9todo de integraci\u00f3n", + "round": "Precisi\u00f3n", + "source": "Sensor de entrada", + "unit_prefix": "Prefijo m\u00e9trico", + "unit_time": "Unidad de tiempo" + }, + "data_description": { + "round": "Controla el n\u00famero de d\u00edgitos decimales en la salida.", + "unit_prefix": "La salida se escalar\u00e1 seg\u00fan el prefijo m\u00e9trico seleccionado.", + "unit_time": "La salida se escalar\u00e1 seg\u00fan la unidad de tiempo seleccionada." + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Precisi\u00f3n" + }, + "data_description": { + "round": "Controla el n\u00famero de d\u00edgitos decimales en la salida." + } + } + } + }, + "title": "Integraci\u00f3n - Sensor integral de suma de Riemann" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/et.json b/homeassistant/components/integration/translations/et.json index 31901bbb6f6..4b0143d0662 100644 --- a/homeassistant/components/integration/translations/et.json +++ b/homeassistant/components/integration/translations/et.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/fr.json b/homeassistant/components/integration/translations/fr.json index 33b5ab86e7f..cbc0e89175b 100644 --- a/homeassistant/components/integration/translations/fr.json +++ b/homeassistant/components/integration/translations/fr.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/he.json b/homeassistant/components/integration/translations/he.json index 4061da5f233..219c75605bb 100644 --- a/homeassistant/components/integration/translations/he.json +++ b/homeassistant/components/integration/translations/he.json @@ -27,12 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/hu.json b/homeassistant/components/integration/translations/hu.json index c892e858b3f..763054d4469 100644 --- a/homeassistant/components/integration/translations/hu.json +++ b/homeassistant/components/integration/translations/hu.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/id.json b/homeassistant/components/integration/translations/id.json index d585a4409e9..8327d6dc555 100644 --- a/homeassistant/components/integration/translations/id.json +++ b/homeassistant/components/integration/translations/id.json @@ -29,12 +29,6 @@ "data_description": { "round": "Mengontrol jumlah digit desimal dalam output." } - }, - "options": { - "data": { - "round": "Presisi" - }, - "description": "Presisi mengontrol jumlah digit desimal pada output." } } }, diff --git a/homeassistant/components/integration/translations/it.json b/homeassistant/components/integration/translations/it.json index a1904f45cd1..92e4077aa90 100644 --- a/homeassistant/components/integration/translations/it.json +++ b/homeassistant/components/integration/translations/it.json @@ -13,7 +13,7 @@ "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." + "unit_time": "L'output sar\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" @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/ja.json b/homeassistant/components/integration/translations/ja.json index 5fac6ba692b..4f35ba53a4a 100644 --- a/homeassistant/components/integration/translations/ja.json +++ b/homeassistant/components/integration/translations/ja.json @@ -29,12 +29,6 @@ "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" } } }, diff --git a/homeassistant/components/integration/translations/ko.json b/homeassistant/components/integration/translations/ko.json new file mode 100644 index 00000000000..d217725b835 --- /dev/null +++ b/homeassistant/components/integration/translations/ko.json @@ -0,0 +1,36 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "\uc801\ubd84 \ubc29\ubc95", + "name": "\uc774\ub984", + "round": "\uc18c\uc218\uc810", + "source": "\uc785\ub825 \uc13c\uc11c", + "unit_prefix": "\ubbf8\ud130\ubc95", + "unit_time": "\uc2dc\uac04 \ub2e8\uc704" + }, + "data_description": { + "round": "\uc18c\uc218\uc810 \uc790\ub9bf\uc218\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4.", + "unit_prefix": "\uc120\ud0dd\ud55c \ubbf8\ud130\ubc95\uc73c\ub85c \ud45c\uc2dc\ub429\ub2c8\ub2e4.", + "unit_time": "\uc120\ud0dd\ud55c \uc2dc\uac04 \ub2e8\uc704\ub85c \ud45c\uc2dc\ub429\ub2c8\ub2e4." + }, + "description": "\ub9ac\ub9cc \ud569\uc744 \uc0ac\uc6a9\ud574\uc11c \uc801\ubd84\uac12\uc744 \uad6c\ud558\ub294 \uc13c\uc11c\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.", + "title": "\ub9ac\ub9cc \ud569 \uc801\ubd84 \uc13c\uc11c \ucd94\uac00" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "\uc18c\uc218\uc810" + }, + "data_description": { + "round": "\uc18c\uc218\uc810 \uc790\ub9bf\uc218\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4." + } + } + } + }, + "title": "\uc801\ubd84 - \ub9ac\ub9cc \ud569 \uc801\ubd84 \uc13c\uc11c" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/nl.json b/homeassistant/components/integration/translations/nl.json index 8753ee30ea7..e3e0259da71 100644 --- a/homeassistant/components/integration/translations/nl.json +++ b/homeassistant/components/integration/translations/nl.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/no.json b/homeassistant/components/integration/translations/no.json index aafa60b5811..9965d1c7521 100644 --- a/homeassistant/components/integration/translations/no.json +++ b/homeassistant/components/integration/translations/no.json @@ -29,12 +29,6 @@ "data_description": { "round": "Styrer antall desimaler i utdataene." } - }, - "options": { - "data": { - "round": "Presisjon" - }, - "description": "Presisjon styrer antall desimaler i utdataene." } } }, diff --git a/homeassistant/components/integration/translations/pl.json b/homeassistant/components/integration/translations/pl.json index 7971d97129c..5dfe00cd31b 100644 --- a/homeassistant/components/integration/translations/pl.json +++ b/homeassistant/components/integration/translations/pl.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/pt-BR.json b/homeassistant/components/integration/translations/pt-BR.json index ae512b93fd4..999873290b8 100644 --- a/homeassistant/components/integration/translations/pt-BR.json +++ b/homeassistant/components/integration/translations/pt-BR.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/ru.json b/homeassistant/components/integration/translations/ru.json index 67891293d7b..8e7ad96b803 100644 --- a/homeassistant/components/integration/translations/ru.json +++ b/homeassistant/components/integration/translations/ru.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/climacell/translations/lb.json b/homeassistant/components/integration/translations/sk.json similarity index 72% rename from homeassistant/components/climacell/translations/lb.json rename to homeassistant/components/integration/translations/sk.json index e075d198b7f..c1372b00a8a 100644 --- a/homeassistant/components/climacell/translations/lb.json +++ b/homeassistant/components/integration/translations/sk.json @@ -3,7 +3,7 @@ "step": { "user": { "data": { - "api_version": "API Versioun" + "round": "Presnos\u0165" } } } diff --git a/homeassistant/components/integration/translations/tr.json b/homeassistant/components/integration/translations/tr.json index de99ebd3633..2271c616150 100644 --- a/homeassistant/components/integration/translations/tr.json +++ b/homeassistant/components/integration/translations/tr.json @@ -29,12 +29,6 @@ "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." } } }, diff --git a/homeassistant/components/integration/translations/zh-Hans.json b/homeassistant/components/integration/translations/zh-Hans.json index f7ebea98f4a..15cc310b2f4 100644 --- a/homeassistant/components/integration/translations/zh-Hans.json +++ b/homeassistant/components/integration/translations/zh-Hans.json @@ -29,12 +29,6 @@ "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" } } }, diff --git a/homeassistant/components/integration/translations/zh-Hant.json b/homeassistant/components/integration/translations/zh-Hant.json index 2adbb3edc28..d7142365b05 100644 --- a/homeassistant/components/integration/translations/zh-Hant.json +++ b/homeassistant/components/integration/translations/zh-Hant.json @@ -29,12 +29,6 @@ "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" } } }, diff --git a/homeassistant/components/intellifire/__init__.py b/homeassistant/components/intellifire/__init__.py index e139626b88c..83c6e05f572 100644 --- a/homeassistant/components/intellifire/__init__.py +++ b/homeassistant/components/intellifire/__init__.py @@ -13,7 +13,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from .const import DOMAIN, LOGGER from .coordinator import IntellifireDataUpdateCoordinator -PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/intellifire/switch.py b/homeassistant/components/intellifire/switch.py new file mode 100644 index 00000000000..9c196a59fd4 --- /dev/null +++ b/homeassistant/components/intellifire/switch.py @@ -0,0 +1,93 @@ +"""Define switch func.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +from intellifire4py import IntellifireControlAsync, IntellifirePollData + +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 .const import DOMAIN +from .coordinator import IntellifireDataUpdateCoordinator +from .entity import IntellifireEntity + + +@dataclass() +class IntellifireSwitchRequiredKeysMixin: + """Mixin for required keys.""" + + on_fn: Callable[[IntellifireControlAsync], Awaitable] + off_fn: Callable[[IntellifireControlAsync], Awaitable] + value_fn: Callable[[IntellifirePollData], bool] + + +@dataclass +class IntellifireSwitchEntityDescription( + SwitchEntityDescription, IntellifireSwitchRequiredKeysMixin +): + """Describes a switch entity.""" + + +INTELLIFIRE_SWITCHES: tuple[IntellifireSwitchEntityDescription, ...] = ( + IntellifireSwitchEntityDescription( + key="on_off", + name="Flame", + on_fn=lambda control_api: control_api.flame_on( + fireplace=control_api.default_fireplace + ), + off_fn=lambda control_api: control_api.flame_off( + fireplace=control_api.default_fireplace + ), + value_fn=lambda data: data.is_on, + ), + IntellifireSwitchEntityDescription( + key="pilot", + name="Pilot Light", + icon="mdi:fire-alert", + on_fn=lambda control_api: control_api.pilot_on( + fireplace=control_api.default_fireplace + ), + off_fn=lambda control_api: control_api.pilot_off( + fireplace=control_api.default_fireplace + ), + value_fn=lambda data: data.pilot_on, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Configure switch entities.""" + coordinator: IntellifireDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + IntellifireSwitch(coordinator=coordinator, description=description) + for description in INTELLIFIRE_SWITCHES + ) + + +class IntellifireSwitch(IntellifireEntity, SwitchEntity): + """Define an Intellifire Switch.""" + + entity_description: IntellifireSwitchEntityDescription + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the switch.""" + await self.entity_description.on_fn(self.coordinator.control_api) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the switch.""" + await self.entity_description.off_fn(self.coordinator.control_api) + + @property + def is_on(self) -> bool | None: + """Return the on state.""" + return self.entity_description.value_fn(self.coordinator.read_api.data) diff --git a/homeassistant/components/intellifire/translations/bg.json b/homeassistant/components/intellifire/translations/bg.json index 1a8dd460097..9df377170f4 100644 --- a/homeassistant/components/intellifire/translations/bg.json +++ b/homeassistant/components/intellifire/translations/bg.json @@ -1,14 +1,22 @@ { "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" + "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", + "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": { + "api_error": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u043b\u0438\u0437\u0430\u043d\u0435", "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" + "iftapi_connect": "\u0413\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u0441 iftapi.net" }, "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "Email" + } + }, "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}?" }, @@ -21,12 +29,8 @@ "pick_device": { "data": { "host": "\u0425\u043e\u0441\u0442" - } - }, - "user": { - "data": { - "host": "\u0425\u043e\u0441\u0442" - } + }, + "title": "\u0418\u0437\u0431\u043e\u0440 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" } } } diff --git a/homeassistant/components/intellifire/translations/ca.json b/homeassistant/components/intellifire/translations/ca.json index 9894ec4920a..d5991750966 100644 --- a/homeassistant/components/intellifire/translations/ca.json +++ b/homeassistant/components/intellifire/translations/ca.json @@ -8,8 +8,7 @@ "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" + "iftapi_connect": "S'ha produ\u00eft un error en connectar a iftapi.net" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 48dbc509ea8..8684c426280 100644 --- a/homeassistant/components/intellifire/translations/cs.json +++ b/homeassistant/components/intellifire/translations/cs.json @@ -4,8 +4,7 @@ "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" }, "error": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, "flow_title": "{serial} ({host})", "step": { @@ -18,11 +17,6 @@ "data": { "host": "Hostitel" } - }, - "user": { - "data": { - "host": "Hostitel" - } } } } diff --git a/homeassistant/components/intellifire/translations/de.json b/homeassistant/components/intellifire/translations/de.json index f1c37a8a475..d9427853cdd 100644 --- a/homeassistant/components/intellifire/translations/de.json +++ b/homeassistant/components/intellifire/translations/de.json @@ -8,8 +8,7 @@ "error": { "api_error": "Login fehlgeschlagen", "cannot_connect": "Verbindung fehlgeschlagen", - "iftapi_connect": "Fehler beim Verbinden mit iftapi.net", - "unknown": "Unerwarteter Fehler" + "iftapi_connect": "Fehler beim Verbinden mit iftapi.net" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 fa72581a4a6..ff286c04952 100644 --- a/homeassistant/components/intellifire/translations/el.json +++ b/homeassistant/components/intellifire/translations/el.json @@ -8,8 +8,7 @@ "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" + "iftapi_connect": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf iftapi.net" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 f0c317efb93..83acafacd48 100644 --- a/homeassistant/components/intellifire/translations/en.json +++ b/homeassistant/components/intellifire/translations/en.json @@ -8,8 +8,7 @@ "error": { "api_error": "Login failed", "cannot_connect": "Failed to connect", - "iftapi_connect": "Error conecting to iftapi.net", - "unknown": "Unexpected error" + "iftapi_connect": "Error conecting to iftapi.net" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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/es.json b/homeassistant/components/intellifire/translations/es.json new file mode 100644 index 00000000000..8d19b2ba3bf --- /dev/null +++ b/homeassistant/components/intellifire/translations/es.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "not_intellifire_device": "No es un dispositivo IntelliFire.", + "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" + }, + "error": { + "api_error": "Ha Fallado el inicio de sesi\u00f3n", + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", + "iftapi_connect": "Se ha producido un error al conectar a iftapi.net" + }, + "flow_title": "{serial} ({host})", + "step": { + "api_config": { + "data": { + "password": "Contrase\u00f1a", + "username": "Correo electr\u00f3nico" + } + }, + "dhcp_confirm": { + "description": "\u00bfQuieres configurar {host} \nSerie: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "Host (direcci\u00f3n IP)" + } + }, + "pick_device": { + "data": { + "host": "Host" + }, + "description": "Se han descubierto los siguientes dispositivos IntelliFire. Selecciona lo que quieras configurar.", + "title": "Selecci\u00f3n de dispositivo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/intellifire/translations/et.json b/homeassistant/components/intellifire/translations/et.json index 53c87f112a7..b2caebb611a 100644 --- a/homeassistant/components/intellifire/translations/et.json +++ b/homeassistant/components/intellifire/translations/et.json @@ -8,8 +8,7 @@ "error": { "api_error": "Sisselogimine nurjus", "cannot_connect": "\u00dchendamine nurjus", - "iftapi_connect": "\u00dchendumine iftapi.net'iga nurjus", - "unknown": "Ootamatu t\u00f5rge" + "iftapi_connect": "\u00dchendumine iftapi.net'iga nurjus" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 5c478358345..650f0cad77e 100644 --- a/homeassistant/components/intellifire/translations/fr.json +++ b/homeassistant/components/intellifire/translations/fr.json @@ -8,8 +8,7 @@ "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" + "iftapi_connect": "Erreur lors de la connexion \u00e0 iftapi.net" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 e4b64392380..18cf71bf358 100644 --- a/homeassistant/components/intellifire/translations/he.json +++ b/homeassistant/components/intellifire/translations/he.json @@ -5,8 +5,7 @@ "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" + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" }, "step": { "api_config": { @@ -24,11 +23,6 @@ "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 f5d11abe4c0..2f680f2e776 100644 --- a/homeassistant/components/intellifire/translations/hu.json +++ b/homeassistant/components/intellifire/translations/hu.json @@ -8,8 +8,7 @@ "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" + "iftapi_connect": "Hiba az iftapi.net-hez val\u00f3 csatlakoz\u00e1sban" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 07ec946b6ba..6a38f501811 100644 --- a/homeassistant/components/intellifire/translations/id.json +++ b/homeassistant/components/intellifire/translations/id.json @@ -8,8 +8,7 @@ "error": { "api_error": "Gagal masuk", "cannot_connect": "Gagal terhubung", - "iftapi_connect": "Kesalahan saat menyambung ke iftapi.net", - "unknown": "Kesalahan yang tidak diharapkan" + "iftapi_connect": "Kesalahan saat menyambung ke iftapi.net" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 8689840c82c..a1282698c88 100644 --- a/homeassistant/components/intellifire/translations/it.json +++ b/homeassistant/components/intellifire/translations/it.json @@ -8,8 +8,7 @@ "error": { "api_error": "Accesso non riuscito", "cannot_connect": "Impossibile connettersi", - "iftapi_connect": "Errore durante la connessione a iftapi.net", - "unknown": "Errore imprevisto" + "iftapi_connect": "Errore durante la connessione a iftapi.net" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 c623f0f59cf..6c74e4743ed 100644 --- a/homeassistant/components/intellifire/translations/ja.json +++ b/homeassistant/components/intellifire/translations/ja.json @@ -8,8 +8,7 @@ "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" + "iftapi_connect": "iftapi.net\u3078\u306e\u63a5\u7d9a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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/ko.json b/homeassistant/components/intellifire/translations/ko.json new file mode 100644 index 00000000000..fde189b9541 --- /dev/null +++ b/homeassistant/components/intellifire/translations/ko.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "api_error": "\ub85c\uadf8\uc778 \uc2e4\ud328", + "iftapi_connect": "iftapi.net\uc5d0 \uc5f0\uacb0\ud558\ub294 \ub3d9\uc548 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." + }, + "step": { + "api_config": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc774\uba54\uc77c" + } + }, + "pick_device": { + "description": "IntelliFire \uc7a5\uce58\uac00 \uac80\uc0c9\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uad6c\uc131\ud560 \ud56d\ubaa9\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624.", + "title": "\uae30\uae30 \uc120\ud0dd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/intellifire/translations/nl.json b/homeassistant/components/intellifire/translations/nl.json index a4f2b1c304a..2af11edede8 100644 --- a/homeassistant/components/intellifire/translations/nl.json +++ b/homeassistant/components/intellifire/translations/nl.json @@ -3,13 +3,12 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "not_intellifire_device": "Niet een IntelliFire apparaat.", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "api_error": "Inloggen mislukt", "cannot_connect": "Kan geen verbinding maken", - "iftapi_connect": "Fout bij het verbinden met iftapi.net", - "unknown": "Onverwachte fout" + "iftapi_connect": "Fout bij het verbinden met iftapi.net" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 8175a085f30..53cf7495b94 100644 --- a/homeassistant/components/intellifire/translations/no.json +++ b/homeassistant/components/intellifire/translations/no.json @@ -8,8 +8,7 @@ "error": { "api_error": "Innlogging feilet", "cannot_connect": "Tilkobling mislyktes", - "iftapi_connect": "Feil ved tilkobling til iftapi.net", - "unknown": "Uventet feil" + "iftapi_connect": "Feil ved tilkobling til iftapi.net" }, "flow_title": "{serial} ( {host} )", "step": { @@ -34,11 +33,6 @@ }, "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 f63be88814f..c470c77d03b 100644 --- a/homeassistant/components/intellifire/translations/pl.json +++ b/homeassistant/components/intellifire/translations/pl.json @@ -8,8 +8,7 @@ "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" + "iftapi_connect": "B\u0142\u0105d po\u0142\u0105czenia z iftapi.net" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 babcc22bd97..31a98a0d6d4 100644 --- a/homeassistant/components/intellifire/translations/pt-BR.json +++ b/homeassistant/components/intellifire/translations/pt-BR.json @@ -8,8 +8,7 @@ "error": { "api_error": "Login falhou", "cannot_connect": "Falha ao conectar", - "iftapi_connect": "Erro ao conectar ao iftapi.net", - "unknown": "Erro inesperado" + "iftapi_connect": "Erro ao conectar ao iftapi.net" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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 b48cd06b592..4471edf0811 100644 --- a/homeassistant/components/intellifire/translations/ru.json +++ b/homeassistant/components/intellifire/translations/ru.json @@ -8,8 +8,7 @@ "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." + "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" }, "flow_title": "{serial} ({host})", "step": { @@ -24,7 +23,7 @@ }, "manual_device_entry": { "data": { - "host": "\u0425\u043e\u0441\u0442 (IP-\u0430\u0434\u0440\u0435\u0441)" + "host": "\u0414\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 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." }, @@ -34,11 +33,6 @@ }, "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/sv.json b/homeassistant/components/intellifire/translations/sv.json index f341a6314ee..be36fec5fe3 100644 --- a/homeassistant/components/intellifire/translations/sv.json +++ b/homeassistant/components/intellifire/translations/sv.json @@ -4,15 +4,7 @@ "already_configured": "Enheten \u00e4r redan konfigurerad" }, "error": { - "cannot_connect": "Det gick inte att ansluta.", - "unknown": "Ov\u00e4ntat fel" - }, - "step": { - "user": { - "data": { - "host": "V\u00e4rd" - } - } + "cannot_connect": "Det gick inte att ansluta." } } } \ No newline at end of file diff --git a/homeassistant/components/intellifire/translations/tr.json b/homeassistant/components/intellifire/translations/tr.json index 3a22e7c3680..1eba2a43e72 100644 --- a/homeassistant/components/intellifire/translations/tr.json +++ b/homeassistant/components/intellifire/translations/tr.json @@ -8,8 +8,7 @@ "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" + "iftapi_connect": "iftapi.net'e ba\u011flan\u0131rken hata olu\u015ftu" }, "flow_title": "{serial} ({host})", "step": { @@ -24,7 +23,7 @@ }, "manual_device_entry": { "data": { - "host": "Sunucu (IP Adresi)" + "host": "Ana Bilgisayar (IP Adresi)" }, "description": "Yerel Yap\u0131land\u0131rma" }, @@ -34,11 +33,6 @@ }, "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 107f9cca74c..b55b8fc55bd 100644 --- a/homeassistant/components/intellifire/translations/zh-Hant.json +++ b/homeassistant/components/intellifire/translations/zh-Hant.json @@ -8,8 +8,7 @@ "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" + "iftapi_connect": "\u9023\u7dda\u81f3 iftapi.net \u932f\u8aa4" }, "flow_title": "{serial} ({host})", "step": { @@ -34,11 +33,6 @@ }, "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/intent/__init__.py b/homeassistant/components/intent/__init__.py index d626daa8c3b..c43643b0244 100644 --- a/homeassistant/components/intent/__init__.py +++ b/homeassistant/components/intent/__init__.py @@ -19,20 +19,23 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass, DOMAIN, _async_process_intent ) - hass.helpers.intent.async_register( + intent.async_register( + hass, intent.ServiceIntentHandler( intent.INTENT_TURN_ON, HA_DOMAIN, SERVICE_TURN_ON, "Turned {} on" - ) + ), ) - hass.helpers.intent.async_register( + intent.async_register( + hass, intent.ServiceIntentHandler( intent.INTENT_TURN_OFF, HA_DOMAIN, SERVICE_TURN_OFF, "Turned {} off" - ) + ), ) - hass.helpers.intent.async_register( + intent.async_register( + hass, intent.ServiceIntentHandler( intent.INTENT_TOGGLE, HA_DOMAIN, SERVICE_TOGGLE, "Toggled {}" - ) + ), ) return True diff --git a/homeassistant/components/ios/translations/es.json b/homeassistant/components/ios/translations/es.json index 3c4064280e7..8adb4041b20 100644 --- a/homeassistant/components/ios/translations/es.json +++ b/homeassistant/components/ios/translations/es.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Solo se necesita una \u00fanica configuraci\u00f3n de Home Assistant iOS." + "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "step": { "confirm": { - "description": "\u00bfQuieres configurar el componente iOS de Home Assistant?" + "description": "\u00bfQuieres iniciar la configuraci\u00f3n?" } } } diff --git a/homeassistant/components/ios/translations/nl.json b/homeassistant/components/ios/translations/nl.json index 1e660ec2f5d..2e50721297a 100644 --- a/homeassistant/components/ios/translations/nl.json +++ b/homeassistant/components/ios/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/iotawatt/translations/ko.json b/homeassistant/components/iotawatt/translations/ko.json new file mode 100644 index 00000000000..f3fac4e1e74 --- /dev/null +++ b/homeassistant/components/iotawatt/translations/ko.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + }, + "step": { + "auth": { + "data": { + "password": "\uc554\ud638" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iotawatt/translations/nl.json b/homeassistant/components/iotawatt/translations/nl.json index 617073e91c0..04c1bb9a262 100644 --- a/homeassistant/components/iotawatt/translations/nl.json +++ b/homeassistant/components/iotawatt/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/ipma/translations/nl.json b/homeassistant/components/ipma/translations/nl.json index 154aafe1033..ddd00b40e51 100644 --- a/homeassistant/components/ipma/translations/nl.json +++ b/homeassistant/components/ipma/translations/nl.json @@ -8,7 +8,7 @@ "data": { "latitude": "Breedtegraad", "longitude": "Lengtegraad", - "mode": "Mode", + "mode": "Modus", "name": "Naam" }, "description": "Instituto Portugu\u00eas do Mar e Atmosfera", diff --git a/homeassistant/components/ipp/translations/bg.json b/homeassistant/components/ipp/translations/bg.json index 4983c9a14b2..d454fe68170 100644 --- a/homeassistant/components/ipp/translations/bg.json +++ b/homeassistant/components/ipp/translations/bg.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "base_path": "\u041e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u0435\u043d \u043f\u044a\u0442 \u0434\u043e \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430", "port": "\u041f\u043e\u0440\u0442" } } diff --git a/homeassistant/components/ipp/translations/nl.json b/homeassistant/components/ipp/translations/nl.json index 5296fcac36f..2a97724a596 100644 --- a/homeassistant/components/ipp/translations/nl.json +++ b/homeassistant/components/ipp/translations/nl.json @@ -20,7 +20,7 @@ "base_path": "Relatief pad naar de printer", "host": "Host", "port": "Poort", - "ssl": "Printer ondersteunt communicatie via SSL / TLS", + "ssl": "Maakt gebruik van een SSL-certificaat", "verify_ssl": "SSL-certificaat verifi\u00ebren" }, "description": "Stel uw printer in via Internet Printing Protocol (IPP) om te integreren met Home Assistant.", diff --git a/homeassistant/components/iqvia/translations/bg.json b/homeassistant/components/iqvia/translations/bg.json index e59b361d2e0..3826c82abf7 100644 --- a/homeassistant/components/iqvia/translations/bg.json +++ b/homeassistant/components/iqvia/translations/bg.json @@ -11,8 +11,7 @@ "data": { "zip_code": "\u041f\u043e\u0449\u0435\u043d\u0441\u043a\u0438 \u043a\u043e\u0434" }, - "description": "\u041f\u043e\u043f\u044a\u043b\u043d\u0435\u0442\u0435 \u0432\u0430\u0448\u0438\u044f \u0430\u043c\u0435\u0440\u0438\u043a\u0430\u043d\u0441\u043a\u0438 \u0438\u043b\u0438 \u043a\u0430\u043d\u0430\u0434\u0441\u043a\u0438 \u043f\u043e\u0449\u0435\u043d\u0441\u043a\u0438 \u043a\u043e\u0434.", - "title": "IQVIA" + "description": "\u041f\u043e\u043f\u044a\u043b\u043d\u0435\u0442\u0435 \u0432\u0430\u0448\u0438\u044f \u0430\u043c\u0435\u0440\u0438\u043a\u0430\u043d\u0441\u043a\u0438 \u0438\u043b\u0438 \u043a\u0430\u043d\u0430\u0434\u0441\u043a\u0438 \u043f\u043e\u0449\u0435\u043d\u0441\u043a\u0438 \u043a\u043e\u0434." } } } diff --git a/homeassistant/components/iqvia/translations/ca.json b/homeassistant/components/iqvia/translations/ca.json index 0e062e192cf..ddea24c4711 100644 --- a/homeassistant/components/iqvia/translations/ca.json +++ b/homeassistant/components/iqvia/translations/ca.json @@ -11,8 +11,7 @@ "data": { "zip_code": "Codi postal" }, - "description": "Introdueix el teu codi postal d'Estats Units o Canad\u00e0.", - "title": "IQVIA" + "description": "Introdueix el teu codi postal d'Estats Units o Canad\u00e0." } } } diff --git a/homeassistant/components/iqvia/translations/cs.json b/homeassistant/components/iqvia/translations/cs.json index 04829af0b8f..ac4be10dc8d 100644 --- a/homeassistant/components/iqvia/translations/cs.json +++ b/homeassistant/components/iqvia/translations/cs.json @@ -11,8 +11,7 @@ "data": { "zip_code": "PS\u010c" }, - "description": "Vypl\u0148te sv\u00e9 americk\u00e9 nebo kanadsk\u00e9 PS\u010c.", - "title": "IQVIA" + "description": "Vypl\u0148te sv\u00e9 americk\u00e9 nebo kanadsk\u00e9 PS\u010c." } } } diff --git a/homeassistant/components/iqvia/translations/da.json b/homeassistant/components/iqvia/translations/da.json index 8cc93be50ec..8e074aacea7 100644 --- a/homeassistant/components/iqvia/translations/da.json +++ b/homeassistant/components/iqvia/translations/da.json @@ -8,8 +8,7 @@ "data": { "zip_code": "Postnummer" }, - "description": "Udfyld dit amerikanske eller canadiske postnummer.", - "title": "IQVIA" + "description": "Udfyld dit amerikanske eller canadiske postnummer." } } } diff --git a/homeassistant/components/iqvia/translations/de.json b/homeassistant/components/iqvia/translations/de.json index 5d307eea829..90cf3a434c8 100644 --- a/homeassistant/components/iqvia/translations/de.json +++ b/homeassistant/components/iqvia/translations/de.json @@ -11,8 +11,7 @@ "data": { "zip_code": "Postleitzahl" }, - "description": "Trage eine US-amerikanische oder kanadische Postleitzahl ein.", - "title": "IQVIA" + "description": "Trage eine US-amerikanische oder kanadische Postleitzahl ein." } } } diff --git a/homeassistant/components/iqvia/translations/el.json b/homeassistant/components/iqvia/translations/el.json index 4a3045201e2..44f2aab43eb 100644 --- a/homeassistant/components/iqvia/translations/el.json +++ b/homeassistant/components/iqvia/translations/el.json @@ -11,8 +11,7 @@ "data": { "zip_code": "\u03a4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2" }, - "description": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1 \u03c4\u03c9\u03bd \u0397\u03a0\u0391 \u03ae \u03c4\u03bf\u03c5 \u039a\u03b1\u03bd\u03b1\u03b4\u03ac.", - "title": "IQVIA" + "description": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1 \u03c4\u03c9\u03bd \u0397\u03a0\u0391 \u03ae \u03c4\u03bf\u03c5 \u039a\u03b1\u03bd\u03b1\u03b4\u03ac." } } } diff --git a/homeassistant/components/iqvia/translations/en.json b/homeassistant/components/iqvia/translations/en.json index 46b9189660b..1732f9c9c2e 100644 --- a/homeassistant/components/iqvia/translations/en.json +++ b/homeassistant/components/iqvia/translations/en.json @@ -11,8 +11,7 @@ "data": { "zip_code": "ZIP Code" }, - "description": "Fill out your U.S. or Canadian ZIP code.", - "title": "IQVIA" + "description": "Fill out your U.S. or Canadian ZIP code." } } } diff --git a/homeassistant/components/iqvia/translations/es-419.json b/homeassistant/components/iqvia/translations/es-419.json index 61fa3f885a2..6c47623626b 100644 --- a/homeassistant/components/iqvia/translations/es-419.json +++ b/homeassistant/components/iqvia/translations/es-419.json @@ -8,8 +8,7 @@ "data": { "zip_code": "C\u00f3digo postal" }, - "description": "Complete su c\u00f3digo postal de EE. UU. o Canad\u00e1.", - "title": "IQVIA" + "description": "Complete su c\u00f3digo postal de EE. UU. o Canad\u00e1." } } } diff --git a/homeassistant/components/iqvia/translations/es.json b/homeassistant/components/iqvia/translations/es.json index 1d02f6e60c8..cecbb7af591 100644 --- a/homeassistant/components/iqvia/translations/es.json +++ b/homeassistant/components/iqvia/translations/es.json @@ -11,8 +11,7 @@ "data": { "zip_code": "C\u00f3digo postal" }, - "description": "Indica tu c\u00f3digo postal de Estados Unidos o Canad\u00e1.", - "title": "IQVIA" + "description": "Indica tu c\u00f3digo postal de Estados Unidos o Canad\u00e1." } } } diff --git a/homeassistant/components/iqvia/translations/et.json b/homeassistant/components/iqvia/translations/et.json index e1f6e51ef7c..5a9771f0d0f 100644 --- a/homeassistant/components/iqvia/translations/et.json +++ b/homeassistant/components/iqvia/translations/et.json @@ -11,8 +11,7 @@ "data": { "zip_code": "Sihtnumber" }, - "description": "Sisesta oma USA v\u00f5i Kanada sihtnumber.", - "title": "" + "description": "Sisesta oma USA v\u00f5i Kanada sihtnumber." } } } diff --git a/homeassistant/components/iqvia/translations/fi.json b/homeassistant/components/iqvia/translations/fi.json index 375176873f0..bc76d09c8e5 100644 --- a/homeassistant/components/iqvia/translations/fi.json +++ b/homeassistant/components/iqvia/translations/fi.json @@ -7,8 +7,7 @@ "user": { "data": { "zip_code": "Postinumero" - }, - "title": "IQVIA" + } } } } diff --git a/homeassistant/components/iqvia/translations/fr.json b/homeassistant/components/iqvia/translations/fr.json index 97adf4c35a6..8bc7bd18ada 100644 --- a/homeassistant/components/iqvia/translations/fr.json +++ b/homeassistant/components/iqvia/translations/fr.json @@ -11,8 +11,7 @@ "data": { "zip_code": "Code postal" }, - "description": "Entrez votre code postal am\u00e9ricain ou canadien.", - "title": "IQVIA" + "description": "Entrez votre code postal am\u00e9ricain ou canadien." } } } diff --git a/homeassistant/components/iqvia/translations/hu.json b/homeassistant/components/iqvia/translations/hu.json index 0ae420e47aa..c28fc2b23ab 100644 --- a/homeassistant/components/iqvia/translations/hu.json +++ b/homeassistant/components/iqvia/translations/hu.json @@ -11,8 +11,7 @@ "data": { "zip_code": "Ir\u00e1ny\u00edt\u00f3sz\u00e1m" }, - "description": "T\u00f6ltse ki amerikai vagy kanadai ir\u00e1ny\u00edt\u00f3sz\u00e1m\u00e1t.", - "title": "IQVIA" + "description": "T\u00f6ltse ki amerikai vagy kanadai ir\u00e1ny\u00edt\u00f3sz\u00e1m\u00e1t." } } } diff --git a/homeassistant/components/iqvia/translations/id.json b/homeassistant/components/iqvia/translations/id.json index 32f9b77135b..ed6999558da 100644 --- a/homeassistant/components/iqvia/translations/id.json +++ b/homeassistant/components/iqvia/translations/id.json @@ -11,8 +11,7 @@ "data": { "zip_code": "Kode Pos" }, - "description": "Isi kode pos AS atau Kanada.", - "title": "IQVIA" + "description": "Isi kode pos AS atau Kanada." } } } diff --git a/homeassistant/components/iqvia/translations/it.json b/homeassistant/components/iqvia/translations/it.json index 67d537bab5f..2e9b1e7fd9a 100644 --- a/homeassistant/components/iqvia/translations/it.json +++ b/homeassistant/components/iqvia/translations/it.json @@ -11,8 +11,7 @@ "data": { "zip_code": "CAP" }, - "description": "Compila il tuo CAP americano o canadese.", - "title": "IQVIA" + "description": "Compila il tuo CAP americano o canadese." } } } diff --git a/homeassistant/components/iqvia/translations/ja.json b/homeassistant/components/iqvia/translations/ja.json index 7a9e136ddc9..e4fde9a5eef 100644 --- a/homeassistant/components/iqvia/translations/ja.json +++ b/homeassistant/components/iqvia/translations/ja.json @@ -11,8 +11,7 @@ "data": { "zip_code": "\u90f5\u4fbf\u756a\u53f7" }, - "description": "\u7c73\u56fd\u307e\u305f\u306f\u30ab\u30ca\u30c0\u306e\u90f5\u4fbf\u756a\u53f7\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "IQVIA" + "description": "\u7c73\u56fd\u307e\u305f\u306f\u30ab\u30ca\u30c0\u306e\u90f5\u4fbf\u756a\u53f7\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" } } } diff --git a/homeassistant/components/iqvia/translations/ko.json b/homeassistant/components/iqvia/translations/ko.json index 1b0dfd980ec..f1b73c4a101 100644 --- a/homeassistant/components/iqvia/translations/ko.json +++ b/homeassistant/components/iqvia/translations/ko.json @@ -11,8 +11,7 @@ "data": { "zip_code": "\uc6b0\ud3b8\ubc88\ud638" }, - "description": "\ubbf8\uad6d \ub610\ub294 \uce90\ub098\ub2e4\uc758 \uc6b0\ud3b8\ubc88\ud638\ub97c \uae30\uc785\ud574\uc8fc\uc138\uc694.", - "title": "IQVIA" + "description": "\ubbf8\uad6d \ub610\ub294 \uce90\ub098\ub2e4\uc758 \uc6b0\ud3b8\ubc88\ud638\ub97c \uae30\uc785\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/iqvia/translations/lb.json b/homeassistant/components/iqvia/translations/lb.json index 02fa61fe5cc..45a7984368c 100644 --- a/homeassistant/components/iqvia/translations/lb.json +++ b/homeassistant/components/iqvia/translations/lb.json @@ -11,8 +11,7 @@ "data": { "zip_code": "Postleitzuel" }, - "description": "Gitt \u00e4r U.S. oder Kanadesch Postleitzuel un.", - "title": "IQVIA" + "description": "Gitt \u00e4r U.S. oder Kanadesch Postleitzuel un." } } } diff --git a/homeassistant/components/iqvia/translations/nl.json b/homeassistant/components/iqvia/translations/nl.json index 753e35c74ea..cea9df26bac 100644 --- a/homeassistant/components/iqvia/translations/nl.json +++ b/homeassistant/components/iqvia/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "invalid_zip_code": "Postcode is ongeldig" @@ -11,8 +11,7 @@ "data": { "zip_code": "Postcode" }, - "description": "Vul uw Amerikaanse of Canadese postcode in.", - "title": "IQVIA" + "description": "Vul uw Amerikaanse of Canadese postcode in." } } } diff --git a/homeassistant/components/iqvia/translations/nn.json b/homeassistant/components/iqvia/translations/nn.json deleted file mode 100644 index edbbd37f82a..00000000000 --- a/homeassistant/components/iqvia/translations/nn.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "IQVIA" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/iqvia/translations/no.json b/homeassistant/components/iqvia/translations/no.json index 5e073cefead..4042cb5346e 100644 --- a/homeassistant/components/iqvia/translations/no.json +++ b/homeassistant/components/iqvia/translations/no.json @@ -11,8 +11,7 @@ "data": { "zip_code": "Postnummer" }, - "description": "Fyll ut ditt amerikanske eller kanadiske postnummer.", - "title": "" + "description": "Fyll ut ditt amerikanske eller kanadiske postnummer." } } } diff --git a/homeassistant/components/iqvia/translations/pl.json b/homeassistant/components/iqvia/translations/pl.json index b5d10e9cc09..9d396521cdf 100644 --- a/homeassistant/components/iqvia/translations/pl.json +++ b/homeassistant/components/iqvia/translations/pl.json @@ -11,8 +11,7 @@ "data": { "zip_code": "Kod pocztowy" }, - "description": "Wprowad\u017a ameryka\u0144ski lub kanadyjski kod pocztowy.", - "title": "IQVIA" + "description": "Wprowad\u017a ameryka\u0144ski lub kanadyjski kod pocztowy." } } } diff --git a/homeassistant/components/iqvia/translations/pt-BR.json b/homeassistant/components/iqvia/translations/pt-BR.json index a366280ec35..0287fbb0d42 100644 --- a/homeassistant/components/iqvia/translations/pt-BR.json +++ b/homeassistant/components/iqvia/translations/pt-BR.json @@ -11,8 +11,7 @@ "data": { "zip_code": "C\u00f3digo postal" }, - "description": "Preencha o seu CEP dos EUA ou Canad\u00e1.", - "title": "IQVIA" + "description": "Preencha o seu CEP dos EUA ou Canad\u00e1." } } } diff --git a/homeassistant/components/iqvia/translations/ru.json b/homeassistant/components/iqvia/translations/ru.json index 6e2ddb93698..a785ef97a05 100644 --- a/homeassistant/components/iqvia/translations/ru.json +++ b/homeassistant/components/iqvia/translations/ru.json @@ -11,8 +11,7 @@ "data": { "zip_code": "\u041f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 \u043f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441 (\u0434\u043b\u044f \u0421\u0428\u0410 \u0438\u043b\u0438 \u041a\u0430\u043d\u0430\u0434\u044b).", - "title": "IQVIA" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 \u043f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441 (\u0434\u043b\u044f \u0421\u0428\u0410 \u0438\u043b\u0438 \u041a\u0430\u043d\u0430\u0434\u044b)." } } } diff --git a/homeassistant/components/iqvia/translations/sl.json b/homeassistant/components/iqvia/translations/sl.json index 351ad666c37..6afc35a489e 100644 --- a/homeassistant/components/iqvia/translations/sl.json +++ b/homeassistant/components/iqvia/translations/sl.json @@ -8,8 +8,7 @@ "data": { "zip_code": "Po\u0161tna \u0161tevilka" }, - "description": "Izpolnite svojo ameri\u0161ko ali kanadsko po\u0161tno \u0161tevilko.", - "title": "IQVIA" + "description": "Izpolnite svojo ameri\u0161ko ali kanadsko po\u0161tno \u0161tevilko." } } } diff --git a/homeassistant/components/iqvia/translations/sv.json b/homeassistant/components/iqvia/translations/sv.json index 88054725823..71eb118a858 100644 --- a/homeassistant/components/iqvia/translations/sv.json +++ b/homeassistant/components/iqvia/translations/sv.json @@ -8,8 +8,7 @@ "data": { "zip_code": "Postnummer" }, - "description": "Fyll i ditt Amerikanska eller Kanadensiska postnummer", - "title": "IQVIA" + "description": "Fyll i ditt Amerikanska eller Kanadensiska postnummer" } } } diff --git a/homeassistant/components/iqvia/translations/tr.json b/homeassistant/components/iqvia/translations/tr.json index 0b8a702d696..c5a6be17c9c 100644 --- a/homeassistant/components/iqvia/translations/tr.json +++ b/homeassistant/components/iqvia/translations/tr.json @@ -11,8 +11,7 @@ "data": { "zip_code": "Posta kodu" }, - "description": "ABD veya Kanada Posta kodunuzu doldurun.", - "title": "IQVIA" + "description": "ABD veya Kanada Posta kodunuzu doldurun." } } } diff --git a/homeassistant/components/iqvia/translations/uk.json b/homeassistant/components/iqvia/translations/uk.json index ab9813d6289..0d4d8940a86 100644 --- a/homeassistant/components/iqvia/translations/uk.json +++ b/homeassistant/components/iqvia/translations/uk.json @@ -11,8 +11,7 @@ "data": { "zip_code": "\u041f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441" }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0441\u0432\u0456\u0439 \u043f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441 (\u0434\u043b\u044f \u0421\u0428\u0410 \u0430\u0431\u043e \u041a\u0430\u043d\u0430\u0434\u0438).", - "title": "IQVIA" + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0441\u0432\u0456\u0439 \u043f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441 (\u0434\u043b\u044f \u0421\u0428\u0410 \u0430\u0431\u043e \u041a\u0430\u043d\u0430\u0434\u0438)." } } } diff --git a/homeassistant/components/iqvia/translations/zh-Hans.json b/homeassistant/components/iqvia/translations/zh-Hans.json index 9b5f24c1d5f..b5150f514c4 100644 --- a/homeassistant/components/iqvia/translations/zh-Hans.json +++ b/homeassistant/components/iqvia/translations/zh-Hans.json @@ -8,8 +8,7 @@ "data": { "zip_code": "\u90ae\u653f\u7f16\u7801" }, - "description": "\u586b\u5199\u60a8\u7684\u7f8e\u56fd\u6216\u52a0\u62ff\u5927\u90ae\u653f\u7f16\u7801\u3002", - "title": "IQVIA" + "description": "\u586b\u5199\u60a8\u7684\u7f8e\u56fd\u6216\u52a0\u62ff\u5927\u90ae\u653f\u7f16\u7801\u3002" } } } diff --git a/homeassistant/components/iqvia/translations/zh-Hant.json b/homeassistant/components/iqvia/translations/zh-Hant.json index 12d1edce896..fcb239e82c7 100644 --- a/homeassistant/components/iqvia/translations/zh-Hant.json +++ b/homeassistant/components/iqvia/translations/zh-Hant.json @@ -11,8 +11,7 @@ "data": { "zip_code": "\u90f5\u905e\u5340\u865f" }, - "description": "\u586b\u5beb\u7f8e\u570b\u6216\u52a0\u62ff\u5927\u90f5\u905e\u5340\u865f\u3002", - "title": "IQVIA" + "description": "\u586b\u5beb\u7f8e\u570b\u6216\u52a0\u62ff\u5927\u90f5\u905e\u5340\u865f\u3002" } } } diff --git a/homeassistant/components/iss/binary_sensor.py b/homeassistant/components/iss/binary_sensor.py index 12a8a7514b2..e2034fb48f9 100644 --- a/homeassistant/components/iss/binary_sensor.py +++ b/homeassistant/components/iss/binary_sensor.py @@ -7,24 +7,14 @@ import logging import pyiss import requests from requests.exceptions import HTTPError -import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - ATTR_LATITUDE, - ATTR_LONGITUDE, - CONF_NAME, - CONF_SHOW_ON_MAP, -) +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE, CONF_SHOW_ON_MAP 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 -from .const import DOMAIN - _LOGGER = logging.getLogger(__name__) ATTR_ISS_NEXT_RISE = "next_rise" @@ -35,35 +25,6 @@ DEFAULT_DEVICE_CLASS = "visible" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean, - } -) - - -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Import ISS configuration from yaml.""" - _LOGGER.warning( - "Configuration of the iss 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, - ) - ) - async def async_setup_entry( hass: HomeAssistant, diff --git a/homeassistant/components/iss/config_flow.py b/homeassistant/components/iss/config_flow.py index dc80126bd14..b43949daadc 100644 --- a/homeassistant/components/iss/config_flow.py +++ b/homeassistant/components/iss/config_flow.py @@ -43,15 +43,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user") - async def async_step_import(self, conf: dict) -> FlowResult: - """Import a configuration from configuration.yaml.""" - return await self.async_step_user( - user_input={ - CONF_NAME: conf[CONF_NAME], - CONF_SHOW_ON_MAP: conf[CONF_SHOW_ON_MAP], - } - ) - class OptionsFlowHandler(config_entries.OptionsFlow): """Config flow options handler for iss.""" diff --git a/homeassistant/components/iss/translations/bg.json b/homeassistant/components/iss/translations/bg.json index 05945056fb4..d1b61daf0ec 100644 --- a/homeassistant/components/iss/translations/bg.json +++ b/homeassistant/components/iss/translations/bg.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "\u0414\u0430 \u0441\u0435 \u043f\u043e\u043a\u0430\u0436\u0435 \u043d\u0430 \u043a\u0430\u0440\u0442\u0430\u0442\u0430?" - }, "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 \u041c\u0435\u0436\u0434\u0443\u043d\u0430\u0440\u043e\u0434\u043d\u0430\u0442\u0430 \u043a\u043e\u0441\u043c\u0438\u0447\u0435\u0441\u043a\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f?" } } diff --git a/homeassistant/components/iss/translations/ca.json b/homeassistant/components/iss/translations/ca.json index 23e4ff4eabd..3ce642e7564 100644 --- a/homeassistant/components/iss/translations/ca.json +++ b/homeassistant/components/iss/translations/ca.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Mostrar al mapa?" - }, "description": "Vols configurar Estaci\u00f3 Espacial Internacional (ISS)?" } } diff --git a/homeassistant/components/iss/translations/de.json b/homeassistant/components/iss/translations/de.json index 04ae0f6e9d5..c7eb9434db2 100644 --- a/homeassistant/components/iss/translations/de.json +++ b/homeassistant/components/iss/translations/de.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Auf der Karte anzeigen?" - }, "description": "M\u00f6chtest du die Internationale Raumstation (ISS) konfigurieren?" } } diff --git a/homeassistant/components/iss/translations/el.json b/homeassistant/components/iss/translations/el.json index 0938fd72c09..8d9570225ba 100644 --- a/homeassistant/components/iss/translations/el.json +++ b/homeassistant/components/iss/translations/el.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c7\u03ac\u03c1\u03c4\u03b7;" - }, "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5 \u0394\u03b9\u03b5\u03b8\u03bd\u03bf\u03cd\u03c2 \u0394\u03b9\u03b1\u03c3\u03c4\u03b7\u03bc\u03b9\u03ba\u03bf\u03cd \u03a3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd (ISS);" } } diff --git a/homeassistant/components/iss/translations/en.json b/homeassistant/components/iss/translations/en.json index 56f9bd79e88..b90ff56964a 100644 --- a/homeassistant/components/iss/translations/en.json +++ b/homeassistant/components/iss/translations/en.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Show on map?" - }, "description": "Do you want to configure International Space Station (ISS)?" } } diff --git a/homeassistant/components/iss/translations/es.json b/homeassistant/components/iss/translations/es.json index 2cc46fefdb1..6ca3e875615 100644 --- a/homeassistant/components/iss/translations/es.json +++ b/homeassistant/components/iss/translations/es.json @@ -5,9 +5,7 @@ }, "step": { "user": { - "data": { - "show_on_map": "\u00bfMostrar en el mapa?" - } + "description": "\u00bfQuieres configurar Estaci\u00f3n Espacial Internacional (ISS)?" } } }, diff --git a/homeassistant/components/iss/translations/et.json b/homeassistant/components/iss/translations/et.json index 60385881b19..d298c57c64b 100644 --- a/homeassistant/components/iss/translations/et.json +++ b/homeassistant/components/iss/translations/et.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Kas n\u00e4idata kaardil?" - }, "description": "Kas seadistada rahvusvahelise kosmosejaama (ISS) sidumist?" } } diff --git a/homeassistant/components/iss/translations/fr.json b/homeassistant/components/iss/translations/fr.json index 2f9ad5d427c..d1ef91aed9d 100644 --- a/homeassistant/components/iss/translations/fr.json +++ b/homeassistant/components/iss/translations/fr.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Afficher sur la carte\u00a0?" - }, "description": "Voulez-vous configurer la Station spatiale internationale (ISS)\u00a0?" } } diff --git a/homeassistant/components/iss/translations/hu.json b/homeassistant/components/iss/translations/hu.json index bfe593e9592..5dd2d53d074 100644 --- a/homeassistant/components/iss/translations/hu.json +++ b/homeassistant/components/iss/translations/hu.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Megjelenjen a t\u00e9rk\u00e9pen?" - }, "description": "Szeretn\u00e9 konfigur\u00e1lni a Nemzetk\u00f6zi \u0170r\u00e1llom\u00e1st (ISS)?" } } diff --git a/homeassistant/components/iss/translations/id.json b/homeassistant/components/iss/translations/id.json index c53287164ee..ddf2b1a4f71 100644 --- a/homeassistant/components/iss/translations/id.json +++ b/homeassistant/components/iss/translations/id.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Tampilkan di peta?" - }, "description": "Ingin mengonfigurasi Stasiun Luar Angkasa Internasional (ISS)?" } } diff --git a/homeassistant/components/iss/translations/it.json b/homeassistant/components/iss/translations/it.json index b3ec1329eae..eaa43bc34aa 100644 --- a/homeassistant/components/iss/translations/it.json +++ b/homeassistant/components/iss/translations/it.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Mostrare sulla mappa?" - }, "description": "Vuoi configurare la Stazione Spaziale Internazionale (ISS)?" } } diff --git a/homeassistant/components/iss/translations/ja.json b/homeassistant/components/iss/translations/ja.json index 40178b203f9..bf5deeaa716 100644 --- a/homeassistant/components/iss/translations/ja.json +++ b/homeassistant/components/iss/translations/ja.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "\u5730\u56f3\u306b\u8868\u793a\u3057\u307e\u3059\u304b\uff1f" - }, "description": "\u56fd\u969b\u5b87\u5b99\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u306e\u8a2d\u5b9a\u3092\u3057\u307e\u3059\u304b\uff1f" } } diff --git a/homeassistant/components/iss/translations/nl.json b/homeassistant/components/iss/translations/nl.json index 08a170b1ccc..9e8d1f6bf95 100644 --- a/homeassistant/components/iss/translations/nl.json +++ b/homeassistant/components/iss/translations/nl.json @@ -2,13 +2,10 @@ "config": { "abort": { "latitude_longitude_not_defined": "Breedte- en lengtegraad zijn niet gedefinieerd in Home Assistant.", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "user": { - "data": { - "show_on_map": "Op kaart tonen?" - }, "description": "Wilt u International Space Station configureren?" } } diff --git a/homeassistant/components/iss/translations/no.json b/homeassistant/components/iss/translations/no.json index c204f5a9012..1999c50c8be 100644 --- a/homeassistant/components/iss/translations/no.json +++ b/homeassistant/components/iss/translations/no.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Vis p\u00e5 kart?" - }, "description": "Vil du konfigurere den internasjonale romstasjonen (ISS)?" } } diff --git a/homeassistant/components/iss/translations/pl.json b/homeassistant/components/iss/translations/pl.json index 2cdf8ef4863..c7e39260365 100644 --- a/homeassistant/components/iss/translations/pl.json +++ b/homeassistant/components/iss/translations/pl.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Pokaza\u0107 na mapie?" - }, "description": "Czy chcesz skonfigurowa\u0107 Mi\u0119dzynarodow\u0105 Stacj\u0119 Kosmiczn\u0105 (ISS)?" } } diff --git a/homeassistant/components/iss/translations/pt-BR.json b/homeassistant/components/iss/translations/pt-BR.json index c69fefed0c5..6618abe68f4 100644 --- a/homeassistant/components/iss/translations/pt-BR.json +++ b/homeassistant/components/iss/translations/pt-BR.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Mostrar no mapa" - }, "description": "Deseja configurar a Esta\u00e7\u00e3o Espacial Internacional (ISS)?" } } @@ -17,7 +14,7 @@ "step": { "init": { "data": { - "show_on_map": "Mostrar no mapa" + "show_on_map": "[%key:component::iss::config::step::user::data::show_on_map%]" } } } diff --git a/homeassistant/components/iss/translations/ru.json b/homeassistant/components/iss/translations/ru.json index 64604c1f460..e0f965bd212 100644 --- a/homeassistant/components/iss/translations/ru.json +++ b/homeassistant/components/iss/translations/ru.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" - }, "description": "\u041d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 International Space Station (ISS)?" } } diff --git a/homeassistant/components/iss/translations/tr.json b/homeassistant/components/iss/translations/tr.json index 3cb92db229c..f647a6c02c2 100644 --- a/homeassistant/components/iss/translations/tr.json +++ b/homeassistant/components/iss/translations/tr.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "Haritada g\u00f6sterilsin mi?" - }, "description": "Uluslararas\u0131 Uzay \u0130stasyonunu (ISS) yap\u0131land\u0131rmak istiyor musunuz?" } } diff --git a/homeassistant/components/iss/translations/uk.json b/homeassistant/components/iss/translations/uk.json index cfcf3e0c458..ca7bf63af76 100644 --- a/homeassistant/components/iss/translations/uk.json +++ b/homeassistant/components/iss/translations/uk.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043d\u0430 \u043c\u0430\u043f\u0456?" - }, "description": "\u0427\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u041c\u0456\u0436\u043d\u0430\u0440\u043e\u0434\u043d\u0443 \u041a\u043e\u0441\u043c\u0456\u0447\u043d\u0443 \u0421\u0442\u0430\u043d\u0446\u0456\u044e?" } } diff --git a/homeassistant/components/iss/translations/zh-Hans.json b/homeassistant/components/iss/translations/zh-Hans.json deleted file mode 100644 index 47c25d7ddff..00000000000 --- a/homeassistant/components/iss/translations/zh-Hans.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "show_on_map": "\u5728\u5730\u56fe\u4e0a\u663e\u793a\uff1f" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/iss/translations/zh-Hant.json b/homeassistant/components/iss/translations/zh-Hant.json index f50730ff9a4..cdccbe14058 100644 --- a/homeassistant/components/iss/translations/zh-Hant.json +++ b/homeassistant/components/iss/translations/zh-Hant.json @@ -6,9 +6,6 @@ }, "step": { "user": { - "data": { - "show_on_map": "\u65bc\u5730\u5716\u986f\u793a\uff1f" - }, "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u570b\u969b\u592a\u7a7a\u7ad9\uff08ISS\uff09\uff1f" } } diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index ae48fc18b5b..e94b8215746 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -17,7 +17,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import Event, HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv import homeassistant.helpers.device_registry as dr from homeassistant.helpers.typing import ConfigType @@ -41,6 +41,7 @@ from .const import ( MANUFACTURER, PLATFORMS, PROGRAM_PLATFORMS, + SENSOR_AUX, ) from .helpers import _categorize_nodes, _categorize_programs, _categorize_variables from .services import async_setup_services, async_unload_services @@ -120,7 +121,7 @@ async def async_setup_entry( hass.data[DOMAIN][entry.entry_id] = {} hass_isy_data = hass.data[DOMAIN][entry.entry_id] - hass_isy_data[ISY994_NODES] = {} + hass_isy_data[ISY994_NODES] = {SENSOR_AUX: []} for platform in PLATFORMS: hass_isy_data[ISY994_NODES][platform] = [] @@ -181,11 +182,7 @@ async def async_setup_entry( f"Timed out initializing the ISY; device may be busy, trying again later: {err}" ) from err except ISYInvalidAuthError as err: - _LOGGER.error( - "Invalid credentials for the ISY, please adjust settings and try again: %s", - err, - ) - return False + raise ConfigEntryAuthFailed(f"Invalid credentials for the ISY: {err}") from err except ISYConnectionError as err: raise ConfigEntryNotReady( f"Failed to connect to the ISY, please adjust settings and try again: {err}" diff --git a/homeassistant/components/isy994/climate.py b/homeassistant/components/isy994/climate.py index 1276207f23c..d68395f14da 100644 --- a/homeassistant/components/isy994/climate.py +++ b/homeassistant/components/isy994/climate.py @@ -6,6 +6,7 @@ from typing import Any from pyisy.constants import ( CMD_CLIMATE_FAN_SETTING, CMD_CLIMATE_MODE, + ISY_VALUE_UNKNOWN, PROP_HEAT_COOL_STATE, PROP_HUMIDITY, PROP_SETPOINT_COOL, @@ -116,6 +117,8 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): """Return the current humidity.""" if not (humidity := self._node.aux_properties.get(PROP_HUMIDITY)): return None + if humidity == ISY_VALUE_UNKNOWN: + return None return int(humidity.value) @property diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index dea4bce4eeb..34d4738db68 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -119,6 +119,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the isy994 config flow.""" self.discovered_conf: dict[str, str] = {} + self._existing_entry: config_entries.ConfigEntry | None = None @staticmethod @callback @@ -142,7 +143,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): except InvalidHost: errors["base"] = "invalid_host" except InvalidAuth: - errors["base"] = "invalid_auth" + errors[CONF_PASSWORD] = "invalid_auth" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -252,6 +253,57 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.context["title_placeholders"] = self.discovered_conf return await self.async_step_user() + async def async_step_reauth( + self, user_input: dict[str, Any] | None = None + ) -> data_entry_flow.FlowResult: + """Handle reauth.""" + self._existing_entry = await self.async_set_unique_id(self.context["unique_id"]) + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> data_entry_flow.FlowResult: + """Handle reauth input.""" + errors = {} + assert self._existing_entry is not None + existing_entry = self._existing_entry + existing_data = existing_entry.data + if user_input is not None: + new_data = { + **existing_data, + CONF_USERNAME: user_input[CONF_USERNAME], + CONF_PASSWORD: user_input[CONF_PASSWORD], + } + try: + await validate_input(self.hass, new_data) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors[CONF_PASSWORD] = "invalid_auth" + else: + cfg_entries = self.hass.config_entries + cfg_entries.async_update_entry(existing_entry, data=new_data) + await cfg_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") + + self.context["title_placeholders"] = { + CONF_NAME: existing_entry.title, + CONF_HOST: existing_data[CONF_HOST], + } + return self.async_show_form( + description_placeholders={CONF_HOST: existing_data[CONF_HOST]}, + step_id="reauth_confirm", + data_schema=vol.Schema( + { + vol.Required( + CONF_USERNAME, default=existing_data[CONF_USERNAME] + ): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + ) + class OptionsFlowHandler(config_entries.OptionsFlow): """Handle a option flow for isy994.""" diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index bc463655f27..ddabe1b9680 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -191,6 +191,8 @@ UOM_INDEX = "25" UOM_ON_OFF = "2" UOM_PERCENTAGE = "51" +SENSOR_AUX = "sensor_aux" + # Do not use the Home Assistant consts for the states here - we're matching exact API # responses, not using them for Home Assistant states # Insteon Types: https://www.universal-devices.com/developers/wsdk/5.0.4/1_fam.xml diff --git a/homeassistant/components/isy994/entity.py b/homeassistant/components/isy994/entity.py index e5db8de5872..54ee9a2ded5 100644 --- a/homeassistant/components/isy994/entity.py +++ b/homeassistant/components/isy994/entity.py @@ -8,6 +8,7 @@ from pyisy.constants import ( EMPTY_TIME, EVENT_PROPS_IGNORED, PROTO_GROUP, + PROTO_INSTEON, PROTO_ZWAVE, ) from pyisy.helpers import EventListener, NodeProperty @@ -35,6 +36,7 @@ class ISYEntity(Entity): """Representation of an ISY994 device.""" _name: str | None = None + _attr_should_poll = False def __init__(self, node: Node) -> None: """Initialize the insteon device.""" @@ -86,7 +88,7 @@ class ISYEntity(Entity): node = self._node url = _async_isy_to_configuration_url(isy) - basename = self.name + basename = self._name or str(self._node.name) if hasattr(self._node, "parent_node") and self._node.parent_node is not None: # This is not the parent node, get the parent node. @@ -151,11 +153,6 @@ class ISYEntity(Entity): """Get the name of the device.""" return self._name or str(self._node.name) - @property - def should_poll(self) -> bool: - """No polling required since we're using the subscription.""" - return False - class ISYNodeEntity(ISYEntity): """Representation of a ISY Nodebase (Node/Group) entity.""" @@ -169,9 +166,13 @@ class ISYNodeEntity(ISYEntity): the combined result are returned as the device state attributes. """ attr = {} - if hasattr(self._node, "aux_properties"): - # Cast as list due to RuntimeError if a new property is added while running. - for name, value in list(self._node.aux_properties.items()): + node = self._node + # Insteon aux_properties are now their own sensors + if ( + hasattr(self._node, "aux_properties") + and getattr(node, "protocol", None) != PROTO_INSTEON + ): + for name, value in self._node.aux_properties.items(): attr_name = COMMAND_FRIENDLY_NAME.get(name, name) attr[attr_name] = str(value.formatted).lower() diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index 6d0a1d303bb..3b0de172a85 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -24,7 +24,7 @@ from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.components.switch import DOMAIN as SWITCH from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers import entity_registry as er from .const import ( _LOGGER, @@ -44,6 +44,7 @@ from .const import ( NODE_FILTERS, PLATFORMS, PROGRAM_PLATFORMS, + SENSOR_AUX, SUBNODE_CLIMATE_COOL, SUBNODE_CLIMATE_HEAT, SUBNODE_EZIO2X4_SENSORS, @@ -295,6 +296,10 @@ def _categorize_nodes( hass_isy_data[ISY994_NODES][ISY_GROUP_PLATFORM].append(node) continue + if getattr(node, "protocol", None) == PROTO_INSTEON: + for control in node.aux_properties: + hass_isy_data[ISY994_NODES][SENSOR_AUX].append((node, control)) + if sensor_identifier in path or sensor_identifier in node.name: # User has specified to treat this as a sensor. First we need to # determine if it should be a binary_sensor. @@ -378,7 +383,7 @@ async def migrate_old_unique_ids( hass: HomeAssistant, platform: str, entities: Sequence[ISYEntity] ) -> None: """Migrate to new controller-specific unique ids.""" - registry = await async_get_registry(hass) + registry = er.async_get(hass) for entity in entities: if entity.old_unique_id is None or entity.unique_id is None: diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index d9751fd707b..b02050f6f7b 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -3,12 +3,33 @@ from __future__ import annotations from typing import Any, cast -from pyisy.constants import ISY_VALUE_UNKNOWN +from pyisy.constants import ( + COMMAND_FRIENDLY_NAME, + ISY_VALUE_UNKNOWN, + PROP_BATTERY_LEVEL, + PROP_BUSY, + PROP_COMMS_ERROR, + PROP_ENERGY_MODE, + PROP_HEAT_COOL_STATE, + PROP_HUMIDITY, + PROP_ON_LEVEL, + PROP_RAMP_RATE, + PROP_STATUS, + PROP_TEMPERATURE, +) +from pyisy.helpers import NodeProperty +from pyisy.nodes import Node -from homeassistant.components.sensor import DOMAIN as SENSOR, SensorEntity +from homeassistant.components.sensor import ( + DOMAIN as SENSOR, + SensorDeviceClass, + SensorEntity, + SensorStateClass, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( @@ -16,6 +37,7 @@ from .const import ( DOMAIN as ISY994_DOMAIN, ISY994_NODES, ISY994_VARIABLES, + SENSOR_AUX, UOM_DOUBLE_TEMP, UOM_FRIENDLY_NAME, UOM_INDEX, @@ -25,6 +47,36 @@ from .const import ( from .entity import ISYEntity, ISYNodeEntity from .helpers import convert_isy_value_to_hass, migrate_old_unique_ids +# Disable general purpose and redundant sensors by default +AUX_DISABLED_BY_DEFAULT_MATCH = ["GV", "DO"] +AUX_DISABLED_BY_DEFAULT_EXACT = { + PROP_ENERGY_MODE, + PROP_HEAT_COOL_STATE, + PROP_ON_LEVEL, + PROP_RAMP_RATE, + PROP_STATUS, +} +SKIP_AUX_PROPERTIES = {PROP_BUSY, PROP_COMMS_ERROR, PROP_STATUS} + +ISY_CONTROL_TO_DEVICE_CLASS = { + PROP_BATTERY_LEVEL: SensorDeviceClass.BATTERY, + PROP_HUMIDITY: SensorDeviceClass.HUMIDITY, + PROP_TEMPERATURE: SensorDeviceClass.TEMPERATURE, + "BARPRES": SensorDeviceClass.PRESSURE, + "CO2LVL": SensorDeviceClass.CO2, + "CV": SensorDeviceClass.VOLTAGE, + "LUMIN": SensorDeviceClass.ILLUMINANCE, + "PF": SensorDeviceClass.POWER_FACTOR, +} +ISY_CONTROL_TO_STATE_CLASS = { + control: SensorStateClass.MEASUREMENT for control in ISY_CONTROL_TO_DEVICE_CLASS +} +ISY_CONTROL_TO_ENTITY_CATEGORY = { + PROP_RAMP_RATE: EntityCategory.CONFIG, + PROP_ON_LEVEL: EntityCategory.CONFIG, + PROP_COMMS_ERROR: EntityCategory.DIAGNOSTIC, +} + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -37,6 +89,21 @@ async def async_setup_entry( _LOGGER.debug("Loading %s", node.name) entities.append(ISYSensorEntity(node)) + aux_nodes = set() + for node, control in hass_isy_data[ISY994_NODES][SENSOR_AUX]: + aux_nodes.add(node) + if control in SKIP_AUX_PROPERTIES: + continue + _LOGGER.debug("Loading %s %s", node.name, node.aux_properties[control]) + enabled_default = control not in AUX_DISABLED_BY_DEFAULT_EXACT and not any( + control.startswith(match) for match in AUX_DISABLED_BY_DEFAULT_MATCH + ) + entities.append(ISYAuxSensorEntity(node, control, enabled_default)) + + for node in aux_nodes: + # Any node in SENSOR_AUX can potentially have communication errors + entities.append(ISYAuxSensorEntity(node, PROP_COMMS_ERROR, False)) + for vname, vobj in hass_isy_data[ISY994_VARIABLES]: entities.append(ISYSensorVariableEntity(vname, vobj)) @@ -47,10 +114,23 @@ async def async_setup_entry( class ISYSensorEntity(ISYNodeEntity, SensorEntity): """Representation of an ISY994 sensor device.""" + @property + def target(self) -> Node | NodeProperty | None: + """Return target for the sensor.""" + return self._node + + @property + def target_value(self) -> Any: + """Return the target value.""" + return self._node.status + @property def raw_unit_of_measurement(self) -> dict | str | None: """Get the raw unit of measurement for the ISY994 sensor device.""" - uom = self._node.uom + if self.target is None: + return None + + uom = self.target.uom # Backwards compatibility for ISYv4 Firmware: if isinstance(uom, list): @@ -69,7 +149,10 @@ class ISYSensorEntity(ISYNodeEntity, SensorEntity): @property def native_value(self) -> float | int | str | None: """Get the state of the ISY994 sensor device.""" - if (value := self._node.status) == ISY_VALUE_UNKNOWN: + if self.target is None: + return None + + if (value := self.target_value) == ISY_VALUE_UNKNOWN: return None # Get the translated ISY Unit of Measurement @@ -80,14 +163,14 @@ class ISYSensorEntity(ISYNodeEntity, SensorEntity): return uom.get(value, value) if uom in (UOM_INDEX, UOM_ON_OFF): - return cast(str, self._node.formatted) + return cast(str, self.target.formatted) # Check if this is an index type and get formatted value - if uom == UOM_INDEX and hasattr(self._node, "formatted"): - return cast(str, self._node.formatted) + if uom == UOM_INDEX and hasattr(self.target, "formatted"): + return cast(str, self.target.formatted) # Handle ISY precision and rounding - value = convert_isy_value_to_hass(value, uom, self._node.prec) + value = convert_isy_value_to_hass(value, uom, self.target.prec) # Convert temperatures to Home Assistant's unit if uom in (TEMP_CELSIUS, TEMP_FAHRENHEIT): @@ -111,6 +194,46 @@ class ISYSensorEntity(ISYNodeEntity, SensorEntity): return raw_units +class ISYAuxSensorEntity(ISYSensorEntity): + """Representation of an ISY994 aux sensor device.""" + + def __init__(self, node: Node, control: str, enabled_default: bool) -> None: + """Initialize the ISY994 aux sensor.""" + super().__init__(node) + self._control = control + self._attr_entity_registry_enabled_default = enabled_default + self._attr_entity_category = ISY_CONTROL_TO_ENTITY_CATEGORY.get(control) + self._attr_device_class = ISY_CONTROL_TO_DEVICE_CLASS.get(control) + self._attr_state_class = ISY_CONTROL_TO_STATE_CLASS.get(control) + + @property + def target(self) -> Node | NodeProperty | None: + """Return target for the sensor.""" + if self._control not in self._node.aux_properties: + # Property not yet set (i.e. no errors) + return None + return cast(NodeProperty, self._node.aux_properties[self._control]) + + @property + def target_value(self) -> Any: + """Return the target value.""" + return None if self.target is None else self.target.value + + @property + def unique_id(self) -> str | None: + """Get the unique identifier of the device and aux sensor.""" + if not hasattr(self._node, "address"): + return None + return f"{self._node.isy.configuration['uuid']}_{self._node.address}_{self._control}" + + @property + def name(self) -> str: + """Get the name of the device and aux sensor.""" + base_name = self._name or str(self._node.name) + name = COMMAND_FRIENDLY_NAME.get(self._control, self._control) + return f"{base_name} {name.replace('_', ' ').title()}" + + class ISYSensorVariableEntity(ISYEntity, SensorEntity): """Representation of an ISY994 variable as a sensor device.""" diff --git a/homeassistant/components/isy994/services.py b/homeassistant/components/isy994/services.py index 8323394803f..654b65309fc 100644 --- a/homeassistant/components/isy994/services.py +++ b/homeassistant/components/isy994/services.py @@ -19,6 +19,7 @@ from homeassistant.helpers import entity_platform import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import async_get_platforms import homeassistant.helpers.entity_registry as er +from homeassistant.helpers.service import entity_service_call from .const import ( _LOGGER, @@ -373,8 +374,8 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 ) async def _async_send_raw_node_command(call: ServiceCall) -> None: - await hass.helpers.service.entity_service_call( - async_get_platforms(hass, DOMAIN), "async_send_raw_node_command", call + await entity_service_call( + hass, async_get_platforms(hass, DOMAIN), "async_send_raw_node_command", call ) hass.services.async_register( @@ -385,8 +386,8 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 ) async def _async_send_node_command(call: ServiceCall) -> None: - await hass.helpers.service.entity_service_call( - async_get_platforms(hass, DOMAIN), "async_send_node_command", call + await entity_service_call( + hass, async_get_platforms(hass, DOMAIN), "async_send_node_command", call ) hass.services.async_register( @@ -397,8 +398,8 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 ) async def _async_get_zwave_parameter(call: ServiceCall) -> None: - await hass.helpers.service.entity_service_call( - async_get_platforms(hass, DOMAIN), "async_get_zwave_parameter", call + await entity_service_call( + hass, async_get_platforms(hass, DOMAIN), "async_get_zwave_parameter", call ) hass.services.async_register( @@ -409,8 +410,8 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 ) async def _async_set_zwave_parameter(call: ServiceCall) -> None: - await hass.helpers.service.entity_service_call( - async_get_platforms(hass, DOMAIN), "async_set_zwave_parameter", call + await entity_service_call( + hass, async_get_platforms(hass, DOMAIN), "async_set_zwave_parameter", call ) hass.services.async_register( @@ -421,8 +422,8 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 ) async def _async_rename_node(call: ServiceCall) -> None: - await hass.helpers.service.entity_service_call( - async_get_platforms(hass, DOMAIN), "async_rename_node", call + await entity_service_call( + hass, async_get_platforms(hass, DOMAIN), "async_rename_node", call ) hass.services.async_register( diff --git a/homeassistant/components/isy994/strings.json b/homeassistant/components/isy994/strings.json index eaba5f7a9da..821f8889978 100644 --- a/homeassistant/components/isy994/strings.json +++ b/homeassistant/components/isy994/strings.json @@ -10,10 +10,19 @@ "tls": "The TLS version of the ISY controller." }, "description": "The host entry must be in full URL format, e.g., http://192.168.10.100:80", - "title": "Connect to your ISY994" + "title": "Connect to your ISY" + }, + "reauth_confirm": { + "description": "The credentials for {host} are no longer valid.", + "title": "Reauthenticate your ISY", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } } }, "error": { + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "unknown": "[%key:common::config_flow::error::unknown%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", @@ -26,7 +35,7 @@ "options": { "step": { "init": { - "title": "ISY994 Options", + "title": "ISY Options", "description": "Set the options for the ISY Integration: \n • Node Sensor String: Any device or folder that contains 'Node Sensor String' in the name will be treated as a sensor or binary sensor. \n • Ignore String: Any device with 'Ignore String' in the name will be ignored. \n • Variable Sensor String: Any variable that contains 'Variable Sensor String' will be added as a sensor. \n • Restore Light Brightness: If enabled, the previous brightness will be restored when turning on a light instead of the device's built-in On-Level.", "data": { "sensor_string": "Node Sensor String", diff --git a/homeassistant/components/isy994/translations/bg.json b/homeassistant/components/isy994/translations/bg.json index 04a015b0d61..bba26a6cf89 100644 --- a/homeassistant/components/isy994/translations/bg.json +++ b/homeassistant/components/isy994/translations/bg.json @@ -6,10 +6,17 @@ "error": { "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \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", + "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" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "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" + } + }, "user": { "data": { "host": "URL", diff --git a/homeassistant/components/isy994/translations/ca.json b/homeassistant/components/isy994/translations/ca.json index cd0a2d2a1de..69c9d02b1bb 100644 --- a/homeassistant/components/isy994/translations/ca.json +++ b/homeassistant/components/isy994/translations/ca.json @@ -7,10 +7,19 @@ "cannot_connect": "[%key::common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key::common::config_flow::error::invalid_auth%]", "invalid_host": "L'entrada de l'amfitri\u00f3 no t\u00e9 el fromat d'URL complet, ex: http://192.168.10.100:80", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament", "unknown": "Error inesperat" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Les credencials de {host} ja no s\u00f3n v\u00e0lides.", + "title": "Re-autenticaci\u00f3 d'ISY" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "[%key::common::config_flow::data::username%]" }, "description": "L'entrada de l'amfitri\u00f3 ha de tenir el format d'URL complet, ex: http://192.168.10.100:80", - "title": "Connexi\u00f3 amb ISY994" + "title": "Connexi\u00f3 amb ISY" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "String de Sensor Variable" }, "description": "Configuraci\u00f3 de les opcions per a la integraci\u00f3 ISY: \n \u2022 String de Sensor Node: qualsevol dispositiu o carpeta que contingui 'String de Sensor Node' dins el nom ser\u00e0 tractat com a un sensor o sensor binari. \n \u2022 String Ignora: qualsevol dispositiu amb 'String Ignora' dins el nom ser\u00e0 ignorat. \n \u2022 String de Sensor Variable: qualsevol variable que contingui 'String de Sensor Variable' s'afegir\u00e0 com a un sensor. \n \u2022 Restaura la brillantor de la llum: si est\u00e0 activat, en encendre un llum es restablir\u00e0 la brillantor anterior en lloc del valor integrat dins el dispositiu.", - "title": "Opcions d'ISY994" + "title": "Opcions d'ISY" } } }, diff --git a/homeassistant/components/isy994/translations/de.json b/homeassistant/components/isy994/translations/de.json index a6e9e0a1498..a183e6f7853 100644 --- a/homeassistant/components/isy994/translations/de.json +++ b/homeassistant/components/isy994/translations/de.json @@ -7,10 +7,19 @@ "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_host": "Der Hosteintrag hatte nicht das vollst\u00e4ndige URL-Format, z. B. http://192.168.10.100:80", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", "unknown": "Unerwarteter Fehler" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Passwort", + "username": "Benutzername" + }, + "description": "Die Anmeldedaten f\u00fcr {host} sind nicht mehr g\u00fcltig.", + "title": "Reauthentifizierung deines ISY" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Benutzername" }, "description": "Der Hosteintrag muss im vollst\u00e4ndigen URL-Format vorliegen, z. B. http://192.168.10.100:80", - "title": "Stelle eine Verbindung zu deinem ISY994 her" + "title": "Verbindung zu deinem ISY" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "Variabler Sensor String" }, "description": "Stelle die Optionen f\u00fcr die ISY-Integration ein: \n - Node Sensor String: Jedes Ger\u00e4t oder jeder Ordner, der 'Node Sensor String' im Namen enth\u00e4lt, wird als Sensor oder bin\u00e4rer Sensor behandelt. \n - String ignorieren: Jedes Ger\u00e4t mit 'Ignore String' im Namen wird ignoriert. \n - Variable Sensor Zeichenfolge: Jede Variable, die 'Variable Sensor String' im Namen enth\u00e4lt, wird als Sensor hinzugef\u00fcgt. \n - Lichthelligkeit wiederherstellen: Wenn diese Option aktiviert ist, wird beim Einschalten eines Lichts die vorherige Helligkeit wiederhergestellt und nicht der integrierte Ein-Pegel des Ger\u00e4ts.", - "title": "ISY994 Optionen" + "title": "ISY-Optionen" } } }, diff --git a/homeassistant/components/isy994/translations/el.json b/homeassistant/components/isy994/translations/el.json index 470975e2117..ec32ea756bc 100644 --- a/homeassistant/components/isy994/translations/el.json +++ b/homeassistant/components/isy994/translations/el.json @@ -7,10 +7,19 @@ "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_auth": "v", "invalid_host": "\u0397 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03b4\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03c3\u03b5 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae URL, \u03c0.\u03c7. http://192.168.10.100:80", + "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", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "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": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03bf {host} \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd \u03ad\u03b3\u03ba\u03c5\u03c1\u03b1.", + "title": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf ISY \u03c3\u03b1\u03c2" + }, "user": { "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", diff --git a/homeassistant/components/isy994/translations/en.json b/homeassistant/components/isy994/translations/en.json index dbeaa75acf4..3610b35c194 100644 --- a/homeassistant/components/isy994/translations/en.json +++ b/homeassistant/components/isy994/translations/en.json @@ -7,10 +7,19 @@ "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "invalid_host": "The host entry was not in full URL format, e.g., http://192.168.10.100:80", + "reauth_successful": "Re-authentication was successful", "unknown": "Unexpected error" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Password", + "username": "Username" + }, + "description": "The credentials for {host} are no longer valid.", + "title": "Reauthenticate your ISY" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Username" }, "description": "The host entry must be in full URL format, e.g., http://192.168.10.100:80", - "title": "Connect to your ISY994" + "title": "Connect to your ISY" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "Variable Sensor String" }, "description": "Set the options for the ISY Integration: \n \u2022 Node Sensor String: Any device or folder that contains 'Node Sensor String' in the name will be treated as a sensor or binary sensor. \n \u2022 Ignore String: Any device with 'Ignore String' in the name will be ignored. \n \u2022 Variable Sensor String: Any variable that contains 'Variable Sensor String' will be added as a sensor. \n \u2022 Restore Light Brightness: If enabled, the previous brightness will be restored when turning on a light instead of the device's built-in On-Level.", - "title": "ISY994 Options" + "title": "ISY Options" } } }, diff --git a/homeassistant/components/isy994/translations/es.json b/homeassistant/components/isy994/translations/es.json index e5e15fc9c13..b9b2c1f1ed5 100644 --- a/homeassistant/components/isy994/translations/es.json +++ b/homeassistant/components/isy994/translations/es.json @@ -11,6 +11,14 @@ }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "description": "Las credenciales de {host} ya no son v\u00e1lidas.", + "title": "Re-autenticaci\u00f3n de ISY" + }, "user": { "data": { "host": "URL", @@ -19,7 +27,7 @@ "username": "Usuario" }, "description": "La entrada del host debe estar en formato URL completo, por ejemplo, http://192.168.10.100:80", - "title": "Conectar con tu ISY994" + "title": "Conexi\u00f3n con ISY" } } }, diff --git a/homeassistant/components/isy994/translations/et.json b/homeassistant/components/isy994/translations/et.json index 233b3a2bfe8..0f7e0e45503 100644 --- a/homeassistant/components/isy994/translations/et.json +++ b/homeassistant/components/isy994/translations/et.json @@ -7,10 +7,19 @@ "cannot_connect": "\u00dchendamine nurjus", "invalid_auth": "Tuvastamine nurjus", "invalid_host": "Hostikirje ei olnud sobivas URL-vormingus, nt http://192.168.10.100:80", + "reauth_successful": "Taastuvastamine \u00f5nnestus", "unknown": "Tundmatu viga" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "Kasutaja {host} mandaat ei kehti enam.", + "title": "Taastuvasta ISY" + }, "user": { "data": { "host": "", @@ -19,7 +28,7 @@ "username": "Kasutajanimi" }, "description": "Hostikirje peab olema t\u00e4ielikus URL-vormingus, nt http://192.168.10.100:80", - "title": "\u00dchendu oma ISY994-ga" + "title": "\u00dchendu oma ISY-ga" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "Muutuja sensori string" }, "description": "Isy isidumisei suvandite m\u00e4\u00e4ramine: \n \u2022 S\u00f5lme sensor string: iga seade v\u00f5i kaust, mis sisaldab nimes \"Node Sensor String\" k\u00e4sitletakse sensorina v\u00f5i binaarandurina. \n \u2022 Ignoreeri stringi: iga seadet, mille nimes on \"Ignore String\", ignoreeritakse. \n \u2022 Muutuv sensorstring: andurina lisatakse k\u00f5ik muutujad, mis sisaldavad \"Variable Sensor String\". \n \u2022 Valguse heleduse taastamine: kui see on lubatud, taastatakse eelmine heledus, kui l\u00fclitate sisse seadme sisseehitatud taseme asemel valguse.", - "title": "ISY994 valikud" + "title": "ISY valikud" } } }, diff --git a/homeassistant/components/isy994/translations/fr.json b/homeassistant/components/isy994/translations/fr.json index c9e53845dfb..29a3c6e54fe 100644 --- a/homeassistant/components/isy994/translations/fr.json +++ b/homeassistant/components/isy994/translations/fr.json @@ -7,10 +7,19 @@ "cannot_connect": "\u00c9chec de connexion", "invalid_auth": "Authentification non valide", "invalid_host": "L'entr\u00e9e d'h\u00f4te n'\u00e9tait pas au format URL complet, par exemple http://192.168.10.100:80", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", "unknown": "Erreur inattendue" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "description": "Les informations d'identification pour {host} ne sont plus valides.", + "title": "R\u00e9authentifiez votre ISY" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Nom d'utilisateur" }, "description": "L'entr\u00e9e d'h\u00f4te doit \u00eatre au format URL complet, par exemple, http://192.168.10.100:80", - "title": "Connect\u00e9 \u00e0 votre ISY994" + "title": "Connexion \u00e0 votre ISY" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "Cha\u00eene de capteur variable" }, "description": "D\u00e9finir les options pour l'int\u00e9gration ISY: \n \u2022 Node Sensor String: tout p\u00e9riph\u00e9rique ou dossier contenant \u00abNode Sensor String\u00bb dans le nom sera trait\u00e9 comme un capteur ou un capteur binaire. \n \u2022 Ignore String : tout p\u00e9riph\u00e9rique avec \u00abIgnore String\u00bb dans le nom sera ignor\u00e9. \n \u2022 Variable Sensor String : toute variable contenant \u00abVariable Sensor String\u00bb sera ajout\u00e9e en tant que capteur. \n \u2022 Restaurer la luminosit\u00e9 : si cette option est activ\u00e9e, la luminosit\u00e9 pr\u00e9c\u00e9dente sera restaur\u00e9e lors de l'allumage d'une lumi\u00e8re au lieu de la fonction int\u00e9gr\u00e9e de l'appareil.", - "title": "Options ISY994" + "title": "Options ISY" } } }, diff --git a/homeassistant/components/isy994/translations/he.json b/homeassistant/components/isy994/translations/he.json index b1874d2675d..e72724785cc 100644 --- a/homeassistant/components/isy994/translations/he.json +++ b/homeassistant/components/isy994/translations/he.json @@ -7,10 +7,17 @@ "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", "invalid_host": "\u05e2\u05e8\u05da \u05d4\u05beHost \u05dc\u05d0 \u05d4\u05d9\u05d4 \u05d1\u05e4\u05d5\u05e8\u05de\u05d8 URL \u05de\u05dc\u05d0, \u05dc\u05de\u05e9\u05dc, http://192.168.10.100:80", + "reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7", "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05dc\u05d0 \u05e6\u05e4\u05d5\u05d9\u05d9\u05d4" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + }, "user": { "data": { "host": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8", diff --git a/homeassistant/components/isy994/translations/hu.json b/homeassistant/components/isy994/translations/hu.json index d9cce2fefcb..e5a5c4d6f98 100644 --- a/homeassistant/components/isy994/translations/hu.json +++ b/homeassistant/components/isy994/translations/hu.json @@ -7,10 +7,19 @@ "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "invalid_host": "A c\u00edm bejegyz\u00e9se nem volt teljes URL-form\u00e1tumban, p\u00e9ld\u00e1ul: http://192.168.10.100:80", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "{host} hiteles\u00edt\u0151 adatai m\u00e1r nem \u00e9rv\u00e9nyesek.", + "title": "ISY \u00fajrahiteles\u00edt\u00e9se" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, "description": "A c\u00edm bejegyz\u00e9s\u00e9nek teljes URL form\u00e1tumban kell lennie, pl. Http://192.168.10.100:80", - "title": "Csatlakozzon az ISY994-hez" + "title": "Csatlakozzon az ISY-hez" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "V\u00e1ltoz\u00f3 \u00e9rz\u00e9kel\u0151 karakterl\u00e1nc" }, "description": "\u00c1ll\u00edtsa be az ISY integr\u00e1ci\u00f3 be\u00e1ll\u00edt\u00e1sait:\n \u2022 Csom\u00f3pont -\u00e9rz\u00e9kel\u0151 karakterl\u00e1nc: B\u00e1rmely eszk\u00f6z vagy mappa, amelynek nev\u00e9ben \u201eNode Sensor String\u201d szerepel, \u00e9rz\u00e9kel\u0151k\u00e9nt vagy bin\u00e1ris \u00e9rz\u00e9kel\u0151k\u00e9nt fog kezelni.\n \u2022 Karakterl\u00e1nc figyelmen k\u00edv\u00fcl hagy\u00e1sa: Minden olyan eszk\u00f6z, amelynek a neve \u201eIgnore String\u201d, figyelmen k\u00edv\u00fcl marad.\n \u2022 V\u00e1ltoz\u00f3 \u00e9rz\u00e9kel\u0151 karakterl\u00e1nc: B\u00e1rmely v\u00e1ltoz\u00f3, amely tartalmazza a \u201eV\u00e1ltoz\u00f3 \u00e9rz\u00e9kel\u0151 karakterl\u00e1ncot\u201d, hozz\u00e1ad\u00f3dik \u00e9rz\u00e9kel\u0151k\u00e9nt.\n \u2022 F\u00e9nyer\u0151ss\u00e9g vissza\u00e1ll\u00edt\u00e1sa: Ha enged\u00e9lyezve van, akkor az el\u0151z\u0151 f\u00e9nyer\u0151 vissza\u00e1ll, amikor a k\u00e9sz\u00fcl\u00e9ket be\u00e9p\u00edtett On-Level helyett bekapcsolja.", - "title": "ISY994 Be\u00e1ll\u00edt\u00e1sok" + "title": "ISY be\u00e1ll\u00edt\u00e1sok" } } }, diff --git a/homeassistant/components/isy994/translations/id.json b/homeassistant/components/isy994/translations/id.json index aeea471514c..f27a8c41f5c 100644 --- a/homeassistant/components/isy994/translations/id.json +++ b/homeassistant/components/isy994/translations/id.json @@ -7,10 +7,19 @@ "cannot_connect": "Gagal terhubung", "invalid_auth": "Autentikasi tidak valid", "invalid_host": "Entri host tidak dalam format URL lengkap, misalnya, http://192.168.10.100:80", + "reauth_successful": "Autentikasi ulang berhasil", "unknown": "Kesalahan yang tidak diharapkan" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Kredensial untuk {host} tidak lagi valid.", + "title": "Autentikasi ulang ISY Anda" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Nama Pengguna" }, "description": "Entri host harus dalam format URL lengkap, misalnya, http://192.168.10.100:80", - "title": "Hubungkan ke ISY994 Anda" + "title": "Hubungkan ke ISY Anda" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "String Sensor Variabel" }, "description": "Mengatur opsi untuk Integrasi ISY: \n \u2022 String Sensor Node: Setiap perangkat atau folder yang berisi 'String Sensor Node' dalam nama akan diperlakukan sebagai sensor atau sensor biner. \n \u2022 Abaikan String: Setiap perangkat dengan 'Abaikan String' dalam nama akan diabaikan. \n \u2022 String Sensor Variabel: Variabel apa pun yang berisi 'String Sensor Variabel' akan ditambahkan sebagai sensor. \n \u2022 Pulihkan Kecerahan Cahaya: Jika diaktifkan, kecerahan sebelumnya akan dipulihkan saat menyalakan lampu alih-alih bawaan perangkat On-Level.", - "title": "Opsi ISY994" + "title": "Opsi ISY" } } }, diff --git a/homeassistant/components/isy994/translations/it.json b/homeassistant/components/isy994/translations/it.json index b59e312717a..dae46ed8275 100644 --- a/homeassistant/components/isy994/translations/it.json +++ b/homeassistant/components/isy994/translations/it.json @@ -7,10 +7,19 @@ "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "invalid_host": "La voce host non era nel formato URL completo, ad esempio http://192.168.10.100:80", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", "unknown": "Errore imprevisto" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Password", + "username": "Nome utente" + }, + "description": "Le credenziali per {host} non sono pi\u00f9 valide.", + "title": "Autentica nuovamente il tuo ISY" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Nome utente" }, "description": "La voce host deve essere nel formato URL completo, ad esempio, http://192.168.10.100:80", - "title": "Connettersi a ISY994" + "title": "Connettiti al tuo ISY" } } }, @@ -32,8 +41,8 @@ "sensor_string": "Stringa Nodo Sensore", "variable_sensor_string": "Stringa Variabile Sensore" }, - "description": "Imposta le opzioni per l'integrazione ISY: \n \u2022 Stringa Nodo Sensore: qualsiasi dispositivo o cartella che contiene \"Stringa Nodo Sensore\" nel nome verr\u00e0 trattato come un sensore o un sensore binario. \n \u2022 Ignora Stringa: qualsiasi dispositivo con \"Ignora Stringa\" nel nome verr\u00e0 ignorato. \n \u2022 Stringa Variabile Sensore: qualsiasi variabile che contiene \"Stringa Variabile Sensore\" verr\u00e0 aggiunta come sensore. \n \u2022 Ripristina Luminosit\u00e0 Luce: se abilitato, verr\u00e0 ripristinata la luminosit\u00e0 precedente quando si accende una luce al posto del livello incorporato nel dispositivo.", - "title": "Opzioni ISY994" + "description": "Imposta le opzioni per l'integrazione ISY: \n \u2022 Stringa nodo sensore: qualsiasi dispositivo o cartella che contiene \"Stringa nodo sensore\" nel nome verr\u00e0 trattato come un sensore o un sensore binario. \n \u2022 Ignora stringa: qualsiasi dispositivo con \"Ignora stringa\" nel nome verr\u00e0 ignorato. \n \u2022 Stringa variabile sensore: qualsiasi variabile che contenga \"Stringa variabile sensore\" sar\u00e0 aggiunta come sensore. \n \u2022 Ripristina luminosit\u00e0 luce: se abilitato, verr\u00e0 ripristinata la luminosit\u00e0 precedente quando si accende una luce al posto del livello incorporato nel dispositivo.", + "title": "Opzioni ISY" } } }, diff --git a/homeassistant/components/isy994/translations/ja.json b/homeassistant/components/isy994/translations/ja.json index 271cba9e0c4..f1a447901b9 100644 --- a/homeassistant/components/isy994/translations/ja.json +++ b/homeassistant/components/isy994/translations/ja.json @@ -7,10 +7,19 @@ "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "invalid_host": "\u30db\u30b9\u30c8\u30a8\u30f3\u30c8\u30ea\u306f\u304d\u3061\u3093\u3068\u3057\u305fURL\u5f62\u5f0f\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3067\u3057\u305f \u4f8b: http://192.168.10.100:80", + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + }, + "description": "{host} \u306e\u8cc7\u683c\u60c5\u5831\u304c\u7121\u52b9\u306b\u306a\u308a\u307e\u3057\u305f\u3002", + "title": "ISY\u306e\u518d\u8a8d\u8a3c" + }, "user": { "data": { "host": "URL", diff --git a/homeassistant/components/isy994/translations/ko.json b/homeassistant/components/isy994/translations/ko.json index 29829e066b9..8673aa7a148 100644 --- a/homeassistant/components/isy994/translations/ko.json +++ b/homeassistant/components/isy994/translations/ko.json @@ -7,10 +7,19 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_host": "\ud638\uc2a4\ud2b8 \ud56d\ubaa9\uc774 \uc644\uc804\ud55c URL \ud615\uc2dd\uc774 \uc544\ub2d9\ub2c8\ub2e4. \uc608: http://192.168.10.100:80", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "ISY994 \ubc94\uc6a9 \uae30\uae30: {name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "{host}\uc5d0 \ub300\ud55c \uc790\uaca9 \uc99d\uba85\uc774 \ub354 \uc774\uc0c1 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "title": "ISY \uc7ac\uc778\uc99d" + }, "user": { "data": { "host": "URL \uc8fc\uc18c", diff --git a/homeassistant/components/isy994/translations/nl.json b/homeassistant/components/isy994/translations/nl.json index c75cdf86473..261be2929fc 100644 --- a/homeassistant/components/isy994/translations/nl.json +++ b/homeassistant/components/isy994/translations/nl.json @@ -4,13 +4,22 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "invalid_host": "De hostvermelding had de niet volledig URL-indeling, bijvoorbeeld http://192.168.10.100:80", + "reauth_successful": "Herauthenticatie geslaagd", "unknown": "Onverwachte fout" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "De inloggegevens voor {host} zijn niet langer geldig.", + "title": "Herautoriseer uw ISY" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Gebruikersnaam" }, "description": "Het hostvermelding moet de volledige URL-indeling hebben, bijvoorbeeld http://192.168.10.100:80", - "title": "Maak verbinding met uw ISY994" + "title": "Maak verbinding met uw ISY" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "Variabele sensor string" }, "description": "Stel de opties in voor de ISY-integratie:\n \u2022 Node Sensor String: elk apparaat of elke map die 'Node Sensor String' in de naam bevat, wordt behandeld als een sensor of binaire sensor.\n \u2022 Ignore String: elk apparaat met 'Ignore String' in de naam wordt genegeerd.\n \u2022 Variabele sensorreeks: elke variabele die 'Variabele sensorreeks' bevat, wordt als sensor toegevoegd.\n \u2022 Lichthelderheid herstellen: indien ingeschakeld, wordt de vorige helderheid hersteld wanneer u een lamp inschakelt in plaats van het ingebouwde Aan-niveau van het apparaat.", - "title": "ISY994-opties" + "title": "ISY-opties" } } }, diff --git a/homeassistant/components/isy994/translations/no.json b/homeassistant/components/isy994/translations/no.json index 7da3fa4c6fe..e18666d7fc4 100644 --- a/homeassistant/components/isy994/translations/no.json +++ b/homeassistant/components/isy994/translations/no.json @@ -7,10 +7,19 @@ "cannot_connect": "Tilkobling mislyktes", "invalid_auth": "Ugyldig godkjenning", "invalid_host": "Vertsoppf\u00f8ringen var ikke i fullstendig URL-format, for eksempel http://192.168.10.100:80", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket", "unknown": "Uventet feil" }, "flow_title": "{name} ( {host} )", "step": { + "reauth_confirm": { + "data": { + "password": "Passord", + "username": "Brukernavn" + }, + "description": "P\u00e5loggingsinformasjonen for {host} er ikke lenger gyldig.", + "title": "Autentiser din ISY p\u00e5 nytt" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Brukernavn" }, "description": "Vertsoppf\u00f8ringen m\u00e5 v\u00e6re i fullstendig URL-format, for eksempel http://192.168.10.100:80", - "title": "Koble til ISY994" + "title": "Koble til din ISY" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "Variabel sensorstreng" }, "description": "Angi alternativene for ISY-integrering: \n \u2022 Nodesensorstreng: Alle enheter eller mapper som inneholder NodeSensor String i navnet, behandles som en sensor eller bin\u00e6r sensor. \n \u2022 Ignorer streng: Alle enheter med 'Ignorer streng' i navnet ignoreres. \n \u2022 Variabel sensorstreng: Alle variabler som inneholder \"Variabel sensorstreng\" vil bli lagt til som en sensor. \n \u2022 Gjenopprett lyslysstyrke: Hvis den er aktivert, gjenopprettes den forrige lysstyrken n\u00e5r du sl\u00e5r p\u00e5 et lys i stedet for enhetens innebygde p\u00e5-niv\u00e5.", - "title": "ISY994 Alternativer" + "title": "ISY-alternativer" } } }, diff --git a/homeassistant/components/isy994/translations/pl.json b/homeassistant/components/isy994/translations/pl.json index 4deeefe391d..a1ca157bd14 100644 --- a/homeassistant/components/isy994/translations/pl.json +++ b/homeassistant/components/isy994/translations/pl.json @@ -7,10 +7,19 @@ "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_auth": "Niepoprawne uwierzytelnienie", "invalid_host": "Wpis hosta nie by\u0142 w pe\u0142nym formacie URL, np. http://192.168.10.100:80", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119", "unknown": "Nieoczekiwany b\u0142\u0105d" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Dane uwierzytelniaj\u0105ce dla {host} nie s\u0105 ju\u017c wa\u017cne.", + "title": "Ponownie uwierzytelnij ISY" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Nazwa u\u017cytkownika" }, "description": "Wpis hosta musi by\u0107 w pe\u0142nym formacie URL, np. http://192.168.10.100:80.", - "title": "Po\u0142\u0105czenie z ISY994" + "title": "Po\u0142\u0105czenie z ISY" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "Ci\u0105g zmiennej sensora" }, "description": "Ustaw opcje dla integracji ISY: \n \u2022 Ci\u0105g sensora w\u0119z\u0142a: ka\u017cde urz\u0105dzenie lub folder, kt\u00f3ry zawiera w nazwie ci\u0105g sensora w\u0119z\u0142a, b\u0119dzie traktowane jako sensor lub sensor binarny. \n \u2022 Ci\u0105g ignorowania: ka\u017cde urz\u0105dzenie z 'ci\u0105giem ignorowania' w nazwie zostanie zignorowane. \n \u2022 Ci\u0105g sensora zmiennej: ka\u017cda zmienna zawieraj\u0105ca 'ci\u0105g sensora zmiennej' zostanie dodana jako sensor. \n \u2022 Przywr\u00f3\u0107 jasno\u015b\u0107 \u015bwiat\u0142a: je\u015bli ta opcja jest w\u0142\u0105czona, poprzednia warto\u015b\u0107 jasno\u015bci zostanie przywr\u00f3cona po w\u0142\u0105czeniu \u015bwiat\u0142a zamiast domy\u015blnej warto\u015bci jasno\u015bci dla urz\u0105dzenia.", - "title": "Opcje ISY994" + "title": "Opcje ISY" } } }, diff --git a/homeassistant/components/isy994/translations/pt-BR.json b/homeassistant/components/isy994/translations/pt-BR.json index b644b6c1bfc..7b1d8e796ba 100644 --- a/homeassistant/components/isy994/translations/pt-BR.json +++ b/homeassistant/components/isy994/translations/pt-BR.json @@ -7,10 +7,19 @@ "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "invalid_host": "A entrada do host n\u00e3o est\u00e1 no formato de URL completo, por exemplo, http://192.168.10.100:80", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", "unknown": "Erro inesperado" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + }, + "description": "As credenciais para {host} n\u00e3o s\u00e3o mais v\u00e1lidas.", + "title": "Reautentique seu ISY" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Usu\u00e1rio" }, "description": "A entrada do endere\u00e7o deve estar no formato de URL completo, por exemplo, http://192.168.10.100:80", - "title": "Conecte-se ao seu ISY994" + "title": "Conecte-se seu ISY" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "Texto da vari\u00e1vel do sensor" }, "description": "Defina as op\u00e7\u00f5es para a Integra\u00e7\u00e3o ISY:\n \u2022 Cadeia de Sensores de N\u00f3: Qualquer dispositivo ou pasta que contenha 'Cadeia de Sensores de N\u00f3' no nome ser\u00e1 tratado como um sensor ou sensor bin\u00e1rio.\n \u2022 Ignore String: Qualquer dispositivo com 'Ignore String' no nome ser\u00e1 ignorado.\n \u2022 Variable Sensor String: Qualquer vari\u00e1vel que contenha 'Variable Sensor String' ser\u00e1 adicionada como sensor.\n \u2022 Restaurar o brilho da luz: Se ativado, o brilho anterior ser\u00e1 restaurado ao acender uma luz em vez do n\u00edvel integrado do dispositivo.", - "title": "ISY994 Op\u00e7\u00f5es" + "title": "Op\u00e7\u00f5es ISY" } } }, diff --git a/homeassistant/components/isy994/translations/ru.json b/homeassistant/components/isy994/translations/ru.json index a5dccc79ddd..ea7d5c6e36c 100644 --- a/homeassistant/components/isy994/translations/ru.json +++ b/homeassistant/components/isy994/translations/ru.json @@ -7,10 +7,19 @@ "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_host": "URL-\u0430\u0434\u0440\u0435\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u0442\u0435 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: 'http://192.168.10.100:80').", + "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.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "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": "\u0423\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f {host} \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\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 \u0412\u0430\u0448\u0435\u0433\u043e ISY" + }, "user": { "data": { "host": "URL-\u0430\u0434\u0440\u0435\u0441", @@ -33,7 +42,7 @@ "variable_sensor_string": "\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u043a\u0430\u043a \u0441\u0435\u043d\u0441\u043e\u0440" }, "description": "\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:\n \u2022 \u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0437\u0435\u043b \u043a\u0430\u043a \u0441\u0435\u043d\u0441\u043e\u0440: \u043b\u044e\u0431\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0438\u043b\u0438 \u043f\u0430\u043f\u043a\u0430, \u0432 \u0438\u043c\u0435\u043d\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430, \u0431\u0443\u0434\u0435\u0442 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u043a\u0430\u043a \u0441\u0435\u043d\u0441\u043e\u0440 \u0438\u043b\u0438 \u0431\u0438\u043d\u0430\u0440\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440.\n \u2022 \u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u043a\u0430\u043a \u0441\u0435\u043d\u0441\u043e\u0440: \u043b\u044e\u0431\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443, \u0431\u0443\u0434\u0435\u0442 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u043a\u0430\u043a \u0441\u0435\u043d\u0441\u043e\u0440.\n \u2022 \u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c: \u043b\u044e\u0431\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u0432 \u0438\u043c\u0435\u043d\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430, \u0431\u0443\u0434\u0435\u0442 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f.\n \u2022 \u0412\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u044f\u0440\u043a\u043e\u0441\u0442\u044c \u0441\u0432\u0435\u0442\u0430: \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u044f\u0440\u043a\u043e\u0441\u0442\u0438, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0435 \u0434\u043e \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 ISY994" + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 ISY" } } }, diff --git a/homeassistant/components/isy994/translations/sv.json b/homeassistant/components/isy994/translations/sv.json index 23c825f256f..bd14c77dd60 100644 --- a/homeassistant/components/isy994/translations/sv.json +++ b/homeassistant/components/isy994/translations/sv.json @@ -1,6 +1,17 @@ { "config": { + "error": { + "reauth_successful": "\u00c5terautentiseringen lyckades" + }, "step": { + "reauth_confirm": { + "data": { + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + }, + "description": "Inloggningsuppgifterna f\u00f6r {host} \u00e4r inte l\u00e4ngre giltiga.", + "title": "Autentisera din ISY igen" + }, "user": { "data": { "username": "Anv\u00e4ndarnamn" diff --git a/homeassistant/components/isy994/translations/tr.json b/homeassistant/components/isy994/translations/tr.json index 78fc66196f1..3999ee16163 100644 --- a/homeassistant/components/isy994/translations/tr.json +++ b/homeassistant/components/isy994/translations/tr.json @@ -7,10 +7,19 @@ "cannot_connect": "Ba\u011flanma hatas\u0131", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "invalid_host": "Ana bilgisayar giri\u015fi tam URL bi\u00e7iminde de\u011fildi, \u00f6r. http://192.168.10.100:80", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", "unknown": "Beklenmeyen hata" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "{host} kimlik bilgileri art\u0131k ge\u00e7erli de\u011fil.", + "title": "ISY'nizi yeniden do\u011frulay\u0131n" + }, "user": { "data": { "host": "URL", @@ -19,7 +28,7 @@ "username": "Kullan\u0131c\u0131 Ad\u0131" }, "description": "Ana bilgisayar giri\u015fi tam URL bi\u00e7iminde olmal\u0131d\u0131r, \u00f6r. http://192.168.10.100:80", - "title": "ISY994'\u00fcn\u00fcze ba\u011flan\u0131n" + "title": "ISY'nize ba\u011flan\u0131n" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "De\u011fi\u015fken Sens\u00f6r Dizesi" }, "description": "ISY Entegrasyonu i\u00e7in se\u00e7enekleri ayarlay\u0131n:\n \u2022 D\u00fc\u011f\u00fcm Sens\u00f6r\u00fc Dizisi: Ad\u0131nda 'D\u00fc\u011f\u00fcm Sens\u00f6r\u00fc Dizisi' i\u00e7eren herhangi bir cihaz veya klas\u00f6r, bir sens\u00f6r veya ikili sens\u00f6r olarak ele al\u0131nacakt\u0131r.\n \u2022 Ignore String: Ad\u0131nda 'Ignore String' olan herhangi bir cihaz yoksay\u0131lacakt\u0131r.\n \u2022 De\u011fi\u015fken Sens\u00f6r Dizisi: 'De\u011fi\u015fken Sens\u00f6r Dizisi' i\u00e7eren herhangi bir de\u011fi\u015fken sens\u00f6r olarak eklenecektir.\n \u2022 I\u015f\u0131k Parlakl\u0131\u011f\u0131n\u0131 Geri Y\u00fckle: Etkinle\u015ftirilirse, bir \u0131\u015f\u0131k a\u00e7\u0131ld\u0131\u011f\u0131nda cihaz\u0131n yerle\u015fik On-Level yerine \u00f6nceki parlakl\u0131k geri y\u00fcklenir.", - "title": "ISY994 Se\u00e7enekleri" + "title": "ISY Se\u00e7enekleri" } } }, diff --git a/homeassistant/components/isy994/translations/zh-Hant.json b/homeassistant/components/isy994/translations/zh-Hant.json index 6370d7bbbf8..c22bbd1b58d 100644 --- a/homeassistant/components/isy994/translations/zh-Hant.json +++ b/homeassistant/components/isy994/translations/zh-Hant.json @@ -7,10 +7,19 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "invalid_host": "\u4e3b\u6a5f\u7aef\u4e26\u672a\u4ee5\u5b8c\u6574\u7db2\u5740\u683c\u5f0f\u8f38\u5165\uff0c\u4f8b\u5982\uff1ahttp://192.168.10.100:80", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "flow_title": "{name} ({host})", "step": { + "reauth_confirm": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "{username} \u6191\u8b49\u4e0d\u518d\u6709\u6548\u3002", + "title": "\u91cd\u65b0\u8a8d\u8b49 ISY \u5e33\u865f" + }, "user": { "data": { "host": "\u7db2\u5740", @@ -19,7 +28,7 @@ "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, "description": "\u4e3b\u6a5f\u7aef\u5fc5\u9808\u4ee5\u5b8c\u6574\u7db2\u5740\u683c\u5f0f\u8f38\u5165\uff0c\u4f8b\u5982\uff1ahttp://192.168.10.100:80", - "title": "\u9023\u7dda\u81f3 ISY994" + "title": "\u9023\u7dda\u81f3 ISY" } } }, @@ -33,7 +42,7 @@ "variable_sensor_string": "\u53ef\u8b8a\u611f\u6e2c\u5668\u5b57\u4e32" }, "description": "ISY \u6574\u5408\u8a2d\u5b9a\u9078\u9805\uff1a \n \u2022 \u7bc0\u9ede\u611f\u6e2c\u5668\u5b57\u4e32\uff08Node Sensor String\uff09\uff1a\u4efb\u4f55\u540d\u7a31\u6216\u8cc7\u6599\u593e\u5305\u542b\u300cNode Sensor String\u300d\u7684\u88dd\u7f6e\u90fd\u6703\u88ab\u8996\u70ba\u611f\u6e2c\u5668\u6216\u4e8c\u9032\u4f4d\u611f\u6e2c\u5668\u3002\n \u2022 \u5ffd\u7565\u5b57\u4e32\uff08Ignore String\uff09\uff1a\u4efb\u4f55\u540d\u7a31\u5305\u542b\u300cIgnore String\u300d\u7684\u88dd\u7f6e\u90fd\u6703\u88ab\u5ffd\u7565\u3002\n \u2022 \u53ef\u8b8a\u611f\u6e2c\u5668\u5b57\u4e32\uff08Variable Sensor String\uff09\uff1a\u4efb\u4f55\u5305\u542b\u300cVariable Sensor String\u300d\u7684\u8b8a\u6578\u90fd\u5c07\u65b0\u589e\u70ba\u611f\u6e2c\u5668\u3002 \n \u2022 \u56de\u5fa9\u4eae\u5ea6\uff08Restore Light Brightness\uff09\uff1a\u958b\u5553\u5f8c\u3001\u7576\u71c8\u5149\u958b\u555f\u6642\u6703\u56de\u5fa9\u5148\u524d\u7684\u4eae\u5ea6\uff0c\u800c\u4e0d\u662f\u4f7f\u7528\u88dd\u7f6e\u9810\u8a2d\u4eae\u5ea6\u3002", - "title": "ISY994 \u9078\u9805" + "title": "ISY \u9078\u9805" } } }, diff --git a/homeassistant/components/izone/translations/nl.json b/homeassistant/components/izone/translations/nl.json index b70bb738df0..707f6fb279d 100644 --- a/homeassistant/components/izone/translations/nl.json +++ b/homeassistant/components/izone/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/jellyfin/translations/nl.json b/homeassistant/components/jellyfin/translations/nl.json index 1072cfff418..947f89dcb81 100644 --- a/homeassistant/components/jellyfin/translations/nl.json +++ b/homeassistant/components/jellyfin/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/jellyfin/translations/sk.json b/homeassistant/components/jellyfin/translations/sk.json index 5ada995aa6e..1b1e671c054 100644 --- a/homeassistant/components/jellyfin/translations/sk.json +++ b/homeassistant/components/jellyfin/translations/sk.json @@ -2,6 +2,13 @@ "config": { "error": { "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "password": "Heslo" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/jewish_calendar/__init__.py b/homeassistant/components/jewish_calendar/__init__.py index 4d2cee744ef..d3291e51bc1 100644 --- a/homeassistant/components/jewish_calendar/__init__.py +++ b/homeassistant/components/jewish_calendar/__init__.py @@ -11,6 +11,7 @@ from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.typing import ConfigType DOMAIN = "jewish_calendar" +PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.BINARY_SENSOR] CONF_DIASPORA = "diaspora" CONF_LANGUAGE = "language" @@ -67,6 +68,9 @@ def get_unique_prefix( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Jewish Calendar component.""" + if DOMAIN not in config: + return True + name = config[DOMAIN][CONF_NAME] language = config[DOMAIN][CONF_LANGUAGE] @@ -97,12 +101,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: "prefix": prefix, } - hass.async_create_task( - async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, config) - ) - - hass.async_create_task( - async_load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config) - ) + for platform in PLATFORMS: + hass.async_create_task(async_load_platform(hass, platform, DOMAIN, {}, config)) return True diff --git a/homeassistant/components/jewish_calendar/sensor.py b/homeassistant/components/jewish_calendar/sensor.py index d53f3702bcc..827a35587f8 100644 --- a/homeassistant/components/jewish_calendar/sensor.py +++ b/homeassistant/components/jewish_calendar/sensor.py @@ -24,7 +24,7 @@ from . import DOMAIN _LOGGER = logging.getLogger(__name__) -DATA_SENSORS = ( +INFO_SENSORS = ( SensorEntityDescription( key="date", name="Date", @@ -143,7 +143,7 @@ async def async_setup_platform( sensors = [ JewishCalendarSensor(hass.data[DOMAIN], description) - for description in DATA_SENSORS + for description in INFO_SENSORS ] sensors.extend( JewishCalendarTimeSensor(hass.data[DOMAIN], description) diff --git a/homeassistant/components/juicenet/__init__.py b/homeassistant/components/juicenet/__init__.py index 92d8de8bc0a..60377d7e85b 100644 --- a/homeassistant/components/juicenet/__init__.py +++ b/homeassistant/components/juicenet/__init__.py @@ -20,7 +20,7 @@ from .device import JuiceNetApi _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.SENSOR, Platform.SWITCH] +PLATFORMS = [Platform.SENSOR, Platform.SWITCH, Platform.NUMBER] CONFIG_SCHEMA = vol.Schema( vol.All( diff --git a/homeassistant/components/juicenet/entity.py b/homeassistant/components/juicenet/entity.py index 4b4e5764a5e..c0151a0cd00 100644 --- a/homeassistant/components/juicenet/entity.py +++ b/homeassistant/components/juicenet/entity.py @@ -1,7 +1,12 @@ """Adapter to wrap the pyjuicenet api for home assistant.""" +from pyjuicenet import Charger + from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) from .const import DOMAIN @@ -9,16 +14,18 @@ from .const import DOMAIN class JuiceNetDevice(CoordinatorEntity): """Represent a base JuiceNet device.""" - def __init__(self, device, sensor_type, coordinator): + def __init__( + self, device: Charger, key: str, coordinator: DataUpdateCoordinator + ) -> None: """Initialise the sensor.""" super().__init__(coordinator) self.device = device - self.type = sensor_type + self.key = key @property def unique_id(self): """Return a unique ID.""" - return f"{self.device.id}-{self.type}" + return f"{self.device.id}-{self.key}" @property def device_info(self) -> DeviceInfo: diff --git a/homeassistant/components/juicenet/number.py b/homeassistant/components/juicenet/number.py new file mode 100644 index 00000000000..24b0ba4f42b --- /dev/null +++ b/homeassistant/components/juicenet/number.py @@ -0,0 +1,98 @@ +"""Support for controlling juicenet/juicepoint/juicebox based EVSE numbers.""" +from __future__ import annotations + +from dataclasses import dataclass + +from pyjuicenet import Api, Charger + +from homeassistant.components.number import NumberEntity, NumberEntityDescription +from homeassistant.components.number.const import DEFAULT_MAX_VALUE +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR +from .entity import JuiceNetDevice + + +@dataclass +class JuiceNetNumberEntityDescriptionMixin: + """Mixin for required keys.""" + + setter_key: str + + +@dataclass +class JuiceNetNumberEntityDescription( + NumberEntityDescription, JuiceNetNumberEntityDescriptionMixin +): + """An entity description for a JuiceNetNumber.""" + + max_value_key: str | None = None + + +NUMBER_TYPES: tuple[JuiceNetNumberEntityDescription, ...] = ( + JuiceNetNumberEntityDescription( + name="Amperage Limit", + key="current_charging_amperage_limit", + min_value=6, + max_value_key="max_charging_amperage", + step=1, + setter_key="set_charging_amperage_limit", + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the JuiceNet Numbers.""" + juicenet_data = hass.data[DOMAIN][config_entry.entry_id] + api: Api = juicenet_data[JUICENET_API] + coordinator = juicenet_data[JUICENET_COORDINATOR] + + entities = [ + JuiceNetNumber(device, description, coordinator) + for device in api.devices + for description in NUMBER_TYPES + ] + async_add_entities(entities) + + +class JuiceNetNumber(JuiceNetDevice, NumberEntity): + """Implementation of a JuiceNet number.""" + + entity_description: JuiceNetNumberEntityDescription + + def __init__( + self, + device: Charger, + description: JuiceNetNumberEntityDescription, + coordinator: DataUpdateCoordinator, + ) -> None: + """Initialise the number.""" + super().__init__(device, description.key, coordinator) + self.entity_description = description + + self._attr_name = f"{self.device.name} {description.name}" + + @property + def value(self) -> float | None: + """Return the value of the entity.""" + return getattr(self.device, self.entity_description.key, None) + + @property + def max_value(self) -> float: + """Return the maximum value.""" + if self.entity_description.max_value_key is not None: + return getattr(self.device, self.entity_description.max_value_key) + if self.entity_description.max_value is not None: + return self.entity_description.max_value + return DEFAULT_MAX_VALUE + + async def async_set_value(self, value: float) -> None: + """Update the current value.""" + await getattr(self.device, self.entity_description.setter_key)(value) diff --git a/homeassistant/components/juicenet/translations/es.json b/homeassistant/components/juicenet/translations/es.json index 12b5159c99c..2fa2b61cd60 100644 --- a/homeassistant/components/juicenet/translations/es.json +++ b/homeassistant/components/juicenet/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada" + "already_configured": "La cuenta ya est\u00e1 configurada" }, "error": { "cannot_connect": "No se pudo conectar", diff --git a/homeassistant/components/juicenet/translations/nl.json b/homeassistant/components/juicenet/translations/nl.json index 5f1b0d37e25..907ac3a68da 100644 --- a/homeassistant/components/juicenet/translations/nl.json +++ b/homeassistant/components/juicenet/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Account is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/kaleidescape/translations/bg.json b/homeassistant/components/kaleidescape/translations/bg.json index cb36ec53c4b..c31eea5d60e 100644 --- a/homeassistant/components/kaleidescape/translations/bg.json +++ b/homeassistant/components/kaleidescape/translations/bg.json @@ -11,14 +11,10 @@ }, "flow_title": "{model} ({name})", "step": { - "discovery_confirm": { - "title": "Kaleidescape" - }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442" - }, - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043d\u0430 Kaleidescape" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/ca.json b/homeassistant/components/kaleidescape/translations/ca.json index ec7eac6ef4f..8a8df0f6503 100644 --- a/homeassistant/components/kaleidescape/translations/ca.json +++ b/homeassistant/components/kaleidescape/translations/ca.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "Vols configurar el reproductor {name} model {model}?", - "title": "Kaleidescape" + "description": "Vols configurar el reproductor {name} model {model}?" }, "user": { "data": { "host": "Amfitri\u00f3" - }, - "title": "Configuraci\u00f3 de Kaleidescape" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/de.json b/homeassistant/components/kaleidescape/translations/de.json index 3b353208a64..0d9a93f8596 100644 --- a/homeassistant/components/kaleidescape/translations/de.json +++ b/homeassistant/components/kaleidescape/translations/de.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "M\u00f6chtest du den Player {model} mit dem Namen {name} einrichten?", - "title": "Kaleidescape" + "description": "M\u00f6chtest du den Player {model} mit dem Namen {name} einrichten?" }, "user": { "data": { "host": "Host" - }, - "title": "Kaleidescape-Setup" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/el.json b/homeassistant/components/kaleidescape/translations/el.json index dc042655b02..e9315dd4cf7 100644 --- a/homeassistant/components/kaleidescape/translations/el.json +++ b/homeassistant/components/kaleidescape/translations/el.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 {model} \u03bc\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1{name};", - "title": "Kaleidescape" + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 {model} \u03bc\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1{name};" }, "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" - }, - "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 Kaleidescape" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/en.json b/homeassistant/components/kaleidescape/translations/en.json index 43be9c030c0..cd6460e0fa9 100644 --- a/homeassistant/components/kaleidescape/translations/en.json +++ b/homeassistant/components/kaleidescape/translations/en.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "Do you want to set up the {model} player named {name}?", - "title": "Kaleidescape" + "description": "Do you want to set up the {model} player named {name}?" }, "user": { "data": { "host": "Host" - }, - "title": "Kaleidescape Setup" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/es.json b/homeassistant/components/kaleidescape/translations/es.json new file mode 100644 index 00000000000..6586a20f202 --- /dev/null +++ b/homeassistant/components/kaleidescape/translations/es.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en curso", + "unsupported": "Dispositiu no compatible" + }, + "flow_title": "{model} ({name})", + "step": { + "discovery_confirm": { + "description": "\u00bfQuieres configurar el reproductor {name} modelo {model}?" + }, + "user": { + "data": { + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kaleidescape/translations/et.json b/homeassistant/components/kaleidescape/translations/et.json index 52ece37ad97..9da5e2a8a68 100644 --- a/homeassistant/components/kaleidescape/translations/et.json +++ b/homeassistant/components/kaleidescape/translations/et.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "Kas seadistada m\u00e4ngijat {model} nimega {name} ?", - "title": "Kaleidescape" + "description": "Kas seadistada m\u00e4ngijat {model} nimega {name} ?" }, "user": { "data": { "host": "Host" - }, - "title": "Kaleidescape'i seadistamine" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/fr.json b/homeassistant/components/kaleidescape/translations/fr.json index 48dfd027765..bf03d0ecacf 100644 --- a/homeassistant/components/kaleidescape/translations/fr.json +++ b/homeassistant/components/kaleidescape/translations/fr.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "Voulez-vous configurer le lecteur {model} nomm\u00e9 {name}\u00a0?", - "title": "Kaleidescape" + "description": "Voulez-vous configurer le lecteur {model} nomm\u00e9 {name}\u00a0?" }, "user": { "data": { "host": "H\u00f4te" - }, - "title": "Configuration de Kaleidescape" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/hu.json b/homeassistant/components/kaleidescape/translations/hu.json index cd8b00e18a3..ad26902cdf3 100644 --- a/homeassistant/components/kaleidescape/translations/hu.json +++ b/homeassistant/components/kaleidescape/translations/hu.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "Szeretn\u00e9 be\u00e1ll\u00edtani a {name} nev\u0171, {model} t\u00edpus\u00fa lej\u00e1tsz\u00f3t?", - "title": "Kaleidescape" + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani a {name} nev\u0171, {model} t\u00edpus\u00fa lej\u00e1tsz\u00f3t?" }, "user": { "data": { "host": "C\u00edm" - }, - "title": "Kaleidescape be\u00e1ll\u00edt\u00e1sa" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/id.json b/homeassistant/components/kaleidescape/translations/id.json index 626f23354d4..7833bf5ad4c 100644 --- a/homeassistant/components/kaleidescape/translations/id.json +++ b/homeassistant/components/kaleidescape/translations/id.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "Ingin menyiapkan pemutar {model} dengan nama {name}?", - "title": "Kaleidescape" + "description": "Ingin menyiapkan pemutar {model} dengan nama {name}?" }, "user": { "data": { "host": "Host" - }, - "title": "Penyiapan Kaleidescape" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/it.json b/homeassistant/components/kaleidescape/translations/it.json index 022b73ae0d7..35235b2986b 100644 --- a/homeassistant/components/kaleidescape/translations/it.json +++ b/homeassistant/components/kaleidescape/translations/it.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "Vuoi configurare il lettore {model} nome {name}?", - "title": "Kaleidescape" + "description": "Vuoi configurare il lettore {model} nome {name}?" }, "user": { "data": { "host": "Host" - }, - "title": "Configurazione di Kaleidescape" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/ja.json b/homeassistant/components/kaleidescape/translations/ja.json index 368a1565648..b4d49c09f74 100644 --- a/homeassistant/components/kaleidescape/translations/ja.json +++ b/homeassistant/components/kaleidescape/translations/ja.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "{model} \u30d7\u30ec\u30a4\u30e4\u30fc\u540d {name} \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f", - "title": "\u30ab\u30ec\u30a4\u30c9\u30b9\u30b1\u30fc\u30d7(Kaleidescape)" + "description": "{model} \u30d7\u30ec\u30a4\u30e4\u30fc\u540d {name} \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" }, "user": { "data": { "host": "\u30db\u30b9\u30c8" - }, - "title": "\u30ab\u30ec\u30a4\u30c9\u30b9\u30b1\u30fc\u30d7(Kaleidescape)\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/nl.json b/homeassistant/components/kaleidescape/translations/nl.json index 2b2491deb85..50a81b361cf 100644 --- a/homeassistant/components/kaleidescape/translations/nl.json +++ b/homeassistant/components/kaleidescape/translations/nl.json @@ -2,25 +2,23 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "Configuratiestroom is al bezig", + "already_in_progress": "De configuratie is momenteel al bezig", "unknown": "Onverwachte fout", "unsupported": "Niet-ondersteund apparaat" }, "error": { - "cannot_connect": "Verbinding mislukt", + "cannot_connect": "Kan geen verbinding maken", "unsupported": "Niet-ondersteund apparaat" }, "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "Wil je de speler {model} met de naam {name} instellen?", - "title": "Kaleidescape" + "description": "Wil je de speler {model} met de naam {name} instellen?" }, "user": { "data": { "host": "Host" - }, - "title": "Kaleidescape configuratie" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/no.json b/homeassistant/components/kaleidescape/translations/no.json index c07276eeeb1..6d3450c0b68 100644 --- a/homeassistant/components/kaleidescape/translations/no.json +++ b/homeassistant/components/kaleidescape/translations/no.json @@ -13,14 +13,12 @@ "flow_title": "{model} ( {name} )", "step": { "discovery_confirm": { - "description": "Vil du sette opp {model} -spilleren med navnet {name} ?", - "title": "Kaleidescape" + "description": "Vil du sette opp {model} -spilleren med navnet {name} ?" }, "user": { "data": { "host": "Vert" - }, - "title": "Kaleidescape-oppsett" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/pl.json b/homeassistant/components/kaleidescape/translations/pl.json index 111dc4db7e6..499f813aa27 100644 --- a/homeassistant/components/kaleidescape/translations/pl.json +++ b/homeassistant/components/kaleidescape/translations/pl.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "Czy chcesz skonfigurowa\u0107 odtwarzacz {model} o nazwie {name}?", - "title": "Kaleidescape" + "description": "Czy chcesz skonfigurowa\u0107 odtwarzacz {model} o nazwie {name}?" }, "user": { "data": { "host": "Nazwa hosta lub adres IP" - }, - "title": "Konfiguracja Kaleidescape" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/pt-BR.json b/homeassistant/components/kaleidescape/translations/pt-BR.json index f87534cb88a..5b5c4a35d2b 100644 --- a/homeassistant/components/kaleidescape/translations/pt-BR.json +++ b/homeassistant/components/kaleidescape/translations/pt-BR.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "Deseja configurar o player {model} chamado {name}?", - "title": "Kaleidescape" + "description": "Deseja configurar o player {model} chamado {name}?" }, "user": { "data": { "host": "Nome do host" - }, - "title": "Configura\u00e7\u00e3o do Kaleidescape" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/ru.json b/homeassistant/components/kaleidescape/translations/ru.json index 3112fdd06e5..d94ffb58900 100644 --- a/homeassistant/components/kaleidescape/translations/ru.json +++ b/homeassistant/components/kaleidescape/translations/ru.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_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 {model} ({name})?", - "title": "Kaleidescape" + "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 {model} ({name})?" }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442" - }, - "title": "Kaleidescape" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/tr.json b/homeassistant/components/kaleidescape/translations/tr.json index 1b891cc450c..40b004c2ea2 100644 --- a/homeassistant/components/kaleidescape/translations/tr.json +++ b/homeassistant/components/kaleidescape/translations/tr.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "{name} adl\u0131 {model} oynat\u0131c\u0131s\u0131n\u0131 kurmak istiyor musunuz?", - "title": "Kaleidescape" + "description": "{name} adl\u0131 {model} oynat\u0131c\u0131s\u0131n\u0131 kurmak istiyor musunuz?" }, "user": { "data": { "host": "Sunucu" - }, - "title": "Kaleidescape Kurulumu" + } } } } diff --git a/homeassistant/components/kaleidescape/translations/zh-Hant.json b/homeassistant/components/kaleidescape/translations/zh-Hant.json index 2fedf1ede20..4e6c5564c61 100644 --- a/homeassistant/components/kaleidescape/translations/zh-Hant.json +++ b/homeassistant/components/kaleidescape/translations/zh-Hant.json @@ -13,14 +13,12 @@ "flow_title": "{model} ({name})", "step": { "discovery_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u540d\u70ba {name} \u7684 {model} \u64ad\u653e\u5668\uff1f", - "title": "Kaleidescape" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u540d\u70ba {name} \u7684 {model} \u64ad\u653e\u5668\uff1f" }, "user": { "data": { "host": "\u4e3b\u6a5f\u7aef" - }, - "title": "Kaleidescape \u8a2d\u5b9a" + } } } } diff --git a/homeassistant/components/keenetic_ndms2/device_tracker.py b/homeassistant/components/keenetic_ndms2/device_tracker.py index 5a92d04e498..b625cabbbc1 100644 --- a/homeassistant/components/keenetic_ndms2/device_tracker.py +++ b/homeassistant/components/keenetic_ndms2/device_tracker.py @@ -40,7 +40,7 @@ async def async_setup_entry( update_from_router() - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) # Restore devices that are not a part of active clients list. restored = [] for entity_entry in registry.entities.values(): diff --git a/homeassistant/components/keenetic_ndms2/translations/ko.json b/homeassistant/components/keenetic_ndms2/translations/ko.json index 9444b447d37..e20111fd722 100644 --- a/homeassistant/components/keenetic_ndms2/translations/ko.json +++ b/homeassistant/components/keenetic_ndms2/translations/ko.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "not_keenetic_ndms2": "\ubc1c\uacac\ub41c \uc7a5\uce58\uac00 Keenetic \ub77c\uc6b0\ud130\uac00 \uc544\ub2d9\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index b3096a75df5..29bd9b4f6a9 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -2,11 +2,10 @@ from __future__ import annotations from collections.abc import Callable -from datetime import datetime from typing import Any from xknx import XKNX -from xknx.devices import Cover as XknxCover, Device as XknxDevice +from xknx.devices import Cover as XknxCover from homeassistant import config_entries from homeassistant.components.cover import ( @@ -22,9 +21,8 @@ from homeassistant.const import ( CONF_NAME, Platform, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.event import async_track_utc_time_change from homeassistant.helpers.typing import ConfigType from .const import DATA_KNX_CONFIG, DOMAIN @@ -68,6 +66,7 @@ class KNXCover(KnxEntity, CoverEntity): group_address_position=config.get(CoverSchema.CONF_POSITION_ADDRESS), travel_time_down=config[CoverSchema.CONF_TRAVELLING_TIME_DOWN], travel_time_up=config[CoverSchema.CONF_TRAVELLING_TIME_UP], + invert_updown=config[CoverSchema.CONF_INVERT_UPDOWN], invert_position=config[CoverSchema.CONF_INVERT_POSITION], invert_angle=config[CoverSchema.CONF_INVERT_ANGLE], ) @@ -104,13 +103,6 @@ class KNXCover(KnxEntity, CoverEntity): f"{self._device.position_target.group_address}" ) - @callback - async def after_update_callback(self, device: XknxDevice) -> None: - """Call after device was updated.""" - self.async_write_ha_state() - if self._device.is_traveling(): - self.start_auto_updater() - @property def current_cover_position(self) -> int | None: """Return the current position of the cover. @@ -118,8 +110,9 @@ class KNXCover(KnxEntity, CoverEntity): None is unknown, 0 is closed, 100 is fully open. """ # In KNX 0 is open, 100 is closed. - pos = self._device.current_position() - return 100 - pos if pos is not None else None + if (pos := self._device.current_position()) is not None: + return 100 - pos + return None @property def is_closed(self) -> bool | None: @@ -155,14 +148,12 @@ class KNXCover(KnxEntity, CoverEntity): async def async_stop_cover(self, **kwargs: Any) -> None: """Stop the cover.""" await self._device.stop() - self.stop_auto_updater() @property def current_cover_tilt_position(self) -> int | None: """Return current tilt position of cover.""" - if self._device.supports_angle: - ang = self._device.current_angle() - return 100 - ang if ang is not None else None + if (angle := self._device.current_angle()) is not None: + return 100 - angle return None async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: @@ -181,25 +172,3 @@ class KNXCover(KnxEntity, CoverEntity): async def async_stop_cover_tilt(self, **kwargs: Any) -> None: """Stop the cover tilt.""" await self._device.stop() - self.stop_auto_updater() - - def start_auto_updater(self) -> None: - """Start the autoupdater to update Home Assistant while cover is moving.""" - if self._unsubscribe_auto_updater is None: - self._unsubscribe_auto_updater = async_track_utc_time_change( - self.hass, self.auto_updater_hook - ) - - def stop_auto_updater(self) -> None: - """Stop the autoupdater.""" - if self._unsubscribe_auto_updater is not None: - self._unsubscribe_auto_updater() - self._unsubscribe_auto_updater = None - - @callback - def auto_updater_hook(self, now: datetime) -> None: - """Call for the autoupdater.""" - self.async_write_ha_state() - if self._device.position_reached(): - self.hass.async_create_task(self._device.auto_stop_if_necessary()) - self.stop_auto_updater() diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 00b4c6cdc5f..b8f9bdcbd30 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -3,7 +3,7 @@ "name": "KNX", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.21.2"], + "requirements": ["xknx==0.21.3"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "platinum", "iot_class": "local_push", diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index abba1b0c027..c7c1e264975 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -489,6 +489,7 @@ class CoverSchema(KNXPlatformSchema): CONF_ANGLE_STATE_ADDRESS = "angle_state_address" CONF_TRAVELLING_TIME_DOWN = "travelling_time_down" CONF_TRAVELLING_TIME_UP = "travelling_time_up" + CONF_INVERT_UPDOWN = "invert_updown" CONF_INVERT_POSITION = "invert_position" CONF_INVERT_ANGLE = "invert_angle" @@ -521,6 +522,7 @@ class CoverSchema(KNXPlatformSchema): vol.Optional( CONF_TRAVELLING_TIME_UP, default=DEFAULT_TRAVEL_TIME ): cv.positive_float, + vol.Optional(CONF_INVERT_UPDOWN, default=False): cv.boolean, 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, diff --git a/homeassistant/components/knx/strings.json b/homeassistant/components/knx/strings.json index 018db071adf..c8161462d66 100644 --- a/homeassistant/components/knx/strings.json +++ b/homeassistant/components/knx/strings.json @@ -101,7 +101,7 @@ "multicast_group": "Used for routing and discovery. Default: `224.0.23.12`", "multicast_port": "Used for routing and discovery. Default: `3671`", "local_ip": "Use `0.0.0.0` for auto-discovery.", - "state_updater": "Globally enable or disable reading states from the KNX Bus. When disabled, Home Assistant will not actively retrieve states from the KNX Bus, `sync_state` entity options will have no effect.", + "state_updater": "Set default for reading states from the KNX Bus. When disabled, Home Assistant will not actively retrieve entity states from the KNX Bus. Can be overridden by `sync_state` entity options.", "rate_limit": "Maximum outgoing telegrams per second.\nRecommended: 20 to 40" } }, diff --git a/homeassistant/components/knx/translations/bg.json b/homeassistant/components/knx/translations/bg.json index 3cbc8b57b0b..331c85d4065 100644 --- a/homeassistant/components/knx/translations/bg.json +++ b/homeassistant/components/knx/translations/bg.json @@ -49,7 +49,6 @@ "tunnel": { "data": { "host": "\u0425\u043e\u0441\u0442", - "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" } diff --git a/homeassistant/components/knx/translations/ca.json b/homeassistant/components/knx/translations/ca.json index b79887e1586..ff83ebec070 100644 --- a/homeassistant/components/knx/translations/ca.json +++ b/homeassistant/components/knx/translations/ca.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "Amfitri\u00f3", - "individual_address": "Adre\u00e7a individual de la connexi\u00f3", "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": { @@ -104,15 +102,13 @@ "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." + "state_updater": "Configuraci\u00f3 predeterminadament per llegir els 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` poden substituir-ho." } }, "tunnel": { "data": { "host": "Amfitri\u00f3", - "local_ip": "IP local (deixa-ho en blanc si no n'est\u00e0s segur/a)", "port": "Port", - "route_back": "Encaminament de retorn / Mode NAT", "tunneling_type": "Tipus de t\u00fanel KNX" }, "data_description": { diff --git a/homeassistant/components/knx/translations/de.json b/homeassistant/components/knx/translations/de.json index 588a853a8b6..1daffa9c301 100644 --- a/homeassistant/components/knx/translations/de.json +++ b/homeassistant/components/knx/translations/de.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "Host", - "individual_address": "Physikalische Adresse f\u00fcr die Verbindung", "local_ip": "Lokale IP von Home Assistant", "port": "Port", - "route_back": "Route Back / NAT-Modus", "tunneling_type": "KNX Tunneling Typ" }, "data_description": { @@ -104,15 +102,13 @@ "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." + "state_updater": "Standardeinstellung f\u00fcr das Lesen von Zust\u00e4nden aus dem KNX-Bus. Wenn diese Option deaktiviert ist, wird der Home Assistant den Zustand der Entit\u00e4ten nicht aktiv vom KNX-Bus abrufen. Kann durch die Entity-Optionen `sync_state` au\u00dfer Kraft gesetzt werden." } }, "tunnel": { "data": { "host": "Host", - "local_ip": "Lokale IP (im Zweifel leer lassen)", "port": "Port", - "route_back": "Route Back / NAT-Modus", "tunneling_type": "KNX Tunneling Typ" }, "data_description": { diff --git a/homeassistant/components/knx/translations/el.json b/homeassistant/components/knx/translations/el.json index 81dd03f73fa..30047781c26 100644 --- a/homeassistant/components/knx/translations/el.json +++ b/homeassistant/components/knx/translations/el.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "individual_address": "\u0391\u03c4\u03bf\u03bc\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7", "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP \u03c4\u03bf\u03c5 Home Assistant (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7)", "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": { @@ -110,9 +108,7 @@ "tunnel": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03b5\u03bd\u03ae \u03b1\u03bd \u03b4\u03b5\u03bd \u03b5\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9)", "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": { diff --git a/homeassistant/components/knx/translations/en.json b/homeassistant/components/knx/translations/en.json index ba073eba888..6dffe059b2a 100644 --- a/homeassistant/components/knx/translations/en.json +++ b/homeassistant/components/knx/translations/en.json @@ -15,10 +15,8 @@ "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": { @@ -104,15 +102,13 @@ "multicast_group": "Used for routing and discovery. Default: `224.0.23.12`", "multicast_port": "Used for routing and discovery. Default: `3671`", "rate_limit": "Maximum outgoing telegrams per second.\nRecommended: 20 to 40", - "state_updater": "Globally enable or disable reading states from the KNX Bus. When disabled, Home Assistant will not actively retrieve states from the KNX Bus, `sync_state` entity options will have no effect." + "state_updater": "Set default for reading states from the KNX Bus. When disabled, Home Assistant will not actively retrieve entity states from the KNX Bus. Can be overridden by `sync_state` entity options." } }, "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/es.json b/homeassistant/components/knx/translations/es.json index b0256167cce..6c8ec3e2eb6 100644 --- a/homeassistant/components/knx/translations/es.json +++ b/homeassistant/components/knx/translations/es.json @@ -5,28 +5,56 @@ "single_instance_allowed": "Ya configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "error": { - "cannot_connect": "Error al conectar" + "cannot_connect": "Error al conectar", + "invalid_individual_address": "El valor no coincide con el patr\u00f3n de direcci\u00f3n KNX individual. 'area.line.device'", + "invalid_ip_address": "Direcci\u00f3n IPv4 inv\u00e1lida." }, "step": { "manual_tunnel": { "data": { "host": "Host", - "individual_address": "Direcci\u00f3n individual para la conexi\u00f3n", - "local_ip": "IP local de Home Assistant (d\u00e9jela vac\u00eda para la detecci\u00f3n autom\u00e1tica)", - "port": "Puerto", - "route_back": "Modo Route Back / NAT" + "local_ip": "IP local de Home Assistant", + "port": "Puerto" + }, + "data_description": { + "local_ip": "D\u00e9jalo en blanco para utilizar el descubrimiento autom\u00e1tico.", + "port": "Puerto del dispositivo de tunelizaci\u00f3n KNX/IP.Puerto del dispositivo de tunelizaci\u00f3n KNX/IP." }, "description": "Introduzca la informaci\u00f3n de conexi\u00f3n de su dispositivo de tunelizaci\u00f3n." }, "routing": { "data": { - "individual_address": "Direcci\u00f3n individual para la conexi\u00f3n de enrutamiento", - "local_ip": "IP local de Home Assistant (d\u00e9jela vac\u00eda para la detecci\u00f3n autom\u00e1tica)", + "individual_address": "Direcci\u00f3n individual", + "local_ip": "IP local de Home Assistant", "multicast_group": "El grupo de multidifusi\u00f3n utilizado para el enrutamiento", "multicast_port": "El puerto de multidifusi\u00f3n utilizado para el enrutamiento" }, "description": "Por favor, configure las opciones de enrutamiento." }, + "secure_knxkeys": { + "data": { + "knxkeys_password": "Contrase\u00f1a para descifrar el archivo `.knxkeys`." + }, + "data_description": { + "knxkeys_password": "Se ha definido durante la exportaci\u00f3n del archivo desde ETS.Se ha definido durante la exportaci\u00f3n del archivo desde ETS." + }, + "description": "Introduce la informaci\u00f3n de tu archivo `.knxkeys`." + }, + "secure_manual": { + "data": { + "user_id": "ID de usuario" + }, + "data_description": { + "user_id": "A menudo, es el n\u00famero del t\u00fanel +1. Por tanto, 'T\u00fanel 2' tendr\u00eda el ID de usuario '3'." + }, + "description": "Introduce la informaci\u00f3n de seguridad IP (IP Secure)." + }, + "secure_tunneling": { + "description": "Selecciona c\u00f3mo quieres configurar KNX/IP Secure.", + "menu_options": { + "secure_manual": "Configura manualmente las claves de seguridad IP (IP Secure)" + } + }, "tunnel": { "data": { "gateway": "Conexi\u00f3n de t\u00fanel KNX" @@ -47,19 +75,25 @@ "data": { "connection_type": "Tipo de conexi\u00f3n KNX", "individual_address": "Direcci\u00f3n individual predeterminada", - "local_ip": "IP local del Asistente Hogar (utilice 0.0.0.0 para la detecci\u00f3n autom\u00e1tica)", - "multicast_group": "Grupo de multidifusi\u00f3n utilizado para enrutamiento y descubrimiento", - "multicast_port": "Puerto de multidifusi\u00f3n utilizado para enrutamiento y descubrimiento", - "rate_limit": "M\u00e1ximo de telegramas salientes por segundo", - "state_updater": "Habilitar globalmente la lectura de estados del Bus KNX" + "local_ip": "IP local de Home Assistant", + "multicast_group": "Grupo multidifusi\u00f3n", + "multicast_port": "Puerto multidifusi\u00f3n", + "rate_limit": "Frecuencia m\u00e1xima", + "state_updater": "Actualizador de estado" + }, + "data_description": { + "individual_address": "Direcci\u00f3n KNX para utilizar con Home Assistant, ej. `0.0.4`", + "rate_limit": "Telegramas de salida m\u00e1ximos por segundo. \nRecomendado: de 20 a 40" } }, "tunnel": { "data": { "host": "Host", - "local_ip": "IP local (d\u00e9jelo en blanco si no est\u00e1 seguro)", - "port": "Puerto", - "route_back": "Modo Route Back / NAT" + "port": "Puerto" + }, + "data_description": { + "host": "Direcci\u00f3n IP del dispositivo de tunelizaci\u00f3n KNX/IP.", + "port": "Puerto del dispositivo de tunelizaci\u00f3n KNX/IP." } } } diff --git a/homeassistant/components/knx/translations/et.json b/homeassistant/components/knx/translations/et.json index c4410d490e4..fe60f5404de 100644 --- a/homeassistant/components/knx/translations/et.json +++ b/homeassistant/components/knx/translations/et.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "Host", - "individual_address": "\u00dchenduse individuaalne aadress", "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": { @@ -104,15 +102,13 @@ "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." + "state_updater": "M\u00e4\u00e4ra KNX siini olekute lugemise vaikev\u00e4\u00e4rtused. Kui see on keelatud, ei too Home Assistant aktiivselt olemi olekuid KNX siinilt. Saab alistada olemivalikute s\u00fcnkroonimise_olekuga." } }, "tunnel": { "data": { "host": "Host", - "local_ip": "Kohalik IP (j\u00e4ta t\u00fchjaks, kui ei ole kindel)", "port": "Port", - "route_back": "Marsruudi tagasitee / NAT-re\u017eiim", "tunneling_type": "KNX tunneli t\u00fc\u00fcp" }, "data_description": { diff --git a/homeassistant/components/knx/translations/fr.json b/homeassistant/components/knx/translations/fr.json index c75fd76debb..803fd71b734 100644 --- a/homeassistant/components/knx/translations/fr.json +++ b/homeassistant/components/knx/translations/fr.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "H\u00f4te", - "individual_address": "Adresse individuelle pour la connexion", "local_ip": "IP locale de Home Assistant", "port": "Port", - "route_back": "Retour/Mode NAT", "tunneling_type": "Type de tunnel KNX" }, "data_description": { @@ -110,9 +108,7 @@ "tunnel": { "data": { "host": "H\u00f4te", - "local_ip": "IP locale (laisser vide en cas de doute)", "port": "Port", - "route_back": "Retour/Mode NAT", "tunneling_type": "Type de tunnel KNX" }, "data_description": { diff --git a/homeassistant/components/knx/translations/hu.json b/homeassistant/components/knx/translations/hu.json index 02b8b0a0465..28cab2cea5f 100644 --- a/homeassistant/components/knx/translations/hu.json +++ b/homeassistant/components/knx/translations/hu.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "C\u00edm", - "individual_address": "A kapcsolat egy\u00e9ni c\u00edme", "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": { @@ -104,15 +102,13 @@ "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." + "state_updater": "Alap\u00e9rtelmezett be\u00e1ll\u00edt\u00e1s a KNX busz \u00e1llapotainak olvas\u00e1s\u00e1hoz. Ha le va tiltva, Home Assistant nem fog akt\u00edvan lek\u00e9rdezni egys\u00e9g\u00e1llapotokat a KNX buszr\u00f3l. Fel\u00fclb\u00edr\u00e1lhat\u00f3 a `sync_state` entit\u00e1s opci\u00f3kkal." } }, "tunnel": { "data": { "host": "C\u00edm", - "local_ip": "Helyi IP c\u00edm (hagyja \u00fcresen, ha nem biztos benne)", "port": "Port", - "route_back": "\u00datvonal (route) vissza / NAT m\u00f3d", "tunneling_type": "KNX alag\u00fat t\u00edpusa" }, "data_description": { diff --git a/homeassistant/components/knx/translations/id.json b/homeassistant/components/knx/translations/id.json index 92d812c1b1b..bbf9a1b7862 100644 --- a/homeassistant/components/knx/translations/id.json +++ b/homeassistant/components/knx/translations/id.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "Host", - "individual_address": "Alamat individu untuk koneksi", "local_ip": "IP lokal Home Assistant", "port": "Port", - "route_back": "Dirutekan Kembali/Mode NAT", "tunneling_type": "Jenis Tunnel KNX" }, "data_description": { @@ -43,7 +41,7 @@ }, "secure_knxkeys": { "data": { - "knxkeys_filename": "Nama file '.knxkeys' Anda (termasuk ekstensi)", + "knxkeys_filename": "Nama file `.knxkeys` Anda (termasuk ekstensi)", "knxkeys_password": "Kata sandi untuk mendekripsi file `.knxkeys`" }, "data_description": { @@ -104,15 +102,13 @@ "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." + "state_updater": "Menyetel default untuk status pembacaan KNX Bus. Saat dinonaktifkan, Home Assistant tidak akan secara aktif mengambil status entitas dari KNX Bus. Hal ini bisa ditimpa dengan opsi entitas `sync_state`." } }, "tunnel": { "data": { "host": "Host", - "local_ip": "IP lokal (kosongkan jika tidak yakin)", "port": "Port", - "route_back": "Dirutekan Kembali/Mode NAT", "tunneling_type": "Jenis Tunnel KNX" }, "data_description": { diff --git a/homeassistant/components/knx/translations/it.json b/homeassistant/components/knx/translations/it.json index c05bdba3944..8c2c58ad2d5 100644 --- a/homeassistant/components/knx/translations/it.json +++ b/homeassistant/components/knx/translations/it.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "Host", - "individual_address": "Indirizzo individuale per la connessione", "local_ip": "IP locale di Home Assistant", "port": "Porta", - "route_back": "Torna indietro / Modalit\u00e0 NAT", "tunneling_type": "Tipo tunnel KNX" }, "data_description": { @@ -47,7 +45,7 @@ "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_filename": "Il file dovrebbe essere trovato nella tua cartella di configurazione in `.storage/knx/`.\n Nel Sistema Operativo di 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`." @@ -104,15 +102,13 @@ "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." + "state_updater": "Impostazione predefinita per la lettura degli stati dal bus KNX. Se disabilitata Home Assistant non recuperer\u00e0 attivamente gli stati delle entit\u00e0 dal bus KNX. Pu\u00f2 essere sovrascritta dalle opzioni dell'entit\u00e0 `sync_state`." } }, "tunnel": { "data": { "host": "Host", - "local_ip": "IP locale (lascia vuoto se non sei sicuro)", "port": "Porta", - "route_back": "Torna indietro / Modalit\u00e0 NAT", "tunneling_type": "Tipo tunnel KNX" }, "data_description": { diff --git a/homeassistant/components/knx/translations/ja.json b/homeassistant/components/knx/translations/ja.json index 52eed8780b8..dcd44d5838f 100644 --- a/homeassistant/components/knx/translations/ja.json +++ b/homeassistant/components/knx/translations/ja.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "\u30db\u30b9\u30c8", - "individual_address": "\u63a5\u7d9a\u7528\u306e\u500b\u5225\u30a2\u30c9\u30ec\u30b9", - "local_ip": "\u30ed\u30fc\u30ab\u30ebIP(\u4e0d\u660e\u306a\u5834\u5408\u306f\u7a7a\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", + "local_ip": "\u30ed\u30fc\u30ab\u30ebIP(\u4e0d\u660e\u306a\u5834\u5408\u306f\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", "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": { @@ -31,7 +29,7 @@ "routing": { "data": { "individual_address": "\u30eb\u30fc\u30c6\u30a3\u30f3\u30b0\u63a5\u7d9a\u306e\u500b\u5225\u306e\u30a2\u30c9\u30ec\u30b9", - "local_ip": "\u30ed\u30fc\u30ab\u30ebIP(\u4e0d\u660e\u306a\u5834\u5408\u306f\u7a7a\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", + "local_ip": "\u30ed\u30fc\u30ab\u30ebIP(\u4e0d\u660e\u306a\u5834\u5408\u306f\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", "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" }, @@ -92,7 +90,7 @@ "data": { "connection_type": "KNX\u63a5\u7d9a\u30bf\u30a4\u30d7", "individual_address": "\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u500b\u5225\u30a2\u30c9\u30ec\u30b9", - "local_ip": "\u30ed\u30fc\u30ab\u30ebIP(\u4e0d\u660e\u306a\u5834\u5408\u306f\u7a7a\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", + "local_ip": "\u30ed\u30fc\u30ab\u30ebIP(\u4e0d\u660e\u306a\u5834\u5408\u306f\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", "multicast_group": "\u30eb\u30fc\u30c6\u30a3\u30f3\u30b0\u3068\u691c\u51fa(discovery)\u306b\u4f7f\u7528\u3055\u308c\u308b\u30de\u30eb\u30c1\u30ad\u30e3\u30b9\u30c8\u30b0\u30eb\u30fc\u30d7", "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", @@ -110,9 +108,7 @@ "tunnel": { "data": { "host": "\u30db\u30b9\u30c8", - "local_ip": "\u30ed\u30fc\u30ab\u30ebIP(\u4e0d\u660e\u306a\u5834\u5408\u306f\u7a7a\u306e\u307e\u307e\u306b\u3057\u3066\u304f\u3060\u3055\u3044)", "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": { diff --git a/homeassistant/components/knx/translations/ko.json b/homeassistant/components/knx/translations/ko.json new file mode 100644 index 00000000000..0aac9be9341 --- /dev/null +++ b/homeassistant/components/knx/translations/ko.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "secure_knxkeys": { + "data": { + "knxkeys_filename": "`.knxkeys` \ud30c\uc77c\uc758 \ud30c\uc77c \uc774\ub984(\ud655\uc7a5\uc790 \ud3ec\ud568)" + } + }, + "secure_manual": { + "description": "IP \ubcf4\uc548 \uc815\ubcf4\ub97c \uc785\ub825\ud558\uc138\uc694." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/knx/translations/nl.json b/homeassistant/components/knx/translations/nl.json index f40b9d7729f..bc8f03eb06c 100644 --- a/homeassistant/components/knx/translations/nl.json +++ b/homeassistant/components/knx/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "already_configured": "Dienst is al geconfigureerd", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "Host", - "individual_address": "Individueel adres voor de verbinding", "local_ip": "Lokale IP van Home Assistant", "port": "Poort", - "route_back": "Route Back / NAT Mode", "tunneling_type": "KNX Tunneling Type" }, "data_description": { @@ -110,9 +108,7 @@ "tunnel": { "data": { "host": "Host", - "local_ip": "Lokaal IP (laat leeg indien niet zeker)", "port": "Poort", - "route_back": "Route Back / NAT Mode", "tunneling_type": "KNX Tunneling Type" }, "data_description": { diff --git a/homeassistant/components/knx/translations/no.json b/homeassistant/components/knx/translations/no.json index f5d03e3160c..596071695b4 100644 --- a/homeassistant/components/knx/translations/no.json +++ b/homeassistant/components/knx/translations/no.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "Vert", - "individual_address": "Individuell adresse for tilkoblingen", "local_ip": "Lokal IP for hjemmeassistent", "port": "Port", - "route_back": "Rute tilbake / NAT-modus", "tunneling_type": "KNX tunneltype" }, "data_description": { @@ -104,15 +102,13 @@ "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." + "state_updater": "Sett standard for lesing av tilstander fra KNX-bussen. N\u00e5r den er deaktivert, vil ikke Home Assistant aktivt hente enhetstilstander fra KNX-bussen. Kan overstyres av entitetsalternativer for \"sync_state\"." } }, "tunnel": { "data": { "host": "Vert", - "local_ip": "Lokal IP (la st\u00e5 tomt hvis du er usikker)", "port": "Port", - "route_back": "Rute tilbake / NAT-modus", "tunneling_type": "KNX tunneltype" }, "data_description": { diff --git a/homeassistant/components/knx/translations/pl.json b/homeassistant/components/knx/translations/pl.json index e0821090f29..23185a0ae49 100644 --- a/homeassistant/components/knx/translations/pl.json +++ b/homeassistant/components/knx/translations/pl.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "Nazwa hosta lub adres IP", - "individual_address": "Indywidualny adres dla po\u0142\u0105czenia", "local_ip": "Lokalny adres IP Home Assistanta", "port": "Port", - "route_back": "Tryb Route Back / NAT", "tunneling_type": "Typ tunelowania KNX" }, "data_description": { @@ -104,15 +102,13 @@ "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." + "state_updater": "Ustaw domy\u015blne odczytywanie stan\u00f3w z magistrali KNX. Po wy\u0142\u0105czeniu, Home Assistant nie b\u0119dzie aktywnie pobiera\u0107 stan\u00f3w encji z magistrali KNX. Mo\u017cna to zast\u0105pi\u0107 przez opcj\u0119 encji `sync_state`." } }, "tunnel": { "data": { "host": "Nazwa hosta lub adres IP", - "local_ip": "Lokalny adres IP (pozostaw pusty, je\u015bli nie masz pewno\u015bci)", "port": "Port", - "route_back": "Tryb Route Back / NAT", "tunneling_type": "Typ tunelowania KNX" }, "data_description": { diff --git a/homeassistant/components/knx/translations/pt-BR.json b/homeassistant/components/knx/translations/pt-BR.json index 950cedc0721..dcdf057493b 100644 --- a/homeassistant/components/knx/translations/pt-BR.json +++ b/homeassistant/components/knx/translations/pt-BR.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "Nome do host", - "individual_address": "Endere\u00e7o individual para a conex\u00e3o", "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": { @@ -104,15 +102,13 @@ "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." + "state_updater": "Defina o padr\u00e3o para estados de leitura do barramento KNX. Quando desativado, o Home Assistant n\u00e3o recuperar\u00e1 ativamente os estados de entidade do barramento KNX. Pode ser substitu\u00eddo pelas op\u00e7\u00f5es de entidade `sync_state`." } }, "tunnel": { "data": { "host": "Nome do host", - "local_ip": "IP local (deixe em branco se n\u00e3o tiver certeza)", "port": "Porta", - "route_back": "Modo Rota de Retorno / NAT", "tunneling_type": "Tipo de t\u00fanel KNX" }, "data_description": { diff --git a/homeassistant/components/knx/translations/ru.json b/homeassistant/components/knx/translations/ru.json index 14b9f919aa2..1ca87d9ac80 100644 --- a/homeassistant/components/knx/translations/ru.json +++ b/homeassistant/components/knx/translations/ru.json @@ -15,10 +15,8 @@ "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", "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": { @@ -110,9 +108,7 @@ "tunnel": { "data": { "host": "\u0425\u043e\u0441\u0442", - "local_ip": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441 (\u0435\u0441\u043b\u0438 \u043d\u0435 \u0437\u043d\u0430\u0435\u0442\u0435, \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c)", "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": { diff --git a/homeassistant/components/knx/translations/sk.json b/homeassistant/components/knx/translations/sk.json index 6668aaa92fb..347dbdfbcef 100644 --- a/homeassistant/components/knx/translations/sk.json +++ b/homeassistant/components/knx/translations/sk.json @@ -8,11 +8,21 @@ "data": { "port": "Port" } + }, + "secure_manual": { + "data": { + "device_authentication": "Heslo na overenie zariadenia" + } } } }, "options": { "step": { + "init": { + "data": { + "local_ip": "Lok\u00e1lna IP adresa Home Assistant-a" + } + }, "tunnel": { "data": { "port": "Port" diff --git a/homeassistant/components/knx/translations/sl.json b/homeassistant/components/knx/translations/sl.json index 2e32080bfa0..d44b7dbd9cb 100644 --- a/homeassistant/components/knx/translations/sl.json +++ b/homeassistant/components/knx/translations/sl.json @@ -11,7 +11,6 @@ "manual_tunnel": { "data": { "host": "Gostitelj", - "individual_address": "Posamezni naslov za povezavo", "port": "Vrata" } }, diff --git a/homeassistant/components/knx/translations/sv.json b/homeassistant/components/knx/translations/sv.json index b1be9557565..de5def120c9 100644 --- a/homeassistant/components/knx/translations/sv.json +++ b/homeassistant/components/knx/translations/sv.json @@ -1,5 +1,9 @@ { "config": { + "error": { + "invalid_individual_address": "V\u00e4rdet matchar inte m\u00f6nstret f\u00f6r en individuell adress i KNX.\n'area.line.device'", + "invalid_ip_address": "Ogiltig IPv4-adress." + }, "step": { "manual_tunnel": { "data": { @@ -13,6 +17,10 @@ "tunnel": { "data": { "tunneling_type": "KNX tunneltyp" + }, + "data_description": { + "host": "IP adress till KNX/IP tunnelingsenhet.", + "port": "Port p\u00e5 KNX/IP tunnelingsenhet." } } } diff --git a/homeassistant/components/knx/translations/tr.json b/homeassistant/components/knx/translations/tr.json index c10f35ca48f..6ed12e70aee 100644 --- a/homeassistant/components/knx/translations/tr.json +++ b/homeassistant/components/knx/translations/tr.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "Sunucu", - "individual_address": "Ba\u011flant\u0131 i\u00e7in bireysel adres", "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": { @@ -104,15 +102,13 @@ "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." + "state_updater": "KNX Bus'tan okuma durumlar\u0131 i\u00e7in varsay\u0131lan\u0131 ayarlay\u0131n. Devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131\u011f\u0131nda, Home Assistant varl\u0131k durumlar\u0131n\u0131 KNX Bus'tan aktif olarak almaz. 'sync_state' varl\u0131k se\u00e7enekleri taraf\u0131ndan ge\u00e7ersiz k\u0131l\u0131nabilir." } }, "tunnel": { "data": { "host": "Sunucu", - "local_ip": "Yerel IP (emin de\u011filseniz bo\u015f b\u0131rak\u0131n)", "port": "Port", - "route_back": "Geri Y\u00f6nlendirme / NAT Modu", "tunneling_type": "KNX T\u00fcnel Tipi" }, "data_description": { diff --git a/homeassistant/components/knx/translations/zh-Hant.json b/homeassistant/components/knx/translations/zh-Hant.json index 8f740b85f6d..b348f38701d 100644 --- a/homeassistant/components/knx/translations/zh-Hant.json +++ b/homeassistant/components/knx/translations/zh-Hant.json @@ -15,10 +15,8 @@ "manual_tunnel": { "data": { "host": "\u4e3b\u6a5f\u7aef", - "individual_address": "\u9023\u7dda\u500b\u5225\u4f4d\u5740", "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": { @@ -104,15 +102,13 @@ "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" + "state_updater": "\u8a2d\u5b9a\u9810\u8a2d KNX Bus \u8b80\u53d6\u72c0\u614b\u3002\u7576\u95dc\u9589\u6642\u3001Home Assistant \u5c07\u4e0d\u6703\u4e3b\u52d5\u5f9e KNX Bus \u7372\u53d6\u5be6\u9ad4\u72c0\u614b\uff0c\u53ef\u88ab`sync_state` \u5be6\u9ad4\u9078\u9805\u8986\u84cb\u3002" } }, "tunnel": { "data": { "host": "\u4e3b\u6a5f\u7aef", - "local_ip": "\u672c\u5730\u7aef IP\uff08\u5047\u5982\u4e0d\u78ba\u5b9a\uff0c\u4fdd\u7559\u7a7a\u767d\uff09", "port": "\u901a\u8a0a\u57e0", - "route_back": "\u8def\u7531\u8fd4\u56de / NAT \u6a21\u5f0f", "tunneling_type": "KNX \u901a\u9053\u985e\u5225" }, "data_description": { diff --git a/homeassistant/components/kodi/device_trigger.py b/homeassistant/components/kodi/device_trigger.py index 68735bfa386..11d0b1567f9 100644 --- a/homeassistant/components/kodi/device_trigger.py +++ b/homeassistant/components/kodi/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for Kodi.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -36,9 +34,9 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Kodi devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 0e254a7d209..e19ffc6219c 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -1,16 +1,17 @@ """Support for interfacing with the XBMC/Kodi JSON-RPC API.""" from __future__ import annotations +from collections.abc import Awaitable, Callable, Coroutine from datetime import timedelta from functools import wraps import logging import re -from typing import Any +from typing import Any, TypeVar import urllib.parse -import jsonrpc_base from jsonrpc_base.jsonrpc import ProtocolError, TransportError from pykodi import CannotConnectError +from typing_extensions import Concatenate, ParamSpec import voluptuous as vol from homeassistant.components import media_source @@ -86,6 +87,9 @@ from .const import ( EVENT_TURN_ON, ) +_KodiEntityT = TypeVar("_KodiEntityT", bound="KodiEntity") +_P = ParamSpec("_P") + _LOGGER = logging.getLogger(__name__) EVENT_KODI_CALL_METHOD_RESULT = "kodi_call_method_result" @@ -243,18 +247,17 @@ async def async_setup_entry( async_add_entities([entity]) -def cmd(func): +def cmd( + func: Callable[Concatenate[_KodiEntityT, _P], Awaitable[Any]] +) -> Callable[Concatenate[_KodiEntityT, _P], Coroutine[Any, Any, None]]: """Catch command exceptions.""" @wraps(func) - async def wrapper(obj, *args, **kwargs): + async def wrapper(obj: _KodiEntityT, *args: _P.args, **kwargs: _P.kwargs) -> None: """Wrap all command methods.""" try: await func(obj, *args, **kwargs) - except ( - jsonrpc_base.jsonrpc.TransportError, - jsonrpc_base.jsonrpc.ProtocolError, - ) as exc: + except (TransportError, ProtocolError) as exc: # If Kodi is off, we expect calls to fail. if obj.state == STATE_OFF: log_function = _LOGGER.debug @@ -294,7 +297,6 @@ class KodiEntity(MediaPlayerEntity): """Initialize the Kodi entity.""" self._connection = connection self._kodi = kodi - self._name = name self._unique_id = uid self._players = None self._properties = {} @@ -304,6 +306,8 @@ class KodiEntity(MediaPlayerEntity): self._media_position = None self._connect_error = False + self._attr_name = name + def _reset_state(self, players=None): self._players = players self._properties = {} @@ -422,7 +426,7 @@ class KodiEntity(MediaPlayerEntity): version = (await self._kodi.get_application_properties(["version"]))["version"] sw_version = f"{version['major']}.{version['minor']}" - dev_reg = await device_registry.async_get_registry(self.hass) + dev_reg = device_registry.async_get(self.hass) device = dev_reg.async_get_device({(DOMAIN, self.unique_id)}) dev_reg.async_update_device(device.id, sw_version=sw_version) @@ -433,7 +437,7 @@ class KodiEntity(MediaPlayerEntity): try: await self._connection.connect() await self._on_ws_connected() - except (jsonrpc_base.jsonrpc.TransportError, CannotConnectError): + except (TransportError, CannotConnectError): if not self._connect_error: self._connect_error = True _LOGGER.warning("Unable to connect to Kodi via websocket") @@ -444,7 +448,7 @@ class KodiEntity(MediaPlayerEntity): async def _ping(self): try: await self._kodi.ping() - except (jsonrpc_base.jsonrpc.TransportError, CannotConnectError): + except (TransportError, CannotConnectError): if not self._connect_error: self._connect_error = True _LOGGER.warning("Unable to ping Kodi via websocket") @@ -521,11 +525,6 @@ class KodiEntity(MediaPlayerEntity): else: self._reset_state([]) - @property - def name(self): - """Return the name of the device.""" - return self._name - @property def should_poll(self): """Return True if entity has to be polled for state.""" @@ -637,6 +636,11 @@ class KodiEntity(MediaPlayerEntity): return None + @property + def available(self): + """Return True if entity is available.""" + return not self._connect_error + async def async_turn_on(self): """Turn the media player on.""" _LOGGER.debug("Firing event to turn on device") @@ -709,7 +713,9 @@ class KodiEntity(MediaPlayerEntity): """Send the play_media command to the media player.""" if media_source.is_media_source_id(media_id): media_type = MEDIA_TYPE_URL - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = play_item.url media_type_lower = media_type.lower() @@ -758,7 +764,7 @@ class KodiEntity(MediaPlayerEntity): try: result = await self._kodi.call_method(method, **kwargs) result_ok = True - except jsonrpc_base.jsonrpc.ProtocolError as exc: + except ProtocolError as exc: result = exc.args[2]["error"] _LOGGER.error( "Run API method %s.%s(%s) error: %s", @@ -767,7 +773,7 @@ class KodiEntity(MediaPlayerEntity): kwargs, result, ) - except jsonrpc_base.jsonrpc.TransportError: + except TransportError: result = None _LOGGER.warning( "TransportError trying to run API method %s.%s(%s)", diff --git a/homeassistant/components/kodi/translations/ja.json b/homeassistant/components/kodi/translations/ja.json index c7cf892bb2b..88855ff4d77 100644 --- a/homeassistant/components/kodi/translations/ja.json +++ b/homeassistant/components/kodi/translations/ja.json @@ -37,7 +37,7 @@ "data": { "ws_port": "\u30dd\u30fc\u30c8" }, - "description": "WebSocket\u30dd\u30fc\u30c8(Kodi\u3067\u306fTCP\u30dd\u30fc\u30c8\u3068\u547c\u3070\u308c\u308b\u3053\u3068\u3082\u3042\u308a\u307e\u3059)\u3002WebSocket\u3092\u4ecb\u3057\u3066\u63a5\u7d9a\u3059\u308b\u306b\u306f\u3001\u30b7\u30b9\u30c6\u30e0/\u8a2d\u5b9a/\u30cd\u30c3\u30c8\u30ef\u30fc\u30af/\u30b5\u30fc\u30d3\u30b9\u306b\u3042\u308b \"\u30d7\u30ed\u30b0\u30e9\u30e0\u306bKodi\u306e\u5236\u5fa1\u3092\u8a31\u53ef\u3059\u308b\" \u3092\u6709\u52b9\u306b\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002WebSocket\u304c\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u3001\u30dd\u30fc\u30c8\u3092\u524a\u9664\u3057\u3066\u7a7a\u306e\u307e\u307e\u306b\u3057\u307e\u3059\u3002" + "description": "WebSocket\u30dd\u30fc\u30c8(Kodi\u3067\u306fTCP\u30dd\u30fc\u30c8\u3068\u547c\u3070\u308c\u308b\u3053\u3068\u3082\u3042\u308a\u307e\u3059)\u3002WebSocket\u3092\u4ecb\u3057\u3066\u63a5\u7d9a\u3059\u308b\u306b\u306f\u3001\u30b7\u30b9\u30c6\u30e0/\u8a2d\u5b9a/\u30cd\u30c3\u30c8\u30ef\u30fc\u30af/\u30b5\u30fc\u30d3\u30b9\u306b\u3042\u308b \"\u30d7\u30ed\u30b0\u30e9\u30e0\u306bKodi\u306e\u5236\u5fa1\u3092\u8a31\u53ef\u3059\u308b\" \u3092\u6709\u52b9\u306b\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002WebSocket\u304c\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u3001\u30dd\u30fc\u30c8\u3092\u524a\u9664\u3057\u3066\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u307e\u3059\u3002" } } }, diff --git a/homeassistant/components/kodi/translations/nl.json b/homeassistant/components/kodi/translations/nl.json index 879ca83051f..6f3bac1e2e0 100644 --- a/homeassistant/components/kodi/translations/nl.json +++ b/homeassistant/components/kodi/translations/nl.json @@ -2,13 +2,13 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "no_uuid": "Kodi-instantie heeft geen unieke ID. Dit komt waarschijnlijk door een oude Kodi-versie (17.x of lager). U kunt de integratie handmatig configureren of upgraden naar een recentere Kodi-versie.", "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, @@ -29,7 +29,7 @@ "data": { "host": "Host", "port": "Poort", - "ssl": "Gebruik een SSL-certificaat" + "ssl": "Maakt gebruik van een SSL-certificaat" }, "description": "Kodi-verbindingsinformatie. Zorg ervoor dat u \"Controle van Kodi via HTTP toestaan\" in Systeem / Instellingen / Netwerk / Services inschakelt." }, diff --git a/homeassistant/components/konnected/strings.json b/homeassistant/components/konnected/strings.json index 905597035d5..cd08638c775 100644 --- a/homeassistant/components/konnected/strings.json +++ b/homeassistant/components/konnected/strings.json @@ -63,7 +63,7 @@ "description": "{zone} options", "data": { "type": "Binary Sensor Type", - "name": "[%key:common::config_flow::data::name%] (optional)", + "name": "[%key:common::config_flow::data::name%]", "inverse": "Invert the open/close state" } }, @@ -72,19 +72,19 @@ "description": "{zone} options", "data": { "type": "Sensor Type", - "name": "[%key:common::config_flow::data::name%] (optional)", - "poll_interval": "Poll Interval (minutes) (optional)" + "name": "[%key:common::config_flow::data::name%]", + "poll_interval": "Poll Interval (minutes)" } }, "options_switch": { "title": "Configure Switchable Output", "description": "{zone} options: state {state}", "data": { - "name": "[%key:common::config_flow::data::name%] (optional)", + "name": "[%key:common::config_flow::data::name%]", "activation": "Output when on", - "momentary": "Pulse duration (ms) (optional)", - "pause": "Pause between pulses (ms) (optional)", - "repeat": "Times to repeat (-1=infinite) (optional)", + "momentary": "Pulse duration (ms)", + "pause": "Pause between pulses (ms)", + "repeat": "Times to repeat (-1=infinite)", "more_states": "Configure additional states for this zone" } }, @@ -95,7 +95,7 @@ "discovery": "Respond to discovery requests on your network", "blink": "Blink panel LED on when sending state change", "override_api_host": "Override default Home Assistant API host panel URL", - "api_host": "Override API host URL (optional)" + "api_host": "Override API host URL" } } }, diff --git a/homeassistant/components/konnected/translations/ca.json b/homeassistant/components/konnected/translations/ca.json index c9bf5414d86..8d769749325 100644 --- a/homeassistant/components/konnected/translations/ca.json +++ b/homeassistant/components/konnected/translations/ca.json @@ -39,7 +39,7 @@ "options_binary": { "data": { "inverse": "Inverteix l'estat obert/tancat", - "name": "Nom (opcional)", + "name": "Nom", "type": "Tipus de sensor binari" }, "description": "Opcions de {zone}", @@ -47,8 +47,8 @@ }, "options_digital": { "data": { - "name": "Nom (opcional)", - "poll_interval": "Interval de sondeig (minuts) (opcional)", + "name": "Nom", + "poll_interval": "Interval de sondeig (minuts)", "type": "Tipus de sensor" }, "description": "Opcions de {zone}", @@ -84,7 +84,7 @@ }, "options_misc": { "data": { - "api_host": "Substitueix l'URL d'amfitri\u00f3 d'API (opcional)", + "api_host": "Substitueix l'URL d'amfitri\u00f3 d'API", "blink": "Parpelleja el LED del panell quan s'envien canvis d'estat", "discovery": "Respon a peticions de descobriment a la teva xarxa", "override_api_host": "Substitueix l'URL per defecte del panell d'amfitri\u00f3 de l'API de Home Assistant" @@ -95,11 +95,11 @@ "options_switch": { "data": { "activation": "Sortida quan estigui ON", - "momentary": "Durada del pols (ms) (opcional)", + "momentary": "Durada del pols (ms)", "more_states": "Configura estats addicionals per a aquesta zona", - "name": "Nom (opcional)", - "pause": "Pausa entre polsos (ms) (opcional)", - "repeat": "Repeticions (-1 = infinit) (opcional)" + "name": "Nom", + "pause": "Pausa entre polsos (ms)", + "repeat": "Repeticions (-1 = infinit)" }, "description": "Opcions de {zone}: estat {state}", "title": "Configuraci\u00f3 de sortida commutable" diff --git a/homeassistant/components/konnected/translations/de.json b/homeassistant/components/konnected/translations/de.json index 937425be1dc..82c99839f25 100644 --- a/homeassistant/components/konnected/translations/de.json +++ b/homeassistant/components/konnected/translations/de.json @@ -39,7 +39,7 @@ "options_binary": { "data": { "inverse": "Invertiere den \u00d6ffnungs- / Schlie\u00dfzustand", - "name": "Name (optional)", + "name": "Name", "type": "Bin\u00e4rer Sensortyp" }, "description": "Bitte w\u00e4hle die Optionen f\u00fcr den an {zone} angeschlossenen Bin\u00e4rsensor", @@ -47,8 +47,8 @@ }, "options_digital": { "data": { - "name": "Name (optional)", - "poll_interval": "Abfrageintervall (Minuten) (optional)", + "name": "Name", + "poll_interval": "Abfrageintervall (Minuten)", "type": "Sensortyp" }, "description": "Bitte w\u00e4hle die Optionen f\u00fcr den an {zone} angeschlossenen digitalen Sensor aus", @@ -84,7 +84,7 @@ }, "options_misc": { "data": { - "api_host": "API-Host-URL \u00fcberschreiben (optional)", + "api_host": "API-Host-URL \u00fcberschreiben", "blink": "LED Panel blinkt beim senden von Status\u00e4nderungen", "discovery": "Reagieren auf Suchanfragen in deinem Netzwerk", "override_api_host": "\u00dcberschreibe die Standard-Host-Panel-URL der Home Assistant-API" @@ -95,11 +95,11 @@ "options_switch": { "data": { "activation": "Ausgabe, wenn eingeschaltet", - "momentary": "Impulsdauer (ms) (optional)", + "momentary": "Impulsdauer (ms)", "more_states": "Konfiguriere zus\u00e4tzliche Zust\u00e4nde f\u00fcr diese Zone", - "name": "Name (optional)", - "pause": "Pause zwischen Impulsen (ms) (optional)", - "repeat": "Mal wiederholen (-1 = unendlich) (optional)" + "name": "Name", + "pause": "Pause zwischen Impulsen (ms)", + "repeat": "Mal wiederholen (-1 = unendlich)" }, "description": "Bitte w\u00e4hlen die Ausgabeoptionen f\u00fcr {zone} : Status {state}", "title": "Konfiguriere den schaltbaren Ausgang" diff --git a/homeassistant/components/konnected/translations/en.json b/homeassistant/components/konnected/translations/en.json index b5e6340b562..0015a81ef31 100644 --- a/homeassistant/components/konnected/translations/en.json +++ b/homeassistant/components/konnected/translations/en.json @@ -39,7 +39,7 @@ "options_binary": { "data": { "inverse": "Invert the open/close state", - "name": "Name (optional)", + "name": "Name", "type": "Binary Sensor Type" }, "description": "{zone} options", @@ -47,8 +47,8 @@ }, "options_digital": { "data": { - "name": "Name (optional)", - "poll_interval": "Poll Interval (minutes) (optional)", + "name": "Name", + "poll_interval": "Poll Interval (minutes)", "type": "Sensor Type" }, "description": "{zone} options", @@ -84,7 +84,7 @@ }, "options_misc": { "data": { - "api_host": "Override API host URL (optional)", + "api_host": "Override API host URL", "blink": "Blink panel LED on when sending state change", "discovery": "Respond to discovery requests on your network", "override_api_host": "Override default Home Assistant API host panel URL" @@ -95,11 +95,11 @@ "options_switch": { "data": { "activation": "Output when on", - "momentary": "Pulse duration (ms) (optional)", + "momentary": "Pulse duration (ms)", "more_states": "Configure additional states for this zone", - "name": "Name (optional)", - "pause": "Pause between pulses (ms) (optional)", - "repeat": "Times to repeat (-1=infinite) (optional)" + "name": "Name", + "pause": "Pause between pulses (ms)", + "repeat": "Times to repeat (-1=infinite)" }, "description": "{zone} options: state {state}", "title": "Configure Switchable Output" diff --git a/homeassistant/components/konnected/translations/es.json b/homeassistant/components/konnected/translations/es.json index 25c79d9f5c7..1074711901c 100644 --- a/homeassistant/components/konnected/translations/es.json +++ b/homeassistant/components/konnected/translations/es.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El dispositivo ya est\u00e1 configurado", - "already_in_progress": "El flujo de configuraci\u00f3n para el dispositivo ya est\u00e1 en marcha.", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "cannot_connect": "No se pudo conectar", "not_konn_panel": "No es un dispositivo Konnected.io reconocido", "unknown": "Se produjo un error desconocido" @@ -33,7 +33,7 @@ "not_konn_panel": "No es un dispositivo Konnected.io reconocido" }, "error": { - "bad_host": "URL del host de la API de invalidaci\u00f3n no v\u00e1lida" + "bad_host": "La URL de sustituci\u00f3n del host de la API es inv\u00e1lida" }, "step": { "options_binary": { @@ -84,7 +84,7 @@ }, "options_misc": { "data": { - "api_host": "Invalidar la direcci\u00f3n URL del host de la API (opcional)", + "api_host": "Sustituye la URL del host de la API (opcional)", "blink": "Parpadea el LED del panel cuando se env\u00eda un cambio de estado", "discovery": "Responde a las solicitudes de descubrimiento en tu red", "override_api_host": "Reemplazar la URL predeterminada del panel host de la API de Home Assistant" diff --git a/homeassistant/components/konnected/translations/et.json b/homeassistant/components/konnected/translations/et.json index ddddea5c800..bbbb417bb9f 100644 --- a/homeassistant/components/konnected/translations/et.json +++ b/homeassistant/components/konnected/translations/et.json @@ -39,7 +39,7 @@ "options_binary": { "data": { "inverse": "Vaheta avatud / suletud olek", - "name": "Nimi (valikuline)", + "name": "Nimi", "type": "Binaaranduri t\u00fc\u00fcp" }, "description": "{zone} suvandid", @@ -47,8 +47,8 @@ }, "options_digital": { "data": { - "name": "Nimi (valikuline)", - "poll_interval": "P\u00e4ringute intervall (sekundites) (valikuline)", + "name": "Nimi", + "poll_interval": "P\u00e4ringute intervall (minutites)", "type": "Anduri t\u00fc\u00fcp" }, "description": "{zone} valikud", @@ -84,7 +84,7 @@ }, "options_misc": { "data": { - "api_host": "Alista API hosti URL (valikuline)", + "api_host": "Alista API hosti URL", "blink": "Vilguts paneeli LEDi oleku muudatuse saatmisel", "discovery": "V\u00f5rgus esitatud tuvastusp\u00e4ringutele vastamine", "override_api_host": "Alista Home Assistanti API hostipaneeli vaikimisi URL" @@ -95,11 +95,11 @@ "options_switch": { "data": { "activation": "V\u00e4ljund sissel\u00fclitatuna", - "momentary": "Impulsi kestus (ms) (valikuline)", + "momentary": "Impulsi kestus (ms)", "more_states": "Selle tsooni t\u00e4iendavate olekute konfigureerimine", - "name": "Nimi (valikuline)", - "pause": "Paus impulsside vahel (ms) (valikuline)", - "repeat": "Korduste arv (-1 = l\u00f5pmatu) (valikuline)" + "name": "Nimi", + "pause": "Paus impulsside vahel (ms)", + "repeat": "Korduste arv (-1 = l\u00f5pmatu)" }, "description": "{tsooni} suvandid: olek {state}", "title": "Seadista l\u00fclitatav v\u00e4ljund" diff --git a/homeassistant/components/konnected/translations/fr.json b/homeassistant/components/konnected/translations/fr.json index 4b84efa351d..42b62ea490d 100644 --- a/homeassistant/components/konnected/translations/fr.json +++ b/homeassistant/components/konnected/translations/fr.json @@ -41,7 +41,7 @@ "options_binary": { "data": { "inverse": "Inverser l'\u00e9tat ouvert / ferm\u00e9", - "name": "Nom (facultatif)", + "name": "Nom", "type": "Type de capteur binaire" }, "description": "Veuillez s\u00e9lectionner les options du capteur binaire attach\u00e9 \u00e0 {zone}", @@ -49,8 +49,8 @@ }, "options_digital": { "data": { - "name": "Nom (facultatif)", - "poll_interval": "Intervalle d'interrogation (minutes) (facultatif)", + "name": "Nom", + "poll_interval": "Intervalle d'interrogation (en minutes)", "type": "Type de capteur" }, "description": "Veuillez s\u00e9lectionner les options du capteur digital attach\u00e9 \u00e0 {zone}", @@ -86,7 +86,7 @@ }, "options_misc": { "data": { - "api_host": "Remplacer l'URL de l'h\u00f4te de l'API (facultatif)", + "api_host": "Remplacer l'URL de l'h\u00f4te de l'API", "blink": "Voyant du panneau clignotant lors de l'envoi d'un changement d'\u00e9tat", "discovery": "R\u00e9pondre aux demandes de d\u00e9couverte sur votre r\u00e9seau", "override_api_host": "Remplacer l'URL par d\u00e9faut du panneau h\u00f4te de l'API Home Assistant" @@ -97,11 +97,11 @@ "options_switch": { "data": { "activation": "Sortie lorsque activ\u00e9", - "momentary": "Dur\u00e9e de l'impulsion (en millisecondes, facultatif)", + "momentary": "Dur\u00e9e de l'impulsion (en millisecondes)", "more_states": "Configurer des \u00e9tats suppl\u00e9mentaires pour cette zone", - "name": "Nom (facultatif)", - "pause": "Pause entre les impulsions (ms) (facultatif)", - "repeat": "Nombre de r\u00e9p\u00e9tition (-1=infini) (facultatif)" + "name": "Nom", + "pause": "Pause entre les impulsions (en millisecondes)", + "repeat": "Nombre de r\u00e9p\u00e9titions (-1\u00a0=\u00a0infini)" }, "description": "Veuillez s\u00e9lectionner les options de sortie pour {zone} : \u00e9tat {state}", "title": "Configurer la sortie commutable" diff --git a/homeassistant/components/konnected/translations/hu.json b/homeassistant/components/konnected/translations/hu.json index c40b1823424..57cbf0d9bb9 100644 --- a/homeassistant/components/konnected/translations/hu.json +++ b/homeassistant/components/konnected/translations/hu.json @@ -41,7 +41,7 @@ "options_binary": { "data": { "inverse": "Invert\u00e1lja a nyitott/z\u00e1rt \u00e1llapotot", - "name": "Elnevez\u00e9s (nem k\u00f6telez\u0151)", + "name": "Elnevez\u00e9s", "type": "Bin\u00e1ris \u00e9rz\u00e9kel\u0151 t\u00edpusa" }, "description": "{zone} opci\u00f3k", @@ -49,8 +49,8 @@ }, "options_digital": { "data": { - "name": "Elnevez\u00e9s (nem k\u00f6telez\u0151)", - "poll_interval": "Lek\u00e9rdez\u00e9si id\u0151k\u00f6z (perc) (opcion\u00e1lis)", + "name": "Elnevez\u00e9s", + "poll_interval": "Lek\u00e9rdez\u00e9si id\u0151k\u00f6z (perc)", "type": "\u00c9rz\u00e9kel\u0151 t\u00edpusa" }, "description": "{zone} opci\u00f3k", @@ -86,7 +86,7 @@ }, "options_misc": { "data": { - "api_host": "API host URL fel\u00fclb\u00edr\u00e1l\u00e1sa (opcion\u00e1lis)", + "api_host": "API host URL fel\u00fclb\u00edr\u00e1l\u00e1sa", "blink": "A panel LED villog\u00e1sa \u00e1llapotv\u00e1ltoz\u00e1skor", "discovery": "V\u00e1laszoljon a h\u00e1l\u00f3zaton \u00e9rkez\u0151 felder\u00edt\u00e9si k\u00e9r\u00e9sekre", "override_api_host": "Az alap\u00e9rtelmezett Home Assistant API host-URL fel\u00fcl\u00edr\u00e1sa" @@ -97,11 +97,11 @@ "options_switch": { "data": { "activation": "Kimenet bekapcsolt \u00e1llapotban", - "momentary": "Impulzus id\u0151tartama (ms) (opcion\u00e1lis)", + "momentary": "Impulzus id\u0151tartama (ms)", "more_states": "Tov\u00e1bbi \u00e1llapotok konfigur\u00e1l\u00e1sa ehhez a z\u00f3n\u00e1hoz", - "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)" + "name": "Elnevez\u00e9s", + "pause": "Sz\u00fcnet impulzusok k\u00f6z\u00f6tt (ms)", + "repeat": "Ism\u00e9tl\u00e9si id\u0151k (-1 = v\u00e9gtelen)" }, "description": "{zone} opci\u00f3k: \u00e1llapot {state}", "title": "Kapcsolhat\u00f3 kimenet konfigur\u00e1l\u00e1sa" diff --git a/homeassistant/components/konnected/translations/id.json b/homeassistant/components/konnected/translations/id.json index f2e2035ca06..0e64cb85032 100644 --- a/homeassistant/components/konnected/translations/id.json +++ b/homeassistant/components/konnected/translations/id.json @@ -39,7 +39,7 @@ "options_binary": { "data": { "inverse": "Balikkan status buka/tutup", - "name": "Nama (opsional)", + "name": "Nama", "type": "Jenis Sensor Biner" }, "description": "Opsi {zone}", @@ -47,8 +47,8 @@ }, "options_digital": { "data": { - "name": "Nama (opsional)", - "poll_interval": "Interval Polling (dalam menit) (opsional)", + "name": "Nama", + "poll_interval": "Interval Polling (menit)", "type": "Jenis Sensor" }, "description": "Opsi {zone}", @@ -84,7 +84,7 @@ }, "options_misc": { "data": { - "api_host": "Ganti URL host API (opsional)", + "api_host": "Timpa URL host API", "blink": "Kedipkan panel LED saat mengirim perubahan status", "discovery": "Tanggapi permintaan penemuan di jaringan Anda", "override_api_host": "Timpa URL panel host API Home Assistant bawaan" @@ -95,11 +95,11 @@ "options_switch": { "data": { "activation": "Keluaran saat nyala", - "momentary": "Durasi pulsa (milidetik) (opsional)", + "momentary": "Durasi pulsa (milidetik)", "more_states": "Konfigurasikan status tambahan untuk zona ini", - "name": "Nama (opsional)", - "pause": "Jeda di antara pulsa (milidetik) (opsional)", - "repeat": "Waktu pengulangan (-1 = tak terbatas) (opsional)" + "name": "Nama", + "pause": "Jeda di antara pulsa (milidetik)", + "repeat": "Waktu pengulangan (-1 = tak terbatas)" }, "description": "Opsi {zone}: status {state}", "title": "Konfigurasikan Output yang Dapat Dialihkan" diff --git a/homeassistant/components/konnected/translations/it.json b/homeassistant/components/konnected/translations/it.json index 682a27b50d0..4872dedcae4 100644 --- a/homeassistant/components/konnected/translations/it.json +++ b/homeassistant/components/konnected/translations/it.json @@ -41,7 +41,7 @@ "options_binary": { "data": { "inverse": "Invertire lo stato di apertura/chiusura", - "name": "Nome (opzionale)", + "name": "Nome", "type": "Tipo di sensore binario" }, "description": "Opzioni {zone}", @@ -49,8 +49,8 @@ }, "options_digital": { "data": { - "name": "Nome (opzionale)", - "poll_interval": "Intervallo di sondaggio (minuti) (opzionale)", + "name": "Nome", + "poll_interval": "Intervallo di sondaggio (minuti)", "type": "Tipo di sensore" }, "description": "Opzioni {zone}", @@ -97,11 +97,11 @@ "options_switch": { "data": { "activation": "Uscita quando acceso", - "momentary": "Durata impulso (ms) (opzionale)", + "momentary": "Durata impulso (ms)", "more_states": "Configura stati aggiuntivi per questa zona", - "name": "Nome (opzionale)", - "pause": "Pausa tra gli impulsi (ms) (opzionale)", - "repeat": "Numero di volte da ripetere (-1 = infinito) (opzionale)" + "name": "Nome", + "pause": "Pausa tra gli impulsi (ms)", + "repeat": "Numero di volte da ripetere (-1 = infinito)" }, "description": "Opzioni {zone}: stato {state}", "title": "Configurare l'uscita commutabile" diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index 1680431a35b..8f0548296b4 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken", "not_konn_panel": "Geen herkend Konnected.io apparaat", "unknown": "Onverwachte fout" @@ -50,7 +50,7 @@ "options_digital": { "data": { "name": "Naam (optional)", - "poll_interval": "Poll interval (minuten) (optioneel)", + "poll_interval": "Poll interval (minuten)", "type": "Type sensor" }, "description": "{zone} opties", @@ -86,7 +86,7 @@ }, "options_misc": { "data": { - "api_host": "API host-URL overschrijven (optioneel)", + "api_host": "API host-URL overschrijven", "blink": "Led knipperen bij het verzenden van statuswijziging", "discovery": "Reageer op detectieverzoeken op uw netwerk", "override_api_host": "Overschrijf standaard Home Assistant API hostpaneel-URL" @@ -97,11 +97,11 @@ "options_switch": { "data": { "activation": "Uitvoer wanneer ingeschakeld", - "momentary": "Pulsduur (ms) (optioneel)", + "momentary": "Pulsduur (ms)", "more_states": "Aanvullende statussen voor deze zone configureren", "name": "Naam (optioneel)", - "pause": "Pauze tussen de pulsen (ms) (optioneel)", - "repeat": "Aantal herhalingen (-1=oneindig) (optioneel)" + "pause": "Pauze tussen de pulsen (ms)", + "repeat": "Aantal herhalingen (-1=oneindig)" }, "description": "{zone} opties: status {state}", "title": "Schakelbare uitgang configureren" diff --git a/homeassistant/components/konnected/translations/no.json b/homeassistant/components/konnected/translations/no.json index 92d3efc4633..af9251f7c39 100644 --- a/homeassistant/components/konnected/translations/no.json +++ b/homeassistant/components/konnected/translations/no.json @@ -39,7 +39,7 @@ "options_binary": { "data": { "inverse": "Inverter \u00e5pen / lukk tilstand", - "name": "Navn (valgfritt)", + "name": "Navn", "type": "Bin\u00e6r sensortype" }, "description": "{zone}-alternativer", @@ -47,8 +47,8 @@ }, "options_digital": { "data": { - "name": "Navn (valgfritt)", - "poll_interval": "Avstemningsintervall (minutter) (valgfritt)", + "name": "Navn", + "poll_interval": "Avstemningsintervall (minutter)", "type": "Sensortype" }, "description": "{zone}-alternativer", @@ -84,7 +84,7 @@ }, "options_misc": { "data": { - "api_host": "Overstyre API-vert-URL (valgfritt)", + "api_host": "Overstyr API-verts-URL", "blink": "Blink p\u00e5 LED-lampen n\u00e5r du sender statusendring", "discovery": "Svar p\u00e5 oppdagelsesforesp\u00f8rsler i nettverket ditt", "override_api_host": "Overstyre standard Home Assistant API-vertspanel-URL" @@ -95,11 +95,11 @@ "options_switch": { "data": { "activation": "Utgang n\u00e5r den er p\u00e5", - "momentary": "Pulsvarighet (ms) (valgfritt)", + "momentary": "Pulsvarighet (ms)", "more_states": "Konfigurere flere tilstander for denne sonen", - "name": "Navn (valgfritt)", - "pause": "Pause mellom pulser (ms) (valgfritt)", - "repeat": "Tider \u00e5 gjenta (-1 = uendelig) (valgfritt)" + "name": "Navn", + "pause": "Pause mellom pulser (ms)", + "repeat": "Tider \u00e5 gjenta (-1=uendelig)" }, "description": "{zone} alternativer: tilstand {state}", "title": "Konfigurer utskiftbar utgang" diff --git a/homeassistant/components/konnected/translations/pl.json b/homeassistant/components/konnected/translations/pl.json index 7352bc82344..3eb24ac2cab 100644 --- a/homeassistant/components/konnected/translations/pl.json +++ b/homeassistant/components/konnected/translations/pl.json @@ -43,7 +43,7 @@ "options_binary": { "data": { "inverse": "Odwr\u00f3\u0107 stan otwarty/zamkni\u0119ty", - "name": "Nazwa (opcjonalnie)", + "name": "Nazwa", "type": "Typ sensora binarnego" }, "description": "Opcje {zone}", @@ -51,8 +51,8 @@ }, "options_digital": { "data": { - "name": "Nazwa (opcjonalnie)", - "poll_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (minuty) (opcjonalnie)", + "name": "Nazwa", + "poll_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (minuty)", "type": "Typ sensora" }, "description": "Opcje {zone}", @@ -88,7 +88,7 @@ }, "options_misc": { "data": { - "api_host": "Zast\u0119powanie adresu URL hosta API (opcjonalnie)", + "api_host": "Zast\u0119powanie adresu URL hosta API", "blink": "Miganie diody LED panelu podczas wysy\u0142ania zmiany stanu", "discovery": "Odpowiadaj na \u017c\u0105dania wykrywania w Twojej sieci", "override_api_host": "Zast\u0105p domy\u015blny adres URL API Home Assistanta" @@ -99,11 +99,11 @@ "options_switch": { "data": { "activation": "Wyj\u015bcie, gdy w\u0142\u0105czone", - "momentary": "Czas trwania impulsu (ms) (opcjonalnie)", + "momentary": "Czas trwania impulsu (ms)", "more_states": "Konfigurowanie dodatkowych stan\u00f3w dla tej strefy", - "name": "Nazwa (opcjonalnie)", - "pause": "Przerwa mi\u0119dzy impulsami (ms) (opcjonalnie)", - "repeat": "Ilo\u015b\u0107 powt\u00f3rze\u0144 (-1=niesko\u0144czenie) (opcjonalnie)" + "name": "Nazwa", + "pause": "Przerwa mi\u0119dzy impulsami (ms)", + "repeat": "Ilo\u015b\u0107 powt\u00f3rze\u0144 (-1=niesko\u0144czenie)" }, "description": "Opcje {zone}: stan {state}", "title": "Konfiguracja prze\u0142\u0105czalne wyj\u015bcie" diff --git a/homeassistant/components/konnected/translations/pt-BR.json b/homeassistant/components/konnected/translations/pt-BR.json index e3079fc50f7..92aaf3ce368 100644 --- a/homeassistant/components/konnected/translations/pt-BR.json +++ b/homeassistant/components/konnected/translations/pt-BR.json @@ -39,7 +39,7 @@ "options_binary": { "data": { "inverse": "Inverter o estado aberto/fechado", - "name": "Nome (opcional)", + "name": "Nome", "type": "Tipo de sensor bin\u00e1rio" }, "description": "Selecione as op\u00e7\u00f5es para o sensor bin\u00e1rio conectado a {zone}", @@ -47,8 +47,8 @@ }, "options_digital": { "data": { - "name": "Nome (opcional)", - "poll_interval": "Intervalo de vota\u00e7\u00e3o (minutos) (opcional)", + "name": "Nome", + "poll_interval": "Intervalo de vota\u00e7\u00e3o (minutos)", "type": "Tipo de sensor" }, "description": "Selecione as op\u00e7\u00f5es para o sensor digital conectado a {zone}", @@ -84,7 +84,7 @@ }, "options_misc": { "data": { - "api_host": "Substituir URL do host da API (opcional)", + "api_host": "Substituir URL do host da API", "blink": "LED do painel piscando ao enviar mudan\u00e7a de estado", "discovery": "Responder \u00e0s solicita\u00e7\u00f5es de descoberta em sua rede", "override_api_host": "Substituir o URL padr\u00e3o do painel do host da API do Home Assistant" @@ -95,11 +95,11 @@ "options_switch": { "data": { "activation": "Sa\u00edda quando ligado", - "momentary": "Dura\u00e7\u00e3o do pulso (ms) (opcional)", + "momentary": "Dura\u00e7\u00e3o do pulso (ms)", "more_states": "Configurar estados adicionais para esta zona", - "name": "Nome (opcional)", - "pause": "Pausa entre pulsos (ms) (opcional)", - "repeat": "Intervalo para repetir (-1=infinito) (opcional)" + "name": "Nome", + "pause": "Pausa entre pulsos (ms)", + "repeat": "Intervalo para repetir (-1=infinito)" }, "description": "Selecione as op\u00e7\u00f5es para o switch conectado a {zone}: estado {state}", "title": "Configure o interruptor de sa\u00edda" diff --git a/homeassistant/components/konnected/translations/ru.json b/homeassistant/components/konnected/translations/ru.json index 79cc78e6a9a..eb93b825d04 100644 --- a/homeassistant/components/konnected/translations/ru.json +++ b/homeassistant/components/konnected/translations/ru.json @@ -39,7 +39,7 @@ "options_binary": { "data": { "inverse": "\u0418\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0435/\u0437\u0430\u043a\u0440\u044b\u0442\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "type": "\u0422\u0438\u043f \u0431\u0438\u043d\u0430\u0440\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430" }, "description": "\u041e\u043f\u0446\u0438\u0438 {zone}", @@ -47,8 +47,8 @@ }, "options_digital": { "data": { - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", - "poll_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 \u0432 \u043c\u0438\u043d\u0443\u0442\u0430\u0445 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "poll_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 (\u0432 \u043c\u0438\u043d\u0443\u0442\u0430\u0445)", "type": "\u0422\u0438\u043f \u0441\u0435\u043d\u0441\u043e\u0440\u0430" }, "description": "\u041e\u043f\u0446\u0438\u0438 {zone}", @@ -84,7 +84,7 @@ }, "options_misc": { "data": { - "api_host": "\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c URL \u0445\u043e\u0441\u0442\u0430 API (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", + "api_host": "\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c URL \u0445\u043e\u0441\u0442\u0430 API", "blink": "LED-\u0438\u043d\u0434\u0438\u043a\u0430\u0446\u0438\u044f \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f", "discovery": "\u041e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u043d\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0432 \u0412\u0430\u0448\u0435\u0439 \u0441\u0435\u0442\u0438", "override_api_host": "\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c URL-\u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442-\u043f\u0430\u043d\u0435\u043b\u0438 Home Assistant API" @@ -95,11 +95,11 @@ "options_switch": { "data": { "activation": "\u0412\u044b\u0445\u043e\u0434 \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438", - "momentary": "\u0414\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u043c\u043f\u0443\u043b\u044c\u0441\u0430 (\u043c\u0441) (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", + "momentary": "\u0414\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u043c\u043f\u0443\u043b\u044c\u0441\u0430 (\u043c\u0441)", "more_states": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0437\u043e\u043d\u044b", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", - "pause": "\u041f\u0430\u0443\u0437\u0430 \u043c\u0435\u0436\u0434\u0443 \u0438\u043c\u043f\u0443\u043b\u044c\u0441\u0430\u043c\u0438 (\u043c\u0441) (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", - "repeat": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0439 (-1 = \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u043e) (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)" + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "pause": "\u041f\u0430\u0443\u0437\u0430 \u043c\u0435\u0436\u0434\u0443 \u0438\u043c\u043f\u0443\u043b\u044c\u0441\u0430\u043c\u0438 (\u043c\u0441)", + "repeat": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0439 (-1 = \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u043e)" }, "description": "{zone}: \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 {state}", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u043e\u0433\u043e \u0432\u044b\u0445\u043e\u0434\u0430" diff --git a/homeassistant/components/konnected/translations/tr.json b/homeassistant/components/konnected/translations/tr.json index 3c23b26ab0d..f5cb48dc088 100644 --- a/homeassistant/components/konnected/translations/tr.json +++ b/homeassistant/components/konnected/translations/tr.json @@ -41,7 +41,7 @@ "options_binary": { "data": { "inverse": "A\u00e7\u0131k / kapal\u0131 durumunu tersine \u00e7evirin", - "name": "Ad (iste\u011fe ba\u011fl\u0131)", + "name": "Ad", "type": "\u0130kili Sens\u00f6r Tipi" }, "description": "{zone} se\u00e7enekleri", @@ -49,8 +49,8 @@ }, "options_digital": { "data": { - "name": "Ad (iste\u011fe ba\u011fl\u0131)", - "poll_interval": "Yoklama Aral\u0131\u011f\u0131 (dakika) (iste\u011fe ba\u011fl\u0131)", + "name": "Ad", + "poll_interval": "Yoklama Aral\u0131\u011f\u0131 (dakika)", "type": "Sens\u00f6r Tipi" }, "description": "{zone} se\u00e7enekleri", @@ -86,7 +86,7 @@ }, "options_misc": { "data": { - "api_host": "API ana makine URL'sini ge\u00e7ersiz k\u0131l (iste\u011fe ba\u011fl\u0131)", + "api_host": "API ana makine URL'sini ge\u00e7ersiz k\u0131l", "blink": "Durum de\u011fi\u015fikli\u011fi g\u00f6nderilirken panelin LED'ini yan\u0131p s\u00f6nd\u00fcr", "discovery": "A\u011f\u0131n\u0131zdaki ke\u015fif isteklerine yan\u0131t verin", "override_api_host": "Varsay\u0131lan Home Assistant API ana bilgisayar paneli URL'sini ge\u00e7ersiz k\u0131l" @@ -97,11 +97,11 @@ "options_switch": { "data": { "activation": "A\u00e7\u0131kken \u00e7\u0131kt\u0131", - "momentary": "Darbe s\u00fcresi (ms) (iste\u011fe ba\u011fl\u0131)", + "momentary": "Darbe s\u00fcresi (ms)", "more_states": "Bu b\u00f6lge i\u00e7in ek durumlar yap\u0131land\u0131r\u0131n", - "name": "Ad (iste\u011fe ba\u011fl\u0131)", - "pause": "Darbeler aras\u0131nda duraklama (ms) (iste\u011fe ba\u011fl\u0131)", - "repeat": "Tekrarlanacak zamanlar (-1=sonsuz) (iste\u011fe ba\u011fl\u0131)" + "name": "Ad", + "pause": "Darbeler aras\u0131nda duraklama (ms)", + "repeat": "Tekrarlanacak zamanlar (-1=sonsuz)" }, "description": "{zone} se\u00e7enekleri: durum {state}", "title": "De\u011fi\u015ftirilebilir \u00c7\u0131k\u0131\u015f\u0131 Yap\u0131land\u0131r" diff --git a/homeassistant/components/konnected/translations/zh-Hant.json b/homeassistant/components/konnected/translations/zh-Hant.json index 9d6d522b7e1..fbce1455270 100644 --- a/homeassistant/components/konnected/translations/zh-Hant.json +++ b/homeassistant/components/konnected/translations/zh-Hant.json @@ -39,7 +39,7 @@ "options_binary": { "data": { "inverse": "\u53cd\u8f49\u958b\u555f/\u95dc\u9589\u72c0\u614b", - "name": "\u540d\u7a31 \uff08\u9078\u9805\uff09", + "name": "\u540d\u7a31", "type": "\u4e8c\u9032\u4f4d\u611f\u61c9\u5668\u985e\u5225" }, "description": "{zone}\u9078\u9805", @@ -47,8 +47,8 @@ }, "options_digital": { "data": { - "name": "\u540d\u7a31 \uff08\u9078\u9805\uff09", - "poll_interval": "\u66f4\u65b0\u9593\u8ddd\uff08\u5206\u9418\uff09\uff08\u9078\u9805\uff09", + "name": "\u540d\u7a31", + "poll_interval": "\u66f4\u65b0\u9593\u8ddd\uff08\u5206\u9418\uff09", "type": "\u611f\u61c9\u5668\u985e\u5225" }, "description": "{zone}\u9078\u9805", @@ -84,7 +84,7 @@ }, "options_misc": { "data": { - "api_host": "\u8986\u5beb API \u4e3b\u6a5f\u7aef URL\uff08\u9078\u9805\uff09", + "api_host": "\u8986\u5beb API \u4e3b\u6a5f\u7aef URL", "blink": "\u7576\u50b3\u9001\u72c0\u614b\u8b8a\u66f4\u6642\u3001\u9583\u720d\u9762\u677f LED", "discovery": "\u56de\u61c9\u7db2\u8def\u4e0a\u7684\u63a2\u7d22\u8acb\u6c42", "override_api_host": "\u8986\u5beb\u9810\u8a2d Home Assistant API \u4e3b\u6a5f\u7aef\u9762\u677f URL" @@ -95,11 +95,11 @@ "options_switch": { "data": { "activation": "\u958b\u555f\u6642\u8f38\u51fa", - "momentary": "\u6301\u7e8c\u6642\u9593\uff08ms\uff09\uff08\u9078\u9805\uff09", + "momentary": "\u6301\u7e8c\u6642\u9593\uff08ms\uff09", "more_states": "\u8a2d\u5b9a\u6b64\u5340\u57df\u7684\u9644\u52a0\u72c0\u614b", - "name": "\u540d\u7a31 \uff08\u9078\u9805\uff09", - "pause": "\u66ab\u505c\u9593\u8ddd\uff08ms\uff09\uff08\u9078\u9805\uff09", - "repeat": "\u91cd\u8907\u6642\u9593\uff08-1=\u7121\u9650\uff09\uff08\u9078\u9805\uff09" + "name": "\u540d\u7a31", + "pause": "\u66ab\u505c\u9593\u8ddd\uff08ms\uff09", + "repeat": "\u91cd\u8907\u6642\u9593\uff08-1=\u7121\u9650\uff09" }, "description": "{zone}\u9078\u9805\uff1a\u72c0\u614b {state}", "title": "\u8a2d\u5b9a Switchable \u8f38\u51fa" diff --git a/homeassistant/components/kraken/translations/ko.json b/homeassistant/components/kraken/translations/ko.json new file mode 100644 index 00000000000..758f3336cd4 --- /dev/null +++ b/homeassistant/components/kraken/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kraken/translations/nl.json b/homeassistant/components/kraken/translations/nl.json index 09b93b205e3..0ecf8885785 100644 --- a/homeassistant/components/kraken/translations/nl.json +++ b/homeassistant/components/kraken/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "already_configured": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "one": "Leeg", @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } }, diff --git a/homeassistant/components/kraken/translations/sk.json b/homeassistant/components/kraken/translations/sk.json new file mode 100644 index 00000000000..ba5e13223a7 --- /dev/null +++ b/homeassistant/components/kraken/translations/sk.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval aktualiz\u00e1cie" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/nl.json b/homeassistant/components/kulersky/translations/nl.json index 0671f0b3674..6fc4a03e824 100644 --- a/homeassistant/components/kulersky/translations/nl.json +++ b/homeassistant/components/kulersky/translations/nl.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/launch_library/translations/nl.json b/homeassistant/components/launch_library/translations/nl.json index 4dd9a218aa5..cc2333416d8 100644 --- a/homeassistant/components/launch_library/translations/nl.json +++ b/homeassistant/components/launch_library/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "user": { diff --git a/homeassistant/components/laundrify/__init__.py b/homeassistant/components/laundrify/__init__.py new file mode 100644 index 00000000000..27fc412abab --- /dev/null +++ b/homeassistant/components/laundrify/__init__.py @@ -0,0 +1,52 @@ +"""The laundrify integration.""" +from __future__ import annotations + +from laundrify_aio import LaundrifyAPI +from laundrify_aio.exceptions import ApiConnectionException, UnauthorizedException + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_ACCESS_TOKEN, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import DEFAULT_POLL_INTERVAL, DOMAIN +from .coordinator import LaundrifyUpdateCoordinator + +PLATFORMS = [Platform.BINARY_SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up laundrify from a config entry.""" + + session = async_get_clientsession(hass) + api_client = LaundrifyAPI(entry.data[CONF_ACCESS_TOKEN], session) + + try: + await api_client.validate_token() + except UnauthorizedException as err: + raise ConfigEntryAuthFailed("Invalid authentication") from err + except ApiConnectionException as err: + raise ConfigEntryNotReady("Cannot reach laundrify API") from err + + coordinator = LaundrifyUpdateCoordinator(hass, api_client, DEFAULT_POLL_INTERVAL) + + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { + "api": api_client, + "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/laundrify/binary_sensor.py b/homeassistant/components/laundrify/binary_sensor.py new file mode 100644 index 00000000000..2f64d8cee78 --- /dev/null +++ b/homeassistant/components/laundrify/binary_sensor.py @@ -0,0 +1,84 @@ +"""Platform for binary sensor integration.""" +from __future__ import annotations + +import logging + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) +from homeassistant.config_entries import ConfigEntry +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 .const import DOMAIN, MANUFACTURER, MODEL +from .coordinator import LaundrifyUpdateCoordinator +from .model import LaundrifyDevice + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, config: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up sensors from a config entry created in the integrations UI.""" + + coordinator = hass.data[DOMAIN][config.entry_id]["coordinator"] + + async_add_entities( + LaundrifyPowerPlug(coordinator, device) for device in coordinator.data.values() + ) + + +class LaundrifyPowerPlug( + CoordinatorEntity[LaundrifyUpdateCoordinator], BinarySensorEntity +): + """Representation of a laundrify Power Plug.""" + + _attr_device_class = BinarySensorDeviceClass.RUNNING + _attr_icon = "mdi:washing-machine" + + def __init__( + self, coordinator: LaundrifyUpdateCoordinator, device: LaundrifyDevice + ) -> None: + """Pass coordinator to CoordinatorEntity.""" + super().__init__(coordinator) + self._device = device + self._attr_unique_id = device["_id"] + + @property + def device_info(self) -> DeviceInfo: + """Configure the Device of this Entity.""" + return DeviceInfo( + identifiers={(DOMAIN, self._device["_id"])}, + name=self.name, + manufacturer=MANUFACTURER, + model=MODEL, + sw_version=self._device["firmwareVersion"], + ) + + @property + def available(self) -> bool: + """Check if the device is available.""" + return ( + self.unique_id in self.coordinator.data + and self.coordinator.last_update_success + ) + + @property + def name(self) -> str: + """Name of the entity.""" + return self._device["name"] + + @property + def is_on(self) -> bool: + """Return entity state.""" + return self._device["status"] == "ON" + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._device = self.coordinator.data[self.unique_id] + super()._handle_coordinator_update() diff --git a/homeassistant/components/laundrify/config_flow.py b/homeassistant/components/laundrify/config_flow.py new file mode 100644 index 00000000000..d8230863d7c --- /dev/null +++ b/homeassistant/components/laundrify/config_flow.py @@ -0,0 +1,94 @@ +"""Config flow for laundrify integration.""" +from __future__ import annotations + +import logging +from typing import Any + +from laundrify_aio import LaundrifyAPI +from laundrify_aio.exceptions import ( + ApiConnectionException, + InvalidFormat, + UnknownAuthCode, +) +from voluptuous import Required, Schema + +from homeassistant.config_entries import ConfigFlow +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CODE +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = Schema({Required(CONF_CODE): str}) + + +class LaundrifyConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for laundrify.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initialized by the user.""" + return await self.async_step_init(user_input) + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if user_input is None: + return self.async_show_form(step_id="init", data_schema=CONFIG_SCHEMA) + + errors = {} + + try: + access_token = await LaundrifyAPI.exchange_auth_code(user_input[CONF_CODE]) + + session = async_get_clientsession(self.hass) + api_client = LaundrifyAPI(access_token, session) + + account_id = await api_client.get_account_id() + except InvalidFormat: + errors[CONF_CODE] = "invalid_format" + except UnknownAuthCode: + errors[CONF_CODE] = "invalid_auth" + except ApiConnectionException: + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + entry_data = {CONF_ACCESS_TOKEN: access_token} + + await self.async_set_unique_id(account_id) + self._abort_if_unique_id_configured() + + # Create a new entry if it doesn't exist + return self.async_create_entry( + title=DOMAIN, + data=entry_data, + ) + + return self.async_show_form( + step_id="init", data_schema=CONFIG_SCHEMA, errors=errors + ) + + async def async_step_reauth( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Perform reauth upon an API authentication error.""" + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Dialog that informs the user that reauth is required.""" + if user_input is None: + return self.async_show_form( + step_id="reauth_confirm", + data_schema=Schema({}), + ) + return await self.async_step_init() diff --git a/homeassistant/components/laundrify/const.py b/homeassistant/components/laundrify/const.py new file mode 100644 index 00000000000..c312b895234 --- /dev/null +++ b/homeassistant/components/laundrify/const.py @@ -0,0 +1,10 @@ +"""Constants for the laundrify integration.""" + +DOMAIN = "laundrify" + +MANUFACTURER = "laundrify" +MODEL = "WLAN-Adapter (SU02)" + +DEFAULT_POLL_INTERVAL = 60 + +REQUEST_TIMEOUT = 10 diff --git a/homeassistant/components/laundrify/coordinator.py b/homeassistant/components/laundrify/coordinator.py new file mode 100644 index 00000000000..31447490506 --- /dev/null +++ b/homeassistant/components/laundrify/coordinator.py @@ -0,0 +1,46 @@ +"""Custom DataUpdateCoordinator for the laundrify integration.""" +from datetime import timedelta +import logging + +import async_timeout +from laundrify_aio import LaundrifyAPI +from laundrify_aio.exceptions import ApiConnectionException, UnauthorizedException + +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN, REQUEST_TIMEOUT +from .model import LaundrifyDevice + +_LOGGER = logging.getLogger(__name__) + + +class LaundrifyUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching laundrify API data.""" + + def __init__( + self, hass: HomeAssistant, laundrify_api: LaundrifyAPI, poll_interval: int + ) -> None: + """Initialize laundrify coordinator.""" + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=timedelta(seconds=poll_interval), + ) + self.laundrify_api = laundrify_api + + async def _async_update_data(self) -> dict[str, LaundrifyDevice]: + """Fetch data from laundrify API.""" + try: + # Note: asyncio.TimeoutError and aiohttp.ClientError are already + # handled by the data update coordinator. + async with async_timeout.timeout(REQUEST_TIMEOUT): + return {m["_id"]: m for m in await self.laundrify_api.get_machines()} + except UnauthorizedException as err: + # Raising ConfigEntryAuthFailed will cancel future updates + # and start a config flow with SOURCE_REAUTH (async_step_reauth) + raise ConfigEntryAuthFailed from err + except ApiConnectionException as err: + raise UpdateFailed(f"Error communicating with API: {err}") from err diff --git a/homeassistant/components/laundrify/manifest.json b/homeassistant/components/laundrify/manifest.json new file mode 100644 index 00000000000..a5737b9cf97 --- /dev/null +++ b/homeassistant/components/laundrify/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "laundrify", + "name": "laundrify", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/laundrify", + "requirements": ["laundrify_aio==1.1.2"], + "codeowners": ["@xLarry"], + "iot_class": "cloud_polling" +} diff --git a/homeassistant/components/laundrify/model.py b/homeassistant/components/laundrify/model.py new file mode 100644 index 00000000000..aa6bf77509f --- /dev/null +++ b/homeassistant/components/laundrify/model.py @@ -0,0 +1,13 @@ +"""Models for laundrify platform.""" +from __future__ import annotations + +from typing import TypedDict + + +class LaundrifyDevice(TypedDict): + """laundrify Power Plug.""" + + _id: str + name: str + status: str + firmwareVersion: str diff --git a/homeassistant/components/laundrify/strings.json b/homeassistant/components/laundrify/strings.json new file mode 100644 index 00000000000..b2fea9f307f --- /dev/null +++ b/homeassistant/components/laundrify/strings.json @@ -0,0 +1,25 @@ +{ + "config": { + "step": { + "init": { + "description": "Please enter your personal Auth Code that is shown in the laundrify-App.", + "data": { + "code": "Auth Code (xxx-xxx)" + } + }, + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "The laundrify integration needs to re-authenticate." + } + }, + "error": { + "invalid_format": "Invalid format. Please specify as xxx-xxx.", + "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%]" + } + } +} diff --git a/homeassistant/components/laundrify/translations/ca.json b/homeassistant/components/laundrify/translations/ca.json new file mode 100644 index 00000000000..99b94ecfd77 --- /dev/null +++ b/homeassistant/components/laundrify/translations/ca.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "invalid_format": "Format inv\u00e0lid. Escriu-lo com a xxx-xxx.", + "unknown": "Error inesperat" + }, + "step": { + "init": { + "data": { + "code": "Codi d'autenticaci\u00f3 (xxx-xxx)" + }, + "description": "Introdueix codi d'autenticaci\u00f3 personal que es mostra a l'aplicaci\u00f3 de Laundrify." + }, + "reauth_confirm": { + "description": "La integraci\u00f3 Laundrify 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/laundrify/translations/de.json b/homeassistant/components/laundrify/translations/de.json new file mode 100644 index 00000000000..016f345e13d --- /dev/null +++ b/homeassistant/components/laundrify/translations/de.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_format": "Ung\u00fcltiges Format. Bitte als xxx-xxx angeben.", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "init": { + "data": { + "code": "Auth-Code (xxx-xxx)" + }, + "description": "Bitte gib deinen pers\u00f6nlichen Auth-Code ein, der in der laundrify-App angezeigt wird." + }, + "reauth_confirm": { + "description": "Die laundrify-Integration muss sich neu authentifizieren.", + "title": "Integration erneut authentifizieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/el.json b/homeassistant/components/laundrify/translations/el.json new file mode 100644 index 00000000000..fae492f678a --- /dev/null +++ b/homeassistant/components/laundrify/translations/el.json @@ -0,0 +1,25 @@ +{ + "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.\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", + "invalid_format": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03ba\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c9\u03c2 xxx-xxx.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "init": { + "data": { + "code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2 (xxx-xxx)" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03cc \u03c3\u03b1\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae laundrify-App." + }, + "reauth_confirm": { + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 laundrify \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b3\u03af\u03bd\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\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/laundrify/translations/en.json b/homeassistant/components/laundrify/translations/en.json new file mode 100644 index 00000000000..0c8375746ed --- /dev/null +++ b/homeassistant/components/laundrify/translations/en.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "invalid_format": "Invalid format. Please specify as xxx-xxx.", + "unknown": "Unexpected error" + }, + "step": { + "init": { + "data": { + "code": "Auth Code (xxx-xxx)" + }, + "description": "Please enter your personal Auth Code that is shown in the laundrify-App." + }, + "reauth_confirm": { + "description": "The laundrify integration needs to re-authenticate.", + "title": "Reauthenticate Integration" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/et.json b/homeassistant/components/laundrify/translations/et.json new file mode 100644 index 00000000000..cec7d1ec849 --- /dev/null +++ b/homeassistant/components/laundrify/translations/et.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus", + "invalid_format": "Kehtetu vorming. Sisesta kui xxx-xxx.", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "init": { + "data": { + "code": "Auth kood (xxx-xxx)" + }, + "description": "Sisesta isiklik autentimiskood, mis kuvatakse pesumasina rakenduses." + }, + "reauth_confirm": { + "description": "laundrify sidumine tuleb uuesti autentida.", + "title": "Taastuvasta sidumine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/fr.json b/homeassistant/components/laundrify/translations/fr.json new file mode 100644 index 00000000000..91674bdd50c --- /dev/null +++ b/homeassistant/components/laundrify/translations/fr.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification non valide", + "invalid_format": "Format non valide. Veuillez sp\u00e9cifier au format xxx-xxx.", + "unknown": "Erreur inattendue" + }, + "step": { + "init": { + "data": { + "code": "Code d'authentification (xxx-xxx)" + }, + "description": "Veuillez saisir votre code d'authentification personnel tel qu'affich\u00e9 dans l'application laundrify." + }, + "reauth_confirm": { + "description": "L'int\u00e9gration laundrify doit \u00eatre r\u00e9-authentifi\u00e9e.", + "title": "R\u00e9-authentifier l'int\u00e9gration" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/hu.json b/homeassistant/components/laundrify/translations/hu.json new file mode 100644 index 00000000000..aa22ddc3da3 --- /dev/null +++ b/homeassistant/components/laundrify/translations/hu.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "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", + "invalid_format": "\u00c9rv\u00e9nytelen form\u00e1tum. K\u00e9rj\u00fck, adja meg \u00edgy: xxx-xxx.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "init": { + "data": { + "code": "Hiteles\u00edt\u00e9si k\u00f3d (xxx-xxx)" + }, + "description": "K\u00e9rj\u00fck, adja meg a laundrify-alkalmaz\u00e1sban megjelen\u0151 szem\u00e9lyes enged\u00e9lyez\u00e9si k\u00f3dj\u00e1t." + }, + "reauth_confirm": { + "description": "A laundrify integr\u00e1ci\u00f3nak \u00fajra kell hiteles\u00edtenie.", + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/id.json b/homeassistant/components/laundrify/translations/id.json new file mode 100644 index 00000000000..ec80b01a84a --- /dev/null +++ b/homeassistant/components/laundrify/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_format": "Format yang tidak valid. Tentukan dengan format xxx-xxx.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "init": { + "data": { + "code": "Kode Autentikasi (xxx-xxx)" + }, + "description": "Masukkan Kode Autentikasi pribadi Anda yang ditampilkan di aplikasi laundrify." + }, + "reauth_confirm": { + "description": "Integrasi laundrify perlu mengautentikasi ulang akun Anda", + "title": "Autentikasi Ulang Integrasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/it.json b/homeassistant/components/laundrify/translations/it.json new file mode 100644 index 00000000000..08fd2dcb87a --- /dev/null +++ b/homeassistant/components/laundrify/translations/it.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "invalid_format": "Formato non valido. Specificare come xxx-xxx.", + "unknown": "Errore imprevisto" + }, + "step": { + "init": { + "data": { + "code": "Codice di autorizzazione (xxx-xxx)" + }, + "description": "Inserisci il tuo codice di autorizzazione personale che viene mostrato nell'app di laundrify." + }, + "reauth_confirm": { + "description": "L'integrazione laundrify deve eseguire nuovamente l'autenticazione.", + "title": "Autentica nuovamente l'integrazione" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/ja.json b/homeassistant/components/laundrify/translations/ja.json new file mode 100644 index 00000000000..153fcf99afb --- /dev/null +++ b/homeassistant/components/laundrify/translations/ja.json @@ -0,0 +1,25 @@ +{ + "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" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "invalid_format": "\u7121\u52b9\u306a\u5f62\u5f0f\u3067\u3059\u3002xxx-xxx\u3068\u3057\u3066\u6307\u5b9a\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "init": { + "data": { + "code": "\u8a8d\u8a3c\u30b3\u30fc\u30c9 (xxx-xxx)" + }, + "description": "Laundrify-App\u306b\u8868\u793a\u3055\u308c\u3066\u3044\u308b\u3001\u3042\u306a\u305f\u306e\u500b\u4eba\u8a8d\u8a3c\u30b3\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + }, + "reauth_confirm": { + "description": "Laundrify\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306f\u3001\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/laundrify/translations/nl.json b/homeassistant/components/laundrify/translations/nl.json new file mode 100644 index 00000000000..1fdb9f35870 --- /dev/null +++ b/homeassistant/components/laundrify/translations/nl.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "invalid_format": "Ongeldig formaat. Specificeer als xxx-xxx.", + "unknown": "Onverwachte fout" + }, + "step": { + "init": { + "data": { + "code": "Verificatiecode (xxx-xxx)" + }, + "description": "Voer de verificatiecode in die wordt weergegeven in de laundrify-app." + }, + "reauth_confirm": { + "description": "De laundrify-integratie moet opnieuw worden geauthenticeerd.", + "title": "Integratie herauthenticeren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/no.json b/homeassistant/components/laundrify/translations/no.json new file mode 100644 index 00000000000..fc2a24414d1 --- /dev/null +++ b/homeassistant/components/laundrify/translations/no.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "invalid_format": "Ugyldig format. Vennligst spesifiser som xxx-xxx.", + "unknown": "Uventet feil" + }, + "step": { + "init": { + "data": { + "code": "Auth-kode (xxx-xxx)" + }, + "description": "Vennligst skriv inn din personlige autentiseringskode som vises i laundrify-appen." + }, + "reauth_confirm": { + "description": "Integrasjonen av vaskeri m\u00e5 autentiseres p\u00e5 nytt.", + "title": "Godkjenne integrering p\u00e5 nytt" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/pl.json b/homeassistant/components/laundrify/translations/pl.json new file mode 100644 index 00000000000..27b33ab5880 --- /dev/null +++ b/homeassistant/components/laundrify/translations/pl.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "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", + "invalid_format": "Niepoprawny format. Prosz\u0119 poda\u0107 jako xxx-xxx.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "init": { + "data": { + "code": "Kod autoryzacyjny (xxx-xxx)" + }, + "description": "Wprowad\u017a sw\u00f3j osobisty kod autoryzacyjny, kt\u00f3ry jest widoczny w aplikacji laundrify." + }, + "reauth_confirm": { + "description": "Integracja laundrify wymaga ponownego uwierzytelnienia Twojego konta.", + "title": "Ponownie uwierzytelnij integracj\u0119" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/pt-BR.json b/homeassistant/components/laundrify/translations/pt-BR.json new file mode 100644 index 00000000000..c053ae34fe0 --- /dev/null +++ b/homeassistant/components/laundrify/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 est\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falhou ao se conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_format": "Formato Inv\u00e1lido. Especifique como xxx-xxx.", + "unknown": "Erro inesperado" + }, + "step": { + "init": { + "data": { + "code": "C\u00f3digo de autentica\u00e7\u00e3o (xxx-xxx)" + }, + "description": "Por favor, insira seu c\u00f3digo de autentica\u00e7\u00e3o pessoal que \u00e9 mostrado no aplicativo laundrify." + }, + "reauth_confirm": { + "description": "A integra\u00e7\u00e3o da laundrify precisa ser autenticada novamente.", + "title": "Reautenticar Integra\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/ru.json b/homeassistant/components/laundrify/translations/ru.json new file mode 100644 index 00000000000..cceb04e1601 --- /dev/null +++ b/homeassistant/components/laundrify/translations/ru.json @@ -0,0 +1,25 @@ +{ + "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." + }, + "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_format": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442. \u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 \u0445\u0445\u0445-\u0445\u0445\u0445.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "init": { + "data": { + "code": "\u041a\u043e\u0434 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 (xxx-xxx)" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043b\u0438\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 laundrify." + }, + "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 laundrify.", + "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/laundrify/translations/tr.json b/homeassistant/components/laundrify/translations/tr.json new file mode 100644 index 00000000000..97a1b13e192 --- /dev/null +++ b/homeassistant/components/laundrify/translations/tr.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "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", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "invalid_format": "Ge\u00e7ersiz format. L\u00fctfen xxx-xxx olarak belirtin.", + "unknown": "Beklenmeyen hata" + }, + "step": { + "init": { + "data": { + "code": "Yetkilendirme Kodu (xxx-xxx)" + }, + "description": "L\u00fctfen laundrify-App g\u00f6sterilen ki\u015fisel Yetkilendirme Kodunuzu girin." + }, + "reauth_confirm": { + "description": "\u00c7ama\u015f\u0131r y\u0131kama entegrasyonunun yeniden kimlik do\u011frulamas\u0131 yapmas\u0131 gerekiyor.", + "title": "Entegrasyonu Yeniden Do\u011frula" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/laundrify/translations/zh-Hant.json b/homeassistant/components/laundrify/translations/zh-Hant.json new file mode 100644 index 00000000000..834ebb9125b --- /dev/null +++ b/homeassistant/components/laundrify/translations/zh-Hant.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "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", + "invalid_format": "\u683c\u5f0f\u932f\u8aa4\u3001\u8acb\u4f7f\u7528 xxx-xxx\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "init": { + "data": { + "code": "\u8a8d\u8b49\u78bc\uff08xxx-xxx\uff09" + }, + "description": "\u8acb\u8f38\u5165\u65bc laundrify App \u4e0a\u6240\u986f\u793a\u4e4b\u8a8d\u8b49\u78bc\u3002" + }, + "reauth_confirm": { + "description": "Laundrify \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/lcn/config_flow.py b/homeassistant/components/lcn/config_flow.py index 924ff5b278c..a8c5a311971 100644 --- a/homeassistant/components/lcn/config_flow.py +++ b/homeassistant/components/lcn/config_flow.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -from typing import Any import pypck @@ -16,15 +15,16 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.typing import ConfigType from .const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN +from .helpers import purge_device_registry, purge_entity_registry _LOGGER = logging.getLogger(__name__) def get_config_entry( - hass: HomeAssistant, data: dict[str, Any] + hass: HomeAssistant, data: ConfigType ) -> config_entries.ConfigEntry | None: """Check config entries for already configured entries based on the ip address/port.""" return next( @@ -38,7 +38,7 @@ def get_config_entry( ) -async def validate_connection(host_name: str, data: dict[str, Any]) -> dict[str, Any]: +async def validate_connection(host_name: str, data: ConfigType) -> ConfigType: """Validate if a connection to LCN can be established.""" host = data[CONF_IP_ADDRESS] port = data[CONF_PORT] @@ -70,7 +70,7 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_import(self, data: dict[str, Any]) -> FlowResult: + async def async_step_import(self, data: ConfigType) -> FlowResult: """Import existing configuration from LCN.""" host_name = data[CONF_HOST] # validate the imported connection parameters @@ -93,13 +93,10 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # check if we already have a host with the same address configured if entry := get_config_entry(self.hass, data): entry.source = config_entries.SOURCE_IMPORT - # Cleanup entity and device registry, if we imported from configuration.yaml to # remove orphans when entities were removed from configuration - entity_registry = er.async_get(self.hass) - entity_registry.async_clear_config_entry(entry.entry_id) - device_registry = dr.async_get(self.hass) - device_registry.async_clear_config_entry(entry.entry_id) + purge_entity_registry(self.hass, entry.entry_id, data) + purge_device_registry(self.hass, entry.entry_id, data) self.hass.config_entries.async_update_entry(entry, data=data) return self.async_abort(reason="existing_configuration_updated") diff --git a/homeassistant/components/lcn/device_trigger.py b/homeassistant/components/lcn/device_trigger.py index 35575a442b2..a6fc17759b8 100644 --- a/homeassistant/components/lcn/device_trigger.py +++ b/homeassistant/components/lcn/device_trigger.py @@ -1,8 +1,6 @@ """Provides device triggers for LCN.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -53,7 +51,7 @@ TYPE_SCHEMAS = { async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for LCN devices.""" device_registry = dr.async_get(hass) if (device := device_registry.async_get(device_id)) is None: @@ -101,6 +99,8 @@ async def async_attach_trigger( ) -async def async_get_trigger_capabilities(hass: HomeAssistant, config: dict) -> dict: +async def async_get_trigger_capabilities( + hass: HomeAssistant, config: ConfigType +) -> dict[str, vol.Schema]: """List trigger capabilities.""" return TYPE_SCHEMAS.get(config[CONF_TYPE], {}) diff --git a/homeassistant/components/lcn/helpers.py b/homeassistant/components/lcn/helpers.py index 0c8edd9b852..e9fb2683f4f 100644 --- a/homeassistant/components/lcn/helpers.py +++ b/homeassistant/components/lcn/helpers.py @@ -30,7 +30,7 @@ from homeassistant.const import ( CONF_USERNAME, ) 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.typing import ConfigType from .const import ( @@ -247,6 +247,75 @@ def import_lcn_config(lcn_config: ConfigType) -> list[ConfigType]: return list(data.values()) +def purge_entity_registry( + hass: HomeAssistant, entry_id: str, imported_entry_data: ConfigType +) -> None: + """Remove orphans from entity registry which are not in entry data.""" + entity_registry = er.async_get(hass) + + # Find all entities that are referenced in the config entry. + references_config_entry = { + entity_entry.entity_id + for entity_entry in er.async_entries_for_config_entry(entity_registry, entry_id) + } + + # Find all entities that are referenced by the entry_data. + references_entry_data = set() + for entity_data in imported_entry_data[CONF_ENTITIES]: + entity_unique_id = generate_unique_id( + entry_id, entity_data[CONF_ADDRESS], entity_data[CONF_RESOURCE] + ) + entity_id = entity_registry.async_get_entity_id( + entity_data[CONF_DOMAIN], DOMAIN, entity_unique_id + ) + if entity_id is not None: + references_entry_data.add(entity_id) + + orphaned_ids = references_config_entry - references_entry_data + for orphaned_id in orphaned_ids: + entity_registry.async_remove(orphaned_id) + + +def purge_device_registry( + hass: HomeAssistant, entry_id: str, imported_entry_data: ConfigType +) -> None: + """Remove orphans from device registry which are not in entry data.""" + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) + + # Find all devices that are referenced in the entity registry. + references_entities = { + entry.device_id for entry in entity_registry.entities.values() + } + + # Find device that references the host. + references_host = set() + host_device = device_registry.async_get_device({(DOMAIN, entry_id)}) + if host_device is not None: + references_host.add(host_device.id) + + # Find all devices that are referenced by the entry_data. + references_entry_data = set() + for device_data in imported_entry_data[CONF_DEVICES]: + device_unique_id = generate_unique_id(entry_id, device_data[CONF_ADDRESS]) + device = device_registry.async_get_device({(DOMAIN, device_unique_id)}) + if device is not None: + references_entry_data.add(device.id) + + orphaned_ids = ( + { + entry.id + for entry in dr.async_entries_for_config_entry(device_registry, entry_id) + } + - references_entities + - references_host + - references_entry_data + ) + + for device_id in orphaned_ids: + device_registry.async_remove_device(device_id) + + def register_lcn_host_device(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Register LCN host for given config_entry in device registry.""" device_registry = dr.async_get(hass) diff --git a/homeassistant/components/life360/translations/es.json b/homeassistant/components/life360/translations/es.json index 019d2bc88d8..02a4a349ee9 100644 --- a/homeassistant/components/life360/translations/es.json +++ b/homeassistant/components/life360/translations/es.json @@ -8,7 +8,7 @@ "default": "Para configurar las opciones avanzadas, consulta la [documentaci\u00f3n de Life360]({docs_url})." }, "error": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", "invalid_username": "Nombre de usuario no v\u00e1lido", "unknown": "Error inesperado" diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index c4df24a0cb0..31e973874d9 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -29,14 +29,11 @@ from homeassistant.components.light import ( COLOR_GROUP, DOMAIN, LIGHT_TURN_ON_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, VALID_BRIGHTNESS, VALID_BRIGHTNESS_PCT, + ColorMode, LightEntity, + LightEntityFeature, preprocess_turn_on_alternatives, ) from homeassistant.config_entries import ConfigEntry @@ -508,6 +505,8 @@ def convert_16_to_8(value): class LIFXLight(LightEntity): """Representation of a LIFX light.""" + _attr_supported_features = LightEntityFeature.TRANSITION | LightEntityFeature.EFFECT + def __init__(self, bulb, effects_conductor): """Initialize the light.""" self.bulb = bulb @@ -579,15 +578,17 @@ class LIFXLight(LightEntity): return math.ceil(color_util.color_temperature_kelvin_to_mired(kelvin)) @property - def supported_features(self): - """Flag supported features.""" - support = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_EFFECT - + def color_mode(self) -> ColorMode: + """Return the color mode of the light.""" bulb_features = lifx_features(self.bulb) if bulb_features["min_kelvin"] != bulb_features["max_kelvin"]: - support |= SUPPORT_COLOR_TEMP + return ColorMode.COLOR_TEMP + return ColorMode.BRIGHTNESS - return support + @property + def supported_color_modes(self) -> set[ColorMode]: + """Flag supported color modes.""" + return {self.color_mode} @property def brightness(self): @@ -737,11 +738,17 @@ class LIFXColor(LIFXLight): """Representation of a color LIFX light.""" @property - def supported_features(self): - """Flag supported features.""" - support = super().supported_features - support |= SUPPORT_COLOR - return support + def color_mode(self) -> ColorMode: + """Return the color mode of the light.""" + sat = self.bulb.color[1] + if sat: + return ColorMode.HS + return ColorMode.COLOR_TEMP + + @property + def supported_color_modes(self) -> set[ColorMode]: + """Flag supported color modes.""" + return {ColorMode.COLOR_TEMP, ColorMode.HS} @property def effect_list(self): diff --git a/homeassistant/components/lifx/translations/es.json b/homeassistant/components/lifx/translations/es.json index c5b157a1499..484d59ba55f 100644 --- a/homeassistant/components/lifx/translations/es.json +++ b/homeassistant/components/lifx/translations/es.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No se encontraron dispositivos LIFX en la red.", - "single_instance_allowed": "S\u00f3lo es posible una \u00fanica configuraci\u00f3n de LIFX." + "no_devices_found": "No se encontraron dispositivos en la red", + "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/nl.json b/homeassistant/components/lifx/translations/nl.json index 0e0a6190f0d..c8d0ca83dd8 100644 --- a/homeassistant/components/lifx/translations/nl.json +++ b/homeassistant/components/lifx/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 4f3240d7b31..099f917bc46 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -116,6 +116,23 @@ COLOR_MODES_COLOR = { } +def filter_supported_color_modes(color_modes: Iterable[ColorMode]) -> set[ColorMode]: + """Filter the given color modes.""" + color_modes = set(color_modes) + if ( + not color_modes + or ColorMode.UNKNOWN in color_modes + or (ColorMode.WHITE in color_modes and not color_supported(color_modes)) + ): + raise HomeAssistantError + + if ColorMode.ONOFF in color_modes and len(color_modes) > 1: + color_modes.remove(ColorMode.ONOFF) + if ColorMode.BRIGHTNESS in color_modes and len(color_modes) > 1: + color_modes.remove(ColorMode.BRIGHTNESS) + return color_modes + + def valid_supported_color_modes( color_modes: Iterable[ColorMode | str], ) -> set[ColorMode | str]: diff --git a/homeassistant/components/light/device_action.py b/homeassistant/components/light/device_action.py index 285e34ebe08..2583bfa972f 100644 --- a/homeassistant/components/light/device_action.py +++ b/homeassistant/components/light/device_action.py @@ -55,7 +55,7 @@ async def async_call_action_from_config( hass: HomeAssistant, config: ConfigType, variables: TemplateVarsType, - context: Context, + context: Context | None, ) -> None: """Change state based on configuration.""" if ( diff --git a/homeassistant/components/light/device_trigger.py b/homeassistant/components/light/device_trigger.py index 5b4083f41a6..6f5f5fd7f52 100644 --- a/homeassistant/components/light/device_trigger.py +++ b/homeassistant/components/light/device_trigger.py @@ -1,8 +1,6 @@ """Provides device trigger for lights.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -36,7 +34,7 @@ async def async_attach_trigger( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/light/intent.py b/homeassistant/components/light/intent.py index 261cbbd973d..2ffd73a9792 100644 --- a/homeassistant/components/light/intent.py +++ b/homeassistant/components/light/intent.py @@ -21,7 +21,7 @@ INTENT_SET = "HassLightSet" async def async_setup_intents(hass: HomeAssistant) -> None: """Set up the light intents.""" - hass.helpers.intent.async_register(SetIntentHandler()) + intent.async_register(hass, SetIntentHandler()) def _test_supports_color(state: State) -> None: @@ -56,8 +56,8 @@ class SetIntentHandler(intent.IntentHandler): """Handle the hass intent.""" hass = intent_obj.hass slots = self.async_validate_slots(intent_obj.slots) - state = hass.helpers.intent.async_match_state( - slots["name"]["value"], hass.states.async_all(DOMAIN) + state = intent.async_match_state( + hass, slots["name"]["value"], hass.states.async_all(DOMAIN) ) service_data = {ATTR_ENTITY_ID: state.entity_id} diff --git a/homeassistant/components/light/translations/ca.json b/homeassistant/components/light/translations/ca.json index 6ceae0d5100..2c59305e4d9 100644 --- a/homeassistant/components/light/translations/ca.json +++ b/homeassistant/components/light/translations/ca.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} s'enc\u00e9n o s'apaga", - "toggled": "{entity_name} s'enc\u00e9n o s'apaga", "turned_off": "{entity_name} apagat", "turned_on": "{entity_name} enc\u00e8s" } diff --git a/homeassistant/components/light/translations/cs.json b/homeassistant/components/light/translations/cs.json index 0a4294a44f3..5f43c684b09 100644 --- a/homeassistant/components/light/translations/cs.json +++ b/homeassistant/components/light/translations/cs.json @@ -13,7 +13,6 @@ "is_on": "{entity_name} je zapnuto" }, "trigger_type": { - "toggled": "{entity_name} zapnuto nebo vypnuto", "turned_off": "{entity_name} vypnuto", "turned_on": "{entity_name} zapnuto" } diff --git a/homeassistant/components/light/translations/de.json b/homeassistant/components/light/translations/de.json index a214f1cb9cf..f1ac946b64a 100644 --- a/homeassistant/components/light/translations/de.json +++ b/homeassistant/components/light/translations/de.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} ein- oder ausgeschaltet", - "toggled": "{entity_name} ein- oder ausgeschaltet", "turned_off": "{entity_name} ausgeschaltet", "turned_on": "{entity_name} eingeschaltet" } diff --git a/homeassistant/components/light/translations/el.json b/homeassistant/components/light/translations/el.json index dbba423dd2a..16198683deb 100644 --- a/homeassistant/components/light/translations/el.json +++ b/homeassistant/components/light/translations/el.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "\u03a4\u03bf {entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", - "toggled": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_off": "{entity_name} \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5" } diff --git a/homeassistant/components/light/translations/en.json b/homeassistant/components/light/translations/en.json index bdaa8a87b84..526f902b158 100644 --- a/homeassistant/components/light/translations/en.json +++ b/homeassistant/components/light/translations/en.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} turned on or off", - "toggled": "{entity_name} turned on or off", "turned_off": "{entity_name} turned off", "turned_on": "{entity_name} turned on" } diff --git a/homeassistant/components/light/translations/es.json b/homeassistant/components/light/translations/es.json index 10e8dfa3d17..53cf50215aa 100644 --- a/homeassistant/components/light/translations/es.json +++ b/homeassistant/components/light/translations/es.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} activado o desactivado", - "toggled": "{entity_name} activado o desactivado", "turned_off": "{entity_name} apagada", "turned_on": "{entity_name} encendida" } diff --git a/homeassistant/components/light/translations/et.json b/homeassistant/components/light/translations/et.json index 05d1a7690fc..224ec559bb6 100644 --- a/homeassistant/components/light/translations/et.json +++ b/homeassistant/components/light/translations/et.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} l\u00fclitus sisse v\u00f5i v\u00e4lja", - "toggled": "{entity_name} l\u00fclitus sisse v\u00f5i v\u00e4lja", "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", "turned_on": "{entity_name} l\u00fclitus sisse" } diff --git a/homeassistant/components/light/translations/fr.json b/homeassistant/components/light/translations/fr.json index bcb6cac594e..c74bd8507a0 100644 --- a/homeassistant/components/light/translations/fr.json +++ b/homeassistant/components/light/translations/fr.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} a \u00e9t\u00e9 allum\u00e9e ou \u00e9teinte", - "toggled": "{entity_name} a \u00e9t\u00e9 allum\u00e9e ou \u00e9teinte", "turned_off": "{entity_name} a \u00e9t\u00e9 \u00e9teinte", "turned_on": "{entity_name} a \u00e9t\u00e9 allum\u00e9e" } diff --git a/homeassistant/components/light/translations/he.json b/homeassistant/components/light/translations/he.json index b802e64ad40..2ebb31d089e 100644 --- a/homeassistant/components/light/translations/he.json +++ b/homeassistant/components/light/translations/he.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05db\u05d5\u05d1\u05d4", - "toggled": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05db\u05d5\u05d1\u05d4", "turned_off": "{entity_name} \u05db\u05d5\u05d1\u05d4", "turned_on": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc" } diff --git a/homeassistant/components/light/translations/hu.json b/homeassistant/components/light/translations/hu.json index 986fd5d1787..73bf81858b0 100644 --- a/homeassistant/components/light/translations/hu.json +++ b/homeassistant/components/light/translations/hu.json @@ -14,7 +14,6 @@ }, "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/light/translations/id.json b/homeassistant/components/light/translations/id.json index 334e938d41e..600644a36c4 100644 --- a/homeassistant/components/light/translations/id.json +++ b/homeassistant/components/light/translations/id.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} diaktifkan atau dinonaktifkan", - "toggled": "{entity_name} diaktifkan atau dinonaktifkan", "turned_off": "{entity_name} dimatikan", "turned_on": "{entity_name} dinyalakan" } diff --git a/homeassistant/components/light/translations/it.json b/homeassistant/components/light/translations/it.json index 9e751a9a4b8..7a71a2dbd91 100644 --- a/homeassistant/components/light/translations/it.json +++ b/homeassistant/components/light/translations/it.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} attivata o disattivata", - "toggled": "{entity_name} attiva o disattiva", "turned_off": "{entity_name} disattivato", "turned_on": "{entity_name} attivato" } diff --git a/homeassistant/components/light/translations/ja.json b/homeassistant/components/light/translations/ja.json index 7e9599af54a..cd1008cc5a0 100644 --- a/homeassistant/components/light/translations/ja.json +++ b/homeassistant/components/light/translations/ja.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u304c\u30aa\u30f3\u307e\u305f\u306f\u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", - "toggled": "{entity_name} \u304c\u30aa\u30f3\u307e\u305f\u306f\u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "turned_off": "{entity_name} \u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "turned_on": "{entity_name} \u30aa\u30f3\u306b\u306a\u3063\u3066\u3044\u307e\u3059" } diff --git a/homeassistant/components/light/translations/nl.json b/homeassistant/components/light/translations/nl.json index f70830601c7..3708a1e9a8d 100644 --- a/homeassistant/components/light/translations/nl.json +++ b/homeassistant/components/light/translations/nl.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} in- of uitgeschakeld", - "toggled": "{entity_name} in- of uitgeschakeld", "turned_off": "{entity_name} is uitgeschakeld", "turned_on": "{entity_name} is ingeschakeld" } diff --git a/homeassistant/components/light/translations/no.json b/homeassistant/components/light/translations/no.json index 92d7102d217..8ed11defddc 100644 --- a/homeassistant/components/light/translations/no.json +++ b/homeassistant/components/light/translations/no.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} sl\u00e5tt p\u00e5 eller av", - "toggled": "{entity_name} sl\u00e5tt p\u00e5 eller av", "turned_off": "{entity_name} sl\u00e5tt av", "turned_on": "{entity_name} sl\u00e5tt p\u00e5" } diff --git a/homeassistant/components/light/translations/pl.json b/homeassistant/components/light/translations/pl.json index 375cf8a8ce3..1a3b9c8b124 100644 --- a/homeassistant/components/light/translations/pl.json +++ b/homeassistant/components/light/translations/pl.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", - "toggled": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "turned_off": "nast\u0105pi wy\u0142\u0105czenie {entity_name}", "turned_on": "nast\u0105pi w\u0142\u0105czenie {entity_name}" } diff --git a/homeassistant/components/light/translations/pt-BR.json b/homeassistant/components/light/translations/pt-BR.json index f884baf9b3c..893def8c67b 100644 --- a/homeassistant/components/light/translations/pt-BR.json +++ b/homeassistant/components/light/translations/pt-BR.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} for ligada ou desligada", - "toggled": "{entity_name} for ligada ou desligada", "turned_off": "{entity_name} for desligada", "turned_on": "{entity_name} for ligada" } diff --git a/homeassistant/components/light/translations/ru.json b/homeassistant/components/light/translations/ru.json index 4010139e381..efe8ba188ec 100644 --- a/homeassistant/components/light/translations/ru.json +++ b/homeassistant/components/light/translations/ru.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", - "toggled": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "turned_off": "{entity_name} \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "turned_on": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f" } diff --git a/homeassistant/components/light/translations/sv.json b/homeassistant/components/light/translations/sv.json index 99f0b123b6a..d5f0bdaf767 100644 --- a/homeassistant/components/light/translations/sv.json +++ b/homeassistant/components/light/translations/sv.json @@ -12,7 +12,6 @@ "is_on": "{entity_name} \u00e4r p\u00e5" }, "trigger_type": { - "toggled": "{entity_name} slogs p\u00e5 eller av", "turned_off": "{entity_name} avst\u00e4ngd", "turned_on": "{entity_name} slogs p\u00e5" } diff --git a/homeassistant/components/light/translations/tr.json b/homeassistant/components/light/translations/tr.json index 4ddde6dabb1..a5efe591214 100644 --- a/homeassistant/components/light/translations/tr.json +++ b/homeassistant/components/light/translations/tr.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name} a\u00e7\u0131ld\u0131 veya kapat\u0131ld\u0131", - "toggled": "{entity_name} a\u00e7\u0131ld\u0131 veya kapat\u0131ld\u0131", "turned_off": "{entity_name} kapat\u0131ld\u0131", "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" } diff --git a/homeassistant/components/light/translations/zh-Hans.json b/homeassistant/components/light/translations/zh-Hans.json index 211fa1ed50b..1054820c6bb 100644 --- a/homeassistant/components/light/translations/zh-Hans.json +++ b/homeassistant/components/light/translations/zh-Hans.json @@ -13,7 +13,6 @@ "is_on": "{entity_name} \u5df2\u6253\u5f00" }, "trigger_type": { - "toggled": "{entity_name} \u88ab\u6253\u5f00\u6216\u5173\u95ed", "turned_off": "{entity_name} \u88ab\u5173\u95ed", "turned_on": "{entity_name} \u88ab\u6253\u5f00" } diff --git a/homeassistant/components/light/translations/zh-Hant.json b/homeassistant/components/light/translations/zh-Hant.json index e008376298e..054c325be82 100644 --- a/homeassistant/components/light/translations/zh-Hant.json +++ b/homeassistant/components/light/translations/zh-Hant.json @@ -14,7 +14,6 @@ }, "trigger_type": { "changed_states": "{entity_name}\u5df2\u958b\u555f\u6216\u95dc\u9589", - "toggled": "{entity_name}\u5df2\u958b\u555f\u6216\u95dc\u9589", "turned_off": "{entity_name}\u5df2\u95dc\u9589", "turned_on": "{entity_name}\u5df2\u958b\u555f" } diff --git a/homeassistant/components/limitlessled/light.py b/homeassistant/components/limitlessled/light.py index e45a4a2ced0..41668470dfb 100644 --- a/homeassistant/components/limitlessled/light.py +++ b/homeassistant/components/limitlessled/light.py @@ -24,13 +24,9 @@ from homeassistant.components.light import ( EFFECT_WHITE, FLASH_LONG, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE, STATE_ON from homeassistant.core import HomeAssistant @@ -62,24 +58,17 @@ MIN_SATURATION = 10 WHITE = [0, 0] -SUPPORT_LIMITLESSLED_WHITE = ( - SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT | SUPPORT_TRANSITION -) -SUPPORT_LIMITLESSLED_DIMMER = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION +COLOR_MODES_LIMITLESS_WHITE = {ColorMode.COLOR_TEMP} +SUPPORT_LIMITLESSLED_WHITE = LightEntityFeature.EFFECT | LightEntityFeature.TRANSITION +COLOR_MODES_LIMITLESS_DIMMER = {ColorMode.BRIGHTNESS} +SUPPORT_LIMITLESSLED_DIMMER = LightEntityFeature.TRANSITION +COLOR_MODES_LIMITLESS_RGB = {ColorMode.HS} SUPPORT_LIMITLESSLED_RGB = ( - SUPPORT_BRIGHTNESS - | SUPPORT_EFFECT - | SUPPORT_FLASH - | SUPPORT_COLOR - | SUPPORT_TRANSITION + LightEntityFeature.EFFECT | LightEntityFeature.FLASH | LightEntityFeature.TRANSITION ) +COLOR_MODES_LIMITLESS_RGBWW = {ColorMode.COLOR_TEMP, ColorMode.HS} SUPPORT_LIMITLESSLED_RGBWW = ( - SUPPORT_BRIGHTNESS - | SUPPORT_COLOR_TEMP - | SUPPORT_EFFECT - | SUPPORT_FLASH - | SUPPORT_COLOR - | SUPPORT_TRANSITION + LightEntityFeature.EFFECT | LightEntityFeature.FLASH | LightEntityFeature.TRANSITION ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -218,18 +207,31 @@ class LimitlessLEDGroup(LightEntity, RestoreEntity): """Initialize a group.""" if isinstance(group, WhiteGroup): - self._supported = SUPPORT_LIMITLESSLED_WHITE + self._attr_supported_color_modes = COLOR_MODES_LIMITLESS_WHITE + self._attr_supported_features = SUPPORT_LIMITLESSLED_WHITE self._effect_list = [EFFECT_NIGHT] elif isinstance(group, DimmerGroup): - self._supported = SUPPORT_LIMITLESSLED_DIMMER + self._attr_supported_color_modes = COLOR_MODES_LIMITLESS_DIMMER + self._attr_supported_features = SUPPORT_LIMITLESSLED_DIMMER self._effect_list = [] elif isinstance(group, RgbwGroup): - self._supported = SUPPORT_LIMITLESSLED_RGB + self._attr_supported_color_modes = COLOR_MODES_LIMITLESS_RGB + self._attr_supported_features = SUPPORT_LIMITLESSLED_RGB self._effect_list = [EFFECT_COLORLOOP, EFFECT_NIGHT, EFFECT_WHITE] elif isinstance(group, RgbwwGroup): - self._supported = SUPPORT_LIMITLESSLED_RGBWW + self._attr_supported_color_modes = COLOR_MODES_LIMITLESS_RGBWW + self._attr_supported_features = SUPPORT_LIMITLESSLED_RGBWW self._effect_list = [EFFECT_COLORLOOP, EFFECT_NIGHT, EFFECT_WHITE] + self._fixed_color_mode = None + if len(self._attr_supported_color_modes) == 1: + self._fixed_color_mode = next(iter(self._attr_supported_color_modes)) + else: + assert self._attr_supported_color_modes == { + ColorMode.COLOR_TEMP, + ColorMode.HS, + } + self.group = group self.config = config self._is_on = False @@ -285,29 +287,27 @@ class LimitlessLEDGroup(LightEntity, RestoreEntity): """Return the warmest color_temp that this light supports.""" return 370 + @property + def color_mode(self) -> str | None: + """Return the color mode of the light.""" + if self._fixed_color_mode: + return self._fixed_color_mode + + # The light supports both hs and white with adjustable color temperature + if self._effect == EFFECT_NIGHT or self._color is None or self._color[1] == 0: + return ColorMode.COLOR_TEMP + return ColorMode.HS + @property def color_temp(self): """Return the temperature property.""" - if self.hs_color is not None: - return None return self._temperature @property def hs_color(self): """Return the color property.""" - if self._effect == EFFECT_NIGHT: - return None - - if self._color is None or self._color[1] == 0: - return None - return self._color - @property - def supported_features(self): - """Flag supported features.""" - return self._supported - @property def effect(self): """Return the current effect for this light.""" @@ -348,7 +348,7 @@ class LimitlessLEDGroup(LightEntity, RestoreEntity): self._brightness = kwargs[ATTR_BRIGHTNESS] args["brightness"] = self.limitlessled_brightness() - if ATTR_HS_COLOR in kwargs and self._supported & SUPPORT_COLOR: + if ATTR_HS_COLOR in kwargs: self._color = kwargs[ATTR_HS_COLOR] # White is a special case. if self._color[1] < MIN_SATURATION: @@ -358,18 +358,17 @@ class LimitlessLEDGroup(LightEntity, RestoreEntity): args["color"] = self.limitlessled_color() if ATTR_COLOR_TEMP in kwargs: - if self._supported & SUPPORT_COLOR: + if ColorMode.HS in self.supported_color_modes: pipeline.white() self._color = WHITE - if self._supported & SUPPORT_COLOR_TEMP: - self._temperature = kwargs[ATTR_COLOR_TEMP] - args["temperature"] = self.limitlessled_temperature() + self._temperature = kwargs[ATTR_COLOR_TEMP] + args["temperature"] = self.limitlessled_temperature() if args: pipeline.transition(transition_time, **args) # Flash. - if ATTR_FLASH in kwargs and self._supported & SUPPORT_FLASH: + if ATTR_FLASH in kwargs and self.supported_features & LightEntityFeature.FLASH: duration = 0 if kwargs[ATTR_FLASH] == FLASH_LONG: duration = 1 diff --git a/homeassistant/components/litejet/translations/nl.json b/homeassistant/components/litejet/translations/nl.json index e2743b4165f..6831d710bad 100644 --- a/homeassistant/components/litejet/translations/nl.json +++ b/homeassistant/components/litejet/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "open_failed": "Kan de opgegeven seri\u00eble poort niet openen." diff --git a/homeassistant/components/litejet/trigger.py b/homeassistant/components/litejet/trigger.py index 21b7927ebe2..5aff5dbc66c 100644 --- a/homeassistant/components/litejet/trigger.py +++ b/homeassistant/components/litejet/trigger.py @@ -1,12 +1,19 @@ """Trigger an automation when a LiteJet switch is released.""" +from __future__ import annotations + from collections.abc import Callable import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import CONF_PLATFORM -from homeassistant.core import HassJob, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_utc_time +from homeassistant.helpers.typing import ConfigType import homeassistant.util.dt as dt_util from .const import DOMAIN @@ -29,14 +36,19 @@ TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( ) -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for events based on configuration.""" trigger_data = automation_info["trigger_data"] number = config.get(CONF_NUMBER) held_more_than = config.get(CONF_HELD_MORE_THAN) held_less_than = config.get(CONF_HELD_LESS_THAN) pressed_time = None - cancel_pressed_more_than: Callable = None + cancel_pressed_more_than: Callable | None = None job = HassJob(action) @callback diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index de3126986d1..01deaa302cf 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -1,14 +1,23 @@ """Support for Litter-Robot sensors.""" from __future__ import annotations +from collections.abc import Callable +from dataclasses import dataclass from datetime import datetime +from typing import Any from pylitterbot.robot import Robot -from homeassistant.components.sensor import SensorDeviceClass, SensorEntity, StateType +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + StateType, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN @@ -27,56 +36,78 @@ def icon_for_gauge_level(gauge_level: int | None = None, offset: int = 0) -> str return "mdi:gauge-low" -class LitterRobotPropertySensor(LitterRobotEntity, SensorEntity): - """Litter-Robot property sensor.""" +@dataclass +class LitterRobotSensorEntityDescription(SensorEntityDescription): + """A class that describes Litter-Robot sensor entities.""" + + icon_fn: Callable[[Any], str | None] = lambda _: None + should_report: Callable[[Robot], bool] = lambda _: True + + +class LitterRobotSensorEntity(LitterRobotEntity, SensorEntity): + """Litter-Robot sensor entity.""" + + entity_description: LitterRobotSensorEntityDescription def __init__( - self, robot: Robot, entity_type: str, hub: LitterRobotHub, sensor_attribute: str + self, + robot: Robot, + hub: LitterRobotHub, + description: LitterRobotSensorEntityDescription, ) -> None: - """Pass robot, entity_type and hub to LitterRobotEntity.""" - super().__init__(robot, entity_type, hub) - self.sensor_attribute = sensor_attribute + """Initialize a Litter-Robot sensor entity.""" + assert description.name + super().__init__(robot, description.name, hub) + self.entity_description = description @property def native_value(self) -> StateType | datetime: """Return the state.""" - return getattr(self.robot, self.sensor_attribute) - - -class LitterRobotWasteSensor(LitterRobotPropertySensor): - """Litter-Robot waste sensor.""" - - @property - def native_unit_of_measurement(self) -> str: - """Return unit of measurement.""" - return PERCENTAGE - - @property - def icon(self) -> str: - """Return the icon to use in the frontend, if any.""" - return icon_for_gauge_level(self.state, 10) - - -class LitterRobotSleepTimeSensor(LitterRobotPropertySensor): - """Litter-Robot sleep time sensor.""" - - @property - def native_value(self) -> StateType | datetime: - """Return the state.""" - if self.robot.sleep_mode_enabled: - return super().native_value + if self.entity_description.should_report(self.robot): + if isinstance(val := getattr(self.robot, self.entity_description.key), str): + return val.lower() + return val return None @property - def device_class(self) -> str: - """Return the device class, if any.""" - return SensorDeviceClass.TIMESTAMP + def icon(self) -> str | None: + """Return the icon to use in the frontend, if any.""" + if (icon := self.entity_description.icon_fn(self.state)) is not None: + return icon + return super().icon -ROBOT_SENSORS: list[tuple[type[LitterRobotPropertySensor], str, str]] = [ - (LitterRobotWasteSensor, "Waste Drawer", "waste_drawer_level"), - (LitterRobotSleepTimeSensor, "Sleep Mode Start Time", "sleep_mode_start_time"), - (LitterRobotSleepTimeSensor, "Sleep Mode End Time", "sleep_mode_end_time"), +ROBOT_SENSORS = [ + LitterRobotSensorEntityDescription( + name="Waste Drawer", + key="waste_drawer_level", + native_unit_of_measurement=PERCENTAGE, + icon_fn=lambda state: icon_for_gauge_level(state, 10), + ), + LitterRobotSensorEntityDescription( + name="Sleep Mode Start Time", + key="sleep_mode_start_time", + device_class=SensorDeviceClass.TIMESTAMP, + should_report=lambda robot: robot.sleep_mode_enabled, + ), + LitterRobotSensorEntityDescription( + name="Sleep Mode End Time", + key="sleep_mode_end_time", + device_class=SensorDeviceClass.TIMESTAMP, + should_report=lambda robot: robot.sleep_mode_enabled, + ), + LitterRobotSensorEntityDescription( + name="Last Seen", + key="last_seen", + device_class=SensorDeviceClass.TIMESTAMP, + entity_category=EntityCategory.DIAGNOSTIC, + ), + LitterRobotSensorEntityDescription( + name="Status Code", + key="status_code", + device_class="litterrobot__status_code", + entity_category=EntityCategory.DIAGNOSTIC, + ), ] @@ -87,17 +118,8 @@ async def async_setup_entry( ) -> None: """Set up Litter-Robot sensors using config entry.""" hub: LitterRobotHub = hass.data[DOMAIN][entry.entry_id] - - entities = [] - for robot in hub.account.robots: - for (sensor_class, entity_type, sensor_attribute) in ROBOT_SENSORS: - entities.append( - sensor_class( - robot=robot, - entity_type=entity_type, - hub=hub, - sensor_attribute=sensor_attribute, - ) - ) - - async_add_entities(entities) + async_add_entities( + LitterRobotSensorEntity(robot=robot, hub=hub, description=description) + for description in ROBOT_SENSORS + for robot in hub.account.robots + ) diff --git a/homeassistant/components/litterrobot/strings.sensor.json b/homeassistant/components/litterrobot/strings.sensor.json new file mode 100644 index 00000000000..d9ad141cf21 --- /dev/null +++ b/homeassistant/components/litterrobot/strings.sensor.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Bonnet Removed", + "ccc": "Clean Cycle Complete", + "ccp": "Clean Cycle In Progress", + "csf": "Cat Sensor Fault", + "csi": "Cat Sensor Interrupted", + "cst": "Cat Sensor Timing", + "df1": "Drawer Almost Full - 2 Cycles Left", + "df2": "Drawer Almost Full - 1 Cycle Left", + "dfs": "Drawer Full", + "dhf": "Dump + Home Position Fault", + "dpf": "Dump Position Fault", + "ec": "Empty Cycle", + "hpf": "Home Position Fault", + "off": "[%key:common::state::off%]", + "offline": "Offline", + "otf": "Over Torque Fault", + "p": "[%key:common::state::paused%]", + "pd": "Pinch Detect", + "rdy": "Ready", + "scf": "Cat Sensor Fault At Startup", + "sdf": "Drawer Full At Startup", + "spf": "Pinch Detect At Startup" + } + } +} diff --git a/homeassistant/components/litterrobot/translations/sensor.bg.json b/homeassistant/components/litterrobot/translations/sensor.bg.json new file mode 100644 index 00000000000..3081fb9b285 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.bg.json @@ -0,0 +1,8 @@ +{ + "state": { + "litterrobot__status_code": { + "off": "\u0418\u0437\u043a\u043b.", + "offline": "\u041e\u0444\u043b\u0430\u0439\u043d" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.ca.json b/homeassistant/components/litterrobot/translations/sensor.ca.json new file mode 100644 index 00000000000..dd8731b78c5 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.ca.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Bossa extreta", + "ccc": "Cicle de neteja completat", + "ccp": "Cicle de neteja en curs", + "csf": "Error del sensor de gat", + "csi": "Sensor de gat interromput", + "cst": "Temps del sensor de gats", + "df1": "Dip\u00f2sit gaireb\u00e9 ple - Queden 2 cicles", + "df2": "Dip\u00f2sit gaireb\u00e9 ple - Queda 1 cicle", + "dfs": "Dip\u00f2sit ple", + "dhf": "Error de posici\u00f3 d'abocament + inici", + "dpf": "Error de posici\u00f3 d'abocament", + "ec": "Cicle de buidatge", + "hpf": "Error de posici\u00f3 d'inici", + "off": "OFF", + "offline": "Fora de l\u00ednia", + "otf": "Error per sobre-parell", + "p": "Pausat/ada", + "pd": "Detecci\u00f3 de pessigada", + "rdy": "A punt", + "scf": "Error del sensor de gat a l'arrencada", + "sdf": "Dip\u00f2sit ple a l'arrencada", + "spf": "Detecci\u00f3 de pessigada a l'arrencada" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.cs.json b/homeassistant/components/litterrobot/translations/sensor.cs.json new file mode 100644 index 00000000000..5b728084bed --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.cs.json @@ -0,0 +1,9 @@ +{ + "state": { + "litterrobot__status_code": { + "off": "Vypnuto", + "offline": "Offline", + "p": "Pozastaveno" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.de.json b/homeassistant/components/litterrobot/translations/sensor.de.json new file mode 100644 index 00000000000..2901b0e5c55 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.de.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Haube entfernt", + "ccc": "Reinigungszyklus abgeschlossen", + "ccp": "Reinigungszyklus l\u00e4uft", + "csf": "Katzensensor Fehler", + "csi": "Katzensensor unterbrochen", + "cst": "Katzensensor Timing", + "df1": "Schublade fast voll - 2 Zyklen \u00fcbrig", + "df2": "Schublade fast voll - 1 Zyklus \u00fcbrig", + "dfs": "Schublade voll", + "dhf": "Fehler in Leerungs- und Grundstellung", + "dpf": "Fehler in der Leerungsstellung", + "ec": "Leerungszyklus", + "hpf": "Fehler in der Grundstellung", + "off": "Aus", + "offline": "Offline", + "otf": "\u00dcberdrehungsfehler", + "p": "Pausiert", + "pd": "Einklemmen erkennen", + "rdy": "Bereit", + "scf": "Katzen-Sensorfehler beim Start", + "sdf": "Schublade voll beim Start", + "spf": "Einklemmerkennung beim Start" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.el.json b/homeassistant/components/litterrobot/translations/sensor.el.json new file mode 100644 index 00000000000..f34d6078495 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.el.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "\u03a4\u03bf \u03ba\u03b1\u03c0\u03cc \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5", + "ccc": "\u039f\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5 \u03bf \u03ba\u03cd\u03ba\u03bb\u03bf\u03c2 \u03ba\u03b1\u03b8\u03b1\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd", + "ccp": "\u039a\u03cd\u03ba\u03bb\u03bf\u03c2 \u03ba\u03b1\u03b8\u03b1\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "csf": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b3\u03ac\u03c4\u03b1\u03c2", + "csi": "\u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b3\u03ac\u03c4\u03b1\u03c2", + "cst": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b3\u03ac\u03c4\u03b1\u03c2", + "df1": "\u03a3\u03c5\u03c1\u03c4\u03ac\u03c1\u03b9 \u03c3\u03c7\u03b5\u03b4\u03cc\u03bd \u03b3\u03b5\u03bc\u03ac\u03c4\u03bf - \u0391\u03c0\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5\u03bd 2 \u03ba\u03cd\u03ba\u03bb\u03bf\u03b9", + "df2": "\u03a3\u03c5\u03c1\u03c4\u03ac\u03c1\u03b9 \u03c3\u03c7\u03b5\u03b4\u03cc\u03bd \u03b3\u03b5\u03bc\u03ac\u03c4\u03bf - \u0391\u03c0\u03bf\u03bc\u03ad\u03bd\u03b5\u03b9 1 \u03ba\u03cd\u03ba\u03bb\u03bf\u03c2", + "dfs": "\u03a3\u03c5\u03c1\u03c4\u03ac\u03c1\u03b9 \u03b3\u03b5\u03bc\u03ac\u03c4\u03bf", + "dhf": "\u0392\u03bb\u03ac\u03b2\u03b7 \u03b1\u03c1\u03c7\u03b9\u03ba\u03ae\u03c2 \u03b8\u03ad\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03b8\u03ad\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2", + "dpf": "\u0392\u03bb\u03ac\u03b2\u03b7 \u03b8\u03ad\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2", + "ec": "\u0386\u03b4\u03b5\u03b9\u03bf\u03c2 \u03ba\u03cd\u03ba\u03bb\u03bf\u03c2", + "hpf": "\u0392\u03bb\u03ac\u03b2\u03b7 \u03c4\u03b7\u03c2 \u03b1\u03c1\u03c7\u03b9\u03ba\u03ae\u03c2 \u03b8\u03ad\u03c3\u03b7\u03c2", + "off": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc", + "offline": "\u0395\u03ba\u03c4\u03cc\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "otf": "\u0392\u03bb\u03ac\u03b2\u03b7 \u03c5\u03c0\u03b5\u03c1\u03b2\u03bf\u03bb\u03b9\u03ba\u03ae\u03c2 \u03c1\u03bf\u03c0\u03ae\u03c2", + "p": "\u03a3\u03b5 \u03c0\u03b1\u03cd\u03c3\u03b7", + "pd": "\u0391\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7 \u03c4\u03c3\u03b9\u03bc\u03c0\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2", + "rdy": "\u0388\u03c4\u03bf\u03b9\u03bc\u03bf", + "scf": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b3\u03ac\u03c4\u03b1\u03c2 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7", + "sdf": "\u0393\u03b5\u03bc\u03ac\u03c4\u03bf \u03c3\u03c5\u03c1\u03c4\u03ac\u03c1\u03b9 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7", + "spf": "\u0391\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7 \u03c4\u03c3\u03b9\u03bc\u03c0\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.en.json b/homeassistant/components/litterrobot/translations/sensor.en.json new file mode 100644 index 00000000000..5491a6a835f --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.en.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Bonnet Removed", + "ccc": "Clean Cycle Complete", + "ccp": "Clean Cycle In Progress", + "csf": "Cat Sensor Fault", + "csi": "Cat Sensor Interrupted", + "cst": "Cat Sensor Timing", + "df1": "Drawer Almost Full - 2 Cycles Left", + "df2": "Drawer Almost Full - 1 Cycle Left", + "dfs": "Drawer Full", + "dhf": "Dump + Home Position Fault", + "dpf": "Dump Position Fault", + "ec": "Empty Cycle", + "hpf": "Home Position Fault", + "off": "Off", + "offline": "Offline", + "otf": "Over Torque Fault", + "p": "Paused", + "pd": "Pinch Detect", + "rdy": "Ready", + "scf": "Cat Sensor Fault At Startup", + "sdf": "Drawer Full At Startup", + "spf": "Pinch Detect At Startup" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.es.json b/homeassistant/components/litterrobot/translations/sensor.es.json new file mode 100644 index 00000000000..1bf023d68bc --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.es.json @@ -0,0 +1,14 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Bolsa extra\u00edda", + "ccc": "Ciclo de limpieza completado", + "ccp": "Ciclo de limpieza en curso", + "dhf": "Error de posici\u00f3n de vertido + inicio", + "ec": "Ciclo vac\u00edo", + "off": "Apagado", + "offline": "Desconectado", + "rdy": "Listo" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.et.json b/homeassistant/components/litterrobot/translations/sensor.et.json new file mode 100644 index 00000000000..98b04dfd207 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.et.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Kaast eemaldatud", + "ccc": "Puhastusts\u00fckkel on l\u00f5ppenud", + "ccp": "Puhastusts\u00fckkel on pooleli", + "csf": "Kassianduri viga", + "csi": "Kassianduri h\u00e4iring", + "cst": "Kassianduri ajastus", + "df1": "Sahtli peaaegu t\u00e4is - 2 ts\u00fcklit j\u00e4\u00e4nud", + "df2": "Sahtli peaaegu t\u00e4is - 1 ts\u00fckkel j\u00e4\u00e4nud", + "dfs": "Sahtli t\u00e4is", + "dhf": "Pr\u00fcgikasti + koduasendi t\u00f5rge", + "dpf": "T\u00fchjenduspunkti viga", + "ec": "T\u00fchjendusts\u00fckkel", + "hpf": "Koduasendi viga", + "off": "V\u00e4ljas", + "offline": "\u00dchenduseta", + "otf": "\u00dclekoornuse viga", + "p": "Ootel", + "pd": "Pinch Detect", + "rdy": "Valmis", + "scf": "Kassianduri viga k\u00e4ivitamisel", + "sdf": "Sahtel t\u00e4is k\u00e4ivitamisel", + "spf": "Pinch Detect k\u00e4ivitamisel" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.fr.json b/homeassistant/components/litterrobot/translations/sensor.fr.json new file mode 100644 index 00000000000..fba0796e2b0 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.fr.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Capot retir\u00e9", + "ccc": "Cycle de nettoyage termin\u00e9", + "ccp": "Cycle de nettoyage en cours", + "csf": "D\u00e9faut du capteur de chat", + "csi": "Interruption du capteur de chat", + "cst": "Minutage du capteur de chat", + "df1": "Tiroir presque plein \u2013\u00a02\u00a0cycles restants", + "df2": "Tiroir presque plein \u2013\u00a01\u00a0cycle restant", + "dfs": "Tiroir plein", + "dhf": "D\u00e9faut de position de vidage +\u00a0origine", + "dpf": "D\u00e9faut de position de vidage", + "ec": "Cycle de vidage", + "hpf": "D\u00e9faut de position d'origine", + "off": "D\u00e9sactiv\u00e9", + "offline": "Hors ligne", + "otf": "D\u00e9faut de surcouple", + "p": "En pause", + "pd": "D\u00e9tection de pincement", + "rdy": "Pr\u00eat", + "scf": "D\u00e9faut du capteur de chat au d\u00e9marrage", + "sdf": "Tiroir plein au d\u00e9marrage", + "spf": "D\u00e9tection de pincement au d\u00e9marrage" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.he.json b/homeassistant/components/litterrobot/translations/sensor.he.json new file mode 100644 index 00000000000..0c693d10157 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.he.json @@ -0,0 +1,8 @@ +{ + "state": { + "litterrobot__status_code": { + "off": "\u05db\u05d1\u05d5\u05d9", + "p": "\u05de\u05d5\u05e9\u05d4\u05d4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.hu.json b/homeassistant/components/litterrobot/translations/sensor.hu.json new file mode 100644 index 00000000000..6c68a231bf4 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.hu.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Tet\u0151 elt\u00e1vol\u00edtva", + "ccc": "Tiszt\u00edt\u00e1s befejez\u0151d\u00f6tt", + "ccp": "Tiszt\u00edt\u00e1s folyamatban", + "csf": "Macska\u00e9rz\u00e9kel\u0151 hiba", + "csi": "Macska\u00e9rz\u00e9kel\u0151 megszak\u00edtva", + "cst": "Macska\u00e9rz\u00e9kel\u0151 id\u0151z\u00edt\u00e9se", + "df1": "A fi\u00f3k majdnem megtelt \u2013 2 ciklus van h\u00e1tra", + "df2": "A fi\u00f3k majdnem megtelt \u2013 1 ciklus maradt", + "dfs": "Fi\u00f3k tele", + "dhf": "Dump + Home poz\u00edci\u00f3 hiba", + "dpf": "Dump poz\u00edci\u00f3 hiba", + "ec": "\u00dcres ciklus", + "hpf": "Home poz\u00edci\u00f3 hiba", + "off": "Ki", + "offline": "Offline", + "otf": "T\u00falzott nyomat\u00e9k hiba", + "p": "Sz\u00fcnetel", + "pd": "Pinch Detect", + "rdy": "K\u00e9sz", + "scf": "Macska\u00e9rz\u00e9kel\u0151 hiba ind\u00edt\u00e1skor", + "sdf": "Ind\u00edt\u00e1skor megtelt a fi\u00f3k", + "spf": "Pinch Detect ind\u00edt\u00e1skor" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.id.json b/homeassistant/components/litterrobot/translations/sensor.id.json new file mode 100644 index 00000000000..20f76ca4322 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.id.json @@ -0,0 +1,24 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Bonnet Dihapus", + "ccc": "Siklus Bersih Selesai", + "ccp": "Siklus Bersih Sedang Berlangsung", + "csf": "Kesalahan Sensor Kucing", + "csi": "Sensor Kucing Terganggu", + "cst": "Waktu Sensor Kucing", + "df1": "Laci Hampir Penuh - Tersisa 2 Siklus", + "df2": "Laci Hampir Penuh - Tersisa 1 Siklus", + "dfs": "Laci Penuh", + "ec": "Siklus Kosong", + "hpf": "Kesalahan Posisi Rumah", + "off": "Mati", + "offline": "Luring", + "otf": "Kesalahan Torsi Berlebih", + "p": "Jeda", + "rdy": "Siap", + "scf": "Kesalahan Sensor Kucing Saat Mulai", + "sdf": "Laci Penuh Saat Memulai" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.it.json b/homeassistant/components/litterrobot/translations/sensor.it.json new file mode 100644 index 00000000000..926cb5d68d7 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.it.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Coperchio rimosso", + "ccc": "Ciclo di pulizia completato", + "ccp": "Ciclo di pulizia in corso", + "csf": "Errore del sensore Gatto", + "csi": "Sensore Gatto interrotto", + "cst": "Temporizzazione del sensore Gatto", + "df1": "Cassetto quasi pieno - 2 cicli rimasti", + "df2": "Cassetto quasi pieno - 1 ciclo rimasto", + "dfs": "Cassetto pieno", + "dhf": "Errore di scarico + posizione iniziale", + "dpf": "Errore di posizione di scarico", + "ec": "Ciclo vuoto", + "hpf": "Errore di posizione iniziale", + "off": "Spento", + "offline": "Non in linea", + "otf": "Errore di sovracoppia", + "p": "In pausa", + "pd": "Antipresa", + "rdy": "Pronto", + "scf": "Errore del sensore Gatto all'avvio", + "sdf": "Cassetto pieno all'avvio", + "spf": "Antipresa all'avvio" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.ja.json b/homeassistant/components/litterrobot/translations/sensor.ja.json new file mode 100644 index 00000000000..4191e47ff4a --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.ja.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "\u30dc\u30f3\u30cd\u30c3\u30c8\u304c\u53d6\u308a\u5916\u3055\u308c\u307e\u3057\u305f", + "ccc": "\u30af\u30ea\u30fc\u30f3\u30b5\u30a4\u30af\u30eb\u5b8c\u4e86", + "ccp": "\u30af\u30ea\u30fc\u30f3\u30b5\u30a4\u30af\u30eb\u304c\u9032\u884c\u4e2d", + "csf": "Cat\u30bb\u30f3\u30b5\u30fc\u4e0d\u826f", + "csi": "Cat\u30bb\u30f3\u30b5\u30fc\u304c\u4e2d\u65ad\u3055\u308c\u307e\u3057\u305f", + "cst": "Cat\u30bb\u30f3\u30b5\u30fc\u30bf\u30a4\u30df\u30f3\u30b0", + "df1": "\u30c9\u30ed\u30ef\u30fc\u307b\u307c\u6e80\u676f - \u6b8b\u308a2\u30b5\u30a4\u30af\u30eb", + "df2": "\u30c9\u30ed\u30ef\u30fc\u307b\u307c\u6e80\u676f - \u6b8b\u308a1\u30b5\u30a4\u30af\u30eb", + "dfs": "\u30c9\u30ed\u30ef\u30fc\u30d5\u30eb", + "dhf": "\u30c0\u30f3\u30d7+\u30db\u30fc\u30e0\u30dd\u30b8\u30b7\u30e7\u30f3\u4e0d\u826f", + "dpf": "\u30c0\u30f3\u30d7\u30dd\u30b8\u30b7\u30e7\u30f3\u4e0d\u826f", + "ec": "\u7a7a\u306e\u30b5\u30a4\u30af\u30eb", + "hpf": "\u30db\u30fc\u30e0\u30dd\u30b8\u30b7\u30e7\u30f3\u4e0d\u826f", + "off": "\u30aa\u30d5", + "offline": "\u30aa\u30d5\u30e9\u30a4\u30f3", + "otf": "\u30aa\u30fc\u30d0\u30fc\u30c8\u30eb\u30af\u4e0d\u826f", + "p": "\u4e00\u6642\u505c\u6b62", + "pd": "\u30d4\u30f3\u30c1\u691c\u51fa", + "rdy": "\u6e96\u5099", + "scf": "\u8d77\u52d5\u6642\u306bCat\u30bb\u30f3\u30b5\u30fc\u4e0d\u826f", + "sdf": "\u8d77\u52d5\u6642\u306b\u30c9\u30ed\u30ef\u30fc\u6e80\u676f", + "spf": "\u8d77\u52d5\u6642\u306b\u30d4\u30f3\u30c1\u691c\u51fa" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.ko.json b/homeassistant/components/litterrobot/translations/sensor.ko.json new file mode 100644 index 00000000000..49812dd242a --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.ko.json @@ -0,0 +1,27 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "\ubcf4\ub137 \uc81c\uac70\ub428", + "ccc": "\uccad\uc18c \uc644\ub8cc", + "ccp": "\uccad\uc18c \uc911", + "csf": "\ucea3\uc13c\uc11c \uc624\ub958", + "csi": "\ucea3\uc13c\uc11c \uc911\uc9c0", + "df1": "\ubcf4\uad00\ud568 \uac70\uc758 \ucc38 - 2\uc0ac\uc774\ud074 \ub0a8\uc74c", + "df2": "\ubcf4\uad00\ud568 \uac70\uc758 \ucc38 - 1\uc0ac\uc774\ud074 \ub0a8\uc74c", + "dfs": "\ubcf4\uad00\ud568 \uac00\ub4dd \ucc38", + "dhf": "\ub364\ud504 + \ud648 \uc704\uce58 \uc624\ub958", + "dpf": "\ub364\ud504 \uc704\uce58 \uc624\ub958", + "ec": "\ube44\uc6c0 \uc8fc\uae30", + "hpf": "\ud648 \uc704\uce58 \uc624\ub958", + "off": "\uaebc\uc9d0", + "offline": "\uc624\ud504\ub77c\uc778", + "otf": "\ud1a0\ud06c \uc624\ub958 \uc774\uc0c1", + "p": "\uc77c\uc2dc\uc911\uc9c0", + "pd": "\ud540\uce58 \uac10\uc9c0", + "rdy": "\uc900\ube44", + "scf": "\ucea3\uc13c\uc11c \uc624\ub958", + "sdf": "\ubcf4\uad00\ud568 \uac00\ub4dd \ucc38", + "spf": "\ud540\uce58 \uac10\uc9c0" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.nl.json b/homeassistant/components/litterrobot/translations/sensor.nl.json new file mode 100644 index 00000000000..6a383627aee --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.nl.json @@ -0,0 +1,25 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Motorkap verwijderd", + "ccc": "Reinigingscyclus voltooid", + "ccp": "Reinigingscyclus in uitvoering", + "csf": "Kattensensor fout", + "csi": "Kattensensor onderbroken", + "cst": "Timing kattensensor", + "df1": "Lade bijna vol - nog 2 cycli over", + "df2": "Lade bijna vol - nog 1 cyclus over", + "dfs": "Lade vol", + "dhf": "Dump + Home positie fout", + "dpf": "Dump Positie Fout", + "ec": "Lege cyclus", + "hpf": "Home Positie Fout", + "off": "Uit", + "offline": "Offline", + "p": "Gepauzeerd", + "rdy": "Gereed", + "scf": "Kattensensorfout bij opstarten", + "sdf": "Lade vol bij opstarten" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.no.json b/homeassistant/components/litterrobot/translations/sensor.no.json new file mode 100644 index 00000000000..2997930e53b --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.no.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Panser fjernet", + "ccc": "Rengj\u00f8ringssyklus fullf\u00f8rt", + "ccp": "Rengj\u00f8ringssyklus p\u00e5g\u00e5r", + "csf": "Feil p\u00e5 kattesensor", + "csi": "Kattesensor avbrutt", + "cst": "Tidsberegning for kattesensor", + "df1": "Skuff nesten full - 2 sykluser igjen", + "df2": "Skuff nesten full - 1 syklus til venstre", + "dfs": "Skuff full", + "dhf": "Dump + Hjemmeposisjonsfeil", + "dpf": "Dumpposisjonsfeil", + "ec": "Tom syklus", + "hpf": "Hjemmeposisjonsfeil", + "off": "Av", + "offline": "Frakoblet", + "otf": "Over dreiemomentfeil", + "p": "Pauset", + "pd": "Knip gjenkjenning", + "rdy": "Klar", + "scf": "Kattesensorfeil ved oppstart", + "sdf": "Skuff full ved oppstart", + "spf": "Knipegjenkjenning ved oppstart" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.pl.json b/homeassistant/components/litterrobot/translations/sensor.pl.json new file mode 100644 index 00000000000..b3063c7ca62 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.pl.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "pokrywa otwarta", + "ccc": "cykl czyszczenia zako\u0144czony", + "ccp": "cykl czyszczenia w toku", + "csf": "b\u0142\u0105d sensora obecno\u015bci", + "csi": "przerwa w pracy sensora", + "cst": "czas pracy sensora", + "df1": "szuflada prawie pe\u0142na - pozosta\u0142y 2 cykle", + "df2": "szuflada prawie pe\u0142na - pozosta\u0142 1 cykl", + "dfs": "szuflada pe\u0142na", + "dhf": "b\u0142\u0105d pozycji wyj\u015bciowej + odchod\u00f3w", + "dpf": "b\u0142\u0105d pozycji odchod\u00f3w", + "ec": "cykl opr\u00f3\u017cniania", + "hpf": "b\u0142\u0105d pozycji wyj\u015bciowej", + "off": "wy\u0142.", + "offline": "offline", + "otf": "b\u0142\u0105d nadmiernego momentu obrotowego", + "p": "wstrzymany", + "pd": "detektor obecno\u015bci", + "rdy": "gotowy", + "scf": "b\u0142\u0105d sensora obecno\u015bci podczas uruchamiania", + "sdf": "szuflada pe\u0142na podczas uruchamiania", + "spf": "detekcja obecno\u015bci podczas uruchamiania" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.pt-BR.json b/homeassistant/components/litterrobot/translations/sensor.pt-BR.json new file mode 100644 index 00000000000..9eb1dd8be8b --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.pt-BR.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Cap\u00f4 removido", + "ccc": "Ciclo de limpeza conclu\u00eddo", + "ccp": "Ciclo de limpeza em andamento", + "csf": "Falha do Sensor Cat", + "csi": "Sensor Cat interrompido", + "cst": "Sincroniza\u00e7\u00e3o do Sensor Cat", + "df1": "Gaveta quase cheia - 2 ciclos restantes", + "df2": "Gaveta quase cheia - 1 ciclo \u00e0 esquerda", + "dfs": "Gaveta cheia", + "dhf": "Despejo + Falha de Posi\u00e7\u00e3o Inicial", + "dpf": "Falha de posi\u00e7\u00e3o de despejo", + "ec": "Ciclo vazio", + "hpf": "Falha de posi\u00e7\u00e3o inicial", + "off": "Desligado", + "offline": "Offline", + "otf": "Falha de sobretorque", + "p": "Pausado", + "pd": "Detec\u00e7\u00e3o de pin\u00e7a", + "rdy": "Pronto", + "scf": "Falha do sensor Cat na inicializa\u00e7\u00e3o", + "sdf": "Gaveta cheia na inicializa\u00e7\u00e3o", + "spf": "Detec\u00e7\u00e3o de pin\u00e7a na inicializa\u00e7\u00e3o" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.ru.json b/homeassistant/components/litterrobot/translations/sensor.ru.json new file mode 100644 index 00000000000..3a28cfe08ec --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.ru.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "\u041a\u043e\u0436\u0443\u0445 \u0441\u043d\u044f\u0442", + "ccc": "\u0426\u0438\u043a\u043b \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d", + "ccp": "\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0446\u0438\u043a\u043b \u043e\u0447\u0438\u0441\u0442\u043a\u0438", + "csf": "\u041d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u044c \u0434\u0430\u0442\u0447\u0438\u043a\u0430", + "csi": "\u0414\u0430\u0442\u0447\u0438\u043a \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0442", + "cst": "\u0412\u0440\u0435\u043c\u044f \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u0442\u0447\u0438\u043a\u0430", + "df1": "\u042f\u0449\u0438\u043a \u043f\u043e\u0447\u0442\u0438 \u043f\u043e\u043b\u043e\u043d \u2014 \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c 2 \u0446\u0438\u043a\u043b\u0430", + "df2": "\u042f\u0449\u0438\u043a \u043f\u043e\u0447\u0442\u0438 \u043f\u043e\u043b\u043e\u043d \u2014 \u043e\u0441\u0442\u0430\u043b\u0441\u044f 1 \u0446\u0438\u043a\u043b", + "dfs": "\u042f\u0449\u0438\u043a \u043f\u043e\u043b\u043d\u044b\u0439", + "dhf": "\u0421\u0431\u0440\u043e\u0441 + \u043e\u0448\u0438\u0431\u043a\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f", + "dpf": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441\u0431\u0440\u043e\u0441\u0430", + "ec": "\u041f\u0443\u0441\u0442\u043e\u0439 \u0446\u0438\u043a\u043b", + "hpf": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f", + "off": "\u0412\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u043e", + "offline": "\u041d\u0435 \u0432 \u0441\u0435\u0442\u0438", + "otf": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u0438\u044f \u043a\u0440\u0443\u0442\u044f\u0449\u0435\u0433\u043e \u043c\u043e\u043c\u0435\u043d\u0442\u0430", + "p": "\u041f\u0430\u0443\u0437\u0430", + "pd": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u0449\u0435\u043c\u043b\u0435\u043d\u0438\u044f", + "rdy": "\u0413\u043e\u0442\u043e\u0432", + "scf": "\u041d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u044c \u0434\u0430\u0442\u0447\u0438\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435", + "sdf": "\u042f\u0449\u0438\u043a \u043f\u043e\u043b\u043d\u044b\u0439 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435", + "spf": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u0449\u0435\u043c\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.sk.json b/homeassistant/components/litterrobot/translations/sensor.sk.json new file mode 100644 index 00000000000..b4c5c43292d --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.sk.json @@ -0,0 +1,8 @@ +{ + "state": { + "litterrobot__status_code": { + "off": "Vypnut\u00fd", + "rdy": "Pripraven\u00fd" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.tr.json b/homeassistant/components/litterrobot/translations/sensor.tr.json new file mode 100644 index 00000000000..2db5e574f7e --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.tr.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "Kapak \u00c7\u0131kar\u0131ld\u0131", + "ccc": "Temizleme Tamamland\u0131", + "ccp": "Temizleme Devam Ediyor", + "csf": "Kedi Sens\u00f6r\u00fc Hatas\u0131", + "csi": "Kedi Sens\u00f6r\u00fc Kesildi", + "cst": "Kedi Sens\u00f6r Zamanlamas\u0131", + "df1": "Hazne Neredeyse Dolu - 2 kez daha s\u00fcp\u00fcrebilir", + "df2": "Hazne Neredeyse Dolu - 1 kez daha s\u00fcp\u00fcrebilir", + "dfs": "Hazne Dolu", + "dhf": "Bo\u015faltma + Ana Konum Hatas\u0131", + "dpf": "Bo\u015faltma Konumu Hatas\u0131", + "ec": "Bo\u015f D\u00f6ng\u00fc", + "hpf": "Ev Konumu Hatas\u0131", + "off": "Kapal\u0131", + "offline": "\u00c7evrimd\u0131\u015f\u0131", + "otf": "A\u015f\u0131r\u0131 Tork Ar\u0131zas\u0131", + "p": "Durduruldu", + "pd": "S\u0131k\u0131\u015fma Alg\u0131lama", + "rdy": "Haz\u0131r", + "scf": "Ba\u015flang\u0131\u00e7ta Cat Sens\u00f6r\u00fc Hatas\u0131", + "sdf": "Ba\u015flang\u0131\u00e7ta Hazne Dolu", + "spf": "Ba\u015flang\u0131\u00e7ta S\u0131k\u0131\u015fma Alg\u0131lama" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.zh-Hans.json b/homeassistant/components/litterrobot/translations/sensor.zh-Hans.json new file mode 100644 index 00000000000..400c2b81dcb --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.zh-Hans.json @@ -0,0 +1,10 @@ +{ + "state": { + "litterrobot__status_code": { + "off": "\u5173\u95ed", + "offline": "\u79bb\u7ebf", + "p": "\u5df2\u6682\u505c", + "rdy": "\u5c31\u7eea" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/sensor.zh-Hant.json b/homeassistant/components/litterrobot/translations/sensor.zh-Hant.json new file mode 100644 index 00000000000..f51c03f9c8c --- /dev/null +++ b/homeassistant/components/litterrobot/translations/sensor.zh-Hant.json @@ -0,0 +1,28 @@ +{ + "state": { + "litterrobot__status_code": { + "br": "\u4e0a\u84cb\u906d\u958b\u555f", + "ccc": "\u8c93\u7802\u6e05\u7406\u5b8c\u6210", + "ccp": "\u8c93\u7802\u6e05\u7406\u4e2d", + "csf": "\u8c93\u54aa\u611f\u6e2c\u5668\u6545\u969c", + "csi": "\u8c93\u54aa\u611f\u6e2c\u5668\u906d\u4e2d\u65b7", + "cst": "\u8c93\u54aa\u611f\u6e2c\u5668\u8a08\u6642", + "df1": "\u6392\u5ee2\u76d2\u5feb\u6eff\u4e86 - \u5269\u4e0b 2 \u6b21\u6e05\u7406", + "df2": "\u6392\u5ee2\u76d2\u5feb\u6eff\u4e86 - \u5269\u4e0b 1 \u6b21\u6e05\u7406", + "dfs": "\u6392\u5ee2\u76d2\u5df2\u6eff", + "dhf": "\u50be\u5012\u8207\u539f\u9ede\u4f4d\u7f6e\u6545\u969c", + "dpf": "\u50be\u5012\u4f4d\u7f6e\u6545\u969c", + "ec": "\u6574\u5e73", + "hpf": "\u539f\u9ede\u5b9a\u4f4d\u6545\u969c", + "off": "\u95dc\u9589", + "offline": "\u96e2\u7dda", + "otf": "\u8f49\u52d5\u5931\u6557", + "p": "\u5df2\u66ab\u505c", + "pd": "\u7570\u7269\u5075\u6e2c", + "rdy": "\u6e96\u5099\u5c31\u7dd2", + "scf": "\u555f\u52d5\u6642\u8c93\u54aa\u611f\u6e2c\u5668\u5931\u6548", + "sdf": "\u555f\u52d5\u6642\u6392\u5ee2\u76d2\u5df2\u6eff", + "spf": "\u555f\u52d5\u6642\u5075\u6e2c\u5230\u7570\u7269" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/vacuum.py b/homeassistant/components/litterrobot/vacuum.py index fe20c6bfe50..dbe51270857 100644 --- a/homeassistant/components/litterrobot/vacuum.py +++ b/homeassistant/components/litterrobot/vacuum.py @@ -1,6 +1,7 @@ """Support for Litter-Robot "Vacuum".""" from __future__ import annotations +from datetime import datetime, timedelta, timezone import logging from typing import Any @@ -34,6 +35,19 @@ SERVICE_RESET_WASTE_DRAWER = "reset_waste_drawer" SERVICE_SET_SLEEP_MODE = "set_sleep_mode" SERVICE_SET_WAIT_TIME = "set_wait_time" +LITTER_BOX_STATUS_STATE_MAP = { + LitterBoxStatus.CLEAN_CYCLE: STATE_CLEANING, + LitterBoxStatus.EMPTY_CYCLE: STATE_CLEANING, + LitterBoxStatus.CLEAN_CYCLE_COMPLETE: STATE_DOCKED, + LitterBoxStatus.CAT_SENSOR_TIMING: STATE_DOCKED, + LitterBoxStatus.DRAWER_FULL_1: STATE_DOCKED, + LitterBoxStatus.DRAWER_FULL_2: STATE_DOCKED, + LitterBoxStatus.READY: STATE_DOCKED, + LitterBoxStatus.CAT_SENSOR_INTERRUPTED: STATE_PAUSED, + LitterBoxStatus.OFF: STATE_OFF, +} +UNAVAILABLE_AFTER = timedelta(minutes=30) + async def async_setup_entry( hass: HomeAssistant, @@ -82,22 +96,15 @@ class LitterRobotCleaner(LitterRobotControlEntity, StateVacuumEntity): | VacuumEntityFeature.TURN_ON ) + @property + def available(self) -> bool: + """Return True if the cleaner has been seen recently.""" + return self.robot.last_seen > datetime.now(timezone.utc) - UNAVAILABLE_AFTER + @property def state(self) -> str: """Return the state of the cleaner.""" - switcher = { - LitterBoxStatus.CLEAN_CYCLE: STATE_CLEANING, - LitterBoxStatus.EMPTY_CYCLE: STATE_CLEANING, - LitterBoxStatus.CLEAN_CYCLE_COMPLETE: STATE_DOCKED, - LitterBoxStatus.CAT_SENSOR_TIMING: STATE_DOCKED, - LitterBoxStatus.DRAWER_FULL_1: STATE_DOCKED, - LitterBoxStatus.DRAWER_FULL_2: STATE_DOCKED, - LitterBoxStatus.READY: STATE_DOCKED, - LitterBoxStatus.CAT_SENSOR_INTERRUPTED: STATE_PAUSED, - LitterBoxStatus.OFF: STATE_OFF, - } - - return switcher.get(self.robot.status, STATE_ERROR) + return LITTER_BOX_STATUS_STATE_MAP.get(self.robot.status, STATE_ERROR) @property def status(self) -> str: @@ -155,11 +162,8 @@ class LitterRobotCleaner(LitterRobotControlEntity, StateVacuumEntity): def extra_state_attributes(self) -> dict[str, Any]: """Return device specific state attributes.""" return { - "clean_cycle_wait_time_minutes": self.robot.clean_cycle_wait_time_minutes, "is_sleeping": self.robot.is_sleeping, "sleep_mode_enabled": self.robot.sleep_mode_enabled, "power_status": self.robot.power_status, - "status_code": self.robot.status_code, - "last_seen": self.robot.last_seen, "status": self.status, } diff --git a/homeassistant/components/local_ip/translations/nl.json b/homeassistant/components/local_ip/translations/nl.json index 4b2672d2a3b..0d09c23ea97 100644 --- a/homeassistant/components/local_ip/translations/nl.json +++ b/homeassistant/components/local_ip/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "user": { - "description": "Wilt u beginnen met instellen?", + "description": "Wil je beginnen met instellen?", "title": "Lokaal IP-adres" } } diff --git a/homeassistant/components/locative/translations/da.json b/homeassistant/components/locative/translations/da.json index 2401428eff4..3d4395b89dc 100644 --- a/homeassistant/components/locative/translations/da.json +++ b/homeassistant/components/locative/translations/da.json @@ -1,7 +1,7 @@ { "config": { "create_entry": { - "default": "For at sende lokationer til Home Assistant skal du konfigurere webhook funktionen i Locative applicationen.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n \n Se [dokumentationen]({docs_url}) for yderligere oplysninger." + "default": "For at sende lokaliteter til Home Assistant skal du konfigurere webhook-funktionen i Locative-applicationen.\n\n Udfyld f\u00f8lgende oplysninger: \n\n - URL: `{webhook_url}`\n - Metode: POST\n \n Se [dokumentationen]({docs_url}) for yderligere oplysninger." }, "step": { "user": { diff --git a/homeassistant/components/locative/translations/es.json b/homeassistant/components/locative/translations/es.json index 9fa02248d42..e3c63d7b35d 100644 --- a/homeassistant/components/locative/translations/es.json +++ b/homeassistant/components/locative/translations/es.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No conectado a Home Assistant Cloud.", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n.", "webhook_not_internet_accessible": "Tu instancia de Home Assistant debe estar accesible desde Internet para recibir mensajes webhook." }, diff --git a/homeassistant/components/locative/translations/nl.json b/homeassistant/components/locative/translations/nl.json index 0a459e566c5..11ccfc059bc 100644 --- a/homeassistant/components/locative/translations/nl.json +++ b/homeassistant/components/locative/translations/nl.json @@ -3,14 +3,14 @@ "abort": { "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." + "webhook_not_internet_accessible": "Je Home Assistant-instantie moet toegankelijk zijn vanaf internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Om locaties naar Home Assistant te sturen, moet u de Webhook-functie instellen in de Locative app. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n\n Zie [de documentatie]({docs_url}) voor meer informatie." }, "step": { "user": { - "description": "Wilt u beginnen met instellen?", + "description": "Wil je beginnen met instellen?", "title": "Stel de Locative Webhook in" } } diff --git a/homeassistant/components/lock/device_action.py b/homeassistant/components/lock/device_action.py index 092aff8878d..3ff8d10c7a2 100644 --- a/homeassistant/components/lock/device_action.py +++ b/homeassistant/components/lock/device_action.py @@ -17,6 +17,7 @@ from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import get_supported_features +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from . import DOMAIN, LockEntityFeature @@ -34,7 +35,7 @@ async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Lock devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) actions = [] # Get all the integrations entities for this device @@ -61,7 +62,10 @@ async def async_get_actions( async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} diff --git a/homeassistant/components/lock/device_condition.py b/homeassistant/components/lock/device_condition.py index a818a2b5fa4..cdaa02de618 100644 --- a/homeassistant/components/lock/device_condition.py +++ b/homeassistant/components/lock/device_condition.py @@ -45,7 +45,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Lock devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) conditions = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/lock/device_trigger.py b/homeassistant/components/lock/device_trigger.py index 75415bbf3e1..b55a2ac254b 100644 --- a/homeassistant/components/lock/device_trigger.py +++ b/homeassistant/components/lock/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for Lock.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -43,9 +41,9 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Lock devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 8b00311a9e7..1abfcaba6ff 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -1,117 +1,53 @@ """Event parser and human readable log generator.""" from __future__ import annotations -from collections.abc import Iterable -from contextlib import suppress -from datetime import datetime as dt, timedelta -from http import HTTPStatus -from itertools import groupby -import json -import re +from collections.abc import Callable from typing import Any -import sqlalchemy -from sqlalchemy.orm import aliased -from sqlalchemy.orm.query import Query -from sqlalchemy.orm.session import Session -from sqlalchemy.sql.expression import literal import voluptuous as vol from homeassistant.components import frontend -from homeassistant.components.automation import EVENT_AUTOMATION_TRIGGERED -from homeassistant.components.history import sqlalchemy_filter_from_include_exclude_conf -from homeassistant.components.http import HomeAssistantView -from homeassistant.components.recorder import get_instance -from homeassistant.components.recorder.models import ( - Events, - StateAttributes, - States, - process_timestamp_to_utc_isoformat, +from homeassistant.components.recorder.const import DOMAIN as RECORDER_DOMAIN +from homeassistant.components.recorder.filters import ( + extract_include_exclude_filter_conf, + merge_include_exclude_filters, + sqlalchemy_filter_from_include_exclude_conf, ) -from homeassistant.components.recorder.util import session_scope -from homeassistant.components.script import EVENT_SCRIPT_STARTED from homeassistant.const import ( ATTR_DOMAIN, ATTR_ENTITY_ID, - ATTR_FRIENDLY_NAME, - ATTR_ICON, ATTR_NAME, - ATTR_SERVICE, - EVENT_CALL_SERVICE, - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, EVENT_LOGBOOK_ENTRY, - EVENT_STATE_CHANGED, ) -from homeassistant.core import ( - DOMAIN as HA_DOMAIN, - HomeAssistant, - ServiceCall, - callback, - split_entity_id, -) -from homeassistant.exceptions import InvalidEntityFormatError -import homeassistant.helpers.config_validation as cv +from homeassistant.core import Context, Event, HomeAssistant, ServiceCall, callback +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entityfilter import ( INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA, convert_include_exclude_filter, - generate_filter, ) from homeassistant.helpers.integration_platform import ( async_process_integration_platforms, ) from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -import homeassistant.util.dt as dt_util -ENTITY_ID_JSON_TEMPLATE = '%"entity_id":"{}"%' -ENTITY_ID_JSON_EXTRACT = re.compile('"entity_id": ?"([^"]+)"') -DOMAIN_JSON_EXTRACT = re.compile('"domain": ?"([^"]+)"') -ICON_JSON_EXTRACT = re.compile('"icon": ?"([^"]+)"') -ATTR_MESSAGE = "message" - -CONTINUOUS_DOMAINS = {"proximity", "sensor"} -CONTINUOUS_ENTITY_ID_LIKE = [f"{domain}.%" for domain in CONTINUOUS_DOMAINS] - -DOMAIN = "logbook" - -GROUP_BY_MINUTES = 15 - -EMPTY_JSON_OBJECT = "{}" -UNIT_OF_MEASUREMENT_JSON = '"unit_of_measurement":' -UNIT_OF_MEASUREMENT_JSON_LIKE = f"%{UNIT_OF_MEASUREMENT_JSON}%" -HA_DOMAIN_ENTITY_ID = f"{HA_DOMAIN}._" +from . import rest_api, websocket_api +from .const import ( + ATTR_MESSAGE, + DOMAIN, + LOGBOOK_ENTITIES_FILTER, + LOGBOOK_ENTRY_DOMAIN, + LOGBOOK_ENTRY_ENTITY_ID, + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, + LOGBOOK_FILTERS, +) +from .models import LazyEventPartialState # noqa: F401 CONFIG_SCHEMA = vol.Schema( {DOMAIN: INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA}, extra=vol.ALLOW_EXTRA ) -HOMEASSISTANT_EVENTS = [ - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, -] - -ALL_EVENT_TYPES_EXCEPT_STATE_CHANGED = [ - EVENT_LOGBOOK_ENTRY, - EVENT_CALL_SERVICE, - *HOMEASSISTANT_EVENTS, -] - -ALL_EVENT_TYPES = [ - EVENT_STATE_CHANGED, - *ALL_EVENT_TYPES_EXCEPT_STATE_CHANGED, -] - -EVENT_COLUMNS = [ - Events.event_type, - Events.event_data, - Events.time_fired, - Events.context_id, - Events.context_user_id, - Events.context_parent_id, -] - -SCRIPT_AUTOMATION_EVENTS = [EVENT_AUTOMATION_TRIGGERED, EVENT_SCRIPT_STARTED] LOG_MESSAGE_SCHEMA = vol.Schema( { @@ -124,21 +60,35 @@ LOG_MESSAGE_SCHEMA = vol.Schema( @bind_hass -def log_entry(hass, name, message, domain=None, entity_id=None, context=None): +def log_entry( + hass: HomeAssistant, + name: str, + message: str, + domain: str | None = None, + entity_id: str | None = None, + context: Context | None = None, +) -> None: """Add an entry to the logbook.""" hass.add_job(async_log_entry, hass, name, message, domain, entity_id, context) @callback @bind_hass -def async_log_entry(hass, name, message, domain=None, entity_id=None, context=None): +def async_log_entry( + hass: HomeAssistant, + name: str, + message: str, + domain: str | None = None, + entity_id: str | None = None, + context: Context | None = None, +) -> None: """Add an entry to the logbook.""" - data = {ATTR_NAME: name, ATTR_MESSAGE: message} + data = {LOGBOOK_ENTRY_NAME: name, LOGBOOK_ENTRY_MESSAGE: message} if domain is not None: - data[ATTR_DOMAIN] = domain + data[LOGBOOK_ENTRY_DOMAIN] = domain if entity_id is not None: - data[ATTR_ENTITY_ID] = entity_id + data[LOGBOOK_ENTRY_ENTITY_ID] = entity_id hass.bus.async_fire(EVENT_LOGBOOK_ENTRY, data, context=context) @@ -162,21 +112,29 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: message.hass = hass message = message.async_render(parse_result=False) - async_log_entry(hass, name, message, domain, entity_id) + async_log_entry(hass, name, message, domain, entity_id, service.context) frontend.async_register_built_in_panel( hass, "logbook", "logbook", "hass:format-list-bulleted-type" ) - if conf := config.get(DOMAIN, {}): - filters = sqlalchemy_filter_from_include_exclude_conf(conf) - entities_filter = convert_include_exclude_filter(conf) + recorder_conf = config.get(RECORDER_DOMAIN, {}) + logbook_conf = config.get(DOMAIN, {}) + recorder_filter = extract_include_exclude_filter_conf(recorder_conf) + logbook_filter = extract_include_exclude_filter_conf(logbook_conf) + merged_filter = merge_include_exclude_filters(recorder_filter, logbook_filter) + + possible_merged_entities_filter = convert_include_exclude_filter(merged_filter) + if not possible_merged_entities_filter.empty_filter: + filters = sqlalchemy_filter_from_include_exclude_conf(merged_filter) + entities_filter = possible_merged_entities_filter else: filters = None entities_filter = None - - hass.http.register_view(LogbookView(conf, filters, entities_filter)) - + hass.data[LOGBOOK_FILTERS] = filters + hass.data[LOGBOOK_ENTITIES_FILTER] = entities_filter + websocket_api.async_setup(hass) + rest_api.async_setup(hass, config, filters, entities_filter) hass.services.async_register(DOMAIN, "log", log_message, schema=LOG_MESSAGE_SCHEMA) await async_process_integration_platforms(hass, DOMAIN, _process_logbook_platform) @@ -184,672 +142,18 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -async def _process_logbook_platform(hass, domain, platform): +async def _process_logbook_platform( + hass: HomeAssistant, domain: str, platform: Any +) -> None: """Process a logbook platform.""" @callback - def _async_describe_event(domain, event_name, describe_callback): + def _async_describe_event( + domain: str, + event_name: str, + describe_callback: Callable[[Event], dict[str, Any]], + ) -> None: """Teach logbook how to describe a new event.""" hass.data[DOMAIN][event_name] = (domain, describe_callback) platform.async_describe_events(hass, _async_describe_event) - - -class LogbookView(HomeAssistantView): - """Handle logbook view requests.""" - - url = "/api/logbook" - name = "api:logbook" - extra_urls = ["/api/logbook/{datetime}"] - - def __init__(self, config, filters, entities_filter): - """Initialize the logbook view.""" - self.config = config - self.filters = filters - self.entities_filter = entities_filter - - async def get(self, request, datetime=None): - """Retrieve logbook entries.""" - if datetime: - if (datetime := dt_util.parse_datetime(datetime)) is None: - return self.json_message("Invalid datetime", HTTPStatus.BAD_REQUEST) - else: - datetime = dt_util.start_of_local_day() - - if (period := request.query.get("period")) is None: - period = 1 - else: - period = int(period) - - if entity_ids := request.query.get("entity"): - try: - entity_ids = cv.entity_ids(entity_ids) - except vol.Invalid: - raise InvalidEntityFormatError( - f"Invalid entity id(s) encountered: {entity_ids}. " - "Format should be ." - ) from vol.Invalid - - if (end_time := request.query.get("end_time")) is None: - start_day = dt_util.as_utc(datetime) - timedelta(days=period - 1) - end_day = start_day + timedelta(days=period) - else: - start_day = datetime - if (end_day := dt_util.parse_datetime(end_time)) is None: - return self.json_message("Invalid end_time", HTTPStatus.BAD_REQUEST) - - hass = request.app["hass"] - - entity_matches_only = "entity_matches_only" in request.query - context_id = request.query.get("context_id") - - if entity_ids and context_id: - return self.json_message( - "Can't combine entity with context_id", HTTPStatus.BAD_REQUEST - ) - - def json_events(): - """Fetch events and generate JSON.""" - return self.json( - _get_events( - hass, - start_day, - end_day, - entity_ids, - self.filters, - self.entities_filter, - entity_matches_only, - context_id, - ) - ) - - return await get_instance(hass).async_add_executor_job(json_events) - - -def humanify(hass, events, entity_attr_cache, context_lookup): - """Generate a converted list of events into Entry objects. - - Will try to group events if possible: - - if 2+ sensor updates in GROUP_BY_MINUTES, show last - - if Home Assistant stop and start happen in same minute call it restarted - """ - external_events = hass.data.get(DOMAIN, {}) - - # Group events in batches of GROUP_BY_MINUTES - for _, g_events in groupby( - events, lambda event: event.time_fired_minute // GROUP_BY_MINUTES - ): - - events_batch = list(g_events) - - # Keep track of last sensor states - last_sensor_event = {} - - # Group HA start/stop events - # Maps minute of event to 1: stop, 2: stop + start - start_stop_events = {} - - # Process events - for event in events_batch: - if event.event_type == EVENT_STATE_CHANGED: - if event.domain in CONTINUOUS_DOMAINS: - last_sensor_event[event.entity_id] = event - - elif event.event_type == EVENT_HOMEASSISTANT_STOP: - if event.time_fired_minute in start_stop_events: - continue - - start_stop_events[event.time_fired_minute] = 1 - - elif event.event_type == EVENT_HOMEASSISTANT_START: - if event.time_fired_minute not in start_stop_events: - continue - - start_stop_events[event.time_fired_minute] = 2 - - # Yield entries - for event in events_batch: - if event.event_type == EVENT_STATE_CHANGED: - entity_id = event.entity_id - domain = event.domain - - if ( - domain in CONTINUOUS_DOMAINS - and event != last_sensor_event[entity_id] - ): - # Skip all but the last sensor state - continue - - data = { - "when": event.time_fired_isoformat, - "name": _entity_name_from_event( - entity_id, event, entity_attr_cache - ), - "state": event.state, - "entity_id": entity_id, - } - - if icon := event.attributes_icon: - data["icon"] = icon - - if event.context_user_id: - data["context_user_id"] = event.context_user_id - - _augment_data_with_context( - data, - entity_id, - event, - context_lookup, - entity_attr_cache, - external_events, - ) - - yield data - - elif event.event_type in external_events: - domain, describe_event = external_events[event.event_type] - data = describe_event(event) - data["when"] = event.time_fired_isoformat - data["domain"] = domain - if event.context_user_id: - data["context_user_id"] = event.context_user_id - - _augment_data_with_context( - data, - data.get(ATTR_ENTITY_ID), - event, - context_lookup, - entity_attr_cache, - external_events, - ) - yield data - - elif event.event_type == EVENT_HOMEASSISTANT_START: - if start_stop_events.get(event.time_fired_minute) == 2: - continue - - yield { - "when": event.time_fired_isoformat, - "name": "Home Assistant", - "message": "started", - "domain": HA_DOMAIN, - } - - elif event.event_type == EVENT_HOMEASSISTANT_STOP: - if start_stop_events.get(event.time_fired_minute) == 2: - action = "restarted" - else: - action = "stopped" - - yield { - "when": event.time_fired_isoformat, - "name": "Home Assistant", - "message": action, - "domain": HA_DOMAIN, - } - - elif event.event_type == EVENT_LOGBOOK_ENTRY: - event_data = event.data - domain = event_data.get(ATTR_DOMAIN) - entity_id = event_data.get(ATTR_ENTITY_ID) - if domain is None and entity_id is not None: - with suppress(IndexError): - domain = split_entity_id(str(entity_id))[0] - - data = { - "when": event.time_fired_isoformat, - "name": event_data.get(ATTR_NAME), - "message": event_data.get(ATTR_MESSAGE), - "domain": domain, - "entity_id": entity_id, - } - - if event.context_user_id: - data["context_user_id"] = event.context_user_id - - _augment_data_with_context( - data, - entity_id, - event, - context_lookup, - entity_attr_cache, - external_events, - ) - - yield data - - -def _get_events( - hass, - start_day, - end_day, - entity_ids=None, - filters=None, - entities_filter=None, - entity_matches_only=False, - context_id=None, -): - """Get events for a period of time.""" - assert not ( - entity_ids and context_id - ), "can't pass in both entity_ids and context_id" - - entity_attr_cache = EntityAttributeCache(hass) - context_lookup = {None: None} - - def yield_events(query): - """Yield Events that are not filtered away.""" - for row in query.yield_per(1000): - event = LazyEventPartialState(row) - context_lookup.setdefault(event.context_id, event) - if event.event_type == EVENT_CALL_SERVICE: - continue - if event.event_type == EVENT_STATE_CHANGED or _keep_event( - hass, event, entities_filter - ): - yield event - - if entity_ids is not None: - entities_filter = generate_filter([], entity_ids, [], []) - - with session_scope(hass=hass) as session: - old_state = aliased(States, name="old_state") - - if entity_ids is not None: - query = _generate_events_query_without_states(session) - query = _apply_event_time_filter(query, start_day, end_day) - query = _apply_event_types_filter( - hass, query, ALL_EVENT_TYPES_EXCEPT_STATE_CHANGED - ) - if entity_matches_only: - # When entity_matches_only is provided, contexts and events that do not - # contain the entity_ids are not included in the logbook response. - query = _apply_event_entity_id_matchers(query, entity_ids) - - query = query.union_all( - _generate_states_query( - session, start_day, end_day, old_state, entity_ids - ) - ) - else: - query = _generate_events_query(session) - query = _apply_event_time_filter(query, start_day, end_day) - query = _apply_events_types_and_states_filter( - hass, query, old_state - ).filter( - (States.last_updated == States.last_changed) - | (Events.event_type != EVENT_STATE_CHANGED) - ) - if filters: - query = query.filter( - filters.entity_filter() | (Events.event_type != EVENT_STATE_CHANGED) - ) - - if context_id is not None: - query = query.filter(Events.context_id == context_id) - - query = query.order_by(Events.time_fired) - - return list( - humanify(hass, yield_events(query), entity_attr_cache, context_lookup) - ) - - -def _generate_events_query(session: Session) -> Query: - return session.query( - *EVENT_COLUMNS, - States.state, - States.entity_id, - States.attributes, - StateAttributes.shared_attrs, - ) - - -def _generate_events_query_without_states(session: Session) -> Query: - return session.query( - *EVENT_COLUMNS, - literal(value=None, type_=sqlalchemy.String).label("state"), - literal(value=None, type_=sqlalchemy.String).label("entity_id"), - literal(value=None, type_=sqlalchemy.Text).label("attributes"), - literal(value=None, type_=sqlalchemy.Text).label("shared_attrs"), - ) - - -def _generate_states_query( - session: Session, - start_day: dt, - end_day: dt, - old_state: States, - entity_ids: Iterable[str], -) -> Query: - return ( - _generate_events_query(session) - .outerjoin(Events, (States.event_id == Events.event_id)) - .outerjoin(old_state, (States.old_state_id == old_state.state_id)) - .filter(_missing_state_matcher(old_state)) - .filter(_not_continuous_entity_matcher()) - .filter((States.last_updated > start_day) & (States.last_updated < end_day)) - .filter( - (States.last_updated == States.last_changed) - & States.entity_id.in_(entity_ids) - ) - .outerjoin( - StateAttributes, (States.attributes_id == StateAttributes.attributes_id) - ) - ) - - -def _apply_events_types_and_states_filter( - hass: HomeAssistant, query: Query, old_state: States -) -> Query: - events_query = ( - query.outerjoin(States, (Events.event_id == States.event_id)) - .outerjoin(old_state, (States.old_state_id == old_state.state_id)) - .filter( - (Events.event_type != EVENT_STATE_CHANGED) - | _missing_state_matcher(old_state) - ) - .filter( - (Events.event_type != EVENT_STATE_CHANGED) - | _not_continuous_entity_matcher() - ) - ) - return _apply_event_types_filter(hass, events_query, ALL_EVENT_TYPES).outerjoin( - StateAttributes, (States.attributes_id == StateAttributes.attributes_id) - ) - - -def _missing_state_matcher(old_state: States) -> Any: - # The below removes state change events that do not have - # and old_state or the old_state is missing (newly added entities) - # or the new_state is missing (removed entities) - return sqlalchemy.and_( - old_state.state_id.isnot(None), - (States.state != old_state.state), - States.state.isnot(None), - ) - - -def _not_continuous_entity_matcher() -> Any: - """Match non continuous entities.""" - return sqlalchemy.or_( - _not_continuous_domain_matcher(), - sqlalchemy.and_( - _continuous_domain_matcher, _not_uom_attributes_matcher() - ).self_group(), - ) - - -def _not_continuous_domain_matcher() -> Any: - """Match not continuous domains.""" - return sqlalchemy.and_( - *[ - ~States.entity_id.like(entity_domain) - for entity_domain in CONTINUOUS_ENTITY_ID_LIKE - ], - ).self_group() - - -def _continuous_domain_matcher() -> Any: - """Match continuous domains.""" - return sqlalchemy.or_( - *[ - States.entity_id.like(entity_domain) - for entity_domain in CONTINUOUS_ENTITY_ID_LIKE - ], - ).self_group() - - -def _not_uom_attributes_matcher() -> Any: - """Prefilter ATTR_UNIT_OF_MEASUREMENT as its much faster in sql.""" - return ~StateAttributes.shared_attrs.like( - UNIT_OF_MEASUREMENT_JSON_LIKE - ) | ~States.attributes.like(UNIT_OF_MEASUREMENT_JSON_LIKE) - - -def _apply_event_time_filter(events_query: Query, start_day: dt, end_day: dt) -> Query: - return events_query.filter( - (Events.time_fired > start_day) & (Events.time_fired < end_day) - ) - - -def _apply_event_types_filter( - hass: HomeAssistant, query: Query, event_types: list[str] -) -> Query: - return query.filter( - Events.event_type.in_(event_types + list(hass.data.get(DOMAIN, {}))) - ) - - -def _apply_event_entity_id_matchers( - events_query: Query, entity_ids: Iterable[str] -) -> Query: - return events_query.filter( - sqlalchemy.or_( - *( - Events.event_data.like(ENTITY_ID_JSON_TEMPLATE.format(entity_id)) - for entity_id in entity_ids - ) - ) - ) - - -def _keep_event(hass, event, entities_filter): - if event.event_type in HOMEASSISTANT_EVENTS: - return entities_filter is None or entities_filter(HA_DOMAIN_ENTITY_ID) - - if entity_id := event.data_entity_id: - return entities_filter is None or entities_filter(entity_id) - - if event.event_type in hass.data[DOMAIN]: - # If the entity_id isn't described, use the domain that describes - # the event for filtering. - domain = hass.data[DOMAIN][event.event_type][0] - else: - domain = event.data_domain - - if domain is None: - return False - - return entities_filter is None or entities_filter(f"{domain}._") - - -def _augment_data_with_context( - data, entity_id, event, context_lookup, entity_attr_cache, external_events -): - if not (context_event := context_lookup.get(event.context_id)): - return - - if event == context_event: - # This is the first event with the given ID. Was it directly caused by - # a parent event? - if event.context_parent_id: - context_event = context_lookup.get(event.context_parent_id) - # Ensure the (parent) context_event exists and is not the root cause of - # this log entry. - if not context_event or event == context_event: - return - - event_type = context_event.event_type - - # State change - if context_entity_id := context_event.entity_id: - data["context_entity_id"] = context_entity_id - data["context_entity_id_name"] = _entity_name_from_event( - context_entity_id, context_event, entity_attr_cache - ) - data["context_event_type"] = event_type - return - - event_data = context_event.data - - # Call service - if event_type == EVENT_CALL_SERVICE: - event_data = context_event.data - data["context_domain"] = event_data.get(ATTR_DOMAIN) - data["context_service"] = event_data.get(ATTR_SERVICE) - data["context_event_type"] = event_type - return - - if not entity_id: - return - - attr_entity_id = event_data.get(ATTR_ENTITY_ID) - if not isinstance(attr_entity_id, str) or ( - event_type in SCRIPT_AUTOMATION_EVENTS and attr_entity_id == entity_id - ): - return - - if context_event == event: - return - - data["context_entity_id"] = attr_entity_id - data["context_entity_id_name"] = _entity_name_from_event( - attr_entity_id, context_event, entity_attr_cache - ) - data["context_event_type"] = event_type - - if event_type in external_events: - domain, describe_event = external_events[event_type] - data["context_domain"] = domain - if name := describe_event(context_event).get(ATTR_NAME): - data["context_name"] = name - - -def _entity_name_from_event(entity_id, event, entity_attr_cache): - """Extract the entity name from the event using the cache if possible.""" - return entity_attr_cache.get( - entity_id, ATTR_FRIENDLY_NAME, event - ) or split_entity_id(entity_id)[1].replace("_", " ") - - -class LazyEventPartialState: - """A lazy version of core Event with limited State joined in.""" - - __slots__ = [ - "_row", - "_event_data", - "_time_fired_isoformat", - "_attributes", - "event_type", - "entity_id", - "state", - "_domain", - "context_id", - "context_user_id", - "context_parent_id", - "time_fired_minute", - ] - - def __init__(self, row): - """Init the lazy event.""" - self._row = row - self._event_data = None - self._time_fired_isoformat = None - self._attributes = None - self._domain = None - self.event_type = self._row.event_type - self.entity_id = self._row.entity_id - self.state = self._row.state - self.context_id = self._row.context_id - self.context_user_id = self._row.context_user_id - self.context_parent_id = self._row.context_parent_id - self.time_fired_minute = self._row.time_fired.minute - - @property - def domain(self): - """Return the domain for the state.""" - if self._domain is None: - self._domain = split_entity_id(self.entity_id)[0] - return self._domain - - @property - def attributes_icon(self): - """Extract the icon from the decoded attributes or json.""" - if self._attributes: - return self._attributes.get(ATTR_ICON) - result = ICON_JSON_EXTRACT.search( - self._row.shared_attrs or self._row.attributes - ) - return result and result.group(1) - - @property - def data_entity_id(self): - """Extract the entity id from the decoded data or json.""" - if self._event_data: - return self._event_data.get(ATTR_ENTITY_ID) - - result = ENTITY_ID_JSON_EXTRACT.search(self._row.event_data) - return result and result.group(1) - - @property - def data_domain(self): - """Extract the domain from the decoded data or json.""" - if self._event_data: - return self._event_data.get(ATTR_DOMAIN) - - result = DOMAIN_JSON_EXTRACT.search(self._row.event_data) - return result and result.group(1) - - @property - def attributes(self): - """State attributes.""" - if self._attributes is None: - source = self._row.shared_attrs or self._row.attributes - if source == EMPTY_JSON_OBJECT or source is None: - self._attributes = {} - else: - self._attributes = json.loads(source) - return self._attributes - - @property - def data(self): - """Event data.""" - if not self._event_data: - if self._row.event_data == EMPTY_JSON_OBJECT: - self._event_data = {} - else: - self._event_data = json.loads(self._row.event_data) - return self._event_data - - @property - def time_fired_isoformat(self): - """Time event was fired in utc isoformat.""" - if not self._time_fired_isoformat: - self._time_fired_isoformat = process_timestamp_to_utc_isoformat( - self._row.time_fired or dt_util.utcnow() - ) - - return self._time_fired_isoformat - - -class EntityAttributeCache: - """A cache to lookup static entity_id attribute. - - This class should not be used to lookup attributes - that are expected to change state. - """ - - def __init__(self, hass: HomeAssistant) -> None: - """Init the cache.""" - self._hass = hass - self._cache: dict[str, dict[str, Any]] = {} - - def get(self, entity_id: str, attribute: str, event: LazyEventPartialState) -> Any: - """Lookup an attribute for an entity or get it from the cache.""" - if entity_id in self._cache: - if attribute in self._cache[entity_id]: - return self._cache[entity_id][attribute] - else: - self._cache[entity_id] = {} - - if current_state := self._hass.states.get(entity_id): - # Try the current state as its faster than decoding the - # attributes - self._cache[entity_id][attribute] = current_state.attributes.get(attribute) - else: - # If the entity has been removed, decode the attributes - # instead - self._cache[entity_id][attribute] = event.attributes.get(attribute) - - return self._cache[entity_id][attribute] diff --git a/homeassistant/components/logbook/const.py b/homeassistant/components/logbook/const.py new file mode 100644 index 00000000000..3f0c6599724 --- /dev/null +++ b/homeassistant/components/logbook/const.py @@ -0,0 +1,42 @@ +"""Event parser and human readable log generator.""" +from __future__ import annotations + +from homeassistant.components.automation import EVENT_AUTOMATION_TRIGGERED +from homeassistant.components.script import EVENT_SCRIPT_STARTED +from homeassistant.const import EVENT_CALL_SERVICE, EVENT_LOGBOOK_ENTRY + +ATTR_MESSAGE = "message" + +DOMAIN = "logbook" + +CONTEXT_USER_ID = "context_user_id" +CONTEXT_ENTITY_ID = "context_entity_id" +CONTEXT_ENTITY_ID_NAME = "context_entity_id_name" +CONTEXT_EVENT_TYPE = "context_event_type" +CONTEXT_DOMAIN = "context_domain" +CONTEXT_STATE = "context_state" +CONTEXT_SOURCE = "context_source" +CONTEXT_SERVICE = "context_service" +CONTEXT_NAME = "context_name" +CONTEXT_MESSAGE = "context_message" + +LOGBOOK_ENTRY_CONTEXT_ID = "context_id" +LOGBOOK_ENTRY_DOMAIN = "domain" +LOGBOOK_ENTRY_ENTITY_ID = "entity_id" +LOGBOOK_ENTRY_ICON = "icon" +LOGBOOK_ENTRY_SOURCE = "source" +LOGBOOK_ENTRY_MESSAGE = "message" +LOGBOOK_ENTRY_NAME = "name" +LOGBOOK_ENTRY_STATE = "state" +LOGBOOK_ENTRY_WHEN = "when" + +ALL_EVENT_TYPES_EXCEPT_STATE_CHANGED = {EVENT_LOGBOOK_ENTRY, EVENT_CALL_SERVICE} +ENTITY_EVENTS_WITHOUT_CONFIG_ENTRY = { + EVENT_LOGBOOK_ENTRY, + EVENT_AUTOMATION_TRIGGERED, + EVENT_SCRIPT_STARTED, +} + + +LOGBOOK_FILTERS = "logbook_filters" +LOGBOOK_ENTITIES_FILTER = "entities_filter" diff --git a/homeassistant/components/logbook/helpers.py b/homeassistant/components/logbook/helpers.py new file mode 100644 index 00000000000..de021994b8d --- /dev/null +++ b/homeassistant/components/logbook/helpers.py @@ -0,0 +1,199 @@ +"""Event parser and human readable log generator.""" +from __future__ import annotations + +from collections.abc import Callable +from typing import Any + +from homeassistant.components.sensor import ATTR_STATE_CLASS +from homeassistant.const import ( + ATTR_DEVICE_ID, + ATTR_ENTITY_ID, + ATTR_UNIT_OF_MEASUREMENT, + EVENT_LOGBOOK_ENTRY, + EVENT_STATE_CHANGED, +) +from homeassistant.core import ( + CALLBACK_TYPE, + Event, + HomeAssistant, + State, + callback, + is_callback, +) +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.event import async_track_state_change_event + +from .const import ( + ALL_EVENT_TYPES_EXCEPT_STATE_CHANGED, + DOMAIN, + ENTITY_EVENTS_WITHOUT_CONFIG_ENTRY, +) +from .models import LazyEventPartialState + + +def async_filter_entities(hass: HomeAssistant, entity_ids: list[str]) -> list[str]: + """Filter out any entities that logbook will not produce results for.""" + ent_reg = er.async_get(hass) + return [ + entity_id + for entity_id in entity_ids + if not _is_entity_id_filtered(hass, ent_reg, entity_id) + ] + + +def async_determine_event_types( + hass: HomeAssistant, entity_ids: list[str] | None, device_ids: list[str] | None +) -> tuple[str, ...]: + """Reduce the event types based on the entity ids and device ids.""" + external_events: dict[ + str, tuple[str, Callable[[LazyEventPartialState], dict[str, Any]]] + ] = hass.data.get(DOMAIN, {}) + if not entity_ids and not device_ids: + return (*ALL_EVENT_TYPES_EXCEPT_STATE_CHANGED, *external_events) + config_entry_ids: set[str] = set() + intrested_event_types: set[str] = set() + + if entity_ids: + # + # Home Assistant doesn't allow firing events from + # entities so we have a limited list to check + # + # automations and scripts can refer to entities + # but they do not have a config entry so we need + # to add them. + # + # We also allow entity_ids to be recorded via + # manual logbook entries. + # + intrested_event_types |= ENTITY_EVENTS_WITHOUT_CONFIG_ENTRY + + if device_ids: + dev_reg = dr.async_get(hass) + for device_id in device_ids: + if (device := dev_reg.async_get(device_id)) and device.config_entries: + config_entry_ids |= device.config_entries + interested_domains: set[str] = set() + for entry_id in config_entry_ids: + if entry := hass.config_entries.async_get_entry(entry_id): + interested_domains.add(entry.domain) + for external_event, domain_call in external_events.items(): + if domain_call[0] in interested_domains: + intrested_event_types.add(external_event) + + return tuple( + event_type + for event_type in (EVENT_LOGBOOK_ENTRY, *external_events) + if event_type in intrested_event_types + ) + + +@callback +def async_subscribe_events( + hass: HomeAssistant, + subscriptions: list[CALLBACK_TYPE], + target: Callable[[Event], None], + event_types: tuple[str, ...], + entity_ids: list[str] | None, + device_ids: list[str] | None, +) -> None: + """Subscribe to events for the entities and devices or all. + + These are the events we need to listen for to do + the live logbook stream. + """ + ent_reg = er.async_get(hass) + assert is_callback(target), "target must be a callback" + event_forwarder = target + + if entity_ids or device_ids: + entity_ids_set = set(entity_ids) if entity_ids else set() + device_ids_set = set(device_ids) if device_ids else set() + + @callback + def _forward_events_filtered(event: Event) -> None: + event_data = event.data + if ( + entity_ids_set and event_data.get(ATTR_ENTITY_ID) in entity_ids_set + ) or (device_ids_set and event_data.get(ATTR_DEVICE_ID) in device_ids_set): + target(event) + + event_forwarder = _forward_events_filtered + + for event_type in event_types: + subscriptions.append( + hass.bus.async_listen(event_type, event_forwarder, run_immediately=True) + ) + + @callback + def _forward_state_events_filtered(event: Event) -> None: + if event.data.get("old_state") is None or event.data.get("new_state") is None: + return + state: State = event.data["new_state"] + if not _is_state_filtered(ent_reg, state): + target(event) + + if device_ids and not entity_ids: + # No entities to subscribe to but we are filtering + # on device ids so we do not want to get any state + # changed events + return + + if entity_ids: + subscriptions.append( + async_track_state_change_event( + hass, entity_ids, _forward_state_events_filtered + ) + ) + return + + # We want the firehose + subscriptions.append( + hass.bus.async_listen( + EVENT_STATE_CHANGED, + _forward_state_events_filtered, + run_immediately=True, + ) + ) + + +def is_sensor_continuous(ent_reg: er.EntityRegistry, entity_id: str) -> bool: + """Determine if a sensor is continuous by checking its state class. + + Sensors with a unit_of_measurement are also considered continuous, but are filtered + already by the SQL query generated by _get_events + """ + if not (entry := ent_reg.async_get(entity_id)): + # Entity not registered, so can't have a state class + return False + return ( + entry.capabilities is not None + and entry.capabilities.get(ATTR_STATE_CLASS) is not None + ) + + +def _is_state_filtered(ent_reg: er.EntityRegistry, state: State) -> bool: + """Check if the logbook should filter a state. + + Used when we are in live mode to ensure + we only get significant changes (state.last_changed != state.last_updated) + """ + return bool( + state.last_changed != state.last_updated + or ATTR_UNIT_OF_MEASUREMENT in state.attributes + or is_sensor_continuous(ent_reg, state.entity_id) + ) + + +def _is_entity_id_filtered( + hass: HomeAssistant, ent_reg: er.EntityRegistry, entity_id: str +) -> bool: + """Check if the logbook should filter an entity. + + Used to setup listeners and which entities to select + from the database when a list of entities is requested. + """ + return bool( + (state := hass.states.get(entity_id)) + and (ATTR_UNIT_OF_MEASUREMENT in state.attributes) + or is_sensor_continuous(ent_reg, entity_id) + ) diff --git a/homeassistant/components/logbook/models.py b/homeassistant/components/logbook/models.py new file mode 100644 index 00000000000..591781745f7 --- /dev/null +++ b/homeassistant/components/logbook/models.py @@ -0,0 +1,113 @@ +"""Event parser and human readable log generator.""" +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime as dt +import json +from typing import Any, cast + +from sqlalchemy.engine.row import Row + +from homeassistant.const import ATTR_ICON, EVENT_STATE_CHANGED +from homeassistant.core import Context, Event, State, callback + + +class LazyEventPartialState: + """A lazy version of core Event with limited State joined in.""" + + __slots__ = [ + "row", + "_event_data", + "_event_data_cache", + "event_type", + "entity_id", + "state", + "context_id", + "context_user_id", + "context_parent_id", + "data", + ] + + def __init__( + self, + row: Row | EventAsRow, + event_data_cache: dict[str, dict[str, Any]], + ) -> None: + """Init the lazy event.""" + self.row = row + self._event_data: dict[str, Any] | None = None + self._event_data_cache = event_data_cache + self.event_type: str | None = self.row.event_type + self.entity_id: str | None = self.row.entity_id + self.state = self.row.state + self.context_id: str | None = self.row.context_id + self.context_user_id: str | None = self.row.context_user_id + self.context_parent_id: str | None = self.row.context_parent_id + if data := getattr(row, "data", None): + # If its an EventAsRow we can avoid the whole + # json decode process as we already have the data + self.data = data + return + source = cast(str, self.row.shared_data or self.row.event_data) + if not source: + self.data = {} + elif event_data := self._event_data_cache.get(source): + self.data = event_data + else: + self.data = self._event_data_cache[source] = cast( + dict[str, Any], json.loads(source) + ) + + +@dataclass(frozen=True) +class EventAsRow: + """Convert an event to a row.""" + + data: dict[str, Any] + context: Context + context_id: str + time_fired: dt + state_id: int + event_data: str | None = None + old_format_icon: None = None + event_id: None = None + entity_id: str | None = None + icon: str | None = None + context_user_id: str | None = None + context_parent_id: str | None = None + event_type: str | None = None + state: str | None = None + shared_data: str | None = None + context_only: None = None + + +@callback +def async_event_to_row(event: Event) -> EventAsRow | None: + """Convert an event to a row.""" + if event.event_type != EVENT_STATE_CHANGED: + return EventAsRow( + data=event.data, + context=event.context, + event_type=event.event_type, + context_id=event.context.id, + context_user_id=event.context.user_id, + context_parent_id=event.context.parent_id, + time_fired=event.time_fired, + state_id=hash(event), + ) + # States are prefiltered so we never get states + # that are missing new_state or old_state + # since the logbook does not show these + new_state: State = event.data["new_state"] + return EventAsRow( + data=event.data, + context=event.context, + entity_id=new_state.entity_id, + state=new_state.state, + context_id=new_state.context.id, + context_user_id=new_state.context.user_id, + context_parent_id=new_state.context.parent_id, + time_fired=new_state.last_updated, + state_id=hash(event), + icon=new_state.attributes.get(ATTR_ICON), + ) diff --git a/homeassistant/components/logbook/processor.py b/homeassistant/components/logbook/processor.py new file mode 100644 index 00000000000..b3a43c2ca35 --- /dev/null +++ b/homeassistant/components/logbook/processor.py @@ -0,0 +1,484 @@ +"""Event parser and human readable log generator.""" +from __future__ import annotations + +from collections.abc import Callable, Generator +from contextlib import suppress +from dataclasses import dataclass +from datetime import datetime as dt +import logging +import re +from typing import Any + +from sqlalchemy.engine.row import Row +from sqlalchemy.orm.query import Query + +from homeassistant.components.recorder.filters import Filters +from homeassistant.components.recorder.models import ( + process_datetime_to_timestamp, + process_timestamp_to_utc_isoformat, +) +from homeassistant.components.recorder.util import session_scope +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import ( + ATTR_DOMAIN, + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + ATTR_NAME, + ATTR_SERVICE, + EVENT_CALL_SERVICE, + EVENT_LOGBOOK_ENTRY, +) +from homeassistant.core import HomeAssistant, split_entity_id +from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.entityfilter import EntityFilter +import homeassistant.util.dt as dt_util + +from .const import ( + ATTR_MESSAGE, + CONTEXT_DOMAIN, + CONTEXT_ENTITY_ID, + CONTEXT_ENTITY_ID_NAME, + CONTEXT_EVENT_TYPE, + CONTEXT_MESSAGE, + CONTEXT_NAME, + CONTEXT_SERVICE, + CONTEXT_SOURCE, + CONTEXT_STATE, + CONTEXT_USER_ID, + DOMAIN, + LOGBOOK_ENTITIES_FILTER, + LOGBOOK_ENTRY_DOMAIN, + LOGBOOK_ENTRY_ENTITY_ID, + LOGBOOK_ENTRY_ICON, + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, + LOGBOOK_ENTRY_SOURCE, + LOGBOOK_ENTRY_STATE, + LOGBOOK_ENTRY_WHEN, + LOGBOOK_FILTERS, +) +from .helpers import is_sensor_continuous +from .models import EventAsRow, LazyEventPartialState, async_event_to_row +from .queries import statement_for_request +from .queries.common import PSUEDO_EVENT_STATE_CHANGED + +_LOGGER = logging.getLogger(__name__) + +ENTITY_ID_JSON_EXTRACT = re.compile('"entity_id": ?"([^"]+)"') +DOMAIN_JSON_EXTRACT = re.compile('"domain": ?"([^"]+)"') + + +@dataclass +class LogbookRun: + """A logbook run which may be a long running event stream or single request.""" + + context_lookup: ContextLookup + external_events: dict[ + str, tuple[str, Callable[[LazyEventPartialState], dict[str, Any]]] + ] + event_cache: EventCache + entity_name_cache: EntityNameCache + include_entity_name: bool + format_time: Callable[[Row], Any] + + +class EventProcessor: + """Stream into logbook format.""" + + def __init__( + self, + hass: HomeAssistant, + event_types: tuple[str, ...], + entity_ids: list[str] | None = None, + device_ids: list[str] | None = None, + context_id: str | None = None, + timestamp: bool = False, + include_entity_name: bool = True, + ) -> None: + """Init the event stream.""" + assert not ( + context_id and (entity_ids or device_ids) + ), "can't pass in both context_id and (entity_ids or device_ids)" + self.hass = hass + self.ent_reg = er.async_get(hass) + self.event_types = event_types + self.entity_ids = entity_ids + self.device_ids = device_ids + self.context_id = context_id + self.filters: Filters | None = hass.data[LOGBOOK_FILTERS] + if self.limited_select: + self.entities_filter: EntityFilter | Callable[[str], bool] | None = None + else: + self.entities_filter = hass.data[LOGBOOK_ENTITIES_FILTER] + format_time = ( + _row_time_fired_timestamp if timestamp else _row_time_fired_isoformat + ) + external_events: dict[ + str, tuple[str, Callable[[LazyEventPartialState], dict[str, Any]]] + ] = hass.data.get(DOMAIN, {}) + self.logbook_run = LogbookRun( + context_lookup=ContextLookup(hass), + external_events=external_events, + event_cache=EventCache({}), + entity_name_cache=EntityNameCache(self.hass), + include_entity_name=include_entity_name, + format_time=format_time, + ) + self.context_augmenter = ContextAugmenter(self.logbook_run) + + @property + def limited_select(self) -> bool: + """Check if the stream is limited by entities context or device ids.""" + return bool(self.entity_ids or self.context_id or self.device_ids) + + def switch_to_live(self) -> None: + """Switch to live stream. + + Clear caches so we can reduce memory pressure. + """ + self.logbook_run.event_cache.clear() + self.logbook_run.context_lookup.clear() + + def get_events( + self, + start_day: dt, + end_day: dt, + ) -> list[dict[str, Any]]: + """Get events for a period of time.""" + + def yield_rows(query: Query) -> Generator[Row, None, None]: + """Yield rows from the database.""" + # end_day - start_day intentionally checks .days and not .total_seconds() + # since we don't want to switch over to buffered if they go + # over one day by a few hours since the UI makes it so easy to do that. + if self.limited_select or (end_day - start_day).days <= 1: + return query.all() # type: ignore[no-any-return] + # Only buffer rows to reduce memory pressure + # if we expect the result set is going to be very large. + # What is considered very large is going to differ + # based on the hardware Home Assistant is running on. + # + # sqlalchemy suggests that is at least 10k, but for + # even and RPi3 that number seems higher in testing + # so we don't switch over until we request > 1 day+ of data. + # + return query.yield_per(1024) # type: ignore[no-any-return] + + stmt = statement_for_request( + start_day, + end_day, + self.event_types, + self.entity_ids, + self.device_ids, + self.filters, + self.context_id, + ) + with session_scope(hass=self.hass) as session: + return self.humanify(yield_rows(session.execute(stmt))) + + def humanify( + self, row_generator: Generator[Row | EventAsRow, None, None] + ) -> list[dict[str, str]]: + """Humanify rows.""" + return list( + _humanify( + row_generator, + self.entities_filter, + self.ent_reg, + self.logbook_run, + self.context_augmenter, + ) + ) + + +def _humanify( + rows: Generator[Row | EventAsRow, None, None], + entities_filter: EntityFilter | Callable[[str], bool] | None, + ent_reg: er.EntityRegistry, + logbook_run: LogbookRun, + context_augmenter: ContextAugmenter, +) -> Generator[dict[str, Any], None, None]: + """Generate a converted list of events into entries.""" + # Continuous sensors, will be excluded from the logbook + continuous_sensors: dict[str, bool] = {} + context_lookup = logbook_run.context_lookup + external_events = logbook_run.external_events + event_cache = logbook_run.event_cache + entity_name_cache = logbook_run.entity_name_cache + include_entity_name = logbook_run.include_entity_name + format_time = logbook_run.format_time + + def _keep_row(row: EventAsRow) -> bool: + """Check if the entity_filter rejects a row.""" + assert entities_filter is not None + if entity_id := row.entity_id: + return entities_filter(entity_id) + if entity_id := row.data.get(ATTR_ENTITY_ID): + return entities_filter(entity_id) + if domain := row.data.get(ATTR_DOMAIN): + return entities_filter(f"{domain}._") + return True + + # Process rows + for row in rows: + context_id = context_lookup.memorize(row) + if row.context_only: + continue + event_type = row.event_type + if event_type == EVENT_CALL_SERVICE or ( + entities_filter + # We literally mean is EventAsRow not a subclass of EventAsRow + and type(row) is EventAsRow # pylint: disable=unidiomatic-typecheck + and not _keep_row(row) + ): + continue + if event_type is PSUEDO_EVENT_STATE_CHANGED: + entity_id = row.entity_id + assert entity_id is not None + # Skip continuous sensors + if ( + is_continuous := continuous_sensors.get(entity_id) + ) is None and split_entity_id(entity_id)[0] == SENSOR_DOMAIN: + is_continuous = is_sensor_continuous(ent_reg, entity_id) + continuous_sensors[entity_id] = is_continuous + if is_continuous: + continue + + data = { + LOGBOOK_ENTRY_WHEN: format_time(row), + LOGBOOK_ENTRY_STATE: row.state, + LOGBOOK_ENTRY_ENTITY_ID: entity_id, + } + if include_entity_name: + data[LOGBOOK_ENTRY_NAME] = entity_name_cache.get(entity_id) + if icon := row.icon or row.old_format_icon: + data[LOGBOOK_ENTRY_ICON] = icon + + context_augmenter.augment(data, row, context_id) + yield data + + elif event_type in external_events: + domain, describe_event = external_events[event_type] + data = describe_event(event_cache.get(row)) + data[LOGBOOK_ENTRY_WHEN] = format_time(row) + data[LOGBOOK_ENTRY_DOMAIN] = domain + context_augmenter.augment(data, row, context_id) + yield data + + elif event_type == EVENT_LOGBOOK_ENTRY: + event = event_cache.get(row) + if not (event_data := event.data): + continue + entry_domain = event_data.get(ATTR_DOMAIN) + entry_entity_id = event_data.get(ATTR_ENTITY_ID) + if entry_domain is None and entry_entity_id is not None: + with suppress(IndexError): + entry_domain = split_entity_id(str(entry_entity_id))[0] + data = { + LOGBOOK_ENTRY_WHEN: format_time(row), + LOGBOOK_ENTRY_NAME: event_data.get(ATTR_NAME), + LOGBOOK_ENTRY_MESSAGE: event_data.get(ATTR_MESSAGE), + LOGBOOK_ENTRY_DOMAIN: entry_domain, + LOGBOOK_ENTRY_ENTITY_ID: entry_entity_id, + } + context_augmenter.augment(data, row, context_id) + yield data + + +class ContextLookup: + """A lookup class for context origins.""" + + def __init__(self, hass: HomeAssistant) -> None: + """Memorize context origin.""" + self.hass = hass + self._memorize_new = True + self._lookup: dict[str | None, Row | EventAsRow | None] = {None: None} + + def memorize(self, row: Row) -> str | None: + """Memorize a context from the database.""" + if self._memorize_new: + context_id: str = row.context_id + self._lookup.setdefault(context_id, row) + return context_id + return None + + def clear(self) -> None: + """Clear the context origins and stop recording new ones.""" + self._lookup.clear() + self._memorize_new = False + + def get(self, context_id: str) -> Row | None: + """Get the context origin.""" + return self._lookup.get(context_id) + + +class ContextAugmenter: + """Augment data with context trace.""" + + def __init__(self, logbook_run: LogbookRun) -> None: + """Init the augmenter.""" + self.context_lookup = logbook_run.context_lookup + self.entity_name_cache = logbook_run.entity_name_cache + self.external_events = logbook_run.external_events + self.event_cache = logbook_run.event_cache + self.include_entity_name = logbook_run.include_entity_name + + def _get_context_row( + self, context_id: str | None, row: Row | EventAsRow + ) -> Row | EventAsRow: + """Get the context row from the id or row context.""" + if context_id: + return self.context_lookup.get(context_id) + if (context := getattr(row, "context", None)) is not None and ( + origin_event := context.origin_event + ) is not None: + return async_event_to_row(origin_event) + return None + + def augment( + self, data: dict[str, Any], row: Row | EventAsRow, context_id: str | None + ) -> None: + """Augment data from the row and cache.""" + if context_user_id := row.context_user_id: + data[CONTEXT_USER_ID] = context_user_id + + if not (context_row := self._get_context_row(context_id, row)): + return + + if _rows_match(row, context_row): + # This is the first event with the given ID. Was it directly caused by + # a parent event? + if ( + not row.context_parent_id + or ( + context_row := self._get_context_row( + row.context_parent_id, context_row + ) + ) + is None + ): + return + # Ensure the (parent) context_event exists and is not the root cause of + # this log entry. + if _rows_match(row, context_row): + return + event_type = context_row.event_type + # State change + if context_entity_id := context_row.entity_id: + data[CONTEXT_STATE] = context_row.state + data[CONTEXT_ENTITY_ID] = context_entity_id + if self.include_entity_name: + data[CONTEXT_ENTITY_ID_NAME] = self.entity_name_cache.get( + context_entity_id + ) + return + + # Call service + if event_type == EVENT_CALL_SERVICE: + event = self.event_cache.get(context_row) + event_data = event.data + data[CONTEXT_DOMAIN] = event_data.get(ATTR_DOMAIN) + data[CONTEXT_SERVICE] = event_data.get(ATTR_SERVICE) + data[CONTEXT_EVENT_TYPE] = event_type + return + + if event_type not in self.external_events: + return + + domain, describe_event = self.external_events[event_type] + data[CONTEXT_EVENT_TYPE] = event_type + data[CONTEXT_DOMAIN] = domain + event = self.event_cache.get(context_row) + described = describe_event(event) + if name := described.get(LOGBOOK_ENTRY_NAME): + data[CONTEXT_NAME] = name + if message := described.get(LOGBOOK_ENTRY_MESSAGE): + data[CONTEXT_MESSAGE] = message + # In 2022.12 and later drop `CONTEXT_MESSAGE` if `CONTEXT_SOURCE` is available + if source := described.get(LOGBOOK_ENTRY_SOURCE): + data[CONTEXT_SOURCE] = source + if not (attr_entity_id := described.get(LOGBOOK_ENTRY_ENTITY_ID)): + return + data[CONTEXT_ENTITY_ID] = attr_entity_id + if self.include_entity_name: + data[CONTEXT_ENTITY_ID_NAME] = self.entity_name_cache.get(attr_entity_id) + + +def _rows_match(row: Row | EventAsRow, other_row: Row | EventAsRow) -> bool: + """Check of rows match by using the same method as Events __hash__.""" + if ( + row is other_row + or (state_id := row.state_id) is not None + and state_id == other_row.state_id + or (event_id := row.event_id) is not None + and event_id == other_row.event_id + ): + return True + return False + + +def _row_event_data_extract(row: Row | EventAsRow, extractor: re.Pattern) -> str | None: + """Extract from event_data row.""" + result = extractor.search(row.shared_data or row.event_data or "") + return result.group(1) if result else None + + +def _row_time_fired_isoformat(row: Row | EventAsRow) -> str: + """Convert the row timed_fired to isoformat.""" + return process_timestamp_to_utc_isoformat(row.time_fired or dt_util.utcnow()) + + +def _row_time_fired_timestamp(row: Row | EventAsRow) -> float: + """Convert the row timed_fired to timestamp.""" + return process_datetime_to_timestamp(row.time_fired or dt_util.utcnow()) + + +class EntityNameCache: + """A cache to lookup the name for an entity. + + This class should not be used to lookup attributes + that are expected to change state. + """ + + def __init__(self, hass: HomeAssistant) -> None: + """Init the cache.""" + self._hass = hass + self._names: dict[str, str] = {} + + def get(self, entity_id: str) -> str: + """Lookup an the friendly name.""" + if entity_id in self._names: + return self._names[entity_id] + if (current_state := self._hass.states.get(entity_id)) and ( + friendly_name := current_state.attributes.get(ATTR_FRIENDLY_NAME) + ): + self._names[entity_id] = friendly_name + else: + return split_entity_id(entity_id)[1].replace("_", " ") + + return self._names[entity_id] + + +class EventCache: + """Cache LazyEventPartialState by row.""" + + def __init__(self, event_data_cache: dict[str, dict[str, Any]]) -> None: + """Init the cache.""" + self._event_data_cache = event_data_cache + self.event_cache: dict[Row | EventAsRow, LazyEventPartialState] = {} + + def get(self, row: EventAsRow | Row) -> LazyEventPartialState: + """Get the event from the row.""" + if isinstance(row, EventAsRow): + return LazyEventPartialState(row, self._event_data_cache) + if event := self.event_cache.get(row): + return event + self.event_cache[row] = lazy_event = LazyEventPartialState( + row, self._event_data_cache + ) + return lazy_event + + def clear(self) -> None: + """Clear the event cache.""" + self._event_data_cache = {} + self.event_cache = {} diff --git a/homeassistant/components/logbook/queries/__init__.py b/homeassistant/components/logbook/queries/__init__.py new file mode 100644 index 00000000000..3c027823612 --- /dev/null +++ b/homeassistant/components/logbook/queries/__init__.py @@ -0,0 +1,78 @@ +"""Queries for logbook.""" +from __future__ import annotations + +from datetime import datetime as dt + +from sqlalchemy.sql.lambdas import StatementLambdaElement + +from homeassistant.components.recorder.filters import Filters + +from .all import all_stmt +from .devices import devices_stmt +from .entities import entities_stmt +from .entities_and_devices import entities_devices_stmt + + +def statement_for_request( + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + entity_ids: list[str] | None = None, + device_ids: list[str] | None = None, + filters: Filters | None = None, + context_id: str | None = None, +) -> StatementLambdaElement: + """Generate the logbook statement for a logbook request.""" + + # No entities: logbook sends everything for the timeframe + # limited by the context_id and the yaml configured filter + if not entity_ids and not device_ids: + states_entity_filter = filters.states_entity_filter() if filters else None + events_entity_filter = filters.events_entity_filter() if filters else None + return all_stmt( + start_day, + end_day, + event_types, + states_entity_filter, + events_entity_filter, + context_id, + ) + + # sqlalchemy caches object quoting, the + # json quotable ones must be a different + # object from the non-json ones to prevent + # sqlalchemy from quoting them incorrectly + + # entities and devices: logbook sends everything for the timeframe for the entities and devices + if entity_ids and device_ids: + json_quotable_entity_ids = list(entity_ids) + json_quotable_device_ids = list(device_ids) + return entities_devices_stmt( + start_day, + end_day, + event_types, + entity_ids, + json_quotable_entity_ids, + json_quotable_device_ids, + ) + + # entities: logbook sends everything for the timeframe for the entities + if entity_ids: + json_quotable_entity_ids = list(entity_ids) + return entities_stmt( + start_day, + end_day, + event_types, + entity_ids, + json_quotable_entity_ids, + ) + + # devices: logbook sends everything for the timeframe for the devices + assert device_ids is not None + json_quotable_device_ids = list(device_ids) + return devices_stmt( + start_day, + end_day, + event_types, + json_quotable_device_ids, + ) diff --git a/homeassistant/components/logbook/queries/all.py b/homeassistant/components/logbook/queries/all.py new file mode 100644 index 00000000000..d321578f545 --- /dev/null +++ b/homeassistant/components/logbook/queries/all.py @@ -0,0 +1,70 @@ +"""All queries for logbook.""" +from __future__ import annotations + +from datetime import datetime as dt + +from sqlalchemy import lambda_stmt +from sqlalchemy.orm import Query +from sqlalchemy.sql.elements import ClauseList +from sqlalchemy.sql.lambdas import StatementLambdaElement + +from homeassistant.components.recorder.models import LAST_UPDATED_INDEX, Events, States + +from .common import ( + apply_states_filters, + legacy_select_events_context_id, + select_events_without_states, + select_states, +) + + +def all_stmt( + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + states_entity_filter: ClauseList | None = None, + events_entity_filter: ClauseList | None = None, + context_id: str | None = None, +) -> StatementLambdaElement: + """Generate a logbook query for all entities.""" + stmt = lambda_stmt( + lambda: select_events_without_states(start_day, end_day, event_types) + ) + if context_id is not None: + # Once all the old `state_changed` events + # are gone from the database remove the + # _legacy_select_events_context_id() + stmt += lambda s: s.where(Events.context_id == context_id).union_all( + _states_query_for_context_id(start_day, end_day, context_id), + legacy_select_events_context_id(start_day, end_day, context_id), + ) + else: + if events_entity_filter is not None: + stmt += lambda s: s.where(events_entity_filter) + + if states_entity_filter is not None: + stmt += lambda s: s.union_all( + _states_query_for_all(start_day, end_day).where(states_entity_filter) + ) + else: + stmt += lambda s: s.union_all(_states_query_for_all(start_day, end_day)) + + stmt += lambda s: s.order_by(Events.time_fired) + return stmt + + +def _states_query_for_all(start_day: dt, end_day: dt) -> Query: + return apply_states_filters(_apply_all_hints(select_states()), start_day, end_day) + + +def _apply_all_hints(query: Query) -> Query: + """Force mysql to use the right index on large selects.""" + return query.with_hint( + States, f"FORCE INDEX ({LAST_UPDATED_INDEX})", dialect_name="mysql" + ) + + +def _states_query_for_context_id(start_day: dt, end_day: dt, context_id: str) -> Query: + return apply_states_filters(select_states(), start_day, end_day).where( + States.context_id == context_id + ) diff --git a/homeassistant/components/logbook/queries/common.py b/homeassistant/components/logbook/queries/common.py new file mode 100644 index 00000000000..6049d6beb81 --- /dev/null +++ b/homeassistant/components/logbook/queries/common.py @@ -0,0 +1,254 @@ +"""Queries for logbook.""" +from __future__ import annotations + +from datetime import datetime as dt + +import sqlalchemy +from sqlalchemy import select +from sqlalchemy.orm import Query +from sqlalchemy.sql.elements import ClauseList +from sqlalchemy.sql.expression import literal +from sqlalchemy.sql.selectable import Select + +from homeassistant.components.proximity import DOMAIN as PROXIMITY_DOMAIN +from homeassistant.components.recorder.models import ( + OLD_FORMAT_ATTRS_JSON, + OLD_STATE, + SHARED_ATTRS_JSON, + EventData, + Events, + StateAttributes, + States, +) +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN + +CONTINUOUS_DOMAINS = {PROXIMITY_DOMAIN, SENSOR_DOMAIN} +CONTINUOUS_ENTITY_ID_LIKE = [f"{domain}.%" for domain in CONTINUOUS_DOMAINS] + +UNIT_OF_MEASUREMENT_JSON = '"unit_of_measurement":' +UNIT_OF_MEASUREMENT_JSON_LIKE = f"%{UNIT_OF_MEASUREMENT_JSON}%" + + +PSUEDO_EVENT_STATE_CHANGED = None +# Since we don't store event_types and None +# and we don't store state_changed in events +# we use a NULL for state_changed events +# when we synthesize them from the states table +# since it avoids another column being sent +# in the payload + +EVENT_COLUMNS = ( + Events.event_id.label("event_id"), + Events.event_type.label("event_type"), + Events.event_data.label("event_data"), + Events.time_fired.label("time_fired"), + Events.context_id.label("context_id"), + Events.context_user_id.label("context_user_id"), + Events.context_parent_id.label("context_parent_id"), +) + +STATE_COLUMNS = ( + States.state_id.label("state_id"), + States.state.label("state"), + States.entity_id.label("entity_id"), + SHARED_ATTRS_JSON["icon"].as_string().label("icon"), + OLD_FORMAT_ATTRS_JSON["icon"].as_string().label("old_format_icon"), +) + +STATE_CONTEXT_ONLY_COLUMNS = ( + States.state_id.label("state_id"), + States.state.label("state"), + States.entity_id.label("entity_id"), + literal(value=None, type_=sqlalchemy.String).label("icon"), + literal(value=None, type_=sqlalchemy.String).label("old_format_icon"), +) + +EVENT_COLUMNS_FOR_STATE_SELECT = [ + literal(value=None, type_=sqlalchemy.Text).label("event_id"), + # We use PSUEDO_EVENT_STATE_CHANGED aka None for + # state_changed events since it takes up less + # space in the response and every row has to be + # marked with the event_type + literal(value=PSUEDO_EVENT_STATE_CHANGED, type_=sqlalchemy.String).label( + "event_type" + ), + literal(value=None, type_=sqlalchemy.Text).label("event_data"), + States.last_updated.label("time_fired"), + States.context_id.label("context_id"), + States.context_user_id.label("context_user_id"), + States.context_parent_id.label("context_parent_id"), + literal(value=None, type_=sqlalchemy.Text).label("shared_data"), +] + +EMPTY_STATE_COLUMNS = ( + literal(value=None, type_=sqlalchemy.String).label("state_id"), + literal(value=None, type_=sqlalchemy.String).label("state"), + literal(value=None, type_=sqlalchemy.String).label("entity_id"), + literal(value=None, type_=sqlalchemy.String).label("icon"), + literal(value=None, type_=sqlalchemy.String).label("old_format_icon"), +) + + +EVENT_ROWS_NO_STATES = ( + *EVENT_COLUMNS, + EventData.shared_data.label("shared_data"), + *EMPTY_STATE_COLUMNS, +) + +# Virtual column to tell logbook if it should avoid processing +# the event as its only used to link contexts +CONTEXT_ONLY = literal("1").label("context_only") +NOT_CONTEXT_ONLY = literal(None).label("context_only") + + +def select_events_context_id_subquery( + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], +) -> Select: + """Generate the select for a context_id subquery.""" + return ( + select(Events.context_id) + .where((Events.time_fired > start_day) & (Events.time_fired < end_day)) + .where(Events.event_type.in_(event_types)) + .outerjoin(EventData, (Events.data_id == EventData.data_id)) + ) + + +def select_events_context_only() -> Select: + """Generate an events query that mark them as for context_only. + + By marking them as context_only we know they are only for + linking context ids and we can avoid processing them. + """ + return select(*EVENT_ROWS_NO_STATES, CONTEXT_ONLY).outerjoin( + EventData, (Events.data_id == EventData.data_id) + ) + + +def select_states_context_only() -> Select: + """Generate an states query that mark them as for context_only. + + By marking them as context_only we know they are only for + linking context ids and we can avoid processing them. + """ + return select( + *EVENT_COLUMNS_FOR_STATE_SELECT, *STATE_CONTEXT_ONLY_COLUMNS, CONTEXT_ONLY + ) + + +def select_events_without_states( + start_day: dt, end_day: dt, event_types: tuple[str, ...] +) -> Select: + """Generate an events select that does not join states.""" + return ( + select(*EVENT_ROWS_NO_STATES, NOT_CONTEXT_ONLY) + .where((Events.time_fired > start_day) & (Events.time_fired < end_day)) + .where(Events.event_type.in_(event_types)) + .outerjoin(EventData, (Events.data_id == EventData.data_id)) + ) + + +def select_states() -> Select: + """Generate a states select that formats the states table as event rows.""" + return select( + *EVENT_COLUMNS_FOR_STATE_SELECT, + *STATE_COLUMNS, + NOT_CONTEXT_ONLY, + ) + + +def legacy_select_events_context_id( + start_day: dt, end_day: dt, context_id: str +) -> Select: + """Generate a legacy events context id select that also joins states.""" + # This can be removed once we no longer have event_ids in the states table + return ( + select( + *EVENT_COLUMNS, + literal(value=None, type_=sqlalchemy.String).label("shared_data"), + *STATE_COLUMNS, + NOT_CONTEXT_ONLY, + ) + .outerjoin(States, (Events.event_id == States.event_id)) + .where( + (States.last_updated == States.last_changed) | States.last_changed.is_(None) + ) + .where(_not_continuous_entity_matcher()) + .outerjoin( + StateAttributes, (States.attributes_id == StateAttributes.attributes_id) + ) + .where((Events.time_fired > start_day) & (Events.time_fired < end_day)) + .where(Events.context_id == context_id) + ) + + +def apply_states_filters(query: Query, start_day: dt, end_day: dt) -> Query: + """Filter states by time range. + + Filters states that do not have an old state or new state (added / removed) + Filters states that are in a continuous domain with a UOM. + Filters states that do not have matching last_updated and last_changed. + """ + return ( + query.filter( + (States.last_updated > start_day) & (States.last_updated < end_day) + ) + .outerjoin(OLD_STATE, (States.old_state_id == OLD_STATE.state_id)) + .where(_missing_state_matcher()) + .where(_not_continuous_entity_matcher()) + .where( + (States.last_updated == States.last_changed) | States.last_changed.is_(None) + ) + .outerjoin( + StateAttributes, (States.attributes_id == StateAttributes.attributes_id) + ) + ) + + +def _missing_state_matcher() -> sqlalchemy.and_: + # The below removes state change events that do not have + # and old_state or the old_state is missing (newly added entities) + # or the new_state is missing (removed entities) + return sqlalchemy.and_( + OLD_STATE.state_id.isnot(None), + (States.state != OLD_STATE.state), + States.state.isnot(None), + ) + + +def _not_continuous_entity_matcher() -> sqlalchemy.or_: + """Match non continuous entities.""" + return sqlalchemy.or_( + _not_continuous_domain_matcher(), + sqlalchemy.and_( + _continuous_domain_matcher, _not_uom_attributes_matcher() + ).self_group(), + ) + + +def _not_continuous_domain_matcher() -> sqlalchemy.and_: + """Match not continuous domains.""" + return sqlalchemy.and_( + *[ + ~States.entity_id.like(entity_domain) + for entity_domain in CONTINUOUS_ENTITY_ID_LIKE + ], + ).self_group() + + +def _continuous_domain_matcher() -> sqlalchemy.or_: + """Match continuous domains.""" + return sqlalchemy.or_( + *[ + States.entity_id.like(entity_domain) + for entity_domain in CONTINUOUS_ENTITY_ID_LIKE + ], + ).self_group() + + +def _not_uom_attributes_matcher() -> ClauseList: + """Prefilter ATTR_UNIT_OF_MEASUREMENT as its much faster in sql.""" + return ~StateAttributes.shared_attrs.like( + UNIT_OF_MEASUREMENT_JSON_LIKE + ) | ~States.attributes.like(UNIT_OF_MEASUREMENT_JSON_LIKE) diff --git a/homeassistant/components/logbook/queries/devices.py b/homeassistant/components/logbook/queries/devices.py new file mode 100644 index 00000000000..64a6477017e --- /dev/null +++ b/homeassistant/components/logbook/queries/devices.py @@ -0,0 +1,84 @@ +"""Devices queries for logbook.""" +from __future__ import annotations + +from collections.abc import Iterable +from datetime import datetime as dt + +from sqlalchemy import lambda_stmt, select, union_all +from sqlalchemy.orm import Query +from sqlalchemy.sql.elements import ClauseList +from sqlalchemy.sql.lambdas import StatementLambdaElement +from sqlalchemy.sql.selectable import CTE, CompoundSelect + +from homeassistant.components.recorder.models import DEVICE_ID_IN_EVENT, Events, States + +from .common import ( + select_events_context_id_subquery, + select_events_context_only, + select_events_without_states, + select_states_context_only, +) + + +def _select_device_id_context_ids_sub_query( + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + json_quotable_device_ids: list[str], +) -> CompoundSelect: + """Generate a subquery to find context ids for multiple devices.""" + return select( + union_all( + select_events_context_id_subquery(start_day, end_day, event_types).where( + apply_event_device_id_matchers(json_quotable_device_ids) + ), + ).c.context_id + ) + + +def _apply_devices_context_union( + query: Query, + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + json_quotable_device_ids: list[str], +) -> CompoundSelect: + """Generate a CTE to find the device context ids and a query to find linked row.""" + devices_cte: CTE = _select_device_id_context_ids_sub_query( + start_day, + end_day, + event_types, + json_quotable_device_ids, + ).cte() + return query.union_all( + select_events_context_only().where(Events.context_id.in_(devices_cte.select())), + select_states_context_only().where(States.context_id.in_(devices_cte.select())), + ) + + +def devices_stmt( + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + json_quotable_device_ids: list[str], +) -> StatementLambdaElement: + """Generate a logbook query for multiple devices.""" + stmt = lambda_stmt( + lambda: _apply_devices_context_union( + select_events_without_states(start_day, end_day, event_types).where( + apply_event_device_id_matchers(json_quotable_device_ids) + ), + start_day, + end_day, + event_types, + json_quotable_device_ids, + ).order_by(Events.time_fired) + ) + return stmt + + +def apply_event_device_id_matchers( + json_quotable_device_ids: Iterable[str], +) -> ClauseList: + """Create matchers for the device_ids in the event_data.""" + return DEVICE_ID_IN_EVENT.in_(json_quotable_device_ids) diff --git a/homeassistant/components/logbook/queries/entities.py b/homeassistant/components/logbook/queries/entities.py new file mode 100644 index 00000000000..4fb211688f3 --- /dev/null +++ b/homeassistant/components/logbook/queries/entities.py @@ -0,0 +1,122 @@ +"""Entities queries for logbook.""" +from __future__ import annotations + +from collections.abc import Iterable +from datetime import datetime as dt + +import sqlalchemy +from sqlalchemy import lambda_stmt, select, union_all +from sqlalchemy.orm import Query +from sqlalchemy.sql.lambdas import StatementLambdaElement +from sqlalchemy.sql.selectable import CTE, CompoundSelect + +from homeassistant.components.recorder.models import ( + ENTITY_ID_IN_EVENT, + ENTITY_ID_LAST_UPDATED_INDEX, + OLD_ENTITY_ID_IN_EVENT, + Events, + States, +) + +from .common import ( + apply_states_filters, + select_events_context_id_subquery, + select_events_context_only, + select_events_without_states, + select_states, + select_states_context_only, +) + + +def _select_entities_context_ids_sub_query( + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + entity_ids: list[str], + json_quotable_entity_ids: list[str], +) -> CompoundSelect: + """Generate a subquery to find context ids for multiple entities.""" + return select( + union_all( + select_events_context_id_subquery(start_day, end_day, event_types).where( + apply_event_entity_id_matchers(json_quotable_entity_ids) + ), + apply_entities_hints(select(States.context_id)) + .filter((States.last_updated > start_day) & (States.last_updated < end_day)) + .where(States.entity_id.in_(entity_ids)), + ).c.context_id + ) + + +def _apply_entities_context_union( + query: Query, + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + entity_ids: list[str], + json_quotable_entity_ids: list[str], +) -> CompoundSelect: + """Generate a CTE to find the entity and device context ids and a query to find linked row.""" + entities_cte: CTE = _select_entities_context_ids_sub_query( + start_day, + end_day, + event_types, + entity_ids, + json_quotable_entity_ids, + ).cte() + return query.union_all( + states_query_for_entity_ids(start_day, end_day, entity_ids), + select_events_context_only().where( + Events.context_id.in_(entities_cte.select()) + ), + select_states_context_only() + .where(States.entity_id.not_in(entity_ids)) + .where(States.context_id.in_(entities_cte.select())), + ) + + +def entities_stmt( + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + entity_ids: list[str], + json_quotable_entity_ids: list[str], +) -> StatementLambdaElement: + """Generate a logbook query for multiple entities.""" + return lambda_stmt( + lambda: _apply_entities_context_union( + select_events_without_states(start_day, end_day, event_types).where( + apply_event_entity_id_matchers(json_quotable_entity_ids) + ), + start_day, + end_day, + event_types, + entity_ids, + json_quotable_entity_ids, + ).order_by(Events.time_fired) + ) + + +def states_query_for_entity_ids( + start_day: dt, end_day: dt, entity_ids: list[str] +) -> Query: + """Generate a select for states from the States table for specific entities.""" + return apply_states_filters( + apply_entities_hints(select_states()), start_day, end_day + ).where(States.entity_id.in_(entity_ids)) + + +def apply_event_entity_id_matchers( + json_quotable_entity_ids: Iterable[str], +) -> sqlalchemy.or_: + """Create matchers for the entity_id in the event_data.""" + return ENTITY_ID_IN_EVENT.in_( + json_quotable_entity_ids + ) | OLD_ENTITY_ID_IN_EVENT.in_(json_quotable_entity_ids) + + +def apply_entities_hints(query: Query) -> Query: + """Force mysql to use the right index on large selects.""" + return query.with_hint( + States, f"FORCE INDEX ({ENTITY_ID_LAST_UPDATED_INDEX})", dialect_name="mysql" + ) diff --git a/homeassistant/components/logbook/queries/entities_and_devices.py b/homeassistant/components/logbook/queries/entities_and_devices.py new file mode 100644 index 00000000000..d1c86ddbec5 --- /dev/null +++ b/homeassistant/components/logbook/queries/entities_and_devices.py @@ -0,0 +1,113 @@ +"""Entities and Devices queries for logbook.""" +from __future__ import annotations + +from collections.abc import Iterable +from datetime import datetime as dt + +import sqlalchemy +from sqlalchemy import lambda_stmt, select, union_all +from sqlalchemy.orm import Query +from sqlalchemy.sql.lambdas import StatementLambdaElement +from sqlalchemy.sql.selectable import CTE, CompoundSelect + +from homeassistant.components.recorder.models import Events, States + +from .common import ( + select_events_context_id_subquery, + select_events_context_only, + select_events_without_states, + select_states_context_only, +) +from .devices import apply_event_device_id_matchers +from .entities import ( + apply_entities_hints, + apply_event_entity_id_matchers, + states_query_for_entity_ids, +) + + +def _select_entities_device_id_context_ids_sub_query( + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + entity_ids: list[str], + json_quotable_entity_ids: list[str], + json_quotable_device_ids: list[str], +) -> CompoundSelect: + """Generate a subquery to find context ids for multiple entities and multiple devices.""" + return select( + union_all( + select_events_context_id_subquery(start_day, end_day, event_types).where( + _apply_event_entity_id_device_id_matchers( + json_quotable_entity_ids, json_quotable_device_ids + ) + ), + apply_entities_hints(select(States.context_id)) + .filter((States.last_updated > start_day) & (States.last_updated < end_day)) + .where(States.entity_id.in_(entity_ids)), + ).c.context_id + ) + + +def _apply_entities_devices_context_union( + query: Query, + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + entity_ids: list[str], + json_quotable_entity_ids: list[str], + json_quotable_device_ids: list[str], +) -> CompoundSelect: + devices_entities_cte: CTE = _select_entities_device_id_context_ids_sub_query( + start_day, + end_day, + event_types, + entity_ids, + json_quotable_entity_ids, + json_quotable_device_ids, + ).cte() + return query.union_all( + states_query_for_entity_ids(start_day, end_day, entity_ids), + select_events_context_only().where( + Events.context_id.in_(devices_entities_cte.select()) + ), + select_states_context_only() + .where(States.entity_id.not_in(entity_ids)) + .where(States.context_id.in_(devices_entities_cte.select())), + ) + + +def entities_devices_stmt( + start_day: dt, + end_day: dt, + event_types: tuple[str, ...], + entity_ids: list[str], + json_quotable_entity_ids: list[str], + json_quotable_device_ids: list[str], +) -> StatementLambdaElement: + """Generate a logbook query for multiple entities.""" + stmt = lambda_stmt( + lambda: _apply_entities_devices_context_union( + select_events_without_states(start_day, end_day, event_types).where( + _apply_event_entity_id_device_id_matchers( + json_quotable_entity_ids, json_quotable_device_ids + ) + ), + start_day, + end_day, + event_types, + entity_ids, + json_quotable_entity_ids, + json_quotable_device_ids, + ).order_by(Events.time_fired) + ) + return stmt + + +def _apply_event_entity_id_device_id_matchers( + json_quotable_entity_ids: Iterable[str], json_quotable_device_ids: Iterable[str] +) -> sqlalchemy.or_: + """Create matchers for the device_id and entity_id in the event_data.""" + return apply_event_entity_id_matchers( + json_quotable_entity_ids + ) | apply_event_device_id_matchers(json_quotable_device_ids) diff --git a/homeassistant/components/logbook/rest_api.py b/homeassistant/components/logbook/rest_api.py new file mode 100644 index 00000000000..a1a7db3ed2c --- /dev/null +++ b/homeassistant/components/logbook/rest_api.py @@ -0,0 +1,120 @@ +"""Event parser and human readable log generator.""" +from __future__ import annotations + +from datetime import timedelta +from http import HTTPStatus +from typing import Any, cast + +from aiohttp import web +import voluptuous as vol + +from homeassistant.components.http import HomeAssistantView +from homeassistant.components.recorder import get_instance +from homeassistant.components.recorder.filters import Filters +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import InvalidEntityFormatError +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.entityfilter import EntityFilter +from homeassistant.helpers.typing import ConfigType +import homeassistant.util.dt as dt_util + +from .helpers import async_determine_event_types +from .processor import EventProcessor + + +@callback +def async_setup( + hass: HomeAssistant, + conf: ConfigType, + filters: Filters | None, + entities_filter: EntityFilter | None, +) -> None: + """Set up the logbook rest API.""" + hass.http.register_view(LogbookView(conf, filters, entities_filter)) + + +class LogbookView(HomeAssistantView): + """Handle logbook view requests.""" + + url = "/api/logbook" + name = "api:logbook" + extra_urls = ["/api/logbook/{datetime}"] + + def __init__( + self, + config: dict[str, Any], + filters: Filters | None, + entities_filter: EntityFilter | None, + ) -> None: + """Initialize the logbook view.""" + self.config = config + self.filters = filters + self.entities_filter = entities_filter + + async def get( + self, request: web.Request, datetime: str | None = None + ) -> web.Response: + """Retrieve logbook entries.""" + if datetime: + if (datetime_dt := dt_util.parse_datetime(datetime)) is None: + return self.json_message("Invalid datetime", HTTPStatus.BAD_REQUEST) + else: + datetime_dt = dt_util.start_of_local_day() + + if (period_str := request.query.get("period")) is None: + period: int = 1 + else: + period = int(period_str) + + if entity_ids_str := request.query.get("entity"): + try: + entity_ids = cv.entity_ids(entity_ids_str) + except vol.Invalid: + raise InvalidEntityFormatError( + f"Invalid entity id(s) encountered: {entity_ids_str}. " + "Format should be ." + ) from vol.Invalid + else: + entity_ids = None + + if (end_time_str := request.query.get("end_time")) is None: + start_day = dt_util.as_utc(datetime_dt) - timedelta(days=period - 1) + end_day = start_day + timedelta(days=period) + else: + start_day = datetime_dt + if (end_day_dt := dt_util.parse_datetime(end_time_str)) is None: + return self.json_message("Invalid end_time", HTTPStatus.BAD_REQUEST) + end_day = end_day_dt + + hass = request.app["hass"] + + context_id = request.query.get("context_id") + + if entity_ids and context_id: + return self.json_message( + "Can't combine entity with context_id", HTTPStatus.BAD_REQUEST + ) + + event_types = async_determine_event_types(hass, entity_ids, None) + event_processor = EventProcessor( + hass, + event_types, + entity_ids, + None, + context_id, + timestamp=False, + include_entity_name=True, + ) + + def json_events() -> web.Response: + """Fetch events and generate JSON.""" + return self.json( + event_processor.get_events( + start_day, + end_day, + ) + ) + + return cast( + web.Response, await get_instance(hass).async_add_executor_job(json_events) + ) diff --git a/homeassistant/components/logbook/websocket_api.py b/homeassistant/components/logbook/websocket_api.py new file mode 100644 index 00000000000..1af44440803 --- /dev/null +++ b/homeassistant/components/logbook/websocket_api.py @@ -0,0 +1,487 @@ +"""Event parser and human readable log generator.""" +from __future__ import annotations + +import asyncio +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime as dt, timedelta +import logging +from typing import Any + +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.components.recorder import get_instance +from homeassistant.components.websocket_api import messages +from homeassistant.components.websocket_api.connection import ActiveConnection +from homeassistant.components.websocket_api.const import JSON_DUMP +from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback +from homeassistant.helpers.event import async_track_point_in_utc_time +import homeassistant.util.dt as dt_util + +from .helpers import ( + async_determine_event_types, + async_filter_entities, + async_subscribe_events, +) +from .models import async_event_to_row +from .processor import EventProcessor + +MAX_PENDING_LOGBOOK_EVENTS = 2048 +EVENT_COALESCE_TIME = 0.35 +MAX_RECORDER_WAIT = 10 +# minimum size that we will split the query +BIG_QUERY_HOURS = 25 +# how many hours to deliver in the first chunk when we split the query +BIG_QUERY_RECENT_HOURS = 24 + +_LOGGER = logging.getLogger(__name__) + + +@dataclass +class LogbookLiveStream: + """Track a logbook live stream.""" + + stream_queue: asyncio.Queue[Event] + subscriptions: list[CALLBACK_TYPE] + end_time_unsub: CALLBACK_TYPE | None = None + task: asyncio.Task | None = None + + +@callback +def async_setup(hass: HomeAssistant) -> None: + """Set up the logbook websocket API.""" + websocket_api.async_register_command(hass, ws_get_events) + websocket_api.async_register_command(hass, ws_event_stream) + + +async def _async_wait_for_recorder_sync(hass: HomeAssistant) -> None: + """Wait for the recorder to sync.""" + try: + await asyncio.wait_for( + get_instance(hass).async_block_till_done(), MAX_RECORDER_WAIT + ) + except asyncio.TimeoutError: + _LOGGER.debug( + "Recorder is behind more than %s seconds, starting live stream; Some results may be missing" + ) + + +async def _async_send_historical_events( + hass: HomeAssistant, + connection: ActiveConnection, + msg_id: int, + start_time: dt, + end_time: dt, + formatter: Callable[[int, Any], dict[str, Any]], + event_processor: EventProcessor, + partial: bool, +) -> dt | None: + """Select historical data from the database and deliver it to the websocket. + + If the query is considered a big query we will split the request into + two chunks so that they get the recent events first and the select + that is expected to take a long time comes in after to ensure + they are not stuck at a loading screen and can start looking at + the data right away. + + This function returns the time of the most recent event we sent to the + websocket. + """ + is_big_query = ( + not event_processor.entity_ids + and not event_processor.device_ids + and ((end_time - start_time) > timedelta(hours=BIG_QUERY_HOURS)) + ) + + if not is_big_query: + message, last_event_time = await _async_get_ws_stream_events( + hass, + msg_id, + start_time, + end_time, + formatter, + event_processor, + partial, + ) + # If there is no last_event_time, there are no historical + # results, but we still send an empty message + # if its the last one (not partial) so + # consumers of the api know their request was + # answered but there were no results + if last_event_time or not partial: + connection.send_message(message) + return last_event_time + + # This is a big query so we deliver + # the first three hours and then + # we fetch the old data + recent_query_start = end_time - timedelta(hours=BIG_QUERY_RECENT_HOURS) + recent_message, recent_query_last_event_time = await _async_get_ws_stream_events( + hass, + msg_id, + recent_query_start, + end_time, + formatter, + event_processor, + partial=True, + ) + if recent_query_last_event_time: + connection.send_message(recent_message) + + older_message, older_query_last_event_time = await _async_get_ws_stream_events( + hass, + msg_id, + start_time, + recent_query_start, + formatter, + event_processor, + partial, + ) + # If there is no last_event_time, there are no historical + # results, but we still send an empty message + # if its the last one (not partial) so + # consumers of the api know their request was + # answered but there were no results + if older_query_last_event_time or not partial: + connection.send_message(older_message) + + # Returns the time of the newest event + return recent_query_last_event_time or older_query_last_event_time + + +async def _async_get_ws_stream_events( + hass: HomeAssistant, + msg_id: int, + start_time: dt, + end_time: dt, + formatter: Callable[[int, Any], dict[str, Any]], + event_processor: EventProcessor, + partial: bool, +) -> tuple[str, dt | None]: + """Async wrapper around _ws_formatted_get_events.""" + return await get_instance(hass).async_add_executor_job( + _ws_stream_get_events, + msg_id, + start_time, + end_time, + formatter, + event_processor, + partial, + ) + + +def _ws_stream_get_events( + msg_id: int, + start_day: dt, + end_day: dt, + formatter: Callable[[int, Any], dict[str, Any]], + event_processor: EventProcessor, + partial: bool, +) -> tuple[str, dt | None]: + """Fetch events and convert them to json in the executor.""" + events = event_processor.get_events(start_day, end_day) + last_time = None + if events: + last_time = dt_util.utc_from_timestamp(events[-1]["when"]) + message = { + "events": events, + "start_time": dt_util.utc_to_timestamp(start_day), + "end_time": dt_util.utc_to_timestamp(end_day), + } + if partial: + # This is a hint to consumers of the api that + # we are about to send a another block of historical + # data in case the UI needs to show that historical + # data is still loading in the future + message["partial"] = True + return JSON_DUMP(formatter(msg_id, message)), last_time + + +async def _async_events_consumer( + subscriptions_setup_complete_time: dt, + connection: ActiveConnection, + msg_id: int, + stream_queue: asyncio.Queue[Event], + event_processor: EventProcessor, +) -> None: + """Stream events from the queue.""" + event_processor.switch_to_live() + + while True: + events: list[Event] = [await stream_queue.get()] + # If the event is older than the last db + # event we already sent it so we skip it. + if events[0].time_fired <= subscriptions_setup_complete_time: + continue + # We sleep for the EVENT_COALESCE_TIME so + # we can group events together to minimize + # the number of websocket messages when the + # system is overloaded with an event storm + await asyncio.sleep(EVENT_COALESCE_TIME) + while not stream_queue.empty(): + events.append(stream_queue.get_nowait()) + + if logbook_events := event_processor.humanify( + async_event_to_row(e) for e in events + ): + connection.send_message( + JSON_DUMP( + messages.event_message( + msg_id, + {"events": logbook_events}, + ) + ) + ) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "logbook/event_stream", + vol.Required("start_time"): str, + vol.Optional("end_time"): str, + vol.Optional("entity_ids"): [str], + vol.Optional("device_ids"): [str], + } +) +@websocket_api.async_response +async def ws_event_stream( + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict +) -> None: + """Handle logbook stream events websocket command.""" + start_time_str = msg["start_time"] + msg_id: int = msg["id"] + utc_now = dt_util.utcnow() + + if start_time := dt_util.parse_datetime(start_time_str): + start_time = dt_util.as_utc(start_time) + + if not start_time or start_time > utc_now: + connection.send_error(msg_id, "invalid_start_time", "Invalid start_time") + return + + end_time_str = msg.get("end_time") + end_time: dt | None = None + if end_time_str: + if not (end_time := dt_util.parse_datetime(end_time_str)): + connection.send_error(msg_id, "invalid_end_time", "Invalid end_time") + return + end_time = dt_util.as_utc(end_time) + if end_time < start_time: + connection.send_error(msg_id, "invalid_end_time", "Invalid end_time") + return + + device_ids = msg.get("device_ids") + entity_ids = msg.get("entity_ids") + if entity_ids: + entity_ids = async_filter_entities(hass, entity_ids) + event_types = async_determine_event_types(hass, entity_ids, device_ids) + event_processor = EventProcessor( + hass, + event_types, + entity_ids, + device_ids, + None, + timestamp=True, + include_entity_name=False, + ) + + if end_time and end_time <= utc_now: + # Not live stream but we it might be a big query + connection.subscriptions[msg_id] = callback(lambda: None) + connection.send_result(msg_id) + # Fetch everything from history + await _async_send_historical_events( + hass, + connection, + msg_id, + start_time, + end_time, + messages.event_message, + event_processor, + partial=False, + ) + return + + subscriptions: list[CALLBACK_TYPE] = [] + stream_queue: asyncio.Queue[Event] = asyncio.Queue(MAX_PENDING_LOGBOOK_EVENTS) + live_stream = LogbookLiveStream( + subscriptions=subscriptions, stream_queue=stream_queue + ) + + @callback + def _unsub(*time: Any) -> None: + """Unsubscribe from all events.""" + for subscription in subscriptions: + subscription() + subscriptions.clear() + if live_stream.task: + live_stream.task.cancel() + if live_stream.end_time_unsub: + live_stream.end_time_unsub() + + if end_time: + live_stream.end_time_unsub = async_track_point_in_utc_time( + hass, _unsub, end_time + ) + + @callback + def _queue_or_cancel(event: Event) -> None: + """Queue an event to be processed or cancel.""" + try: + stream_queue.put_nowait(event) + except asyncio.QueueFull: + _LOGGER.debug( + "Client exceeded max pending messages of %s", + MAX_PENDING_LOGBOOK_EVENTS, + ) + _unsub() + + async_subscribe_events( + hass, subscriptions, _queue_or_cancel, event_types, entity_ids, device_ids + ) + subscriptions_setup_complete_time = dt_util.utcnow() + connection.subscriptions[msg_id] = _unsub + connection.send_result(msg_id) + # Fetch everything from history + last_event_time = await _async_send_historical_events( + hass, + connection, + msg_id, + start_time, + subscriptions_setup_complete_time, + messages.event_message, + event_processor, + partial=True, + ) + + await _async_wait_for_recorder_sync(hass) + if msg_id not in connection.subscriptions: + # Unsubscribe happened while waiting for recorder + return + + # + # Fetch any events from the database that have + # not been committed since the original fetch + # so we can switch over to using the subscriptions + # + # We only want events that happened after the last event + # we had from the last database query or the maximum + # time we allow the recorder to be behind + # + max_recorder_behind = subscriptions_setup_complete_time - timedelta( + seconds=MAX_RECORDER_WAIT + ) + second_fetch_start_time = max( + last_event_time or max_recorder_behind, max_recorder_behind + ) + await _async_send_historical_events( + hass, + connection, + msg_id, + second_fetch_start_time, + subscriptions_setup_complete_time, + messages.event_message, + event_processor, + partial=False, + ) + + if not subscriptions: + # Unsubscribe happened while waiting for formatted events + # or there are no supported entities (all UOM or state class) + # or devices + return + + live_stream.task = asyncio.create_task( + _async_events_consumer( + subscriptions_setup_complete_time, + connection, + msg_id, + stream_queue, + event_processor, + ) + ) + + +def _ws_formatted_get_events( + msg_id: int, + start_time: dt, + end_time: dt, + event_processor: EventProcessor, +) -> str: + """Fetch events and convert them to json in the executor.""" + return JSON_DUMP( + messages.result_message( + msg_id, event_processor.get_events(start_time, end_time) + ) + ) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "logbook/get_events", + vol.Required("start_time"): str, + vol.Optional("end_time"): str, + vol.Optional("entity_ids"): [str], + vol.Optional("device_ids"): [str], + vol.Optional("context_id"): str, + } +) +@websocket_api.async_response +async def ws_get_events( + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict +) -> None: + """Handle logbook get events websocket command.""" + start_time_str = msg["start_time"] + end_time_str = msg.get("end_time") + utc_now = dt_util.utcnow() + + if start_time := dt_util.parse_datetime(start_time_str): + start_time = dt_util.as_utc(start_time) + else: + connection.send_error(msg["id"], "invalid_start_time", "Invalid start_time") + return + + if not end_time_str: + end_time = utc_now + elif parsed_end_time := dt_util.parse_datetime(end_time_str): + end_time = dt_util.as_utc(parsed_end_time) + else: + connection.send_error(msg["id"], "invalid_end_time", "Invalid end_time") + return + + if start_time > utc_now: + connection.send_result(msg["id"], []) + return + + device_ids = msg.get("device_ids") + entity_ids = msg.get("entity_ids") + context_id = msg.get("context_id") + if entity_ids: + entity_ids = async_filter_entities(hass, entity_ids) + if not entity_ids and not device_ids: + # Everything has been filtered away + connection.send_result(msg["id"], []) + return + + event_types = async_determine_event_types(hass, entity_ids, device_ids) + + event_processor = EventProcessor( + hass, + event_types, + entity_ids, + device_ids, + context_id, + timestamp=True, + include_entity_name=False, + ) + + connection.send_message( + await get_instance(hass).async_add_executor_job( + _ws_formatted_get_events, + msg["id"], + start_time, + end_time, + event_processor, + ) + ) diff --git a/homeassistant/components/logi_circle/translations/es.json b/homeassistant/components/logi_circle/translations/es.json index 6bd99289230..7fe62a28d05 100644 --- a/homeassistant/components/logi_circle/translations/es.json +++ b/homeassistant/components/logi_circle/translations/es.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "external_error": "Se produjo una excepci\u00f3n de otro flujo.", "external_setup": "Logi Circle se ha configurado correctamente a partir de otro flujo.", "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n." }, "error": { - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "follow_link": "Accede al enlace e identif\u00edcate antes de pulsar Enviar.", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" }, diff --git a/homeassistant/components/logi_circle/translations/nl.json b/homeassistant/components/logi_circle/translations/nl.json index 96231086830..fcf867036db 100644 --- a/homeassistant/components/logi_circle/translations/nl.json +++ b/homeassistant/components/logi_circle/translations/nl.json @@ -4,10 +4,10 @@ "already_configured": "Account is al geconfigureerd", "external_error": "Uitzondering opgetreden uit een andere stroom.", "external_setup": "Logi Circle is met succes geconfigureerd vanuit een andere stroom.", - "missing_configuration": "De component is niet geconfigureerd. Gelieve de documentatie volgen." + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie." }, "error": { - "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", "follow_link": "Volg de link en authenticeer voordat u op Verzenden drukt.", "invalid_auth": "Ongeldige authenticatie" }, diff --git a/homeassistant/components/lookin/translations/nl.json b/homeassistant/components/lookin/translations/nl.json index 89f94de97ca..67ccb5da847 100644 --- a/homeassistant/components/lookin/translations/nl.json +++ b/homeassistant/components/lookin/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken", "no_devices_found": "Geen apparaten gevonden op het netwerk" }, diff --git a/homeassistant/components/lovelace/translations/sv.json b/homeassistant/components/lovelace/translations/sv.json new file mode 100644 index 00000000000..b622e036a0f --- /dev/null +++ b/homeassistant/components/lovelace/translations/sv.json @@ -0,0 +1,7 @@ +{ + "system_health": { + "info": { + "dashboards": "Kontrollpaneler" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/translations/bg.json b/homeassistant/components/luftdaten/translations/bg.json index 784b010219d..5221cf5fc0e 100644 --- a/homeassistant/components/luftdaten/translations/bg.json +++ b/homeassistant/components/luftdaten/translations/bg.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0432\u0430\u043d\u0435 \u043d\u0430 \u043a\u0430\u0440\u0442\u0430\u0442\u0430", "station_id": "ID \u043d\u0430 \u0441\u0435\u043d\u0437\u043e\u0440\u0430 \u043d\u0430 Luftdaten" - }, - "title": "\u0414\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/ca.json b/homeassistant/components/luftdaten/translations/ca.json index 84794f6bccb..97ca6a0eebc 100644 --- a/homeassistant/components/luftdaten/translations/ca.json +++ b/homeassistant/components/luftdaten/translations/ca.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Mostrar al mapa", "station_id": "ID del sensor" - }, - "title": "Configuraci\u00f3 de Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/cs.json b/homeassistant/components/luftdaten/translations/cs.json index 2a9d786cf12..a3522f1fb7f 100644 --- a/homeassistant/components/luftdaten/translations/cs.json +++ b/homeassistant/components/luftdaten/translations/cs.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Uka\u017e na map\u011b", "station_id": "ID senzoru Luftdaten" - }, - "title": "Definujte Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/da.json b/homeassistant/components/luftdaten/translations/da.json index fa32979112b..0612d406aa5 100644 --- a/homeassistant/components/luftdaten/translations/da.json +++ b/homeassistant/components/luftdaten/translations/da.json @@ -8,8 +8,7 @@ "data": { "show_on_map": "Vis p\u00e5 kort", "station_id": "Luftdaten sensor-id" - }, - "title": "Definer Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/de.json b/homeassistant/components/luftdaten/translations/de.json index 09fd47e4e20..56615ee355f 100644 --- a/homeassistant/components/luftdaten/translations/de.json +++ b/homeassistant/components/luftdaten/translations/de.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Auf Karte anzeigen", "station_id": "Sensor-ID" - }, - "title": "Luftdaten einrichten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/el.json b/homeassistant/components/luftdaten/translations/el.json index 59519c251ef..5b2db34c47e 100644 --- a/homeassistant/components/luftdaten/translations/el.json +++ b/homeassistant/components/luftdaten/translations/el.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c7\u03ac\u03c1\u03c4\u03b7", "station_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1" - }, - "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/en.json b/homeassistant/components/luftdaten/translations/en.json index ed6983eaa92..fa6a65dcaa7 100644 --- a/homeassistant/components/luftdaten/translations/en.json +++ b/homeassistant/components/luftdaten/translations/en.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Show on map", "station_id": "Sensor ID" - }, - "title": "Define Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/es-419.json b/homeassistant/components/luftdaten/translations/es-419.json index e9e97ff034d..cdd130f7266 100644 --- a/homeassistant/components/luftdaten/translations/es-419.json +++ b/homeassistant/components/luftdaten/translations/es-419.json @@ -8,8 +8,7 @@ "data": { "show_on_map": "Mostrar en el mapa", "station_id": "ID del sensor de Luftdaten" - }, - "title": "Definir Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/es.json b/homeassistant/components/luftdaten/translations/es.json index bc33a13d870..075d30dc751 100644 --- a/homeassistant/components/luftdaten/translations/es.json +++ b/homeassistant/components/luftdaten/translations/es.json @@ -9,9 +9,8 @@ "user": { "data": { "show_on_map": "Mostrar en el mapa", - "station_id": "Sensro ID de Luftdaten" - }, - "title": "Definir Luftdaten" + "station_id": "ID del sensor" + } } } } diff --git a/homeassistant/components/luftdaten/translations/et.json b/homeassistant/components/luftdaten/translations/et.json index 148007a0251..96897e55784 100644 --- a/homeassistant/components/luftdaten/translations/et.json +++ b/homeassistant/components/luftdaten/translations/et.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Kuva kaardil", "station_id": "Anduri ID" - }, - "title": "M\u00e4\u00e4ratle Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/fi.json b/homeassistant/components/luftdaten/translations/fi.json index 452cda748f7..64a83b3ed50 100644 --- a/homeassistant/components/luftdaten/translations/fi.json +++ b/homeassistant/components/luftdaten/translations/fi.json @@ -5,8 +5,7 @@ "data": { "show_on_map": "N\u00e4yt\u00e4 kartalla", "station_id": "Luftdaten-anturin ID" - }, - "title": "M\u00e4\u00e4rit\u00e4 Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/fr.json b/homeassistant/components/luftdaten/translations/fr.json index 4eacd984ad8..225ce28d13d 100644 --- a/homeassistant/components/luftdaten/translations/fr.json +++ b/homeassistant/components/luftdaten/translations/fr.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Montrer sur la carte", "station_id": "ID du capteur" - }, - "title": "D\u00e9finir Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/hu.json b/homeassistant/components/luftdaten/translations/hu.json index 7439546f796..c867456e945 100644 --- a/homeassistant/components/luftdaten/translations/hu.json +++ b/homeassistant/components/luftdaten/translations/hu.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Megjelen\u00edt\u00e9s a t\u00e9rk\u00e9pen", "station_id": "\u00c9rz\u00e9kel\u0151 azonos\u00edt\u00f3" - }, - "title": "Luftdaten be\u00e1ll\u00edt\u00e1sa" + } } } } diff --git a/homeassistant/components/luftdaten/translations/id.json b/homeassistant/components/luftdaten/translations/id.json index 11fc2917061..2ed6e65e2c3 100644 --- a/homeassistant/components/luftdaten/translations/id.json +++ b/homeassistant/components/luftdaten/translations/id.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Tampilkan di peta", "station_id": "ID Sensor" - }, - "title": "Konfigurasikan Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/it.json b/homeassistant/components/luftdaten/translations/it.json index a39b3dd7e59..84a870fd8be 100644 --- a/homeassistant/components/luftdaten/translations/it.json +++ b/homeassistant/components/luftdaten/translations/it.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Mostra sulla mappa", "station_id": "ID sensore" - }, - "title": "Definisci Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/ja.json b/homeassistant/components/luftdaten/translations/ja.json index 15dc417c156..ab01dc703d6 100644 --- a/homeassistant/components/luftdaten/translations/ja.json +++ b/homeassistant/components/luftdaten/translations/ja.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "\u5730\u56f3\u306b\u8868\u793a", "station_id": "Luftdaten\u30bb\u30f3\u30b5\u30fcID" - }, - "title": "Luftdaten\u306e\u5b9a\u7fa9" + } } } } diff --git a/homeassistant/components/luftdaten/translations/ko.json b/homeassistant/components/luftdaten/translations/ko.json index fbb5a26e7ee..820f47803ad 100644 --- a/homeassistant/components/luftdaten/translations/ko.json +++ b/homeassistant/components/luftdaten/translations/ko.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "\uc9c0\ub3c4\uc5d0 \ud45c\uc2dc\ud558\uae30", "station_id": "Luftdaten \uc13c\uc11c ID" - }, - "title": "Luftdaten \uc815\uc758\ud558\uae30" + } } } } diff --git a/homeassistant/components/luftdaten/translations/lb.json b/homeassistant/components/luftdaten/translations/lb.json index 83254e05c83..5990ccefd17 100644 --- a/homeassistant/components/luftdaten/translations/lb.json +++ b/homeassistant/components/luftdaten/translations/lb.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Op der Kaart uweisen", "station_id": "Luftdaten Sensor ID" - }, - "title": "Luftdaten d\u00e9fin\u00e9ieren" + } } } } diff --git a/homeassistant/components/luftdaten/translations/nl.json b/homeassistant/components/luftdaten/translations/nl.json index 44ace40baa9..9da8bb945e7 100644 --- a/homeassistant/components/luftdaten/translations/nl.json +++ b/homeassistant/components/luftdaten/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "error": { - "already_configured": "Service is al geconfigureerd", + "already_configured": "Dienst is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", "invalid_sensor": "Sensor niet beschikbaar of ongeldig" }, @@ -10,8 +10,7 @@ "data": { "show_on_map": "Toon op kaart", "station_id": "Sensor ID" - }, - "title": "Definieer Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/no.json b/homeassistant/components/luftdaten/translations/no.json index 9ae681bcf91..bd5b6c97cda 100644 --- a/homeassistant/components/luftdaten/translations/no.json +++ b/homeassistant/components/luftdaten/translations/no.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Vis p\u00e5 kart", "station_id": "Sensor ID" - }, - "title": "" + } } } } diff --git a/homeassistant/components/luftdaten/translations/pl.json b/homeassistant/components/luftdaten/translations/pl.json index 7045a0a3dbe..03b004037c5 100644 --- a/homeassistant/components/luftdaten/translations/pl.json +++ b/homeassistant/components/luftdaten/translations/pl.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Poka\u017c na mapie", "station_id": "ID sensora" - }, - "title": "Konfiguracja Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/pt-BR.json b/homeassistant/components/luftdaten/translations/pt-BR.json index 82b1f09735b..877cc5d133c 100644 --- a/homeassistant/components/luftdaten/translations/pt-BR.json +++ b/homeassistant/components/luftdaten/translations/pt-BR.json @@ -8,10 +8,9 @@ "step": { "user": { "data": { - "show_on_map": "Mostrar no mapa", + "show_on_map": "[%key:component::iss::config::step::user::data::show_on_map%]", "station_id": "ID do Sensor Luftdaten" - }, - "title": "Definir Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/pt.json b/homeassistant/components/luftdaten/translations/pt.json index 5811494fffb..709ed6af0a1 100644 --- a/homeassistant/components/luftdaten/translations/pt.json +++ b/homeassistant/components/luftdaten/translations/pt.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Mostrar no mapa", "station_id": "Luftdaten Sensor ID" - }, - "title": "Definir Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/ru.json b/homeassistant/components/luftdaten/translations/ru.json index 82813ee3a53..cfb5ca68f93 100644 --- a/homeassistant/components/luftdaten/translations/ru.json +++ b/homeassistant/components/luftdaten/translations/ru.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435", "station_id": "ID \u0434\u0430\u0442\u0447\u0438\u043a\u0430" - }, - "title": "Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/sl.json b/homeassistant/components/luftdaten/translations/sl.json index 249a0258536..7697abcf9e7 100644 --- a/homeassistant/components/luftdaten/translations/sl.json +++ b/homeassistant/components/luftdaten/translations/sl.json @@ -8,8 +8,7 @@ "data": { "show_on_map": "Prika\u017ei na zemljevidu", "station_id": "Luftdaten ID Senzorja" - }, - "title": "Dolo\u010dite Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/sv.json b/homeassistant/components/luftdaten/translations/sv.json index 52180093ba4..63de146c6d7 100644 --- a/homeassistant/components/luftdaten/translations/sv.json +++ b/homeassistant/components/luftdaten/translations/sv.json @@ -8,8 +8,7 @@ "data": { "show_on_map": "Visa p\u00e5 karta", "station_id": "Luftdaten Sensor-ID" - }, - "title": "Definiera Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/tr.json b/homeassistant/components/luftdaten/translations/tr.json index a74b35252c1..f8bbaf818d3 100644 --- a/homeassistant/components/luftdaten/translations/tr.json +++ b/homeassistant/components/luftdaten/translations/tr.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "Haritada g\u00f6ster", "station_id": "Sens\u00f6r Kimli\u011fi" - }, - "title": "Luftdaten'i tan\u0131mlay\u0131n" + } } } } diff --git a/homeassistant/components/luftdaten/translations/uk.json b/homeassistant/components/luftdaten/translations/uk.json index 9fd33dc3da2..d4cb21d89bb 100644 --- a/homeassistant/components/luftdaten/translations/uk.json +++ b/homeassistant/components/luftdaten/translations/uk.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043d\u0430 \u043c\u0430\u043f\u0456", "station_id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0441\u0435\u043d\u0441\u043e\u0440\u0430 Luftdaten" - }, - "title": "Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/zh-Hans.json b/homeassistant/components/luftdaten/translations/zh-Hans.json index d66c44499df..193c2078bc1 100644 --- a/homeassistant/components/luftdaten/translations/zh-Hans.json +++ b/homeassistant/components/luftdaten/translations/zh-Hans.json @@ -9,8 +9,7 @@ "data": { "show_on_map": "\u5728\u5730\u56fe\u4e0a\u663e\u793a", "station_id": "Luftdaten \u4f20\u611f\u5668 ID" - }, - "title": "\u5b9a\u4e49 Luftdaten" + } } } } diff --git a/homeassistant/components/luftdaten/translations/zh-Hant.json b/homeassistant/components/luftdaten/translations/zh-Hant.json index ae9aa11da65..7fc2a8725e8 100644 --- a/homeassistant/components/luftdaten/translations/zh-Hant.json +++ b/homeassistant/components/luftdaten/translations/zh-Hant.json @@ -10,8 +10,7 @@ "data": { "show_on_map": "\u65bc\u5730\u5716\u986f\u793a", "station_id": "\u611f\u61c9\u5668 ID" - }, - "title": "\u5b9a\u7fa9 Luftdaten" + } } } } diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 3d9e07519a8..dd64ed4ec6f 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -12,7 +12,7 @@ from pylutron_caseta.smartbridge import Smartbridge import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import ATTR_SUGGESTED_AREA, CONF_HOST, Platform +from homeassistant.const import ATTR_DEVICE_ID, ATTR_SUGGESTED_AREA, CONF_HOST, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr @@ -197,15 +197,15 @@ def _async_register_button_devices( if "serial" not in device or device["serial"] in seen: continue seen.add(device["serial"]) + area, name = _area_and_name_from_name(device["name"]) device_args = { - "name": device["name"], + "name": f"{area} {name}", "manufacturer": MANUFACTURER, "config_entry_id": config_entry_id, "identifiers": {(DOMAIN, device["serial"])}, "model": f"{device['model']} ({device['type']})", "via_device": (DOMAIN, bridge_device["serial"]), } - area, _ = _area_and_name_from_name(device["name"]) if area != UNASSIGNED_AREA: device_args["suggested_area"] = area @@ -229,6 +229,7 @@ def _async_subscribe_pico_remote_events( button_devices_by_id: dict[int, dict], ): """Subscribe to lutron events.""" + dev_reg = dr.async_get(hass) @callback def _async_button_event(button_id, event_type): @@ -257,6 +258,7 @@ def _async_subscribe_pico_remote_events( ) return lip_button_number = sub_type_to_lip_button[sub_type] + hass_device = dev_reg.async_get_device({(DOMAIN, device["serial"])}) hass.bus.async_fire( LUTRON_CASETA_BUTTON_EVENT, @@ -266,6 +268,7 @@ def _async_subscribe_pico_remote_events( ATTR_BUTTON_NUMBER: lip_button_number, ATTR_LEAP_BUTTON_NUMBER: button_number, ATTR_DEVICE_NAME: name, + ATTR_DEVICE_ID: hass_device.id, ATTR_AREA_NAME: area, ATTR_ACTION: action, }, @@ -309,15 +312,16 @@ class LutronCasetaDevice(Entity): self._bridge_device = bridge_device if "serial" not in self._device: return + area, name = _area_and_name_from_name(device["name"]) + self._attr_name = full_name = f"{area} {name}" info = DeviceInfo( identifiers={(DOMAIN, self.serial)}, manufacturer=MANUFACTURER, model=f"{device['model']} ({device['type']})", - name=self.name, + name=full_name, via_device=(DOMAIN, self._bridge_device["serial"]), configuration_url=CONFIG_URL, ) - area, _ = _area_and_name_from_name(device["name"]) if area != UNASSIGNED_AREA: info[ATTR_SUGGESTED_AREA] = area self._attr_device_info = info @@ -331,11 +335,6 @@ class LutronCasetaDevice(Entity): """Return the device ID used for calling pylutron_caseta.""" return self._device["device_id"] - @property - def name(self): - """Return the name of the device.""" - return self._device["name"] - @property def serial(self): """Return the serial number of the device.""" @@ -350,3 +349,12 @@ class LutronCasetaDevice(Entity): def extra_state_attributes(self): """Return the state attributes.""" return {"device_id": self.device_id, "zone_id": self._device["zone"]} + + +class LutronCasetaDeviceUpdatableEntity(LutronCasetaDevice): + """A lutron_caseta entity that can update by syncing data from the bridge.""" + + async def async_update(self): + """Update when forcing a refresh of the device.""" + self._device = self._smartbridge.get_device_by_id(self.device_id) + _LOGGER.debug(self._device) diff --git a/homeassistant/components/lutron_caseta/binary_sensor.py b/homeassistant/components/lutron_caseta/binary_sensor.py index f61e644a331..6a6e3853280 100644 --- a/homeassistant/components/lutron_caseta/binary_sensor.py +++ b/homeassistant/components/lutron_caseta/binary_sensor.py @@ -26,22 +26,21 @@ async def async_setup_entry( Adds occupancy groups from the Caseta bridge associated with the config_entry as binary_sensor entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] bridge_device = data[BRIDGE_DEVICE] occupancy_groups = bridge.occupancy_groups - - for occupancy_group in occupancy_groups.values(): - entity = LutronOccupancySensor(occupancy_group, bridge, bridge_device) - entities.append(entity) - - async_add_entities(entities, True) + async_add_entities( + LutronOccupancySensor(occupancy_group, bridge, bridge_device) + for occupancy_group in occupancy_groups.values() + ) class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity): """Representation of a Lutron occupancy group.""" + _attr_device_class = BinarySensorDeviceClass.OCCUPANCY + def __init__(self, device, bridge, bridge_device): """Init an occupancy sensor.""" super().__init__(device, bridge, bridge_device) @@ -59,11 +58,6 @@ class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity): info[ATTR_SUGGESTED_AREA] = area self._attr_device_info = info - @property - def device_class(self): - """Flag supported features.""" - return BinarySensorDeviceClass.OCCUPANCY - @property def is_on(self): """Return the brightness of the light.""" diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py index 61c9c42a1b0..afddb2677a7 100644 --- a/homeassistant/components/lutron_caseta/cover.py +++ b/homeassistant/components/lutron_caseta/cover.py @@ -1,5 +1,4 @@ """Support for Lutron Caseta shades.""" -import logging from homeassistant.components.cover import ( ATTR_POSITION, @@ -12,11 +11,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import LutronCasetaDevice +from . import LutronCasetaDeviceUpdatableEntity from .const import BRIDGE_DEVICE, BRIDGE_LEAP, DOMAIN as CASETA_DOMAIN -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry( hass: HomeAssistant, @@ -28,20 +25,17 @@ async def async_setup_entry( Adds shades from the Caseta bridge associated with the config_entry as cover entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] bridge_device = data[BRIDGE_DEVICE] cover_devices = bridge.get_devices_by_domain(DOMAIN) - - for cover_device in cover_devices: - entity = LutronCasetaCover(cover_device, bridge, bridge_device) - entities.append(entity) - - async_add_entities(entities, True) + async_add_entities( + LutronCasetaCover(cover_device, bridge, bridge_device) + for cover_device in cover_devices + ) -class LutronCasetaCover(LutronCasetaDevice, CoverEntity): +class LutronCasetaCover(LutronCasetaDeviceUpdatableEntity, CoverEntity): """Representation of a Lutron shade.""" _attr_supported_features = ( @@ -50,6 +44,7 @@ class LutronCasetaCover(LutronCasetaDevice, CoverEntity): | CoverEntityFeature.STOP | CoverEntityFeature.SET_POSITION ) + _attr_device_class = CoverDeviceClass.SHADE @property def is_closed(self): @@ -61,11 +56,6 @@ class LutronCasetaCover(LutronCasetaDevice, CoverEntity): """Return the current position of cover.""" return self._device["current_state"] - @property - def device_class(self): - """Return the device class.""" - return CoverDeviceClass.SHADE - async def async_stop_cover(self, **kwargs): """Top the cover.""" await self._smartbridge.stop_cover(self.device_id) @@ -87,8 +77,3 @@ class LutronCasetaCover(LutronCasetaDevice, CoverEntity): if ATTR_POSITION in kwargs: position = kwargs[ATTR_POSITION] await self._smartbridge.set_value(self.device_id, position) - - async def async_update(self): - """Call when forcing a refresh of the device.""" - self._device = self._smartbridge.get_device_by_id(self.device_id) - _LOGGER.debug(self._device) diff --git a/homeassistant/components/lutron_caseta/device_trigger.py b/homeassistant/components/lutron_caseta/device_trigger.py index 4974a10d9ba..68394667764 100644 --- a/homeassistant/components/lutron_caseta/device_trigger.py +++ b/homeassistant/components/lutron_caseta/device_trigger.py @@ -1,8 +1,6 @@ """Provides device triggers for lutron caseta.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -329,7 +327,9 @@ TRIGGER_SCHEMA = vol.Any( ) -async def async_validate_trigger_config(hass: HomeAssistant, config: ConfigType): +async def async_validate_trigger_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: """Validate config.""" # if device is available verify parameters against device capabilities device = get_button_device_by_dr_id(hass, config[CONF_DEVICE_ID]) @@ -347,14 +347,14 @@ async def async_validate_trigger_config(hass: HomeAssistant, config: ConfigType) async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for lutron caseta devices.""" triggers = [] if not (device := get_button_device_by_dr_id(hass, device_id)): raise InvalidDeviceAutomationConfig(f"Device not found: {device_id}") - valid_buttons = DEVICE_TYPE_SUBTYPE_MAP_TO_LIP.get(device["type"], []) + valid_buttons = DEVICE_TYPE_SUBTYPE_MAP_TO_LIP.get(device["type"], {}) for trigger in SUPPORTED_INPUTS_EVENTS_TYPES: for subtype in valid_buttons: diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index 00236405735..cdf00959ed1 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -1,8 +1,6 @@ """Support for Lutron Caseta fans.""" from __future__ import annotations -import logging - from pylutron_caseta import FAN_HIGH, FAN_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_OFF from homeassistant.components.fan import DOMAIN, FanEntity, FanEntityFeature @@ -14,11 +12,9 @@ from homeassistant.util.percentage import ( percentage_to_ordered_list_item, ) -from . import LutronCasetaDevice +from . import LutronCasetaDeviceUpdatableEntity from .const import BRIDGE_DEVICE, BRIDGE_LEAP, DOMAIN as CASETA_DOMAIN -_LOGGER = logging.getLogger(__name__) - DEFAULT_ON_PERCENTAGE = 50 ORDERED_NAMED_FAN_SPEEDS = [FAN_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_HIGH] @@ -33,23 +29,20 @@ async def async_setup_entry( Adds fan controllers from the Caseta bridge associated with the config_entry as fan entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] bridge_device = data[BRIDGE_DEVICE] fan_devices = bridge.get_devices_by_domain(DOMAIN) - - for fan_device in fan_devices: - entity = LutronCasetaFan(fan_device, bridge, bridge_device) - entities.append(entity) - - async_add_entities(entities, True) + async_add_entities( + LutronCasetaFan(fan_device, bridge, bridge_device) for fan_device in fan_devices + ) -class LutronCasetaFan(LutronCasetaDevice, FanEntity): +class LutronCasetaFan(LutronCasetaDeviceUpdatableEntity, FanEntity): """Representation of a Lutron Caseta fan. Including Fan Speed.""" _attr_supported_features = FanEntityFeature.SET_SPEED + _attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS) @property def percentage(self) -> int | None: @@ -62,11 +55,6 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): ORDERED_NAMED_FAN_SPEEDS, self._device["fan_speed"] ) - @property - def speed_count(self) -> int: - """Return the number of speeds the fan supports.""" - return len(ORDERED_NAMED_FAN_SPEEDS) - async def async_turn_on( self, percentage: int = None, @@ -98,8 +86,3 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): def is_on(self): """Return true if device is on.""" return self.percentage and self.percentage > 0 - - async def async_update(self): - """Update when forcing a refresh of the device.""" - self._device = self._smartbridge.get_device_by_id(self.device_id) - _LOGGER.debug("State of this lutron fan device is %s", self._device) diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py index cba4450bf27..a58fc21aadf 100644 --- a/homeassistant/components/lutron_caseta/light.py +++ b/homeassistant/components/lutron_caseta/light.py @@ -1,6 +1,5 @@ """Support for Lutron Caseta lights.""" from datetime import timedelta -import logging from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -14,11 +13,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import LutronCasetaDevice +from . import LutronCasetaDeviceUpdatableEntity from .const import BRIDGE_DEVICE, BRIDGE_LEAP, DOMAIN as CASETA_DOMAIN -_LOGGER = logging.getLogger(__name__) - def to_lutron_level(level): """Convert the given Home Assistant light level (0-255) to Lutron (0-100).""" @@ -40,20 +37,17 @@ async def async_setup_entry( Adds dimmers from the Caseta bridge associated with the config_entry as light entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] bridge_device = data[BRIDGE_DEVICE] light_devices = bridge.get_devices_by_domain(DOMAIN) - - for light_device in light_devices: - entity = LutronCasetaLight(light_device, bridge, bridge_device) - entities.append(entity) - - async_add_entities(entities, True) + async_add_entities( + LutronCasetaLight(light_device, bridge, bridge_device) + for light_device in light_devices + ) -class LutronCasetaLight(LutronCasetaDevice, LightEntity): +class LutronCasetaLight(LutronCasetaDeviceUpdatableEntity, LightEntity): """Representation of a Lutron Light, including dimmable.""" _attr_color_mode = ColorMode.BRIGHTNESS @@ -88,8 +82,3 @@ class LutronCasetaLight(LutronCasetaDevice, LightEntity): def is_on(self): """Return true if device is on.""" return self._device["current_state"] > 0 - - async def async_update(self): - """Call when forcing a refresh of the device.""" - self._device = self._smartbridge.get_device_by_id(self.device_id) - _LOGGER.debug(self._device) diff --git a/homeassistant/components/lutron_caseta/logbook.py b/homeassistant/components/lutron_caseta/logbook.py new file mode 100644 index 00000000000..bcca548f64b --- /dev/null +++ b/homeassistant/components/lutron_caseta/logbook.py @@ -0,0 +1,46 @@ +"""Describe lutron_caseta logbook events.""" +from __future__ import annotations + +from collections.abc import Callable + +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) +from homeassistant.core import Event, HomeAssistant, callback + +from .const import ( + ATTR_ACTION, + ATTR_AREA_NAME, + ATTR_DEVICE_NAME, + ATTR_LEAP_BUTTON_NUMBER, + ATTR_TYPE, + DOMAIN, + LUTRON_CASETA_BUTTON_EVENT, +) +from .device_trigger import LEAP_TO_DEVICE_TYPE_SUBTYPE_MAP + + +@callback +def async_describe_events( + hass: HomeAssistant, + async_describe_event: Callable[[str, str, Callable[[Event], dict[str, str]]], None], +) -> None: + """Describe logbook events.""" + + @callback + def async_describe_button_event(event: Event) -> dict[str, str]: + """Describe lutron_caseta_button_event logbook event.""" + data = event.data + device_type = data[ATTR_TYPE] + leap_button_number = data[ATTR_LEAP_BUTTON_NUMBER] + button_map = LEAP_TO_DEVICE_TYPE_SUBTYPE_MAP[device_type] + button_description = button_map[leap_button_number] + return { + LOGBOOK_ENTRY_NAME: f"{data[ATTR_AREA_NAME]} {data[ATTR_DEVICE_NAME]}", + LOGBOOK_ENTRY_MESSAGE: f"{data[ATTR_ACTION]} {button_description}", + } + + async_describe_event( + DOMAIN, LUTRON_CASETA_BUTTON_EVENT, async_describe_button_event + ) diff --git a/homeassistant/components/lutron_caseta/scene.py b/homeassistant/components/lutron_caseta/scene.py index d81a741359c..d73d8011481 100644 --- a/homeassistant/components/lutron_caseta/scene.py +++ b/homeassistant/components/lutron_caseta/scene.py @@ -19,16 +19,10 @@ async def async_setup_entry( Adds scenes from the Caseta bridge associated with the config_entry as scene entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] scenes = bridge.get_scenes() - - for scene in scenes: - entity = LutronCasetaScene(scenes[scene], bridge) - entities.append(entity) - - async_add_entities(entities, True) + async_add_entities(LutronCasetaScene(scenes[scene], bridge) for scene in scenes) class LutronCasetaScene(Scene): @@ -36,15 +30,10 @@ class LutronCasetaScene(Scene): def __init__(self, scene, bridge): """Initialize the Lutron Caseta scene.""" - self._scene_name = scene["name"] + self._attr_name = scene["name"] self._scene_id = scene["scene_id"] self._bridge = bridge - @property - def name(self): - """Return the name of the scene.""" - return self._scene_name - async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" await self._bridge.activate_scene(self._scene_id) diff --git a/homeassistant/components/lutron_caseta/switch.py b/homeassistant/components/lutron_caseta/switch.py index 836968c3796..7e963352264 100644 --- a/homeassistant/components/lutron_caseta/switch.py +++ b/homeassistant/components/lutron_caseta/switch.py @@ -1,16 +1,13 @@ """Support for Lutron Caseta switches.""" -import logging from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import LutronCasetaDevice +from . import LutronCasetaDeviceUpdatableEntity from .const import BRIDGE_DEVICE, BRIDGE_LEAP, DOMAIN as CASETA_DOMAIN -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry( hass: HomeAssistant, @@ -22,21 +19,17 @@ async def async_setup_entry( Adds switches from the Caseta bridge associated with the config_entry as switch entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] bridge_device = data[BRIDGE_DEVICE] switch_devices = bridge.get_devices_by_domain(DOMAIN) - - for switch_device in switch_devices: - entity = LutronCasetaLight(switch_device, bridge, bridge_device) - entities.append(entity) - - async_add_entities(entities, True) - return True + async_add_entities( + LutronCasetaLight(switch_device, bridge, bridge_device) + for switch_device in switch_devices + ) -class LutronCasetaLight(LutronCasetaDevice, SwitchEntity): +class LutronCasetaLight(LutronCasetaDeviceUpdatableEntity, SwitchEntity): """Representation of a Lutron Caseta switch.""" async def async_turn_on(self, **kwargs): @@ -51,8 +44,3 @@ class LutronCasetaLight(LutronCasetaDevice, SwitchEntity): def is_on(self): """Return true if device is on.""" return self._device["current_state"] > 0 - - async def async_update(self): - """Update when forcing a refresh of the device.""" - self._device = self._smartbridge.get_device_by_id(self.device_id) - _LOGGER.debug(self._device) diff --git a/homeassistant/components/lutron_caseta/translations/es.json b/homeassistant/components/lutron_caseta/translations/es.json index 098a90377d8..d13fded562e 100644 --- a/homeassistant/components/lutron_caseta/translations/es.json +++ b/homeassistant/components/lutron_caseta/translations/es.json @@ -8,10 +8,10 @@ "error": { "cannot_connect": "No se pudo conectar" }, - "flow_title": "Lutron Cas\u00e9ta {name} ({host})", + "flow_title": "{name} ({host})", "step": { "import_failed": { - "description": "No se puede configurar bridge (host: {host}) importado desde configuration.yaml.", + "description": "No se ha podido configurar el enlace (anfitri\u00f3n: {host}) importado de configuration.yaml.", "title": "Error al importar la configuraci\u00f3n del bridge Cas\u00e9ta." }, "link": { diff --git a/homeassistant/components/lutron_caseta/translations/nl.json b/homeassistant/components/lutron_caseta/translations/nl.json index c2a342123d0..0d1063eee8c 100644 --- a/homeassistant/components/lutron_caseta/translations/nl.json +++ b/homeassistant/components/lutron_caseta/translations/nl.json @@ -2,11 +2,11 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "not_lutron_device": "Ontdekt apparaat is geen Lutron-apparaat" }, "error": { - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "flow_title": "{name} ({host})", "step": { diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py index 3709fa5445f..2eaee440ae7 100644 --- a/homeassistant/components/lyric/__init__.py +++ b/homeassistant/components/lyric/__init__.py @@ -13,6 +13,10 @@ from aiolyric.objects.location import LyricLocation import async_timeout import voluptuous as vol +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, Platform from homeassistant.core import HomeAssistant @@ -36,18 +40,20 @@ from .api import ( LyricLocalOAuth2Implementation, OAuth2SessionLyric, ) -from .config_flow import OAuth2FlowHandler -from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN +from .const import DOMAIN CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_CLIENT_SECRET): cv.string, - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) @@ -63,20 +69,23 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if DOMAIN not in config: return True - hass.data[DOMAIN][CONF_CLIENT_ID] = config[DOMAIN][CONF_CLIENT_ID] - - OAuth2FlowHandler.async_register_implementation( + await async_import_client_credential( hass, - LyricLocalOAuth2Implementation( - hass, - DOMAIN, + DOMAIN, + ClientCredential( config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET], - OAUTH2_AUTHORIZE, - OAUTH2_TOKEN, ), ) + _LOGGER.warning( + "Configuration of Honeywell Lyric integration in YAML is deprecated " + "and will be removed in a future release; Your existing OAuth " + "Application Credentials have been imported into the UI " + "automatically and can be safely removed from your " + "configuration.yaml file" + ) + return True @@ -87,13 +96,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass, entry ) ) + if not isinstance(implementation, LyricLocalOAuth2Implementation): + raise ValueError("Unexpected auth implementation; can't find oauth client id") session = aiohttp_client.async_get_clientsession(hass) oauth_session = OAuth2SessionLyric(hass, entry, implementation) client = ConfigEntryLyricClient(session, oauth_session) - client_id = hass.data[DOMAIN][CONF_CLIENT_ID] + client_id = implementation.client_id lyric = Lyric(client, client_id) async def async_update_data(force_refresh_token: bool = False) -> Lyric: diff --git a/homeassistant/components/lyric/api.py b/homeassistant/components/lyric/api.py index 4a8aa44417f..171e010137a 100644 --- a/homeassistant/components/lyric/api.py +++ b/homeassistant/components/lyric/api.py @@ -4,6 +4,7 @@ from typing import cast from aiohttp import BasicAuth, ClientSession from aiolyric.client import LyricClient +from homeassistant.components.application_credentials import AuthImplementation from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -41,7 +42,7 @@ class ConfigEntryLyricClient(LyricClient): class LyricLocalOAuth2Implementation( - config_entry_oauth2_flow.LocalOAuth2Implementation + AuthImplementation, ): """Lyric Local OAuth2 implementation.""" diff --git a/homeassistant/components/lyric/application_credentials.py b/homeassistant/components/lyric/application_credentials.py new file mode 100644 index 00000000000..2ccdca72bb6 --- /dev/null +++ b/homeassistant/components/lyric/application_credentials.py @@ -0,0 +1,26 @@ +"""Application credentials platform for the Honeywell Lyric integration.""" + +from homeassistant.components.application_credentials import ( + AuthorizationServer, + ClientCredential, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow + +from .api import LyricLocalOAuth2Implementation +from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN + + +async def async_get_auth_implementation( + hass: HomeAssistant, auth_domain: str, credential: ClientCredential +) -> config_entry_oauth2_flow.AbstractOAuth2Implementation: + """Return custom auth implementation.""" + return LyricLocalOAuth2Implementation( + hass, + auth_domain, + credential, + AuthorizationServer( + authorize_url=OAUTH2_AUTHORIZE, + token_url=OAUTH2_TOKEN, + ), + ) diff --git a/homeassistant/components/lyric/manifest.json b/homeassistant/components/lyric/manifest.json index da60b046eb7..c0d9168f46f 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": ["auth"], + "dependencies": ["application_credentials"], "requirements": ["aiolyric==1.0.8"], "codeowners": ["@timmo001"], "quality_scale": "silver", diff --git a/homeassistant/components/lyric/translations/es.json b/homeassistant/components/lyric/translations/es.json index 404a812e676..12692849ce3 100644 --- a/homeassistant/components/lyric/translations/es.json +++ b/homeassistant/components/lyric/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n.", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, diff --git a/homeassistant/components/lyric/translations/ko.json b/homeassistant/components/lyric/translations/ko.json index 37093d340df..9a4b1cce222 100644 --- a/homeassistant/components/lyric/translations/ko.json +++ b/homeassistant/components/lyric/translations/ko.json @@ -13,6 +13,7 @@ "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, "reauth_confirm": { + "description": "Lyric \ud1b5\ud569\uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" } } diff --git a/homeassistant/components/lyric/translations/nl.json b/homeassistant/components/lyric/translations/nl.json index 0d1f9da12e8..e820174aa5a 100644 --- a/homeassistant/components/lyric/translations/nl.json +++ b/homeassistant/components/lyric/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "De component is niet geconfigureerd. Gelieve de documentatie volgen.", - "reauth_successful": "Herauthenticatie was succesvol" + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "reauth_successful": "Herauthenticatie geslaagd" }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "step": { "pick_implementation": { @@ -14,7 +14,7 @@ }, "reauth_confirm": { "description": "De Lyric-integratie moet uw account opnieuw verifi\u00ebren.", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" } } } diff --git a/homeassistant/components/mailgun/translations/es.json b/homeassistant/components/mailgun/translations/es.json index 50290059f54..1fcd54a5266 100644 --- a/homeassistant/components/mailgun/translations/es.json +++ b/homeassistant/components/mailgun/translations/es.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No conectado a Home Assistant Cloud.", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n.", "webhook_not_internet_accessible": "Tu instancia de Home Assistant debe estar accesible desde Internet para recibir mensajes webhook." }, diff --git a/homeassistant/components/mailgun/translations/fr.json b/homeassistant/components/mailgun/translations/fr.json index 80c961babba..9d114cec058 100644 --- a/homeassistant/components/mailgun/translations/fr.json +++ b/homeassistant/components/mailgun/translations/fr.json @@ -10,7 +10,7 @@ }, "step": { "user": { - "description": "\u00cates-vous s\u00fbr de vouloir configurer Mailgun?", + "description": "Voulez-vous vraiment configurer Mailgun\u00a0?", "title": "Configurer le Webhook Mailgun" } } diff --git a/homeassistant/components/mailgun/translations/nl.json b/homeassistant/components/mailgun/translations/nl.json index 5e84c62a314..309c5b502dc 100644 --- a/homeassistant/components/mailgun/translations/nl.json +++ b/homeassistant/components/mailgun/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." + "webhook_not_internet_accessible": "Je Home Assistant-instantie moet toegankelijk zijn vanaf internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Om evenementen naar Home Assistant te verzenden, moet u [Webhooks with Mailgun]({mailgun_url}) instellen. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n - Inhoudstype: application/json \n\n Zie [de documentatie]({docs_url}) voor informatie over het configureren van automatiseringen om binnenkomende gegevens te verwerken." diff --git a/homeassistant/components/mazda/sensor.py b/homeassistant/components/mazda/sensor.py index 99f8c74d64d..7e8b45e0ca1 100644 --- a/homeassistant/components/mazda/sensor.py +++ b/homeassistant/components/mazda/sensor.py @@ -188,6 +188,7 @@ SENSOR_ENTITIES = [ key="front_left_tire_pressure", name_suffix="Front Left Tire Pressure", icon="mdi:car-tire-alert", + device_class=SensorDeviceClass.PRESSURE, native_unit_of_measurement=PRESSURE_PSI, state_class=SensorStateClass.MEASUREMENT, is_supported=_front_left_tire_pressure_supported, @@ -197,6 +198,7 @@ SENSOR_ENTITIES = [ key="front_right_tire_pressure", name_suffix="Front Right Tire Pressure", icon="mdi:car-tire-alert", + device_class=SensorDeviceClass.PRESSURE, native_unit_of_measurement=PRESSURE_PSI, state_class=SensorStateClass.MEASUREMENT, is_supported=_front_right_tire_pressure_supported, @@ -206,6 +208,7 @@ SENSOR_ENTITIES = [ key="rear_left_tire_pressure", name_suffix="Rear Left Tire Pressure", icon="mdi:car-tire-alert", + device_class=SensorDeviceClass.PRESSURE, native_unit_of_measurement=PRESSURE_PSI, state_class=SensorStateClass.MEASUREMENT, is_supported=_rear_left_tire_pressure_supported, @@ -215,6 +218,7 @@ SENSOR_ENTITIES = [ key="rear_right_tire_pressure", name_suffix="Rear Right Tire Pressure", icon="mdi:car-tire-alert", + device_class=SensorDeviceClass.PRESSURE, native_unit_of_measurement=PRESSURE_PSI, state_class=SensorStateClass.MEASUREMENT, is_supported=_rear_right_tire_pressure_supported, diff --git a/homeassistant/components/mazda/translations/es.json b/homeassistant/components/mazda/translations/es.json index f0ba0f4da49..01140eb3aad 100644 --- a/homeassistant/components/mazda/translations/es.json +++ b/homeassistant/components/mazda/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { diff --git a/homeassistant/components/mazda/translations/nl.json b/homeassistant/components/mazda/translations/nl.json index f7532b9fc45..5b267b914cb 100644 --- a/homeassistant/components/mazda/translations/nl.json +++ b/homeassistant/components/mazda/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "account_locked": "Account vergrendeld. Probeer het later nog eens.", diff --git a/homeassistant/components/meater/__init__.py b/homeassistant/components/meater/__init__.py index a722928a13f..904d9e412c0 100644 --- a/homeassistant/components/meater/__init__.py +++ b/homeassistant/components/meater/__init__.py @@ -14,7 +14,7 @@ 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.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -40,8 +40,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 + raise ConfigEntryAuthFailed( + f"Unable to authenticate with the Meater API: {err}" + ) from err async def async_update_data() -> dict[str, MeaterProbe]: """Fetch data from API endpoint.""" @@ -51,7 +52,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 + raise ConfigEntryAuthFailed("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" diff --git a/homeassistant/components/meater/config_flow.py b/homeassistant/components/meater/config_flow.py index 1b1a8a0eca4..07dbd4bd4a5 100644 --- a/homeassistant/components/meater/config_flow.py +++ b/homeassistant/components/meater/config_flow.py @@ -1,14 +1,18 @@ """Config flow for Meater.""" +from __future__ import annotations + 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.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import DOMAIN -FLOW_SCHEMA = vol.Schema( +REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) +USER_SCHEMA = vol.Schema( {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} ) @@ -16,12 +20,17 @@ FLOW_SCHEMA = vol.Schema( class MeaterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Meater Config Flow.""" - async def async_step_user(self, user_input=None): + _data_schema = USER_SCHEMA + _username: str + + async def async_step_user( + self, user_input: dict[str, str] | None = None + ) -> FlowResult: """Define the login user step.""" if user_input is None: return self.async_show_form( step_id="user", - data_schema=FLOW_SCHEMA, + data_schema=self._data_schema, ) username: str = user_input[CONF_USERNAME] @@ -31,13 +40,41 @@ class MeaterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] + return await self._try_connect_meater("user", None, username, password) + + async def async_step_reauth(self, data: dict[str, str]) -> FlowResult: + """Handle configuration by re-auth.""" + self._data_schema = REAUTH_SCHEMA + self._username = data[CONF_USERNAME] + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, str] | None = None + ) -> FlowResult: + """Handle re-auth completion.""" + placeholders = {"username": self._username} + if not user_input: + return self.async_show_form( + step_id="reauth_confirm", + data_schema=self._data_schema, + description_placeholders=placeholders, + ) + + password = user_input[CONF_PASSWORD] + return await self._try_connect_meater( + "reauth_confirm", placeholders, self._username, password + ) + + async def _try_connect_meater( + self, step_id, placeholders: dict[str, str] | None, username: str, password: str + ) -> FlowResult: session = aiohttp_client.async_get_clientsession(self.hass) api = MeaterApi(session) errors = {} try: - await api.authenticate(user_input[CONF_USERNAME], user_input[CONF_PASSWORD]) + await api.authenticate(username, password) except AuthenticationError: errors["base"] = "invalid_auth" except ServiceUnavailableError: @@ -45,13 +82,20 @@ class MeaterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): except Exception: # pylint: disable=broad-except errors["base"] = "unknown_auth_error" else: + data = {"username": username, "password": password} + existing_entry = await self.async_set_unique_id(username.lower()) + 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="Meater", - data={"username": username, "password": password}, + data=data, ) return self.async_show_form( - step_id="user", - data_schema=FLOW_SCHEMA, + step_id=step_id, + data_schema=self._data_schema, + description_placeholders=placeholders, errors=errors, ) diff --git a/homeassistant/components/meater/sensor.py b/homeassistant/components/meater/sensor.py index fecde148a5e..84ef3a2e2a9 100644 --- a/homeassistant/components/meater/sensor.py +++ b/homeassistant/components/meater/sensor.py @@ -52,7 +52,7 @@ 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) + return dt_util.utcnow() + timedelta(seconds=probe.cook.time_remaining) SENSOR_TYPES = ( @@ -136,9 +136,9 @@ 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" - ] + coordinator: DataUpdateCoordinator[dict[str, MeaterProbe]] = hass.data[DOMAIN][ + entry.entry_id + ]["coordinator"] @callback def async_update_data(): @@ -146,22 +146,22 @@ async def async_setup_entry( if not coordinator.last_update_success: return - devices: dict[str, MeaterProbe] = coordinator.data + 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 in known_probes: + for device_id in devices: + if device_id in known_probes: continue entities.extend( [ - MeaterProbeTemperature(coordinator, dev, sensor_description) + MeaterProbeTemperature(coordinator, device_id, sensor_description) for sensor_description in SENSOR_TYPES ] ) - known_probes.add(dev) + known_probes.add(device_id) async_add_entities(entities) diff --git a/homeassistant/components/meater/strings.json b/homeassistant/components/meater/strings.json index 772e6afd080..635c71d324c 100644 --- a/homeassistant/components/meater/strings.json +++ b/homeassistant/components/meater/strings.json @@ -6,6 +6,15 @@ "data": { "password": "[%key:common::config_flow::data::password%]", "username": "[%key:common::config_flow::data::username%]" + }, + "data_description": { + "username": "Meater Cloud username, typically an email address." + } + }, + "reauth_confirm": { + "description": "Confirm the password for Meater Cloud account {username}.", + "data": { + "password": "[%key:common::config_flow::data::password%]" } } }, diff --git a/homeassistant/components/meater/translations/bg.json b/homeassistant/components/meater/translations/bg.json index e5396fbc999..cb1a84abf51 100644 --- a/homeassistant/components/meater/translations/bg.json +++ b/homeassistant/components/meater/translations/bg.json @@ -5,6 +5,11 @@ "unknown_auth_error": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, "step": { + "reauth_confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + } + }, "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u0430", diff --git a/homeassistant/components/meater/translations/ca.json b/homeassistant/components/meater/translations/ca.json index 0174767bc52..cd2c626ef8a 100644 --- a/homeassistant/components/meater/translations/ca.json +++ b/homeassistant/components/meater/translations/ca.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Error inesperat" }, "step": { + "reauth_confirm": { + "data": { + "password": "Contrasenya" + }, + "description": "Confirma la contrasenya del compte de Meater Cloud {username}." + }, "user": { "data": { "password": "Contrasenya", "username": "Nom d'usuari" }, + "data_description": { + "username": "Nom d'usuari de Meater Cloud, normalment una adre\u00e7a de correu electr\u00f2nic." + }, "description": "Configura el teu compte de Meater Cloud." } } diff --git a/homeassistant/components/meater/translations/de.json b/homeassistant/components/meater/translations/de.json index ed143e26b4c..9e85c5e56f8 100644 --- a/homeassistant/components/meater/translations/de.json +++ b/homeassistant/components/meater/translations/de.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Unerwarteter Fehler" }, "step": { + "reauth_confirm": { + "data": { + "password": "Passwort" + }, + "description": "Best\u00e4tige das Passwort f\u00fcr das Meater Cloud-Konto {username} ." + }, "user": { "data": { "password": "Passwort", "username": "Benutzername" }, + "data_description": { + "username": "Meater Cloud-Benutzername, normalerweise eine E-Mail-Adresse." + }, "description": "Richte dein Meater Cloud-Konto ein." } } diff --git a/homeassistant/components/meater/translations/el.json b/homeassistant/components/meater/translations/el.json index 2d02110dd49..3114eb17286 100644 --- a/homeassistant/components/meater/translations/el.json +++ b/homeassistant/components/meater/translations/el.json @@ -6,11 +6,20 @@ "unknown_auth_error": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { + "reauth_confirm": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, + "description": "\u0395\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03b9\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Meater Cloud {username}." + }, "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" }, + "data_description": { + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 Meater Cloud, \u03c3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 email." + }, "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." } } diff --git a/homeassistant/components/meater/translations/en.json b/homeassistant/components/meater/translations/en.json index 3ceb94bcef0..707c6dc6ed6 100644 --- a/homeassistant/components/meater/translations/en.json +++ b/homeassistant/components/meater/translations/en.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Unexpected error" }, "step": { + "reauth_confirm": { + "data": { + "password": "Password" + }, + "description": "Confirm the password for Meater Cloud account {username}." + }, "user": { "data": { "password": "Password", "username": "Username" }, + "data_description": { + "username": "Meater Cloud username, typically an email address." + }, "description": "Set up your Meater Cloud account." } } diff --git a/homeassistant/components/meater/translations/es.json b/homeassistant/components/meater/translations/es.json new file mode 100644 index 00000000000..39d35b38d4a --- /dev/null +++ b/homeassistant/components/meater/translations/es.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "service_unavailable_error": "La API no est\u00e1 disponible actualmente, vuelva a intentarlo m\u00e1s tarde.", + "unknown_auth_error": "Error inesperado" + }, + "step": { + "reauth_confirm": { + "description": "Confirma la contrase\u00f1a de la cuenta de Meater Cloud {username}." + }, + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/et.json b/homeassistant/components/meater/translations/et.json index 55328467d3a..dc5f6f9102f 100644 --- a/homeassistant/components/meater/translations/et.json +++ b/homeassistant/components/meater/translations/et.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Ootamatu t\u00f5rge" }, "step": { + "reauth_confirm": { + "data": { + "password": "Salas\u00f5na" + }, + "description": "Kinnita Meater Cloudi konto {username} salas\u00f5na." + }, "user": { "data": { "password": "Salas\u00f5na", "username": "Kasutajanimi" }, + "data_description": { + "username": "Meater Cloudi kasutajanimi, tavaliselt e-posti aadress." + }, "description": "Seadista Meater Cloudi konto." } } diff --git a/homeassistant/components/meater/translations/fr.json b/homeassistant/components/meater/translations/fr.json index 9940cb6e24b..af9b76d722a 100644 --- a/homeassistant/components/meater/translations/fr.json +++ b/homeassistant/components/meater/translations/fr.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Erreur inattendue" }, "step": { + "reauth_confirm": { + "data": { + "password": "Mot de passe" + }, + "description": "Confirmez le mot de passe du compte Meater Cloud {username}." + }, "user": { "data": { "password": "Mot de passe", "username": "Nom d'utilisateur" }, + "data_description": { + "username": "Nom d'utilisateur Meater Cloud, g\u00e9n\u00e9ralement une adresse \u00e9lectronique." + }, "description": "Configurez votre compte Meater Cloud." } } diff --git a/homeassistant/components/meater/translations/he.json b/homeassistant/components/meater/translations/he.json index f1376b2cf0d..59aa348444f 100644 --- a/homeassistant/components/meater/translations/he.json +++ b/homeassistant/components/meater/translations/he.json @@ -5,6 +5,11 @@ "unknown_auth_error": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" }, "step": { + "reauth_confirm": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + }, "user": { "data": { "password": "\u05e1\u05d9\u05e1\u05de\u05d4", diff --git a/homeassistant/components/meater/translations/hu.json b/homeassistant/components/meater/translations/hu.json index fd55bae3bdd..964c240b4e3 100644 --- a/homeassistant/components/meater/translations/hu.json +++ b/homeassistant/components/meater/translations/hu.json @@ -6,11 +6,20 @@ "unknown_auth_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "reauth_confirm": { + "data": { + "password": "Jelsz\u00f3" + }, + "description": "Er\u0151s\u00edtse meg a Meater Cloud-fi\u00f3k {username} jelszav\u00e1t." + }, "user": { "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, + "data_description": { + "username": "Meater Cloud felhaszn\u00e1l\u00f3n\u00e9v, \u00e1ltal\u00e1ban egy e-mail c\u00edm." + }, "description": "\u00c1ll\u00edtsa be a Meater Cloud fi\u00f3kj\u00e1t." } } diff --git a/homeassistant/components/meater/translations/id.json b/homeassistant/components/meater/translations/id.json index 5d9e28c583c..e27bc97fce5 100644 --- a/homeassistant/components/meater/translations/id.json +++ b/homeassistant/components/meater/translations/id.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Kesalahan yang tidak diharapkan" }, "step": { + "reauth_confirm": { + "data": { + "password": "Kata Sandi" + }, + "description": "Konfirmasikan kata sandi untuk akun Meater Cloud {username}." + }, "user": { "data": { "password": "Kata Sandi", "username": "Nama Pengguna" }, + "data_description": { + "username": "Nama pengguna Meater Cloud, biasanya berupa alamat email." + }, "description": "Siapkan akun Meater Cloud Anda." } } diff --git a/homeassistant/components/meater/translations/it.json b/homeassistant/components/meater/translations/it.json index fe3bc189ef6..d006b2f7e64 100644 --- a/homeassistant/components/meater/translations/it.json +++ b/homeassistant/components/meater/translations/it.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Errore imprevisto" }, "step": { + "reauth_confirm": { + "data": { + "password": "Password" + }, + "description": "Conferma la password per l'account Meater Cloud {username}." + }, "user": { "data": { "password": "Password", "username": "Nome utente" }, + "data_description": { + "username": "Nome utente di Meater Cloud, tipicamente un indirizzo email" + }, "description": "Configura il tuo account Meater Cloud." } } diff --git a/homeassistant/components/meater/translations/ja.json b/homeassistant/components/meater/translations/ja.json index db4dd2e9c6b..72912036ddd 100644 --- a/homeassistant/components/meater/translations/ja.json +++ b/homeassistant/components/meater/translations/ja.json @@ -6,11 +6,20 @@ "unknown_auth_error": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { + "reauth_confirm": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" + }, + "description": "Meater Cloud\u30a2\u30ab\u30a6\u30f3\u30c8 {username} \u306e\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + }, "user": { "data": { "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", "username": "\u30e6\u30fc\u30b6\u30fc\u540d" }, + "data_description": { + "username": "Meater Cloud\u306e\u30e6\u30fc\u30b6\u30fc\u540d\u3001\u901a\u5e38\u306f\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u3067\u3059\u3002" + }, "description": "Meater Cloud\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" } } diff --git a/homeassistant/components/meater/translations/ko.json b/homeassistant/components/meater/translations/ko.json new file mode 100644 index 00000000000..b414109dedc --- /dev/null +++ b/homeassistant/components/meater/translations/ko.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "service_unavailable_error": "API\ub294 \ud604\uc7ac \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \uc8fc\uc138\uc694.", + "unknown_auth_error": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "reauth_confirm": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638" + }, + "description": "Meater Cloud \uacc4\uc815 {\uc0ac\uc6a9\uc790 \uc774\ub984}\uc5d0 \ub300\ud55c \uc554\ud638\ub97c \ud655\uc778\ud569\ub2c8\ub2e4." + }, + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "data_description": { + "username": "Meater Cloud \uc0ac\uc6a9\uc790 \uc774\ub984, \uc77c\ubc18\uc801\uc73c\ub85c \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4." + }, + "description": "Meater Cloud \uacc4\uc815\uc744 \uc124\uc815\ud569\ub2c8\ub2e4." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/nl.json b/homeassistant/components/meater/translations/nl.json index a87175c7574..05165dbaa8a 100644 --- a/homeassistant/components/meater/translations/nl.json +++ b/homeassistant/components/meater/translations/nl.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Onverwachte fout" }, "step": { + "reauth_confirm": { + "data": { + "password": "Wachtwoord" + }, + "description": "Bevestig het wachtwoord voor Meater Cloud account {username}." + }, "user": { "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" }, + "data_description": { + "username": "Meater Cloud gebruikersnaam, meestal een e-mailadres." + }, "description": "Stel uw Meater Cloud-account in." } } diff --git a/homeassistant/components/meater/translations/no.json b/homeassistant/components/meater/translations/no.json index 60bec4e6864..f97f8ce5f8f 100644 --- a/homeassistant/components/meater/translations/no.json +++ b/homeassistant/components/meater/translations/no.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Uventet feil" }, "step": { + "reauth_confirm": { + "data": { + "password": "Passord" + }, + "description": "Bekreft passordet for Meater Cloud-kontoen {username} ." + }, "user": { "data": { "password": "Passord", "username": "Brukernavn" }, + "data_description": { + "username": "Meater Cloud brukernavn, vanligvis en e-postadresse." + }, "description": "Sett opp din Meater Cloud-konto." } } diff --git a/homeassistant/components/meater/translations/pl.json b/homeassistant/components/meater/translations/pl.json index 1816069dd34..1afaffb4bb7 100644 --- a/homeassistant/components/meater/translations/pl.json +++ b/homeassistant/components/meater/translations/pl.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Nieoczekiwany b\u0142\u0105d" }, "step": { + "reauth_confirm": { + "data": { + "password": "Has\u0142o" + }, + "description": "Potwierd\u017a has\u0142o do konta Meater Cloud {username}." + }, "user": { "data": { "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" }, + "data_description": { + "username": "Nazwa u\u017cytkownika Meater Cloud, zazwyczaj adres e-mail." + }, "description": "Skonfiguruj swoje konto w Meater Cloud." } } diff --git a/homeassistant/components/meater/translations/pt-BR.json b/homeassistant/components/meater/translations/pt-BR.json index 103f3f76986..4e379482baa 100644 --- a/homeassistant/components/meater/translations/pt-BR.json +++ b/homeassistant/components/meater/translations/pt-BR.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Erro inesperado" }, "step": { + "reauth_confirm": { + "data": { + "password": "Senha" + }, + "description": "Confirme a senha da conta do Meeater Cloud {username}." + }, "user": { "data": { "password": "Senha", "username": "Usu\u00e1rio" }, + "data_description": { + "username": "Nome de usu\u00e1rio do Meeater Cloud, normalmente um endere\u00e7o de e-mail." + }, "description": "Configure sua conta Meeater Cloud." } } diff --git a/homeassistant/components/meater/translations/ru.json b/homeassistant/components/meater/translations/ru.json index a56f2f8ebbe..182e2d03e33 100644 --- a/homeassistant/components/meater/translations/ru.json +++ b/homeassistant/components/meater/translations/ru.json @@ -6,11 +6,20 @@ "unknown_auth_error": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { + "reauth_confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Meater Cloud {username}." + }, "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" }, + "data_description": { + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f Meater Cloud, \u043e\u0431\u044b\u0447\u043d\u043e \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b." + }, "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." } } diff --git a/homeassistant/components/meater/translations/sk.json b/homeassistant/components/meater/translations/sk.json new file mode 100644 index 00000000000..a52d3b46e7c --- /dev/null +++ b/homeassistant/components/meater/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "service_unavailable_error": "Rozhranie API je moment\u00e1lne nedostupn\u00e9, sk\u00faste to pros\u00edm nesk\u00f4r." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/sv.json b/homeassistant/components/meater/translations/sv.json new file mode 100644 index 00000000000..383fbbeb5a6 --- /dev/null +++ b/homeassistant/components/meater/translations/sv.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "password": "L\u00f6senord" + }, + "description": "Bekr\u00e4fta l\u00f6senordet f\u00f6r Meater Cloud-kontot {username}." + }, + "user": { + "data_description": { + "username": "Meater Cloud anv\u00e4ndarnamn, vanligtvis en e-postadress." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/tr.json b/homeassistant/components/meater/translations/tr.json index 03f77d2ed51..14618f6c955 100644 --- a/homeassistant/components/meater/translations/tr.json +++ b/homeassistant/components/meater/translations/tr.json @@ -6,11 +6,20 @@ "unknown_auth_error": "Beklenmeyen hata" }, "step": { + "reauth_confirm": { + "data": { + "password": "Parola" + }, + "description": "Meater Bulut hesab\u0131 {username} i\u00e7in parolay\u0131 onaylay\u0131n." + }, "user": { "data": { "password": "Parola", "username": "Kullan\u0131c\u0131 Ad\u0131" }, + "data_description": { + "username": "Meater Cloud kullan\u0131c\u0131 ad\u0131, genellikle bir e-posta adresi." + }, "description": "Meater Cloud hesab\u0131n\u0131z\u0131 kurun." } } diff --git a/homeassistant/components/meater/translations/zh-Hant.json b/homeassistant/components/meater/translations/zh-Hant.json index b04f4a54076..1033f5c993a 100644 --- a/homeassistant/components/meater/translations/zh-Hant.json +++ b/homeassistant/components/meater/translations/zh-Hant.json @@ -6,11 +6,20 @@ "unknown_auth_error": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { + "reauth_confirm": { + "data": { + "password": "\u5bc6\u78bc" + }, + "description": "\u78ba\u8a8d Meater Cloud \u5e33\u865f {username} \u5bc6\u78bc\u3002" + }, "user": { "data": { "password": "\u5bc6\u78bc", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, + "data_description": { + "username": "Meater Cloud \u4f7f\u7528\u8005\u540d\u7a31\u3001\u901a\u5e38\u70ba\u96fb\u5b50\u90f5\u4ef6\u4f4d\u5740\u3002" + }, "description": "\u8a2d\u5b9a Meater Cloud \u5e33\u865f\u3002" } } diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index bf006e2bd4e..dc2f3624a0e 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -76,6 +76,7 @@ from .const import ( # noqa: F401 ATTR_INPUT_SOURCE_LIST, ATTR_MEDIA_ALBUM_ARTIST, ATTR_MEDIA_ALBUM_NAME, + ATTR_MEDIA_ANNOUNCE, ATTR_MEDIA_ARTIST, ATTR_MEDIA_CHANNEL, ATTR_MEDIA_CONTENT_ID, @@ -147,6 +148,19 @@ ENTITY_IMAGE_CACHE = {CACHE_IMAGES: collections.OrderedDict(), CACHE_MAXSIZE: 16 SCAN_INTERVAL = dt.timedelta(seconds=10) +class MediaPlayerEnqueue(StrEnum): + """Enqueue types for playing media.""" + + # add given media item to end of the queue + ADD = "add" + # play the given media item next, keep queue + NEXT = "next" + # play the given media item now, keep queue + PLAY = "play" + # play the given media item now, clear queue + REPLACE = "replace" + + class MediaPlayerDeviceClass(StrEnum): """Device class for media players.""" @@ -169,7 +183,10 @@ DEVICE_CLASS_RECEIVER = MediaPlayerDeviceClass.RECEIVER.value MEDIA_PLAYER_PLAY_MEDIA_SCHEMA = { vol.Required(ATTR_MEDIA_CONTENT_TYPE): cv.string, vol.Required(ATTR_MEDIA_CONTENT_ID): cv.string, - vol.Optional(ATTR_MEDIA_ENQUEUE): cv.boolean, + vol.Exclusive(ATTR_MEDIA_ENQUEUE, "enqueue_announce"): vol.Any( + cv.boolean, vol.Coerce(MediaPlayerEnqueue) + ), + vol.Exclusive(ATTR_MEDIA_ANNOUNCE, "enqueue_announce"): cv.boolean, vol.Optional(ATTR_MEDIA_EXTRA, default={}): dict, } @@ -350,10 +367,30 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: "async_select_sound_mode", [MediaPlayerEntityFeature.SELECT_SOUND_MODE], ) + + # Remove in Home Assistant 2022.9 + def _rewrite_enqueue(value): + """Rewrite the enqueue value.""" + if ATTR_MEDIA_ENQUEUE not in value: + pass + elif value[ATTR_MEDIA_ENQUEUE] is True: + value[ATTR_MEDIA_ENQUEUE] = MediaPlayerEnqueue.ADD + _LOGGER.warning( + "Playing media with enqueue set to True is deprecated. Use 'add' instead" + ) + elif value[ATTR_MEDIA_ENQUEUE] is False: + value[ATTR_MEDIA_ENQUEUE] = MediaPlayerEnqueue.PLAY + _LOGGER.warning( + "Playing media with enqueue set to False is deprecated. Use 'play' instead" + ) + + return value + component.async_register_entity_service( SERVICE_PLAY_MEDIA, vol.All( cv.make_entity_service_schema(MEDIA_PLAYER_PLAY_MEDIA_SCHEMA), + _rewrite_enqueue, _rename_keys( media_type=ATTR_MEDIA_CONTENT_TYPE, media_id=ATTR_MEDIA_CONTENT_ID, diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py index b12f0c4ae01..4d534467ad6 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -10,6 +10,7 @@ ATTR_ENTITY_PICTURE_LOCAL = "entity_picture_local" ATTR_GROUP_MEMBERS = "group_members" ATTR_INPUT_SOURCE = "source" ATTR_INPUT_SOURCE_LIST = "source_list" +ATTR_MEDIA_ANNOUNCE = "announce" ATTR_MEDIA_ALBUM_ARTIST = "media_album_artist" ATTR_MEDIA_ALBUM_NAME = "media_album_name" ATTR_MEDIA_ARTIST = "media_artist" diff --git a/homeassistant/components/media_player/device_condition.py b/homeassistant/components/media_player/device_condition.py index 5f57bcea48e..2f398790ac3 100644 --- a/homeassistant/components/media_player/device_condition.py +++ b/homeassistant/components/media_player/device_condition.py @@ -45,7 +45,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Media player devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) conditions = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/media_player/device_trigger.py b/homeassistant/components/media_player/device_trigger.py index aeed2fd646a..e0c88489841 100644 --- a/homeassistant/components/media_player/device_trigger.py +++ b/homeassistant/components/media_player/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for Media player.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -55,9 +53,9 @@ TRIGGER_SCHEMA = vol.All( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Media player entities.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers = await entity.async_get_triggers(hass, device_id, DOMAIN) # Get all the integration entities for this device diff --git a/homeassistant/components/media_player/reproduce_state.py b/homeassistant/components/media_player/reproduce_state.py index 586ac61b4e1..bdfc0bf3acb 100644 --- a/homeassistant/components/media_player/reproduce_state.py +++ b/homeassistant/components/media_player/reproduce_state.py @@ -27,7 +27,6 @@ from .const import ( ATTR_INPUT_SOURCE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, - ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_SOUND_MODE, @@ -118,7 +117,7 @@ async def _async_reproduce_states( if features & MediaPlayerEntityFeature.PLAY_MEDIA: await call_service( SERVICE_PLAY_MEDIA, - [ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_ENQUEUE], + [ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_CONTENT_ID], ) already_playing = True diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index 2e8585d0127..b698b87aec6 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -151,6 +151,29 @@ play_media: selector: text: + enqueue: + name: Enqueue + description: If the content should be played now or be added to the queue. + required: false + selector: + select: + options: + - label: "Play now" + value: "play" + - label: "Play next" + value: "next" + - label: "Add to queue" + value: "add" + - label: "Play now and clear queue" + value: "replace" + announce: + name: Announce + description: If the media should be played as an announcement. + required: false + example: "true" + selector: + boolean: + select_source: name: Select source description: Send the media player the command to change input source. diff --git a/homeassistant/components/media_player/translations/bg.json b/homeassistant/components/media_player/translations/bg.json index 408f3c1e682..ddc7ab9ee6b 100644 --- a/homeassistant/components/media_player/translations/bg.json +++ b/homeassistant/components/media_player/translations/bg.json @@ -10,6 +10,7 @@ }, "state": { "_": { + "buffering": "\u0411\u0443\u0444\u0435\u0440\u0438\u0440\u0430\u043d\u0435", "idle": "\u041d\u0435\u0440\u0430\u0431\u043e\u0442\u0435\u0449", "off": "\u0418\u0437\u043a\u043b\u044e\u0447\u0435\u043d", "on": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d", diff --git a/homeassistant/components/media_player/translations/ca.json b/homeassistant/components/media_player/translations/ca.json index eede8911e5f..98ad4bb5a95 100644 --- a/homeassistant/components/media_player/translations/ca.json +++ b/homeassistant/components/media_player/translations/ca.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} s'est\u00e0 carregant en mem\u00f2ria", "is_idle": "{entity_name} est\u00e0 inactiu", "is_off": "{entity_name} est\u00e0 apagat", "is_on": "{entity_name} est\u00e0 enc\u00e8s", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} est\u00e0 reproduint" }, "trigger_type": { + "buffering": "{entity_name} comen\u00e7a a carregar-se en mem\u00f2ria", "changed_states": "{entity_name} canvia d'estat", "idle": "{entity_name} es torna inactiu", "paused": "{entity_name} est\u00e0 en pausa", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Carregant", "idle": "Inactiu", "off": "OFF", "on": "ON", diff --git a/homeassistant/components/media_player/translations/es.json b/homeassistant/components/media_player/translations/es.json index 0dfc063a035..fd60d09f562 100644 --- a/homeassistant/components/media_player/translations/es.json +++ b/homeassistant/components/media_player/translations/es.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} se est\u00e1 cargando en memoria", "is_idle": "{entity_name} est\u00e1 inactivo", "is_off": "{entity_name} est\u00e1 apagado", "is_on": "{entity_name} est\u00e1 activado", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} est\u00e1 reproduciendo" }, "trigger_type": { + "buffering": "{entity_name} comienza a cargarse en memoria", "changed_states": "{entity_name} ha cambiado de estado", "idle": "{entity_name} est\u00e1 inactivo", "paused": "{entity_name} est\u00e1 en pausa", @@ -18,12 +20,13 @@ }, "state": { "_": { + "buffering": "Cargando", "idle": "Inactivo", "off": "Apagado", "on": "Encendido", "paused": "En pausa", "playing": "Reproduciendo", - "standby": "Apagado" + "standby": "En espera" } }, "title": "Reproductor multimedia" diff --git a/homeassistant/components/media_player/translations/he.json b/homeassistant/components/media_player/translations/he.json index d4ba1b29bcb..645490856af 100644 --- a/homeassistant/components/media_player/translations/he.json +++ b/homeassistant/components/media_player/translations/he.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} \u05d0\u05d5\u05d2\u05e8", "is_idle": "{entity_name} \u05de\u05de\u05ea\u05d9\u05df", "is_off": "{entity_name} \u05db\u05d1\u05d5\u05d9", "is_on": "{entity_name} \u05e4\u05d5\u05e2\u05dc", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} \u05de\u05ea\u05e0\u05d2\u05df" }, "trigger_type": { + "buffering": "{entity_name} \u05de\u05ea\u05d7\u05d9\u05dc \u05dc\u05d0\u05d2\u05d5\u05e8", "changed_states": "{entity_name} \u05e9\u05d9\u05e0\u05d4 \u05de\u05e6\u05d1", "idle": "{entity_name} \u05d4\u05d5\u05e4\u05da \u05dc\u05de\u05de\u05ea\u05d9\u05df", "paused": "{entity_name} \u05de\u05d5\u05e9\u05d4\u05d4", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "\u05d0\u05d5\u05d2\u05e8", "idle": "\u05de\u05de\u05ea\u05d9\u05df", "off": "\u05db\u05d1\u05d5\u05d9", "on": "\u05de\u05d5\u05e4\u05e2\u05dc", diff --git a/homeassistant/components/media_player/translations/ja.json b/homeassistant/components/media_player/translations/ja.json index c553308a3e7..a11bc516b38 100644 --- a/homeassistant/components/media_player/translations/ja.json +++ b/homeassistant/components/media_player/translations/ja.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} \u306f\u3001\u30d0\u30c3\u30d5\u30a1\u30ea\u30f3\u30b0\u4e2d\u3067\u3059", "is_idle": "{entity_name} \u306f\u3001\u30a2\u30a4\u30c9\u30eb\u72b6\u614b\u3067\u3059", "is_off": "{entity_name} \u306f\u30aa\u30d5\u3067\u3059", "is_on": "{entity_name} \u304c\u30aa\u30f3\u3067\u3059", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} \u304c\u518d\u751f\u3055\u308c\u3066\u3044\u307e\u3059" }, "trigger_type": { + "buffering": "{entity_name} \u306e\u30d0\u30c3\u30d5\u30a1\u30ea\u30f3\u30b0\u3092\u958b\u59cb\u3057\u307e\u3059", "changed_states": "{entity_name} \u306e\u72b6\u614b\u304c\u5909\u66f4\u3055\u308c\u307e\u3057\u305f", "idle": "{entity_name} \u304c\u30a2\u30a4\u30c9\u30eb\u72b6\u614b\u306b\u306a\u308a\u307e\u3059", "paused": "{entity_name} \u306f\u3001\u4e00\u6642\u505c\u6b62\u3057\u3066\u3044\u307e\u3059", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "\u30d0\u30c3\u30d5\u30a1\u30ea\u30f3\u30b0", "idle": "\u30a2\u30a4\u30c9\u30eb", "off": "\u30aa\u30d5", "on": "\u30aa\u30f3", diff --git a/homeassistant/components/media_player/translations/nl.json b/homeassistant/components/media_player/translations/nl.json index 23fe64d452a..f3ad43fb5ec 100644 --- a/homeassistant/components/media_player/translations/nl.json +++ b/homeassistant/components/media_player/translations/nl.json @@ -25,7 +25,7 @@ "off": "Uit", "on": "Aan", "paused": "Gepauzeerd", - "playing": "Afspelen", + "playing": "Speelt", "standby": "Stand-by" } }, diff --git a/homeassistant/components/media_player/translations/ru.json b/homeassistant/components/media_player/translations/ru.json index 74a4fd3fbbd..bb531479c32 100644 --- a/homeassistant/components/media_player/translations/ru.json +++ b/homeassistant/components/media_player/translations/ru.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0431\u0443\u0444\u0435\u0440\u0438\u0437\u0430\u0446\u0438\u044e", "is_idle": "{entity_name} \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f", "is_off": "{entity_name} \u0432 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438", "is_on": "{entity_name} \u0432\u043e \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442 \u043c\u0435\u0434\u0438\u0430" }, "trigger_type": { + "buffering": "{entity_name} \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0431\u0443\u0444\u0435\u0440\u0438\u0437\u0430\u0446\u0438\u044e", "changed_states": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435", "idle": "{entity_name} \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442 \u0432 \u0440\u0435\u0436\u0438\u043c \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f", "paused": "{entity_name} \u043d\u0430 \u043f\u0430\u0443\u0437\u0435", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "\u0411\u0443\u0444\u0435\u0440\u0438\u0437\u0430\u0446\u0438\u044f", "idle": "\u0411\u0435\u0437\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435", "off": "\u0412\u044b\u043a\u043b\u044e\u0447\u0435\u043d", "on": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d", diff --git a/homeassistant/components/media_player/translations/sv.json b/homeassistant/components/media_player/translations/sv.json index 95f098f2f3e..6aae6d6b208 100644 --- a/homeassistant/components/media_player/translations/sv.json +++ b/homeassistant/components/media_player/translations/sv.json @@ -1,15 +1,20 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} buffrar", "is_idle": "{entity_name} \u00e4r inaktiv", "is_off": "{entity_name} \u00e4r avst\u00e4ngd", "is_on": "{entity_name} \u00e4r p\u00e5", "is_paused": "{entity_name} \u00e4r pausad", "is_playing": "{entity_name} spelar" + }, + "trigger_type": { + "buffering": "{entity_name} b\u00f6rjar buffra" } }, "state": { "_": { + "buffering": "Buffrar", "idle": "Inaktiv", "off": "Av", "on": "P\u00e5", diff --git a/homeassistant/components/media_player/translations/tr.json b/homeassistant/components/media_player/translations/tr.json index 5782730f6bf..62958c54fea 100644 --- a/homeassistant/components/media_player/translations/tr.json +++ b/homeassistant/components/media_player/translations/tr.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} arabelle\u011fe al\u0131yor", "is_idle": "{entity_name} bo\u015fta", "is_off": "{entity_name} kapal\u0131", "is_on": "{entity_name} a\u00e7\u0131k", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} oynat\u0131l\u0131yor" }, "trigger_type": { + "buffering": "{entity_name} arabelle\u011fe almaya ba\u015flar", "changed_states": "{entity_name} durumlar\u0131 de\u011fi\u015ftirdi", "idle": "{entity_name} bo\u015fta", "paused": "{entity_name} duraklat\u0131ld\u0131", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "\u00d6n belle\u011fe al\u0131n\u0131yor", "idle": "Bo\u015fta", "off": "Kapal\u0131", "on": "A\u00e7\u0131k", diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 3c42016f8f7..4818934d1dd 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -18,10 +18,11 @@ from homeassistant.components.media_player.browse_media import ( ) from homeassistant.components.websocket_api import ActiveConnection from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.frame import report from homeassistant.helpers.integration_platform import ( async_process_integration_platforms, ) -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import UNDEFINED, ConfigType, UndefinedType from homeassistant.loader import bind_hass from . import local_source @@ -80,15 +81,15 @@ async def _process_media_source_platform( @callback def _get_media_item( - hass: HomeAssistant, media_content_id: str | None + hass: HomeAssistant, media_content_id: str | None, target_media_player: str | None ) -> MediaSourceItem: """Return media item.""" if media_content_id: - item = MediaSourceItem.from_uri(hass, media_content_id) + item = MediaSourceItem.from_uri(hass, media_content_id, target_media_player) else: # We default to our own domain if its only one registered domain = None if len(hass.data[DOMAIN]) > 1 else DOMAIN - return MediaSourceItem(hass, domain, "") + return MediaSourceItem(hass, domain, "", target_media_player) if item.domain is not None and item.domain not in hass.data[DOMAIN]: raise ValueError("Unknown media source") @@ -108,7 +109,7 @@ async def async_browse_media( raise BrowseError("Media Source not loaded") try: - item = await _get_media_item(hass, media_content_id).async_browse() + item = await _get_media_item(hass, media_content_id, None).async_browse() except ValueError as err: raise BrowseError(str(err)) from err @@ -124,13 +125,21 @@ async def async_browse_media( @bind_hass -async def async_resolve_media(hass: HomeAssistant, media_content_id: str) -> PlayMedia: +async def async_resolve_media( + hass: HomeAssistant, + media_content_id: str, + target_media_player: str | None | UndefinedType = UNDEFINED, +) -> PlayMedia: """Get info to play media.""" if DOMAIN not in hass.data: raise Unresolvable("Media Source not loaded") + if target_media_player is UNDEFINED: + report("calls media_source.async_resolve_media without passing an entity_id") + target_media_player = None + try: - item = _get_media_item(hass, media_content_id) + item = _get_media_item(hass, media_content_id, target_media_player) except ValueError as err: raise Unresolvable(str(err)) from err diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index 89feba5317f..863380b7600 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -264,7 +264,7 @@ class UploadMediaView(http.HomeAssistantView): raise web.HTTPBadRequest() from err try: - item = MediaSourceItem.from_uri(self.hass, data["media_content_id"]) + item = MediaSourceItem.from_uri(self.hass, data["media_content_id"], None) except ValueError as err: LOGGER.error("Received invalid upload data: %s", err) raise web.HTTPBadRequest() from err @@ -328,7 +328,7 @@ async def websocket_remove_media( ) -> None: """Remove media.""" try: - item = MediaSourceItem.from_uri(hass, msg["media_content_id"]) + item = MediaSourceItem.from_uri(hass, msg["media_content_id"], None) except ValueError as err: connection.send_error(msg["id"], websocket_api.ERR_INVALID_FORMAT, str(err)) return diff --git a/homeassistant/components/media_source/models.py b/homeassistant/components/media_source/models.py index ceb57ef1fb4..0aee6ad1330 100644 --- a/homeassistant/components/media_source/models.py +++ b/homeassistant/components/media_source/models.py @@ -50,6 +50,7 @@ class MediaSourceItem: hass: HomeAssistant domain: str | None identifier: str + target_media_player: str | None async def async_browse(self) -> BrowseMediaSource: """Browse this item.""" @@ -94,7 +95,9 @@ class MediaSourceItem: return cast(MediaSource, self.hass.data[DOMAIN][self.domain]) @classmethod - def from_uri(cls, hass: HomeAssistant, uri: str) -> MediaSourceItem: + def from_uri( + cls, hass: HomeAssistant, uri: str, target_media_player: str | None + ) -> MediaSourceItem: """Create an item from a uri.""" if not (match := URI_SCHEME_REGEX.match(uri)): raise ValueError("Invalid media source URI") @@ -102,7 +105,7 @@ class MediaSourceItem: domain = match.group("domain") identifier = match.group("identifier") - return cls(hass, domain, identifier) + return cls(hass, domain, identifier, target_media_player) class MediaSource(ABC): diff --git a/homeassistant/components/melcloud/translations/es.json b/homeassistant/components/melcloud/translations/es.json index caba17be17a..be4f6cabe6c 100644 --- a/homeassistant/components/melcloud/translations/es.json +++ b/homeassistant/components/melcloud/translations/es.json @@ -4,7 +4,7 @@ "already_configured": "Integraci\u00f3n mELCloud ya configurada para este correo electr\u00f3nico. Se ha actualizado el token de acceso." }, "error": { - "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntelo de nuevo.", + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", "invalid_auth": "Autentificaci\u00f3n inv\u00e1lida", "unknown": "Error inesperado" }, diff --git a/homeassistant/components/met/translations/ko.json b/homeassistant/components/met/translations/ko.json index 17175c196c0..b8e89380d52 100644 --- a/homeassistant/components/met/translations/ko.json +++ b/homeassistant/components/met/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "no_home": "Home Assistant\uc5d0 \ud648 \uc88c\ud45c\uac00 \uc124\uc815\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." + }, "error": { "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/met/translations/nl.json b/homeassistant/components/met/translations/nl.json index 7c3d03fdb1f..1d64c174e07 100644 --- a/homeassistant/components/met/translations/nl.json +++ b/homeassistant/components/met/translations/nl.json @@ -4,7 +4,7 @@ "no_home": "Er zijn geen thuisco\u00f6rdinaten ingesteld in de Home Assistant-configuratie" }, "error": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/met_eireann/translations/nl.json b/homeassistant/components/met_eireann/translations/nl.json index b67c167ca8d..13b008b6a45 100644 --- a/homeassistant/components/met_eireann/translations/nl.json +++ b/homeassistant/components/met_eireann/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "error": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/meteo_france/translations/es.json b/homeassistant/components/meteo_france/translations/es.json index 8843d301779..cd6d2d80812 100644 --- a/homeassistant/components/meteo_france/translations/es.json +++ b/homeassistant/components/meteo_france/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La ciudad ya est\u00e1 configurada", + "already_configured": "La ubicaci\u00f3n ya est\u00e1 configurada", "unknown": "Error desconocido: por favor, vuelva a intentarlo m\u00e1s tarde" }, "error": { diff --git a/homeassistant/components/meteo_france/translations/nl.json b/homeassistant/components/meteo_france/translations/nl.json index 5e36e0585c7..1272d2c11e8 100644 --- a/homeassistant/components/meteo_france/translations/nl.json +++ b/homeassistant/components/meteo_france/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd.", + "already_configured": "Locatie is al geconfigureerd", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/metoffice/translations/es.json b/homeassistant/components/metoffice/translations/es.json index 098e8fb0335..5751db1f760 100644 --- a/homeassistant/components/metoffice/translations/es.json +++ b/homeassistant/components/metoffice/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado" + "already_configured": "El servicio ya est\u00e1 configurado" }, "error": { "cannot_connect": "No se pudo conectar", diff --git a/homeassistant/components/metoffice/translations/nl.json b/homeassistant/components/metoffice/translations/nl.json index a6ba36f07af..00f9400a78b 100644 --- a/homeassistant/components/metoffice/translations/nl.json +++ b/homeassistant/components/metoffice/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/mikrotik/__init__.py b/homeassistant/components/mikrotik/__init__.py index f0dc249d26a..1ef250a3f4e 100644 --- a/homeassistant/components/mikrotik/__init__.py +++ b/homeassistant/components/mikrotik/__init__.py @@ -11,7 +11,7 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.typing import ConfigType from .const import ( @@ -76,7 +76,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b return False hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = hub - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(DOMAIN, hub.serial_num)}, diff --git a/homeassistant/components/mikrotik/device_tracker.py b/homeassistant/components/mikrotik/device_tracker.py index 166415caf39..16c3ed233d8 100644 --- a/homeassistant/components/mikrotik/device_tracker.py +++ b/homeassistant/components/mikrotik/device_tracker.py @@ -34,7 +34,7 @@ async def async_setup_entry( tracked: dict[str, MikrotikHubTracker] = {} - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) # Restore clients that is not a part of active clients list. for entity in registry.entities.values(): @@ -102,7 +102,8 @@ class MikrotikHubTracker(ScannerEntity): @property def name(self) -> str: """Return the name of the client.""" - return self.device.name + # Stringify to ensure we return a string + return str(self.device.name) @property def hostname(self) -> str: diff --git a/homeassistant/components/mill/translations/bg.json b/homeassistant/components/mill/translations/bg.json index 43611df6920..63c760b7bee 100644 --- a/homeassistant/components/mill/translations/bg.json +++ b/homeassistant/components/mill/translations/bg.json @@ -18,12 +18,6 @@ "ip_address": "IP \u0430\u0434\u0440\u0435\u0441" }, "description": "\u041b\u043e\u043a\u0430\u043b\u0435\u043d IP \u0430\u0434\u0440\u0435\u0441 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e." - }, - "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" - } } } } diff --git a/homeassistant/components/mill/translations/ca.json b/homeassistant/components/mill/translations/ca.json index ffb567fc218..ba350e217ac 100644 --- a/homeassistant/components/mill/translations/ca.json +++ b/homeassistant/components/mill/translations/ca.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Selecciona el tipus de connexi\u00f3", - "password": "Contrasenya", - "username": "Nom d'usuari" + "connection_type": "Selecciona el tipus de connexi\u00f3" }, "description": "Selecciona el tipus de connexi\u00f3. La local necessita escalfadors de generaci\u00f3 3" } diff --git a/homeassistant/components/mill/translations/cs.json b/homeassistant/components/mill/translations/cs.json index fcbdf3fb428..f6c47b4c840 100644 --- a/homeassistant/components/mill/translations/cs.json +++ b/homeassistant/components/mill/translations/cs.json @@ -5,14 +5,6 @@ }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" - }, - "step": { - "user": { - "data": { - "password": "Heslo", - "username": "U\u017eivatelsk\u00e9 jm\u00e9no" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/mill/translations/de.json b/homeassistant/components/mill/translations/de.json index 1688dddb321..6b8e912674a 100644 --- a/homeassistant/components/mill/translations/de.json +++ b/homeassistant/components/mill/translations/de.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Verbindungstyp ausw\u00e4hlen", - "password": "Passwort", - "username": "Benutzername" + "connection_type": "Verbindungstyp ausw\u00e4hlen" }, "description": "W\u00e4hle die Anschlussart. Lokal erfordert Heizger\u00e4te der Generation 3" } diff --git a/homeassistant/components/mill/translations/el.json b/homeassistant/components/mill/translations/el.json index 18743aada0a..dc5fb70915b 100644 --- a/homeassistant/components/mill/translations/el.json +++ b/homeassistant/components/mill/translations/el.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "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" + "connection_type": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03cd\u03c0\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2. \u0397 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03b8\u03b5\u03c1\u03bc\u03b1\u03bd\u03c4\u03ae\u03c1\u03b5\u03c2 3\u03b7\u03c2 \u03b3\u03b5\u03bd\u03b9\u03ac\u03c2" } diff --git a/homeassistant/components/mill/translations/en.json b/homeassistant/components/mill/translations/en.json index 20291847893..ee66706832e 100644 --- a/homeassistant/components/mill/translations/en.json +++ b/homeassistant/components/mill/translations/en.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Select connection type", - "password": "Password", - "username": "Username" + "connection_type": "Select connection type" }, "description": "Select connection type. Local requires generation 3 heaters" } diff --git a/homeassistant/components/mill/translations/es.json b/homeassistant/components/mill/translations/es.json index 4f2ddd29651..280d4ad4ba9 100644 --- a/homeassistant/components/mill/translations/es.json +++ b/homeassistant/components/mill/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada" + "already_configured": "La cuenta ya est\u00e1 configurada" }, "error": { "cannot_connect": "No se pudo conectar" @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Seleccione el tipo de conexi\u00f3n", - "password": "Contrase\u00f1a", - "username": "Usuario" + "connection_type": "Seleccione el tipo de conexi\u00f3n" }, "description": "Seleccione el tipo de conexi\u00f3n. Local requiere calentadores de generaci\u00f3n 3" } diff --git a/homeassistant/components/mill/translations/et.json b/homeassistant/components/mill/translations/et.json index dad66420441..7b1be866b6a 100644 --- a/homeassistant/components/mill/translations/et.json +++ b/homeassistant/components/mill/translations/et.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Vali \u00fchenduse t\u00fc\u00fcp", - "password": "Salas\u00f5na", - "username": "Kasutajanimi" + "connection_type": "Vali \u00fchenduse t\u00fc\u00fcp" }, "description": "Vali \u00fchenduse t\u00fc\u00fcp. Kohalik vajab 3. p\u00f5lvkonna k\u00fctteseadmeid" } diff --git a/homeassistant/components/mill/translations/fi.json b/homeassistant/components/mill/translations/fi.json deleted file mode 100644 index 61febe9dd9c..00000000000 --- a/homeassistant/components/mill/translations/fi.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "password": "Salasana", - "username": "K\u00e4ytt\u00e4j\u00e4tunnus" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/fr.json b/homeassistant/components/mill/translations/fr.json index 440ef77ea0a..78b25cd079a 100644 --- a/homeassistant/components/mill/translations/fr.json +++ b/homeassistant/components/mill/translations/fr.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "S\u00e9lectionner le type de connexion", - "password": "Mot de passe", - "username": "Nom d'utilisateur" + "connection_type": "S\u00e9lectionner le type de connexion" }, "description": "S\u00e9lectionnez le type de connexion. Local n\u00e9cessite des radiateurs de g\u00e9n\u00e9ration 3" } diff --git a/homeassistant/components/mill/translations/he.json b/homeassistant/components/mill/translations/he.json index b551a319b2b..35c0b30e0de 100644 --- a/homeassistant/components/mill/translations/he.json +++ b/homeassistant/components/mill/translations/he.json @@ -17,12 +17,6 @@ "data": { "ip_address": "\u05db\u05ea\u05d5\u05d1\u05ea IP" } - }, - "user": { - "data": { - "password": "\u05e1\u05d9\u05e1\u05de\u05d4", - "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" - } } } } diff --git a/homeassistant/components/mill/translations/hu.json b/homeassistant/components/mill/translations/hu.json index 8a77a822fc8..38c3c96dbdb 100644 --- a/homeassistant/components/mill/translations/hu.json +++ b/homeassistant/components/mill/translations/hu.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "V\u00e1lassza ki a kapcsolat t\u00edpus\u00e1t", - "password": "Jelsz\u00f3", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + "connection_type": "V\u00e1lassza ki a kapcsolat t\u00edpus\u00e1t" }, "description": "V\u00e1lassza ki a kapcsolat t\u00edpus\u00e1t. A helyi kapcsolat 3. gener\u00e1ci\u00f3s f\u0171t\u0151berendez\u00e9seket ig\u00e9nyel" } diff --git a/homeassistant/components/mill/translations/id.json b/homeassistant/components/mill/translations/id.json index 92670be251a..ee5c548bdf6 100644 --- a/homeassistant/components/mill/translations/id.json +++ b/homeassistant/components/mill/translations/id.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Pilih jenis koneksi", - "password": "Kata Sandi", - "username": "Nama Pengguna" + "connection_type": "Pilih jenis koneksi" }, "description": "Pilih jenis koneksi. Lokal membutuhkan pemanas generasi 3" } diff --git a/homeassistant/components/mill/translations/it.json b/homeassistant/components/mill/translations/it.json index fa19ebc4cbd..5085817a6ca 100644 --- a/homeassistant/components/mill/translations/it.json +++ b/homeassistant/components/mill/translations/it.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Seleziona il tipo di connessione", - "password": "Password", - "username": "Nome utente" + "connection_type": "Seleziona il tipo di connessione" }, "description": "Seleziona il tipo di connessione. Locale richiede riscaldatori di terza generazione" } diff --git a/homeassistant/components/mill/translations/ja.json b/homeassistant/components/mill/translations/ja.json index 9250a503b58..8bb2ab5f0ea 100644 --- a/homeassistant/components/mill/translations/ja.json +++ b/homeassistant/components/mill/translations/ja.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "\u63a5\u7d9a\u30bf\u30a4\u30d7\u306e\u9078\u629e", - "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", - "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + "connection_type": "\u63a5\u7d9a\u30bf\u30a4\u30d7\u306e\u9078\u629e" }, "description": "\u63a5\u7d9a\u30bf\u30a4\u30d7\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u30ed\u30fc\u30ab\u30eb\u306b\u306f\u7b2c3\u4e16\u4ee3\u306e\u30d2\u30fc\u30bf\u30fc\u304c\u5fc5\u8981\u3067\u3059" } diff --git a/homeassistant/components/mill/translations/ko.json b/homeassistant/components/mill/translations/ko.json index 48c8cdc6eaa..a38c2d98582 100644 --- a/homeassistant/components/mill/translations/ko.json +++ b/homeassistant/components/mill/translations/ko.json @@ -7,11 +7,8 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { - "user": { - "data": { - "password": "\ube44\ubc00\ubc88\ud638", - "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + "local": { + "description": "\ub85c\uceec IP \uc8fc\uc18c" } } } diff --git a/homeassistant/components/mill/translations/lb.json b/homeassistant/components/mill/translations/lb.json index 84aeac8bd97..36841f1020f 100644 --- a/homeassistant/components/mill/translations/lb.json +++ b/homeassistant/components/mill/translations/lb.json @@ -5,14 +5,6 @@ }, "error": { "cannot_connect": "Feeler beim verbannen" - }, - "step": { - "user": { - "data": { - "password": "Passwuert", - "username": "Benotzernumm" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/mill/translations/nl.json b/homeassistant/components/mill/translations/nl.json index f37a5cf0758..1d431395b48 100644 --- a/homeassistant/components/mill/translations/nl.json +++ b/homeassistant/components/mill/translations/nl.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Selecteer verbindingstype", - "password": "Wachtwoord", - "username": "Gebruikersnaam" + "connection_type": "Selecteer verbindingstype" }, "description": "Selecteer verbindingstype. Lokaal vereist generatie 3 kachels" } diff --git a/homeassistant/components/mill/translations/no.json b/homeassistant/components/mill/translations/no.json index d5306fd090d..df51f7d73d3 100644 --- a/homeassistant/components/mill/translations/no.json +++ b/homeassistant/components/mill/translations/no.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Velg tilkoblingstype", - "password": "Passord", - "username": "Brukernavn" + "connection_type": "Velg tilkoblingstype" }, "description": "Velg tilkoblingstype. Lokal krever generasjon 3 varmeovner" } diff --git a/homeassistant/components/mill/translations/pl.json b/homeassistant/components/mill/translations/pl.json index 73abdedbfbc..b75a5da2df3 100644 --- a/homeassistant/components/mill/translations/pl.json +++ b/homeassistant/components/mill/translations/pl.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Wybierz typ po\u0142\u0105czenia", - "password": "Has\u0142o", - "username": "Nazwa u\u017cytkownika" + "connection_type": "Wybierz typ po\u0142\u0105czenia" }, "description": "Wybierz typ po\u0142\u0105czenia. Lokalnie wymaga grzejnik\u00f3w 3 generacji" } diff --git a/homeassistant/components/mill/translations/pt-BR.json b/homeassistant/components/mill/translations/pt-BR.json index 8d90531191a..73eb1808a1c 100644 --- a/homeassistant/components/mill/translations/pt-BR.json +++ b/homeassistant/components/mill/translations/pt-BR.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Selecione o tipo de conex\u00e3o", - "password": "Senha", - "username": "Usu\u00e1rio" + "connection_type": "Selecione o tipo de conex\u00e3o" }, "description": "Selecione o tipo de conex\u00e3o. Local requer aquecedores de 3\u00aa gera\u00e7\u00e3o" } diff --git a/homeassistant/components/mill/translations/pt.json b/homeassistant/components/mill/translations/pt.json index 4348cecf5c3..63c9b4e0011 100644 --- a/homeassistant/components/mill/translations/pt.json +++ b/homeassistant/components/mill/translations/pt.json @@ -5,14 +5,6 @@ }, "error": { "cannot_connect": "Falha na liga\u00e7\u00e3o" - }, - "step": { - "user": { - "data": { - "password": "Palavra-passe", - "username": "Nome de Utilizador" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/mill/translations/ru.json b/homeassistant/components/mill/translations/ru.json index ab09ec1fdaa..bac13e68163 100644 --- a/homeassistant/components/mill/translations/ru.json +++ b/homeassistant/components/mill/translations/ru.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", - "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + "connection_type": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u043f\u0446\u0438\u0438 Local \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043e\u0431\u043e\u0433\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u044c \u0442\u0440\u0435\u0442\u044c\u0435\u0433\u043e \u043f\u043e\u043a\u043e\u043b\u0435\u043d\u0438\u044f." } diff --git a/homeassistant/components/mill/translations/tr.json b/homeassistant/components/mill/translations/tr.json index 63ddc4ee7b1..1b75d70c51c 100644 --- a/homeassistant/components/mill/translations/tr.json +++ b/homeassistant/components/mill/translations/tr.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "Ba\u011flant\u0131 t\u00fcr\u00fcn\u00fc se\u00e7in", - "password": "Parola", - "username": "Kullan\u0131c\u0131 Ad\u0131" + "connection_type": "Ba\u011flant\u0131 t\u00fcr\u00fcn\u00fc se\u00e7in" }, "description": "Ba\u011flant\u0131 t\u00fcr\u00fcn\u00fc se\u00e7in. Yerel, 3. nesil \u0131s\u0131t\u0131c\u0131lar gerektirir" } diff --git a/homeassistant/components/mill/translations/uk.json b/homeassistant/components/mill/translations/uk.json index b8a5aea578e..ceee910d966 100644 --- a/homeassistant/components/mill/translations/uk.json +++ b/homeassistant/components/mill/translations/uk.json @@ -5,14 +5,6 @@ }, "error": { "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" - }, - "step": { - "user": { - "data": { - "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/mill/translations/zh-Hans.json b/homeassistant/components/mill/translations/zh-Hans.json index 79079e6c408..2941dfd9383 100644 --- a/homeassistant/components/mill/translations/zh-Hans.json +++ b/homeassistant/components/mill/translations/zh-Hans.json @@ -2,13 +2,6 @@ "config": { "error": { "cannot_connect": "\u8fde\u63a5\u5931\u8d25" - }, - "step": { - "user": { - "data": { - "username": "\u7528\u6237\u540d" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/mill/translations/zh-Hant.json b/homeassistant/components/mill/translations/zh-Hant.json index 5c4745b23ae..cf8960a2a71 100644 --- a/homeassistant/components/mill/translations/zh-Hant.json +++ b/homeassistant/components/mill/translations/zh-Hant.json @@ -21,9 +21,7 @@ }, "user": { "data": { - "connection_type": "\u9078\u64c7\u9023\u7dda\u985e\u5225", - "password": "\u5bc6\u78bc", - "username": "\u4f7f\u7528\u8005\u540d\u7a31" + "connection_type": "\u9078\u64c7\u9023\u7dda\u985e\u5225" }, "description": "\u9078\u64c7\u9023\u7dda\u985e\u5225\u3002\u672c\u5730\u7aef\u5c07\u9700\u8981\u7b2c\u4e09\u4ee3\u52a0\u71b1\u5668" } diff --git a/homeassistant/components/min_max/translations/ca.json b/homeassistant/components/min_max/translations/ca.json index b114e8ecf40..4bc9e69f182 100644 --- a/homeassistant/components/min_max/translations/ca.json +++ b/homeassistant/components/min_max/translations/ca.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/cs.json b/homeassistant/components/min_max/translations/cs.json index a969a39234a..181a5c93f14 100644 --- a/homeassistant/components/min_max/translations/cs.json +++ b/homeassistant/components/min_max/translations/cs.json @@ -20,14 +20,6 @@ "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." } } } diff --git a/homeassistant/components/min_max/translations/de.json b/homeassistant/components/min_max/translations/de.json index c8b9d5ddf63..cb4ad7b9996 100644 --- a/homeassistant/components/min_max/translations/de.json +++ b/homeassistant/components/min_max/translations/de.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/el.json b/homeassistant/components/min_max/translations/el.json index 218c46fd683..53499682941 100644 --- a/homeassistant/components/min_max/translations/el.json +++ b/homeassistant/components/min_max/translations/el.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/en.json b/homeassistant/components/min_max/translations/en.json index b5b6eb5d731..8cc0d41c419 100644 --- a/homeassistant/components/min_max/translations/en.json +++ b/homeassistant/components/min_max/translations/en.json @@ -27,14 +27,6 @@ "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/es.json b/homeassistant/components/min_max/translations/es.json new file mode 100644 index 00000000000..aceaa287a3b --- /dev/null +++ b/homeassistant/components/min_max/translations/es.json @@ -0,0 +1,32 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Entidades de entrada", + "name": "Nombre", + "type": "Caracter\u00edstica estad\u00edstica" + }, + "data_description": { + "round_digits": "Controla el n\u00famero de d\u00edgitos decimales cuando la caracter\u00edstica estad\u00edstica es la media o mediana." + }, + "title": "Agregar sensor m\u00edn / m\u00e1x / media / mediana" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Entidades de entrada", + "round_digits": "Precisi\u00f3n", + "type": "Caracter\u00edstica estad\u00edstica" + }, + "data_description": { + "round_digits": "Controla el n\u00famero de d\u00edgitos decimales cuando la caracter\u00edstica estad\u00edstica es la media o mediana." + } + } + } + }, + "title": "Sensor m\u00edn / m\u00e1x / media / mediana" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/et.json b/homeassistant/components/min_max/translations/et.json index 2f1689e8577..c57b4a055a8 100644 --- a/homeassistant/components/min_max/translations/et.json +++ b/homeassistant/components/min_max/translations/et.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/fr.json b/homeassistant/components/min_max/translations/fr.json index b42ccf0bb2a..562e7c7f735 100644 --- a/homeassistant/components/min_max/translations/fr.json +++ b/homeassistant/components/min_max/translations/fr.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/he.json b/homeassistant/components/min_max/translations/he.json index 267e38f0ce2..b9f2f18a4c7 100644 --- a/homeassistant/components/min_max/translations/he.json +++ b/homeassistant/components/min_max/translations/he.json @@ -24,13 +24,6 @@ "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" - } } } } diff --git a/homeassistant/components/min_max/translations/hu.json b/homeassistant/components/min_max/translations/hu.json index 98d021dbb31..7330df0d3e0 100644 --- a/homeassistant/components/min_max/translations/hu.json +++ b/homeassistant/components/min_max/translations/hu.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/id.json b/homeassistant/components/min_max/translations/id.json index 32ccfd2596d..9e06b47de95 100644 --- a/homeassistant/components/min_max/translations/id.json +++ b/homeassistant/components/min_max/translations/id.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/it.json b/homeassistant/components/min_max/translations/it.json index 49c715a7507..4574b80e863 100644 --- a/homeassistant/components/min_max/translations/it.json +++ b/homeassistant/components/min_max/translations/it.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/ja.json b/homeassistant/components/min_max/translations/ja.json index c853a7c389e..f9e767082d6 100644 --- a/homeassistant/components/min_max/translations/ja.json +++ b/homeassistant/components/min_max/translations/ja.json @@ -27,14 +27,6 @@ "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" } } }, diff --git a/homeassistant/components/min_max/translations/ko.json b/homeassistant/components/min_max/translations/ko.json new file mode 100644 index 00000000000..3b9bcebca94 --- /dev/null +++ b/homeassistant/components/min_max/translations/ko.json @@ -0,0 +1,34 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "\uad6c\uc131\uc694\uc18c \uc785\ub825", + "name": "\uc774\ub984", + "round_digits": "\uc18c\uc218\uc810", + "type": "\ud1b5\uacc4\uc801 \ud2b9\uc131" + }, + "data_description": { + "round_digits": "\ud1b5\uacc4\uc801 \ud2b9\uc131\uc774 \ud3c9\uade0 \ub610\ub294 \uc911\uc559\uac12\uc77c \ub54c\uc758 \uc18c\uc218\uc810 \uc790\ub9bf\uc218\ub97c \uacb0\uc815\ud569\ub2c8\ub2e4." + }, + "description": "\ucd5c\uc18c, \ucd5c\ub300, \ud3c9\uade0 \ub610\ub294 \uc911\uc559\uac12\uc744 \uacc4\uc0b0\ud558\ub294 \uc13c\uc11c\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.", + "title": "\ucd5c\uc18c/\ucd5c\ub300/\ud3c9\uade0/\uc911\uc559\uac12 \uc13c\uc11c \ucd94\uac00" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "\uad6c\uc131\uc694\uc18c \uc785\ub825", + "round_digits": "\uc18c\uc218\uc810", + "type": "\ud1b5\uacc4\uc801 \ud2b9\uc131" + }, + "data_description": { + "round_digits": "\ud1b5\uacc4\uc801 \ud2b9\uc131\uc774 \ud3c9\uade0 \ub610\ub294 \uc911\uc559\uac12\uc77c \ub54c\uc758 \uc18c\uc218\uc810 \uc790\ub9bf\uc218\ub97c \uacb0\uc815\ud569\ub2c8\ub2e4." + } + } + } + }, + "title": "\ucd5c\uc18c / \ucd5c\ub300 / \ud3c9\uade0 / \uc911\uc559\uac12 \uc13c\uc11c" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/nl.json b/homeassistant/components/min_max/translations/nl.json index 36f09f97d54..eb2106cc366 100644 --- a/homeassistant/components/min_max/translations/nl.json +++ b/homeassistant/components/min_max/translations/nl.json @@ -3,7 +3,7 @@ "step": { "user": { "data": { - "entity_ids": "Entiteiten invoeren", + "entity_ids": "Invoerentiteiten", "name": "Naam", "round_digits": "Precisie", "type": "statistisch kenmerk" @@ -20,21 +20,13 @@ "step": { "init": { "data": { - "entity_ids": "Entiteiten invoeren", + "entity_ids": "Invoerentiteiten", "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." } } }, diff --git a/homeassistant/components/min_max/translations/no.json b/homeassistant/components/min_max/translations/no.json index 798430cec03..5c9391b374f 100644 --- a/homeassistant/components/min_max/translations/no.json +++ b/homeassistant/components/min_max/translations/no.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/pl.json b/homeassistant/components/min_max/translations/pl.json index 0a29ad78339..cf4157eb612 100644 --- a/homeassistant/components/min_max/translations/pl.json +++ b/homeassistant/components/min_max/translations/pl.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/pt-BR.json b/homeassistant/components/min_max/translations/pt-BR.json index 9c1c77c304a..5a2c9a85d4f 100644 --- a/homeassistant/components/min_max/translations/pt-BR.json +++ b/homeassistant/components/min_max/translations/pt-BR.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/ru.json b/homeassistant/components/min_max/translations/ru.json index 551660fcabb..0fdd9198a72 100644 --- a/homeassistant/components/min_max/translations/ru.json +++ b/homeassistant/components/min_max/translations/ru.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/tr.json b/homeassistant/components/min_max/translations/tr.json index 7f192888615..ab826ed913f 100644 --- a/homeassistant/components/min_max/translations/tr.json +++ b/homeassistant/components/min_max/translations/tr.json @@ -27,14 +27,6 @@ "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." } } }, diff --git a/homeassistant/components/min_max/translations/zh-Hans.json b/homeassistant/components/min_max/translations/zh-Hans.json index ca5555bbd0a..bd0562d2e48 100644 --- a/homeassistant/components/min_max/translations/zh-Hans.json +++ b/homeassistant/components/min_max/translations/zh-Hans.json @@ -11,7 +11,7 @@ "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", + "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\u6216\u4e2d\u4f4d\u6570\u3002", "title": "\u6dfb\u52a0\u6700\u5927\u503c/\u6700\u5c0f\u503c/\u5e73\u5747\u503c/\u4e2d\u4f4d\u6570\u4f20\u611f\u5668" } } @@ -27,14 +27,6 @@ "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" } } }, diff --git a/homeassistant/components/min_max/translations/zh-Hant.json b/homeassistant/components/min_max/translations/zh-Hant.json index b0166e26cac..abdf3fe737f 100644 --- a/homeassistant/components/min_max/translations/zh-Hant.json +++ b/homeassistant/components/min_max/translations/zh-Hant.json @@ -27,14 +27,6 @@ "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" } } }, diff --git a/homeassistant/components/minecraft_server/translations/nl.json b/homeassistant/components/minecraft_server/translations/nl.json index 0170964d5b0..b6dbe56016e 100644 --- a/homeassistant/components/minecraft_server/translations/nl.json +++ b/homeassistant/components/minecraft_server/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken met de server. Controleer de host en de poort en probeer het opnieuw. Zorg er ook voor dat u minimaal Minecraft versie 1.7 op uw server uitvoert.", diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index 9f92285625b..1b308705624 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -10,6 +10,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_DEVICE_ID, CONF_WEBHOOK_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, discovery +from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType from . import websocket_api @@ -37,8 +38,10 @@ PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER] async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the mobile app component.""" - store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) - if (app_config := await store.async_load()) is None: + store = Store(hass, STORAGE_VERSION, STORAGE_KEY) + if (app_config := await store.async_load()) is None or not isinstance( + app_config, dict + ): app_config = { DATA_CONFIG_ENTRIES: {}, DATA_DELETED_IDS: [], diff --git a/homeassistant/components/mobile_app/binary_sensor.py b/homeassistant/components/mobile_app/binary_sensor.py index 4d40e42a47e..fd8545b1f98 100644 --- a/homeassistant/components/mobile_app/binary_sensor.py +++ b/homeassistant/components/mobile_app/binary_sensor.py @@ -3,14 +3,13 @@ from typing import Any from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID, STATE_ON +from homeassistant.const import CONF_WEBHOOK_ID, STATE_ON 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 ( - ATTR_DEVICE_NAME, ATTR_SENSOR_ATTRIBUTES, ATTR_SENSOR_DEVICE_CLASS, ATTR_SENSOR_ENTITY_CATEGORY, @@ -22,7 +21,7 @@ from .const import ( ATTR_SENSOR_UNIQUE_ID, DOMAIN, ) -from .entity import MobileAppEntity, unique_id +from .entity import MobileAppEntity async def async_setup_entry( @@ -59,13 +58,6 @@ async def async_setup_entry( if data[CONF_WEBHOOK_ID] != webhook_id: return - data[CONF_UNIQUE_ID] = unique_id( - data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID] - ) - data[ - CONF_NAME - ] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}" - async_add_entities([MobileAppBinarySensor(data, config_entry)]) async_dispatcher_connect( diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index ba81a0484cf..efc105a80ea 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -67,6 +67,7 @@ ERR_INVALID_FORMAT = "invalid_format" ATTR_SENSOR_ATTRIBUTES = "attributes" ATTR_SENSOR_DEVICE_CLASS = "device_class" +ATTR_SENSOR_DISABLED = "disabled" ATTR_SENSOR_ENTITY_CATEGORY = "entity_category" ATTR_SENSOR_ICON = "icon" ATTR_SENSOR_NAME = "name" diff --git a/homeassistant/components/mobile_app/device_action.py b/homeassistant/components/mobile_app/device_action.py index d17702ec24f..f6e870fc7d6 100644 --- a/homeassistant/components/mobile_app/device_action.py +++ b/homeassistant/components/mobile_app/device_action.py @@ -9,6 +9,7 @@ from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_TYPE from homeassistant.core import Context, HomeAssistant from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv, template +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from .const import DOMAIN from .util import get_notify_service, supports_push, webhook_id_from_device_id @@ -36,7 +37,10 @@ async def async_get_actions( async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" webhook_id = webhook_id_from_device_id(hass, config[CONF_DEVICE_ID]) @@ -73,7 +77,9 @@ async def async_call_action_from_config( ) -async def async_get_action_capabilities(hass, config): +async def async_get_action_capabilities( + hass: HomeAssistant, config: ConfigType +) -> dict[str, vol.Schema]: """List action capabilities.""" if config[CONF_TYPE] != "notify": return {} diff --git a/homeassistant/components/mobile_app/entity.py b/homeassistant/components/mobile_app/entity.py index 0cb6cfc6fcc..d4c4374b8d9 100644 --- a/homeassistant/components/mobile_app/entity.py +++ b/homeassistant/components/mobile_app/entity.py @@ -2,13 +2,7 @@ from __future__ import annotations from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - ATTR_ICON, - CONF_NAME, - CONF_UNIQUE_ID, - CONF_WEBHOOK_ID, - STATE_UNAVAILABLE, -) +from homeassistant.const import ATTR_ICON, CONF_NAME, CONF_UNIQUE_ID, STATE_UNAVAILABLE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity @@ -16,32 +10,27 @@ from homeassistant.helpers.restore_state import RestoreEntity from .const import ( ATTR_SENSOR_ATTRIBUTES, ATTR_SENSOR_DEVICE_CLASS, + ATTR_SENSOR_DISABLED, ATTR_SENSOR_ENTITY_CATEGORY, ATTR_SENSOR_ICON, ATTR_SENSOR_STATE, - ATTR_SENSOR_TYPE, - ATTR_SENSOR_UNIQUE_ID, SIGNAL_SENSOR_UPDATE, ) from .helpers import device_info -def unique_id(webhook_id, sensor_unique_id): - """Return a unique sensor ID.""" - return f"{webhook_id}_{sensor_unique_id}" - - class MobileAppEntity(RestoreEntity): """Representation of an mobile app entity.""" + _attr_should_poll = False + def __init__(self, config: dict, entry: ConfigEntry) -> None: """Initialize the entity.""" self._config = config self._entry = entry self._registration = entry.data - self._unique_id = config[CONF_UNIQUE_ID] - self._entity_type = config[ATTR_SENSOR_TYPE] - self._name = config[CONF_NAME] + self._attr_unique_id = config[CONF_UNIQUE_ID] + self._name = self._config[CONF_NAME] async def async_added_to_hass(self): """Register callbacks.""" @@ -67,16 +56,16 @@ class MobileAppEntity(RestoreEntity): if ATTR_ICON in last_state.attributes: self._config[ATTR_SENSOR_ICON] = last_state.attributes[ATTR_ICON] - @property - def should_poll(self) -> bool: - """Declare that this entity pushes its state to HA.""" - return False - @property def name(self): """Return the name of the mobile app sensor.""" return self._name + @property + def entity_registry_enabled_default(self) -> bool: + """Return if entity should be enabled by default.""" + return not self._config.get(ATTR_SENSOR_DISABLED) + @property def device_class(self): """Return the device class.""" @@ -97,11 +86,6 @@ class MobileAppEntity(RestoreEntity): """Return the entity category, if any.""" return self._config.get(ATTR_SENSOR_ENTITY_CATEGORY) - @property - def unique_id(self): - """Return the unique ID of this sensor.""" - return self._unique_id - @property def device_info(self): """Return device registry information for this entity.""" @@ -113,10 +97,9 @@ class MobileAppEntity(RestoreEntity): return self._config.get(ATTR_SENSOR_STATE) != STATE_UNAVAILABLE @callback - def _handle_update(self, data): + def _handle_update(self, incoming_id, data): """Handle async event updates.""" - incoming_id = unique_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID]) - if incoming_id != self._unique_id: + if incoming_id != self._attr_unique_id: return self._config = {**self._config, **data} diff --git a/homeassistant/components/mobile_app/logbook.py b/homeassistant/components/mobile_app/logbook.py new file mode 100644 index 00000000000..6f7c2e4e99c --- /dev/null +++ b/homeassistant/components/mobile_app/logbook.py @@ -0,0 +1,60 @@ +"""Describe mobile_app logbook events.""" +from __future__ import annotations + +from collections.abc import Callable + +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_ENTITY_ID, + LOGBOOK_ENTRY_ICON, + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) +from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.core import Event, HomeAssistant, callback + +from .const import DOMAIN + +IOS_EVENT_ZONE_ENTERED = "ios.zone_entered" +IOS_EVENT_ZONE_EXITED = "ios.zone_exited" + +ATTR_ZONE = "zone" +ATTR_SOURCE_DEVICE_NAME = "sourceDeviceName" +ATTR_SOURCE_DEVICE_ID = "sourceDeviceID" +EVENT_TO_DESCRIPTION = { + IOS_EVENT_ZONE_ENTERED: "entered zone", + IOS_EVENT_ZONE_EXITED: "exited zone", +} + + +@callback +def async_describe_events( + hass: HomeAssistant, + async_describe_event: Callable[[str, str, Callable[[Event], dict[str, str]]], None], +) -> None: + """Describe logbook events.""" + + @callback + def async_describe_zone_event(event: Event) -> dict[str, str]: + """Describe mobile_app logbook event.""" + data = event.data + event_description = EVENT_TO_DESCRIPTION[event.event_type] + zone_entity_id = data.get(ATTR_ZONE) + source_device_name = data.get( + ATTR_SOURCE_DEVICE_NAME, data.get(ATTR_SOURCE_DEVICE_ID) + ) + zone_name = None + zone_icon = None + if zone_entity_id and (zone_state := hass.states.get(zone_entity_id)): + zone_name = zone_state.attributes.get(ATTR_FRIENDLY_NAME) + zone_icon = zone_state.attributes.get(ATTR_ICON) + description = { + LOGBOOK_ENTRY_NAME: source_device_name, + LOGBOOK_ENTRY_MESSAGE: f"{event_description} {zone_name or zone_entity_id}", + LOGBOOK_ENTRY_ICON: zone_icon or "mdi:crosshairs-gps", + } + if zone_entity_id: + description[LOGBOOK_ENTRY_ENTITY_ID] = zone_entity_id + return description + + async_describe_event(DOMAIN, IOS_EVENT_ZONE_ENTERED, async_describe_zone_event) + async_describe_event(DOMAIN, IOS_EVENT_ZONE_EXITED, async_describe_zone_event) diff --git a/homeassistant/components/mobile_app/sensor.py b/homeassistant/components/mobile_app/sensor.py index 45bc4acd6a2..d7cfc9545f6 100644 --- a/homeassistant/components/mobile_app/sensor.py +++ b/homeassistant/components/mobile_app/sensor.py @@ -5,12 +5,7 @@ from typing import Any from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_NAME, - CONF_UNIQUE_ID, - CONF_WEBHOOK_ID, - STATE_UNKNOWN, -) +from homeassistant.const import CONF_WEBHOOK_ID, STATE_UNKNOWN from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -18,7 +13,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import dt as dt_util from .const import ( - ATTR_DEVICE_NAME, ATTR_SENSOR_ATTRIBUTES, ATTR_SENSOR_DEVICE_CLASS, ATTR_SENSOR_ENTITY_CATEGORY, @@ -32,7 +26,7 @@ from .const import ( ATTR_SENSOR_UOM, DOMAIN, ) -from .entity import MobileAppEntity, unique_id +from .entity import MobileAppEntity async def async_setup_entry( @@ -70,13 +64,6 @@ async def async_setup_entry( if data[CONF_WEBHOOK_ID] != webhook_id: return - data[CONF_UNIQUE_ID] = unique_id( - data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID] - ) - data[ - CONF_NAME - ] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}" - async_add_entities([MobileAppSensor(data, config_entry)]) async_dispatcher_connect( diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index f0c7c628976..61b69ebad5c 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -34,6 +34,8 @@ from homeassistant.const import ( ATTR_SERVICE, ATTR_SERVICE_DATA, ATTR_SUPPORTED_FEATURES, + CONF_NAME, + CONF_UNIQUE_ID, CONF_WEBHOOK_ID, ) from homeassistant.core import EventOrigin, HomeAssistant @@ -63,6 +65,7 @@ from .const import ( ATTR_OS_VERSION, ATTR_SENSOR_ATTRIBUTES, ATTR_SENSOR_DEVICE_CLASS, + ATTR_SENSOR_DISABLED, ATTR_SENSOR_ENTITY_CATEGORY, ATTR_SENSOR_ICON, ATTR_SENSOR_NAME, @@ -350,8 +353,8 @@ async def webhook_render_template(hass, config_entry, data): ) async def webhook_update_location(hass, config_entry, data): """Handle an update location webhook.""" - hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_LOCATION_UPDATE.format(config_entry.entry_id), data + async_dispatcher_send( + hass, SIGNAL_LOCATION_UPDATE.format(config_entry.entry_id), data ) return empty_okay_response() @@ -431,6 +434,16 @@ def _validate_state_class_sensor(value: dict): return value +def _gen_unique_id(webhook_id, sensor_unique_id): + """Return a unique sensor ID.""" + return f"{webhook_id}_{sensor_unique_id}" + + +def _extract_sensor_unique_id(webhook_id, unique_id): + """Return a unique sensor ID.""" + return unique_id[len(webhook_id) + 1 :] + + @WEBHOOK_COMMANDS.register("register_sensor") @validate_schema( vol.All( @@ -449,6 +462,7 @@ def _validate_state_class_sensor(value: dict): 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), + vol.Optional(ATTR_SENSOR_DISABLED): bool, }, _validate_state_class_sensor, ) @@ -459,7 +473,7 @@ async def webhook_register_sensor(hass, config_entry, data): unique_id = data[ATTR_SENSOR_UNIQUE_ID] device_name = config_entry.data[ATTR_DEVICE_NAME] - unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}" + unique_store_key = _gen_unique_id(config_entry.data[CONF_WEBHOOK_ID], unique_id) entity_registry = er.async_get(hass) existing_sensor = entity_registry.async_get_entity_id( entity_type, DOMAIN, unique_store_key @@ -481,6 +495,15 @@ async def webhook_register_sensor(hass, config_entry, data): ) != entry.original_name: changes["original_name"] = new_name + if ( + should_be_disabled := data.get(ATTR_SENSOR_DISABLED) + ) is None or should_be_disabled == entry.disabled: + pass + elif should_be_disabled: + changes["disabled_by"] = er.RegistryEntryDisabler.INTEGRATION + else: + changes["disabled_by"] = None + for ent_reg_key, data_key in ( ("device_class", ATTR_SENSOR_DEVICE_CLASS), ("unit_of_measurement", ATTR_SENSOR_UOM), @@ -493,8 +516,13 @@ async def webhook_register_sensor(hass, config_entry, data): if changes: entity_registry.async_update_entity(existing_sensor, **changes) - async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data) + async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, unique_store_key, data) else: + data[CONF_UNIQUE_ID] = unique_store_key + data[ + CONF_NAME + ] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}" + register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register" async_dispatcher_send(hass, register_signal, data) @@ -537,19 +565,21 @@ async def webhook_update_sensor_states(hass, config_entry, data): device_name = config_entry.data[ATTR_DEVICE_NAME] resp = {} + entity_registry = er.async_get(hass) for sensor in data: entity_type = sensor[ATTR_SENSOR_TYPE] unique_id = sensor[ATTR_SENSOR_UNIQUE_ID] - unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}" + unique_store_key = _gen_unique_id(config_entry.data[CONF_WEBHOOK_ID], unique_id) - entity_registry = er.async_get(hass) - if not entity_registry.async_get_entity_id( - entity_type, DOMAIN, unique_store_key + if not ( + entity_id := entity_registry.async_get_entity_id( + entity_type, DOMAIN, unique_store_key + ) ): - _LOGGER.error( + _LOGGER.debug( "Refusing to update %s non-registered sensor: %s", device_name, unique_store_key, @@ -578,10 +608,21 @@ async def webhook_update_sensor_states(hass, config_entry, data): continue sensor[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID] - async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, sensor) + async_dispatcher_send( + hass, + SIGNAL_SENSOR_UPDATE, + unique_store_key, + sensor, + ) resp[unique_id] = {"success": True} + # Check if disabled + entry = entity_registry.async_get(entity_id) + + if entry.disabled_by: + resp[unique_id]["is_disabled"] = True + return webhook_response(resp, registration=config_entry.data) @@ -618,6 +659,21 @@ async def webhook_get_config(hass, config_entry, data): with suppress(hass.components.cloud.CloudNotAvailable): resp[CONF_REMOTE_UI_URL] = cloud.async_remote_ui_url(hass) + webhook_id = config_entry.data[CONF_WEBHOOK_ID] + + entities = {} + for entry in er.async_entries_for_config_entry( + er.async_get(hass), config_entry.entry_id + ): + if entry.domain in ("binary_sensor", "sensor"): + unique_id = _extract_sensor_unique_id(webhook_id, entry.unique_id) + else: + unique_id = entry.unique_id + + entities[unique_id] = {"disabled": entry.disabled} + + resp["entities"] = entities + return webhook_response(resp, registration=config_entry.data) diff --git a/homeassistant/components/modem_callerid/translations/bg.json b/homeassistant/components/modem_callerid/translations/bg.json index 7383941d1ca..15ece1452ad 100644 --- a/homeassistant/components/modem_callerid/translations/bg.json +++ b/homeassistant/components/modem_callerid/translations/bg.json @@ -7,15 +7,11 @@ "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "step": { - "usb_confirm": { - "title": "\u0422\u0435\u043b\u0435\u0444\u043e\u043d\u0435\u043d \u043c\u043e\u0434\u0435\u043c" - }, "user": { "data": { "name": "\u0418\u043c\u0435", "port": "\u041f\u043e\u0440\u0442" - }, - "title": "\u0422\u0435\u043b\u0435\u0444\u043e\u043d\u0435\u043d \u043c\u043e\u0434\u0435\u043c" + } } } } diff --git a/homeassistant/components/modem_callerid/translations/ca.json b/homeassistant/components/modem_callerid/translations/ca.json index d94d4cf392d..996406acdbe 100644 --- a/homeassistant/components/modem_callerid/translations/ca.json +++ b/homeassistant/components/modem_callerid/translations/ca.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Integraci\u00f3 per a trucades fixes amb el m\u00f2dem de veu CX93001. Pot obtenir l'identificador del que truca i pot rebutjar trucades entrants.", - "title": "M\u00f2dem telef\u00f2nic" + "description": "Integraci\u00f3 per a trucades fixes amb el m\u00f2dem de veu CX93001. Pot obtenir l'identificador del que truca i pot rebutjar trucades entrants." }, "user": { "data": { "name": "Nom", "port": "Port" }, - "description": "Integraci\u00f3 per a trucades fixes amb el m\u00f2dem de veu CX93001. Pot obtenir l'identificador del que truca i pot rebutjar trucades entrants.", - "title": "M\u00f2dem telef\u00f2nic" + "description": "Integraci\u00f3 per a trucades fixes amb el m\u00f2dem de veu CX93001. Pot obtenir l'identificador del que truca i pot rebutjar trucades entrants." } } } diff --git a/homeassistant/components/modem_callerid/translations/de.json b/homeassistant/components/modem_callerid/translations/de.json index 0bc505be5c8..51056a601c3 100644 --- a/homeassistant/components/modem_callerid/translations/de.json +++ b/homeassistant/components/modem_callerid/translations/de.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Dies ist eine Integration f\u00fcr Festnetzanrufe mit einem CX93001 Sprachmodem. Damit k\u00f6nnen Anrufer-ID-Informationen mit einer Option zum Abweisen eines eingehenden Anrufs abgerufen werden.", - "title": "Telefonmodem" + "description": "Dies ist eine Integration f\u00fcr Festnetzanrufe mit einem CX93001 Sprachmodem. Damit k\u00f6nnen Anrufer-ID-Informationen mit einer Option zum Abweisen eines eingehenden Anrufs abgerufen werden." }, "user": { "data": { "name": "Name", "port": "Port" }, - "description": "Dies ist eine Integration f\u00fcr Festnetzanrufe mit einem CX93001 Sprachmodem. Damit k\u00f6nnen Anrufer-ID-Informationen mit einer Option zum Abweisen eines eingehenden Anrufs abgerufen werden.", - "title": "Telefonmodem" + "description": "Dies ist eine Integration f\u00fcr Festnetzanrufe mit einem CX93001 Sprachmodem. Damit k\u00f6nnen Anrufer-ID-Informationen mit einer Option zum Abweisen eines eingehenden Anrufs abgerufen werden." } } } diff --git a/homeassistant/components/modem_callerid/translations/el.json b/homeassistant/components/modem_callerid/translations/el.json index 6d3961cc258..cf2ef08b92e 100644 --- a/homeassistant/components/modem_callerid/translations/el.json +++ b/homeassistant/components/modem_callerid/translations/el.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc CX93001. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03b1\u03bb\u03bf\u03cd\u03bd\u03c4\u03bf\u03c2 \u03bc\u03b5 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2.", - "title": "\u039c\u03cc\u03bd\u03c4\u03b5\u03bc \u03c4\u03b7\u03bb\u03b5\u03c6\u03ce\u03bd\u03bf\u03c5" + "description": "\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc CX93001. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03b1\u03bb\u03bf\u03cd\u03bd\u03c4\u03bf\u03c2 \u03bc\u03b5 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2." }, "user": { "data": { "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "port": "\u0398\u03cd\u03c1\u03b1" }, - "description": "\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc CX93001. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03b1\u03bb\u03bf\u03cd\u03bd\u03c4\u03bf\u03c2 \u03bc\u03b5 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2.", - "title": "\u039c\u03cc\u03bd\u03c4\u03b5\u03bc \u03c4\u03b7\u03bb\u03b5\u03c6\u03ce\u03bd\u03bf\u03c5" + "description": "\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03c9\u03bd\u03af\u03b1\u03c2 \u03bc\u03b5 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc CX93001. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ba\u03b1\u03bb\u03bf\u03cd\u03bd\u03c4\u03bf\u03c2 \u03bc\u03b5 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b1\u03c0\u03cc\u03c1\u03c1\u03b9\u03c8\u03b7\u03c2 \u03bc\u03b9\u03b1\u03c2 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b7\u03c2 \u03ba\u03bb\u03ae\u03c3\u03b7\u03c2." } } } diff --git a/homeassistant/components/modem_callerid/translations/en.json b/homeassistant/components/modem_callerid/translations/en.json index 5450a930ff3..1cad101f36d 100644 --- a/homeassistant/components/modem_callerid/translations/en.json +++ b/homeassistant/components/modem_callerid/translations/en.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "This is an integration for landline calls using a CX93001 voice modem. This can retrieve caller ID information with an option to reject an incoming call.", - "title": "Phone Modem" + "description": "This is an integration for landline calls using a CX93001 voice modem. This can retrieve caller ID information with an option to reject an incoming call." }, "user": { "data": { "name": "Name", "port": "Port" }, - "description": "This is an integration for landline calls using a CX93001 voice modem. This can retrieve caller ID information with an option to reject an incoming call.", - "title": "Phone Modem" + "description": "This is an integration for landline calls using a CX93001 voice modem. This can retrieve caller ID information with an option to reject an incoming call." } } } diff --git a/homeassistant/components/modem_callerid/translations/es.json b/homeassistant/components/modem_callerid/translations/es.json index 3a9853dd5f7..6c62f64a1d6 100644 --- a/homeassistant/components/modem_callerid/translations/es.json +++ b/homeassistant/components/modem_callerid/translations/es.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Se trata de una integraci\u00f3n para llamadas a tel\u00e9fonos fijos que utilizan un m\u00f3dem de voz CX93001. Puede recuperar la informaci\u00f3n del identificador de llamadas con una opci\u00f3n para rechazar una llamada entrante.", - "title": "M\u00f3dem telef\u00f3nico" + "description": "Se trata de una integraci\u00f3n para llamadas a tel\u00e9fonos fijos que utilizan un m\u00f3dem de voz CX93001. Puede recuperar la informaci\u00f3n del identificador de llamadas con una opci\u00f3n para rechazar una llamada entrante." }, "user": { "data": { "name": "Nombre", "port": "Puerto" }, - "description": "Se trata de una integraci\u00f3n para llamadas a tel\u00e9fonos fijos que utilizan un m\u00f3dem de voz CX93001. Puede recuperar la informaci\u00f3n del identificador de llamadas con una opci\u00f3n para rechazar una llamada entrante.", - "title": "M\u00f3dem telef\u00f3nico" + "description": "Se trata de una integraci\u00f3n para llamadas a tel\u00e9fonos fijos que utilizan un m\u00f3dem de voz CX93001. Puede recuperar la informaci\u00f3n del identificador de llamadas con una opci\u00f3n para rechazar una llamada entrante." } } } diff --git a/homeassistant/components/modem_callerid/translations/et.json b/homeassistant/components/modem_callerid/translations/et.json index 463d24e8f9f..10f80b53068 100644 --- a/homeassistant/components/modem_callerid/translations/et.json +++ b/homeassistant/components/modem_callerid/translations/et.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "See on sidumine fiksv\u00f5rgu telefonile kasutades CX93001 modemit. See v\u00f5ib hankida helistaja ID teabe koos sissetulevast k\u00f5nestloobumise v\u00f5imalusega.", - "title": "Telefoniliini modem" + "description": "See on sidumine fiksv\u00f5rgu telefonile kasutades CX93001 modemit. See v\u00f5ib hankida helistaja ID teabe koos sissetulevast k\u00f5nestloobumise v\u00f5imalusega." }, "user": { "data": { "name": "Nimi", "port": "Port" }, - "description": "See on sidumine fiksv\u00f5rgu telefonile kasutades CX93001 modemit. See v\u00f5ib hankida helistaja ID teabe koos sissetulevast k\u00f5nestloobumise v\u00f5imalusega.", - "title": "Telefoniliini modem" + "description": "See on sidumine fiksv\u00f5rgu telefonile kasutades CX93001 modemit. See v\u00f5ib hankida helistaja ID teabe koos sissetulevast k\u00f5nestloobumise v\u00f5imalusega." } } } diff --git a/homeassistant/components/modem_callerid/translations/fr.json b/homeassistant/components/modem_callerid/translations/fr.json index 696927d66db..36a07399836 100644 --- a/homeassistant/components/modem_callerid/translations/fr.json +++ b/homeassistant/components/modem_callerid/translations/fr.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Il s'agit d'une int\u00e9gration pour les appels fixes utilisant un modem vocal CX93001. Cela peut r\u00e9cup\u00e9rer les informations d'identification de l'appelant avec une option pour rejeter un appel entrant.", - "title": "Modem t\u00e9l\u00e9phonique" + "description": "Il s'agit d'une int\u00e9gration pour les appels fixes utilisant un modem vocal CX93001. Cela peut r\u00e9cup\u00e9rer les informations d'identification de l'appelant avec une option pour rejeter un appel entrant." }, "user": { "data": { "name": "Nom", "port": "Port" }, - "description": "Il s'agit d'une int\u00e9gration pour les appels fixes utilisant un modem vocal CX93001. Cela peut r\u00e9cup\u00e9rer les informations d'identification de l'appelant avec une option pour rejeter un appel entrant.", - "title": "Modem t\u00e9l\u00e9phonique" + "description": "Il s'agit d'une int\u00e9gration pour les appels fixes utilisant un modem vocal CX93001. Cela peut r\u00e9cup\u00e9rer les informations d'identification de l'appelant avec une option pour rejeter un appel entrant." } } } diff --git a/homeassistant/components/modem_callerid/translations/hu.json b/homeassistant/components/modem_callerid/translations/hu.json index 2a53c24d902..5e3f7727314 100644 --- a/homeassistant/components/modem_callerid/translations/hu.json +++ b/homeassistant/components/modem_callerid/translations/hu.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "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.", - "title": "Telefon modem" + "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." }, "user": { "data": { "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.", - "title": "Telefon modem" + "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/modem_callerid/translations/id.json b/homeassistant/components/modem_callerid/translations/id.json index 3aa455d5f4c..accce6cea78 100644 --- a/homeassistant/components/modem_callerid/translations/id.json +++ b/homeassistant/components/modem_callerid/translations/id.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Integrasi ini adalah integrasi untuk panggilan darat menggunakan modem suara CX93001. Integrasi dapat mengambil informasi ID pemanggil dengan opsi untuk menolak panggilan masuk.", - "title": "Modem Telepon" + "description": "Integrasi ini adalah integrasi untuk panggilan darat menggunakan modem suara CX93001. Integrasi dapat mengambil informasi ID pemanggil dengan opsi untuk menolak panggilan masuk." }, "user": { "data": { "name": "Nama", "port": "Port" }, - "description": "Integrasi ini adalah integrasi untuk panggilan darat menggunakan modem suara CX93001. Integrasi dapat mengambil informasi ID pemanggil dengan opsi untuk menolak panggilan masuk.", - "title": "Modem Telepon" + "description": "Integrasi ini adalah integrasi untuk panggilan darat menggunakan modem suara CX93001. Integrasi dapat mengambil informasi ID pemanggil dengan opsi untuk menolak panggilan masuk." } } } diff --git a/homeassistant/components/modem_callerid/translations/it.json b/homeassistant/components/modem_callerid/translations/it.json index 65d1c74f956..1282541da5c 100644 --- a/homeassistant/components/modem_callerid/translations/it.json +++ b/homeassistant/components/modem_callerid/translations/it.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Questa \u00e8 un'integrazione per le chiamate su linea fissa che utilizza un modem vocale CX93001. Questo pu\u00f2 recuperare le informazioni sull'ID del chiamante con un'opzione per rifiutare una chiamata in arrivo.", - "title": "Modem del telefono" + "description": "Questa \u00e8 un'integrazione per le chiamate su linea fissa che utilizza un modem vocale CX93001. Questo pu\u00f2 recuperare le informazioni sull'ID del chiamante con un'opzione per rifiutare una chiamata in arrivo." }, "user": { "data": { "name": "Nome", "port": "Porta" }, - "description": "Questa \u00e8 un'integrazione per le chiamate su linea fissa che utilizza un modem vocale CX93001. Questo pu\u00f2 recuperare le informazioni sull'ID del chiamante con un'opzione per rifiutare una chiamata in arrivo.", - "title": "Modem del telefono" + "description": "Questa \u00e8 un'integrazione per le chiamate su linea fissa che utilizza un modem vocale CX93001. Questo pu\u00f2 recuperare le informazioni sull'ID del chiamante con un'opzione per rifiutare una chiamata in arrivo." } } } diff --git a/homeassistant/components/modem_callerid/translations/ja.json b/homeassistant/components/modem_callerid/translations/ja.json index ab5ab732931..a5b9df933ef 100644 --- a/homeassistant/components/modem_callerid/translations/ja.json +++ b/homeassistant/components/modem_callerid/translations/ja.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "\u3053\u308c\u306f\u3001CX93001\u97f3\u58f0\u30e2\u30c7\u30e0\u3092\u4f7f\u7528\u3057\u305f\u56fa\u5b9a\u96fb\u8a71\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u3059\u3002\u767a\u4fe1\u8005\u756a\u53f7\u60c5\u5831\u3092\u53d6\u5f97\u3059\u308b\u3053\u3068\u3067\u3001\u7740\u4fe1\u3092\u62d2\u5426\u3059\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u3082\u3042\u308a\u307e\u3059\u3002", - "title": "Phone\u30e2\u30c7\u30e0" + "description": "\u3053\u308c\u306f\u3001CX93001\u97f3\u58f0\u30e2\u30c7\u30e0\u3092\u4f7f\u7528\u3057\u305f\u56fa\u5b9a\u96fb\u8a71\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u3059\u3002\u767a\u4fe1\u8005\u756a\u53f7\u60c5\u5831\u3092\u53d6\u5f97\u3059\u308b\u3053\u3068\u3067\u3001\u7740\u4fe1\u3092\u62d2\u5426\u3059\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u3082\u3042\u308a\u307e\u3059\u3002" }, "user": { "data": { "name": "\u540d\u524d", "port": "\u30dd\u30fc\u30c8" }, - "description": "\u3053\u308c\u306f\u3001CX93001\u97f3\u58f0\u30e2\u30c7\u30e0\u3092\u4f7f\u7528\u3057\u305f\u56fa\u5b9a\u96fb\u8a71\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u3059\u3002\u767a\u4fe1\u8005\u756a\u53f7\u60c5\u5831\u3092\u53d6\u5f97\u3059\u308b\u3053\u3068\u3067\u3001\u7740\u4fe1\u3092\u62d2\u5426\u3059\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u3082\u3042\u308a\u307e\u3059\u3002", - "title": "Phone\u30e2\u30c7\u30e0" + "description": "\u3053\u308c\u306f\u3001CX93001\u97f3\u58f0\u30e2\u30c7\u30e0\u3092\u4f7f\u7528\u3057\u305f\u56fa\u5b9a\u96fb\u8a71\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u3059\u3002\u767a\u4fe1\u8005\u756a\u53f7\u60c5\u5831\u3092\u53d6\u5f97\u3059\u308b\u3053\u3068\u3067\u3001\u7740\u4fe1\u3092\u62d2\u5426\u3059\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u3082\u3042\u308a\u307e\u3059\u3002" } } } diff --git a/homeassistant/components/modem_callerid/translations/nl.json b/homeassistant/components/modem_callerid/translations/nl.json index 4077a03105b..c5107ce6e53 100644 --- a/homeassistant/components/modem_callerid/translations/nl.json +++ b/homeassistant/components/modem_callerid/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "no_devices_found": "Geen resterende apparaten gevonden" }, "error": { @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Dit is een integratie voor vaste telefoongesprekken met een CX93001 spraakmodem. Hiermee kan beller-ID informatie worden opgehaald met een optie om een inkomende oproep te weigeren.", - "title": "Telefoonmodem" + "description": "Dit is een integratie voor vaste telefoongesprekken met een CX93001 spraakmodem. Hiermee kan beller-ID informatie worden opgehaald met een optie om een inkomende oproep te weigeren." }, "user": { "data": { "name": "Naam", "port": "Poort" }, - "description": "Dit is een integratie voor vaste telefoongesprekken met een CX93001 spraakmodem. Hiermee kan beller-ID informatie worden opgehaald met een optie om een inkomende oproep te weigeren.", - "title": "Telefoonmodem" + "description": "Dit is een integratie voor vaste telefoongesprekken met een CX93001 spraakmodem. Hiermee kan beller-ID informatie worden opgehaald met een optie om een inkomende oproep te weigeren." } } } diff --git a/homeassistant/components/modem_callerid/translations/no.json b/homeassistant/components/modem_callerid/translations/no.json index 2e1103b5092..768d8a29ce3 100644 --- a/homeassistant/components/modem_callerid/translations/no.json +++ b/homeassistant/components/modem_callerid/translations/no.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Dette er en integrasjon for fasttelefonsamtaler ved hjelp av et talemodem CX93001. Dette kan hente oppringer -ID -informasjon med et alternativ for \u00e5 avvise et innkommende anrop.", - "title": "Telefonmodem" + "description": "Dette er en integrasjon for fasttelefonsamtaler ved hjelp av et talemodem CX93001. Dette kan hente oppringer -ID -informasjon med et alternativ for \u00e5 avvise et innkommende anrop." }, "user": { "data": { "name": "Navn", "port": "Port" }, - "description": "Dette er en integrasjon for fasttelefonsamtaler ved hjelp av et talemodem CX93001. Dette kan hente oppringer -ID -informasjon med et alternativ for \u00e5 avvise et innkommende anrop.", - "title": "Telefonmodem" + "description": "Dette er en integrasjon for fasttelefonsamtaler ved hjelp av et talemodem CX93001. Dette kan hente oppringer -ID -informasjon med et alternativ for \u00e5 avvise et innkommende anrop." } } } diff --git a/homeassistant/components/modem_callerid/translations/pl.json b/homeassistant/components/modem_callerid/translations/pl.json index 5101fc574a0..b608a467395 100644 --- a/homeassistant/components/modem_callerid/translations/pl.json +++ b/homeassistant/components/modem_callerid/translations/pl.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Jest to integracja dla po\u0142\u0105cze\u0144 stacjonarnych przy u\u017cyciu modemu g\u0142osowego CX93001. Mo\u017ce ona pobra\u0107 informacje o identyfikatorze dzwoni\u0105cego z opcj\u0105 odrzucenia po\u0142\u0105czenia przychodz\u0105cego.", - "title": "Modem telefoniczny" + "description": "Jest to integracja dla po\u0142\u0105cze\u0144 stacjonarnych przy u\u017cyciu modemu g\u0142osowego CX93001. Mo\u017ce ona pobra\u0107 informacje o identyfikatorze dzwoni\u0105cego z opcj\u0105 odrzucenia po\u0142\u0105czenia przychodz\u0105cego." }, "user": { "data": { "name": "Nazwa", "port": "Port" }, - "description": "Jest to integracja dla po\u0142\u0105cze\u0144 stacjonarnych przy u\u017cyciu modemu g\u0142osowego CX93001. Mo\u017ce ona pobra\u0107 informacje o identyfikatorze dzwoni\u0105cego z opcj\u0105 odrzucenia po\u0142\u0105czenia przychodz\u0105cego.", - "title": "Modem telefoniczny" + "description": "Jest to integracja dla po\u0142\u0105cze\u0144 stacjonarnych przy u\u017cyciu modemu g\u0142osowego CX93001. Mo\u017ce ona pobra\u0107 informacje o identyfikatorze dzwoni\u0105cego z opcj\u0105 odrzucenia po\u0142\u0105czenia przychodz\u0105cego." } } } diff --git a/homeassistant/components/modem_callerid/translations/pt-BR.json b/homeassistant/components/modem_callerid/translations/pt-BR.json index 39394d0752e..d82fbf8e5b6 100644 --- a/homeassistant/components/modem_callerid/translations/pt-BR.json +++ b/homeassistant/components/modem_callerid/translations/pt-BR.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Esta \u00e9 uma integra\u00e7\u00e3o para chamadas fixas usando um modem de voz CX93001. Isso pode recuperar informa\u00e7\u00f5es de identifica\u00e7\u00e3o de chamadas com a op\u00e7\u00e3o de rejeitar uma chamada recebida.", - "title": "Modem do telefone" + "description": "Esta \u00e9 uma integra\u00e7\u00e3o para chamadas fixas usando um modem de voz CX93001. Isso pode recuperar informa\u00e7\u00f5es de identifica\u00e7\u00e3o de chamadas com a op\u00e7\u00e3o de rejeitar uma chamada recebida." }, "user": { "data": { "name": "Nome", "port": "Porta" }, - "description": "Esta \u00e9 uma integra\u00e7\u00e3o para chamadas fixas usando um modem de voz CX93001. Isso pode recuperar informa\u00e7\u00f5es de identifica\u00e7\u00e3o de chamadas com a op\u00e7\u00e3o de rejeitar uma chamada recebida.", - "title": "Modem do telefone" + "description": "Esta \u00e9 uma integra\u00e7\u00e3o para chamadas fixas usando um modem de voz CX93001. Isso pode recuperar informa\u00e7\u00f5es de identifica\u00e7\u00e3o de chamadas com a op\u00e7\u00e3o de rejeitar uma chamada recebida." } } } diff --git a/homeassistant/components/modem_callerid/translations/ru.json b/homeassistant/components/modem_callerid/translations/ru.json index f5fa5061a4a..7217131b78f 100644 --- a/homeassistant/components/modem_callerid/translations/ru.json +++ b/homeassistant/components/modem_callerid/translations/ru.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0433\u043e \u043c\u043e\u0434\u0435\u043c\u0430 CX93001 \u0434\u043b\u044f \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u043f\u043e \u0441\u0442\u0430\u0446\u0438\u043e\u043d\u0430\u0440\u043d\u043e\u0439 \u043b\u0438\u043d\u0438\u0438. \u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e \u0430\u0431\u043e\u043d\u0435\u043d\u0442\u0430 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u044f \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430.", - "title": "\u0422\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u044b\u0439 \u043c\u043e\u0434\u0435\u043c" + "description": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0433\u043e \u043c\u043e\u0434\u0435\u043c\u0430 CX93001 \u0434\u043b\u044f \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u043f\u043e \u0441\u0442\u0430\u0446\u0438\u043e\u043d\u0430\u0440\u043d\u043e\u0439 \u043b\u0438\u043d\u0438\u0438. \u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e \u0430\u0431\u043e\u043d\u0435\u043d\u0442\u0430 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u044f \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430." }, "user": { "data": { "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "port": "\u041f\u043e\u0440\u0442" }, - "description": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0433\u043e \u043c\u043e\u0434\u0435\u043c\u0430 CX93001 \u0434\u043b\u044f \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u043f\u043e \u0441\u0442\u0430\u0446\u0438\u043e\u043d\u0430\u0440\u043d\u043e\u0439 \u043b\u0438\u043d\u0438\u0438. \u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e \u0430\u0431\u043e\u043d\u0435\u043d\u0442\u0430 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u044f \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430.", - "title": "\u0422\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u044b\u0439 \u043c\u043e\u0434\u0435\u043c" + "description": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0433\u043e \u043c\u043e\u0434\u0435\u043c\u0430 CX93001 \u0434\u043b\u044f \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u043f\u043e \u0441\u0442\u0430\u0446\u0438\u043e\u043d\u0430\u0440\u043d\u043e\u0439 \u043b\u0438\u043d\u0438\u0438. \u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e \u0430\u0431\u043e\u043d\u0435\u043d\u0442\u0430 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u044f \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430." } } } diff --git a/homeassistant/components/modem_callerid/translations/tr.json b/homeassistant/components/modem_callerid/translations/tr.json index eec011d8c6f..373fe0d5e2f 100644 --- a/homeassistant/components/modem_callerid/translations/tr.json +++ b/homeassistant/components/modem_callerid/translations/tr.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "Bu, CX93001 sesli modem kullanan sabit hat aramalar\u0131 i\u00e7in bir entegrasyondur. Bu, gelen bir aramay\u0131 reddetme se\u00e7ene\u011fi ile arayan kimli\u011fi bilgilerini alabilir.", - "title": "Telefon Modemi" + "description": "Bu, CX93001 sesli modem kullanan sabit hat aramalar\u0131 i\u00e7in bir entegrasyondur. Bu, gelen bir aramay\u0131 reddetme se\u00e7ene\u011fi ile arayan kimli\u011fi bilgilerini alabilir." }, "user": { "data": { "name": "Ad", "port": "Port" }, - "description": "Bu, CX93001 sesli modem kullanan sabit hat aramalar\u0131 i\u00e7in bir entegrasyondur. Bu, gelen bir aramay\u0131 reddetme se\u00e7ene\u011fi ile arayan kimli\u011fi bilgilerini alabilir.", - "title": "Telefon Modemi" + "description": "Bu, CX93001 sesli modem kullanan sabit hat aramalar\u0131 i\u00e7in bir entegrasyondur. Bu, gelen bir aramay\u0131 reddetme se\u00e7ene\u011fi ile arayan kimli\u011fi bilgilerini alabilir." } } } diff --git a/homeassistant/components/modem_callerid/translations/zh-Hant.json b/homeassistant/components/modem_callerid/translations/zh-Hant.json index 542a12e8c5d..2d07c73cb1e 100644 --- a/homeassistant/components/modem_callerid/translations/zh-Hant.json +++ b/homeassistant/components/modem_callerid/translations/zh-Hant.json @@ -10,16 +10,14 @@ }, "step": { "usb_confirm": { - "description": "\u6b64\u6574\u5408\u4f7f\u7528 CX93001 \u8a9e\u97f3\u6578\u64da\u6a5f\u9032\u884c\u5e02\u8a71\u901a\u8a71\u3002\u53ef\u7528\u4ee5\u6aa2\u67e5\u4f86\u96fb ID \u8cc7\u8a0a\u3001\u4e26\u9032\u884c\u62d2\u63a5\u4f86\u96fb\u7684\u529f\u80fd\u3002", - "title": "\u624b\u6a5f\u6578\u64da\u6a5f" + "description": "\u6b64\u6574\u5408\u4f7f\u7528 CX93001 \u8a9e\u97f3\u6578\u64da\u6a5f\u9032\u884c\u5e02\u8a71\u901a\u8a71\u3002\u53ef\u7528\u4ee5\u6aa2\u67e5\u4f86\u96fb ID \u8cc7\u8a0a\u3001\u4e26\u9032\u884c\u62d2\u63a5\u4f86\u96fb\u7684\u529f\u80fd\u3002" }, "user": { "data": { "name": "\u540d\u7a31", "port": "\u901a\u8a0a\u57e0" }, - "description": "\u6b64\u6574\u5408\u4f7f\u7528 CX93001 \u8a9e\u97f3\u6578\u64da\u6a5f\u9032\u884c\u5e02\u8a71\u901a\u8a71\u3002\u53ef\u7528\u4ee5\u6aa2\u67e5\u4f86\u96fb ID \u8cc7\u8a0a\u3001\u4e26\u9032\u884c\u62d2\u63a5\u4f86\u96fb\u7684\u529f\u80fd\u3002", - "title": "\u624b\u6a5f\u6578\u64da\u6a5f" + "description": "\u6b64\u6574\u5408\u4f7f\u7528 CX93001 \u8a9e\u97f3\u6578\u64da\u6a5f\u9032\u884c\u5e02\u8a71\u901a\u8a71\u3002\u53ef\u7528\u4ee5\u6aa2\u67e5\u4f86\u96fb ID \u8cc7\u8a0a\u3001\u4e26\u9032\u884c\u62d2\u63a5\u4f86\u96fb\u7684\u529f\u80fd\u3002" } } } diff --git a/homeassistant/components/modern_forms/translations/bg.json b/homeassistant/components/modern_forms/translations/bg.json index 4200524546f..d2124752f04 100644 --- a/homeassistant/components/modern_forms/translations/bg.json +++ b/homeassistant/components/modern_forms/translations/bg.json @@ -9,15 +9,11 @@ }, "flow_title": "{name}", "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?" - }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442" } } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/ca.json b/homeassistant/components/modern_forms/translations/ca.json index e7a70e80b93..4296368ac89 100644 --- a/homeassistant/components/modern_forms/translations/ca.json +++ b/homeassistant/components/modern_forms/translations/ca.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Vols comen\u00e7ar la configuraci\u00f3?" - }, "user": { "data": { "host": "Amfitri\u00f3" @@ -23,6 +20,5 @@ "title": "Dispositiu ventilador Modern Forms descobert" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/de.json b/homeassistant/components/modern_forms/translations/de.json index bf16c9532fc..3c9280b22f3 100644 --- a/homeassistant/components/modern_forms/translations/de.json +++ b/homeassistant/components/modern_forms/translations/de.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "M\u00f6chtest Du mit der Einrichtung beginnen?" - }, "user": { "data": { "host": "Host" @@ -23,6 +20,5 @@ "title": "Erkannter Modern Forms Ventilator" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/el.json b/homeassistant/components/modern_forms/translations/el.json index b8cb77ffa99..10811327076 100644 --- a/homeassistant/components/modern_forms/translations/el.json +++ b/homeassistant/components/modern_forms/translations/el.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "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;" - }, "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" @@ -23,6 +20,5 @@ "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b5\u03bc\u03b9\u03c3\u03c4\u03ae\u03c1\u03c9\u03bd Modern Forms" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/en.json b/homeassistant/components/modern_forms/translations/en.json index 6c51e42a2d1..f61cd67cfde 100644 --- a/homeassistant/components/modern_forms/translations/en.json +++ b/homeassistant/components/modern_forms/translations/en.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Do you want to start set up?" - }, "user": { "data": { "host": "Host" @@ -23,6 +20,5 @@ "title": "Discovered Modern Forms fan device" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/es.json b/homeassistant/components/modern_forms/translations/es.json index 4512c0bc595..29b51c03cf1 100644 --- a/homeassistant/components/modern_forms/translations/es.json +++ b/homeassistant/components/modern_forms/translations/es.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "\u00bfQuieres iniciar la configuraci\u00f3n?" - }, "user": { "data": { "host": "Host" @@ -23,6 +20,5 @@ "title": "Dispositivo de ventilador de Modern Forms descubierto" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/et.json b/homeassistant/components/modern_forms/translations/et.json index 7140888db84..155c081f67c 100644 --- a/homeassistant/components/modern_forms/translations/et.json +++ b/homeassistant/components/modern_forms/translations/et.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Kas soovid alustada seadistamist?" - }, "user": { "data": { "host": "Host" @@ -23,6 +20,5 @@ "title": "Leitud Modern Forms ventilaator" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/fr.json b/homeassistant/components/modern_forms/translations/fr.json index d68f4a7f680..85b7681057a 100644 --- a/homeassistant/components/modern_forms/translations/fr.json +++ b/homeassistant/components/modern_forms/translations/fr.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Voulez-vous commencer la configuration\u00a0?" - }, "user": { "data": { "host": "H\u00f4te" @@ -23,6 +20,5 @@ "title": "D\u00e9couverte du dispositif de ventilateur Modern Forms" } } - }, - "title": "Formes modernes" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/he.json b/homeassistant/components/modern_forms/translations/he.json index 701a3689598..1cd249b4daa 100644 --- a/homeassistant/components/modern_forms/translations/he.json +++ b/homeassistant/components/modern_forms/translations/he.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05d7\u05d9\u05dc \u05d1\u05d4\u05d2\u05d3\u05e8\u05d4?" - }, "user": { "data": { "host": "\u05de\u05d0\u05e8\u05d7" diff --git a/homeassistant/components/modern_forms/translations/hu.json b/homeassistant/components/modern_forms/translations/hu.json index 49f5da5339f..a4ef399188c 100644 --- a/homeassistant/components/modern_forms/translations/hu.json +++ b/homeassistant/components/modern_forms/translations/hu.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "El szeretn\u00e9 kezdeni a be\u00e1ll\u00edt\u00e1st?" - }, "user": { "data": { "host": "C\u00edm" @@ -23,6 +20,5 @@ "title": "Felfedezte a Modern Forms rajong\u00f3i eszk\u00f6zt" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/id.json b/homeassistant/components/modern_forms/translations/id.json index c85667c4449..703ffce37be 100644 --- a/homeassistant/components/modern_forms/translations/id.json +++ b/homeassistant/components/modern_forms/translations/id.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Ingin memulai penyiapan?" - }, "user": { "data": { "host": "Host" @@ -23,6 +20,5 @@ "title": "Perangkat kipas Modern Forms yang Ditemukan" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/it.json b/homeassistant/components/modern_forms/translations/it.json index 18f1d5f503a..8feeab0a058 100644 --- a/homeassistant/components/modern_forms/translations/it.json +++ b/homeassistant/components/modern_forms/translations/it.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Vuoi iniziare la configurazione?" - }, "user": { "data": { "host": "Host" @@ -23,6 +20,5 @@ "title": "Rilevato il dispositivo ventilatore di Modern Forms" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/ja.json b/homeassistant/components/modern_forms/translations/ja.json index 7806675b5c6..064f4b8f4cd 100644 --- a/homeassistant/components/modern_forms/translations/ja.json +++ b/homeassistant/components/modern_forms/translations/ja.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u958b\u59cb\u3057\u307e\u3059\u304b\uff1f" - }, "user": { "data": { "host": "\u30db\u30b9\u30c8" @@ -23,6 +20,5 @@ "title": "Modern Forms fan device\u3092\u767a\u898b" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/ko.json b/homeassistant/components/modern_forms/translations/ko.json new file mode 100644 index 00000000000..6079edcd0da --- /dev/null +++ b/homeassistant/components/modern_forms/translations/ko.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "flow_title": "{name}", + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8" + }, + "description": "Home Assistant\uc640 \uc5f0\uacb0\ub418\ub3c4\ub85d Modern Forms \ud32c\uc744 \uc124\uc815\ud558\uc2ed\uc2dc\uc624." + }, + "zeroconf_confirm": { + "description": "` {name} ` Modern Forms \ud32c\uc744 Home Assistant\uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Modern Forms \ud32c \uc7a5\uce58 \ubc1c\uacac" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/nl.json b/homeassistant/components/modern_forms/translations/nl.json index ccbdf7d5b44..ecb65db17d8 100644 --- a/homeassistant/components/modern_forms/translations/nl.json +++ b/homeassistant/components/modern_forms/translations/nl.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Wilt u beginnen met instellen?" - }, "user": { "data": { "host": "Host" @@ -23,6 +20,5 @@ "title": "Ontdekt Modern Forms ventilator apparaat" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/no.json b/homeassistant/components/modern_forms/translations/no.json index e7da915d39b..a96d1617117 100644 --- a/homeassistant/components/modern_forms/translations/no.json +++ b/homeassistant/components/modern_forms/translations/no.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Vil du starte oppsettet?" - }, "user": { "data": { "host": "Vert" @@ -23,6 +20,5 @@ "title": "Oppdaget Modern Forms-vifteenhet" } } - }, - "title": "Moderne former" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/pl.json b/homeassistant/components/modern_forms/translations/pl.json index f68be10b7cd..8437f00f2f8 100644 --- a/homeassistant/components/modern_forms/translations/pl.json +++ b/homeassistant/components/modern_forms/translations/pl.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" - }, "user": { "data": { "host": "Nazwa hosta lub adres IP" @@ -23,6 +20,5 @@ "title": "Wykryto wentylator Modern Forms" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/pt-BR.json b/homeassistant/components/modern_forms/translations/pt-BR.json index 07a68e2fb84..63b5f7e12f5 100644 --- a/homeassistant/components/modern_forms/translations/pt-BR.json +++ b/homeassistant/components/modern_forms/translations/pt-BR.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Deseja iniciar a configura\u00e7\u00e3o?" - }, "user": { "data": { "host": "Nome do host" @@ -23,6 +20,5 @@ "title": "Dispositivo de ventilador do Modern Forms descoberto" } } - }, - "title": "Formas modernas" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/ru.json b/homeassistant/components/modern_forms/translations/ru.json index 68b46c57f47..86f2080a90f 100644 --- a/homeassistant/components/modern_forms/translations/ru.json +++ b/homeassistant/components/modern_forms/translations/ru.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" - }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442" @@ -23,6 +20,5 @@ "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Modern Forms" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/tr.json b/homeassistant/components/modern_forms/translations/tr.json index e27881802fb..d80e905e17a 100644 --- a/homeassistant/components/modern_forms/translations/tr.json +++ b/homeassistant/components/modern_forms/translations/tr.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "Kuruluma ba\u015flamak ister misiniz?" - }, "user": { "data": { "host": "Sunucu" @@ -23,6 +20,5 @@ "title": "Ke\u015ffedilen Modern Formlar fan cihaz\u0131" } } - }, - "title": "Modern Formlar" + } } \ No newline at end of file diff --git a/homeassistant/components/modern_forms/translations/zh-Hant.json b/homeassistant/components/modern_forms/translations/zh-Hant.json index df3ebf486c7..054ad19ee1a 100644 --- a/homeassistant/components/modern_forms/translations/zh-Hant.json +++ b/homeassistant/components/modern_forms/translations/zh-Hant.json @@ -9,9 +9,6 @@ }, "flow_title": "{name}", "step": { - "confirm": { - "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" - }, "user": { "data": { "host": "\u4e3b\u6a5f\u7aef" @@ -23,6 +20,5 @@ "title": "\u6240\u767c\u73fe\u7684\u88dd\u7f6e\u4e26\u975e Modern Forms \u98a8\u6247\u88dd\u7f6e" } } - }, - "title": "Modern Forms" + } } \ No newline at end of file diff --git a/homeassistant/components/moehlenhoff_alpha2/__init__.py b/homeassistant/components/moehlenhoff_alpha2/__init__.py index 62e18917dc6..86306a56033 100644 --- a/homeassistant/components/moehlenhoff_alpha2/__init__.py +++ b/homeassistant/components/moehlenhoff_alpha2/__init__.py @@ -17,7 +17,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.CLIMATE] +PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.BINARY_SENSOR] UPDATE_INTERVAL = timedelta(seconds=60) @@ -65,10 +65,16 @@ class Alpha2BaseCoordinator(DataUpdateCoordinator[dict[str, dict]]): update_interval=UPDATE_INTERVAL, ) - async def _async_update_data(self) -> dict[str, dict]: + async def _async_update_data(self) -> dict[str, dict[str, dict]]: """Fetch the latest data from the source.""" await self.base.update_data() - return {ha["ID"]: ha for ha in self.base.heat_areas if ha.get("ID")} + return { + "heat_areas": {ha["ID"]: ha for ha in self.base.heat_areas if ha.get("ID")}, + "heat_controls": { + hc["ID"]: hc for hc in self.base.heat_controls if hc.get("ID") + }, + "io_devices": {io["ID"]: io for io in self.base.io_devices if io.get("ID")}, + } def get_cooling(self) -> bool: """Return if cooling mode is enabled.""" @@ -92,7 +98,7 @@ class Alpha2BaseCoordinator(DataUpdateCoordinator[dict[str, dict]]): update_data = {"T_TARGET": target_temperature} is_cooling = self.get_cooling() - heat_area_mode = self.data[heat_area_id]["HEATAREA_MODE"] + heat_area_mode = self.data["heat_areas"][heat_area_id]["HEATAREA_MODE"] if heat_area_mode == 1: if is_cooling: update_data["T_COOL_DAY"] = target_temperature @@ -110,7 +116,7 @@ class Alpha2BaseCoordinator(DataUpdateCoordinator[dict[str, dict]]): raise HomeAssistantError( "Failed to set target temperature, communication error with alpha2 base" ) from http_err - self.data[heat_area_id].update(update_data) + self.data["heat_areas"][heat_area_id].update(update_data) for update_callback in self._listeners: update_callback() @@ -135,25 +141,25 @@ class Alpha2BaseCoordinator(DataUpdateCoordinator[dict[str, dict]]): "Failed to set heat area mode, communication error with alpha2 base" ) from http_err - self.data[heat_area_id]["HEATAREA_MODE"] = heat_area_mode + self.data["heat_areas"][heat_area_id]["HEATAREA_MODE"] = heat_area_mode is_cooling = self.get_cooling() if heat_area_mode == 1: if is_cooling: - self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][ - "T_COOL_DAY" - ] + self.data["heat_areas"][heat_area_id]["T_TARGET"] = self.data[ + "heat_areas" + ][heat_area_id]["T_COOL_DAY"] else: - self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][ - "T_HEAT_DAY" - ] + self.data["heat_areas"][heat_area_id]["T_TARGET"] = self.data[ + "heat_areas" + ][heat_area_id]["T_HEAT_DAY"] elif heat_area_mode == 2: if is_cooling: - self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][ - "T_COOL_NIGHT" - ] + self.data["heat_areas"][heat_area_id]["T_TARGET"] = self.data[ + "heat_areas" + ][heat_area_id]["T_COOL_NIGHT"] else: - self.data[heat_area_id]["T_TARGET"] = self.data[heat_area_id][ - "T_HEAT_NIGHT" - ] + self.data["heat_areas"][heat_area_id]["T_TARGET"] = self.data[ + "heat_areas" + ][heat_area_id]["T_HEAT_NIGHT"] for update_callback in self._listeners: update_callback() diff --git a/homeassistant/components/moehlenhoff_alpha2/binary_sensor.py b/homeassistant/components/moehlenhoff_alpha2/binary_sensor.py new file mode 100644 index 00000000000..ddd92c3a70b --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/binary_sensor.py @@ -0,0 +1,56 @@ +"""Support for Alpha2 IO device battery sensors.""" + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) +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 homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import Alpha2BaseCoordinator +from .const import DOMAIN + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Add Alpha2 sensor entities from a config_entry.""" + + coordinator: Alpha2BaseCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + async_add_entities( + Alpha2IODeviceBatterySensor(coordinator, io_device_id) + for io_device_id, io_device in coordinator.data["io_devices"].items() + if io_device["_HEATAREA_ID"] + ) + + +class Alpha2IODeviceBatterySensor( + CoordinatorEntity[Alpha2BaseCoordinator], BinarySensorEntity +): + """Alpha2 IO device battery binary sensor.""" + + _attr_device_class = BinarySensorDeviceClass.BATTERY + _attr_entity_category = EntityCategory.DIAGNOSTIC + + def __init__(self, coordinator: Alpha2BaseCoordinator, io_device_id: str) -> None: + """Initialize Alpha2IODeviceBatterySensor.""" + super().__init__(coordinator) + self.io_device_id = io_device_id + self._attr_unique_id = f"{io_device_id}:battery" + io_device = self.coordinator.data["io_devices"][io_device_id] + heat_area = self.coordinator.data["heat_areas"][io_device["_HEATAREA_ID"]] + self._attr_name = ( + f"{heat_area['HEATAREA_NAME']} IO device {io_device['NR']} battery" + ) + + @property + def is_on(self): + """Return the state of the sensor.""" + # 0=empty, 1=weak, 2=good + return self.coordinator.data["io_devices"][self.io_device_id]["BATTERY"] < 2 diff --git a/homeassistant/components/moehlenhoff_alpha2/climate.py b/homeassistant/components/moehlenhoff_alpha2/climate.py index 225735293ca..e14f4801661 100644 --- a/homeassistant/components/moehlenhoff_alpha2/climate.py +++ b/homeassistant/components/moehlenhoff_alpha2/climate.py @@ -29,7 +29,8 @@ async def async_setup_entry( coordinator: Alpha2BaseCoordinator = hass.data[DOMAIN][config_entry.entry_id] async_add_entities( - Alpha2Climate(coordinator, heat_area_id) for heat_area_id in coordinator.data + Alpha2Climate(coordinator, heat_area_id) + for heat_area_id in coordinator.data["heat_areas"] ) @@ -51,26 +52,34 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): super().__init__(coordinator) self.heat_area_id = heat_area_id self._attr_unique_id = heat_area_id - - @property - def name(self) -> str: - """Return the name of the climate device.""" - return self.coordinator.data[self.heat_area_id]["HEATAREA_NAME"] + self._attr_name = self.coordinator.data["heat_areas"][heat_area_id][ + "HEATAREA_NAME" + ] @property def min_temp(self) -> float: """Return the minimum temperature.""" - return float(self.coordinator.data[self.heat_area_id].get("T_TARGET_MIN", 0.0)) + return float( + self.coordinator.data["heat_areas"][self.heat_area_id].get( + "T_TARGET_MIN", 0.0 + ) + ) @property def max_temp(self) -> float: """Return the maximum temperature.""" - return float(self.coordinator.data[self.heat_area_id].get("T_TARGET_MAX", 30.0)) + return float( + self.coordinator.data["heat_areas"][self.heat_area_id].get( + "T_TARGET_MAX", 30.0 + ) + ) @property def current_temperature(self) -> float: """Return the current temperature.""" - return float(self.coordinator.data[self.heat_area_id].get("T_ACTUAL", 0.0)) + return float( + self.coordinator.data["heat_areas"][self.heat_area_id].get("T_ACTUAL", 0.0) + ) @property def hvac_mode(self) -> HVACMode: @@ -86,7 +95,9 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): @property def hvac_action(self) -> HVACAction: """Return the current running hvac operation.""" - if not self.coordinator.data[self.heat_area_id]["_HEATCTRL_STATE"]: + if not self.coordinator.data["heat_areas"][self.heat_area_id][ + "_HEATCTRL_STATE" + ]: return HVACAction.IDLE if self.coordinator.get_cooling(): return HVACAction.COOLING @@ -95,7 +106,9 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): @property def target_temperature(self) -> float: """Return the temperature we try to reach.""" - return float(self.coordinator.data[self.heat_area_id].get("T_TARGET", 0.0)) + return float( + self.coordinator.data["heat_areas"][self.heat_area_id].get("T_TARGET", 0.0) + ) async def async_set_temperature(self, **kwargs) -> None: """Set new target temperatures.""" @@ -109,9 +122,9 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): @property def preset_mode(self) -> str: """Return the current preset mode.""" - if self.coordinator.data[self.heat_area_id]["HEATAREA_MODE"] == 1: + if self.coordinator.data["heat_areas"][self.heat_area_id]["HEATAREA_MODE"] == 1: return PRESET_DAY - if self.coordinator.data[self.heat_area_id]["HEATAREA_MODE"] == 2: + if self.coordinator.data["heat_areas"][self.heat_area_id]["HEATAREA_MODE"] == 2: return PRESET_NIGHT return PRESET_AUTO diff --git a/homeassistant/components/moehlenhoff_alpha2/manifest.json b/homeassistant/components/moehlenhoff_alpha2/manifest.json index db362163e72..12e7a927906 100644 --- a/homeassistant/components/moehlenhoff_alpha2/manifest.json +++ b/homeassistant/components/moehlenhoff_alpha2/manifest.json @@ -3,7 +3,7 @@ "name": "Möhlenhoff Alpha 2", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/moehlenhoff_alpha2", - "requirements": ["moehlenhoff-alpha2==1.1.2"], + "requirements": ["moehlenhoff-alpha2==1.2.1"], "iot_class": "local_push", "codeowners": ["@j-a-n"] } diff --git a/homeassistant/components/moehlenhoff_alpha2/sensor.py b/homeassistant/components/moehlenhoff_alpha2/sensor.py new file mode 100644 index 00000000000..d26786e1923 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/sensor.py @@ -0,0 +1,56 @@ +"""Support for Alpha2 heat control valve opening sensors.""" + +from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import Alpha2BaseCoordinator +from .const import DOMAIN + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Add Alpha2 sensor entities from a config_entry.""" + + coordinator: Alpha2BaseCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + # HEATCTRL attribute ACTOR_PERCENT is not available in older firmware versions + async_add_entities( + Alpha2HeatControlValveOpeningSensor(coordinator, heat_control_id) + for heat_control_id, heat_control in coordinator.data["heat_controls"].items() + if heat_control["INUSE"] + and heat_control["_HEATAREA_ID"] + and heat_control.get("ACTOR_PERCENT") is not None + ) + + +class Alpha2HeatControlValveOpeningSensor( + CoordinatorEntity[Alpha2BaseCoordinator], SensorEntity +): + """Alpha2 heat control valve opening sensor.""" + + _attr_native_unit_of_measurement = PERCENTAGE + + def __init__( + self, coordinator: Alpha2BaseCoordinator, heat_control_id: str + ) -> None: + """Initialize Alpha2HeatControlValveOpeningSensor.""" + super().__init__(coordinator) + self.heat_control_id = heat_control_id + self._attr_unique_id = f"{heat_control_id}:valve_opening" + heat_control = self.coordinator.data["heat_controls"][heat_control_id] + heat_area = self.coordinator.data["heat_areas"][heat_control["_HEATAREA_ID"]] + self._attr_name = f"{heat_area['HEATAREA_NAME']} heat control {heat_control['NR']} valve opening" + + @property + def native_value(self) -> int: + """Return the current valve opening percentage.""" + return self.coordinator.data["heat_controls"][self.heat_control_id][ + "ACTOR_PERCENT" + ] diff --git a/homeassistant/components/moehlenhoff_alpha2/translations/es.json b/homeassistant/components/moehlenhoff_alpha2/translations/es.json index 81e3d96b7bc..12821ae906d 100644 --- a/homeassistant/components/moehlenhoff_alpha2/translations/es.json +++ b/homeassistant/components/moehlenhoff_alpha2/translations/es.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "host": "Anfitri\u00f3n" + "host": "Host" } } } diff --git a/homeassistant/components/monoprice/translations/fr.json b/homeassistant/components/monoprice/translations/fr.json index 5489fb4e0e6..7f1a2a7a72e 100644 --- a/homeassistant/components/monoprice/translations/fr.json +++ b/homeassistant/components/monoprice/translations/fr.json @@ -11,12 +11,12 @@ "user": { "data": { "port": "Port", - "source_1": "Nom de la source #1", - "source_2": "Nom de la source #2", - "source_3": "Nom de la source #3", - "source_4": "Nom de la source #4", - "source_5": "Nom de la source #5", - "source_6": "Nom de la source #6" + "source_1": "Nom de la source n\u00ba\u00a01", + "source_2": "Nom de la source n\u00ba\u00a02", + "source_3": "Nom de la source n\u00ba\u00a03", + "source_4": "Nom de la source n\u00ba\u00a04", + "source_5": "Nom de la source n\u00ba\u00a05", + "source_6": "Nom de la source n\u00ba\u00a06" }, "title": "Se connecter \u00e0 l'appareil" } @@ -26,12 +26,12 @@ "step": { "init": { "data": { - "source_1": "Nom de la source #1", - "source_2": "Nom de la source #2", - "source_3": "Nom de la source #3", - "source_4": "Nom de la source #4", - "source_5": "Nom de la source #5", - "source_6": "Nom de la source #6" + "source_1": "Nom de la source n\u00ba\u00a01", + "source_2": "Nom de la source n\u00ba\u00a02", + "source_3": "Nom de la source n\u00ba\u00a03", + "source_4": "Nom de la source n\u00ba\u00a04", + "source_5": "Nom de la source n\u00ba\u00a05", + "source_6": "Nom de la source n\u00ba\u00a06" }, "title": "Configurer les sources" } diff --git a/homeassistant/components/moon/translations/ko.json b/homeassistant/components/moon/translations/ko.json new file mode 100644 index 00000000000..758f3336cd4 --- /dev/null +++ b/homeassistant/components/moon/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/translations/nl.json b/homeassistant/components/moon/translations/nl.json index ebcef695e04..0d626abfd7b 100644 --- a/homeassistant/components/moon/translations/nl.json +++ b/homeassistant/components/moon/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "user": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } }, diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index 63c80d33dba..95ac1c5fd44 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING from motionblinds import DEVICE_TYPES_WIFI, AsyncMotionMulticast, ParseException -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.const import CONF_API_KEY, CONF_HOST, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady @@ -25,6 +25,8 @@ from .const import ( KEY_COORDINATOR, KEY_GATEWAY, KEY_MULTICAST_LISTENER, + KEY_SETUP_LOCK, + KEY_UNSUB_STOP, KEY_VERSION, MANUFACTURER, PLATFORMS, @@ -106,6 +108,7 @@ class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the motion_blinds components from a config entry.""" hass.data.setdefault(DOMAIN, {}) + setup_lock = hass.data[DOMAIN].setdefault(KEY_SETUP_LOCK, asyncio.Lock()) host = entry.data[CONF_HOST] key = entry.data[CONF_API_KEY] multicast_interface = entry.data.get(CONF_INTERFACE, DEFAULT_INTERFACE) @@ -113,33 +116,41 @@ 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=working_interface) - hass.data[DOMAIN][KEY_MULTICAST_LISTENER] = multicast - # start listening for local pushes (only once) - await multicast.Start_listen() + async with setup_lock: + if KEY_MULTICAST_LISTENER not in hass.data[DOMAIN]: + # 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, + ) - # register stop callback to shutdown listening for local pushes - def stop_motion_multicast(event): - """Stop multicast thread.""" - _LOGGER.debug("Shutting down Motion Listener") - multicast.Stop_listen() + multicast = AsyncMotionMulticast(interface=working_interface) + hass.data[DOMAIN][KEY_MULTICAST_LISTENER] = multicast + # start listening for local pushes (only once) + await multicast.Start_listen() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_motion_multicast) + # register stop callback to shutdown listening for local pushes + def stop_motion_multicast(event): + """Stop multicast thread.""" + _LOGGER.debug("Shutting down Motion Listener") + multicast.Stop_listen() + + unsub = hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, stop_motion_multicast + ) + hass.data[DOMAIN][KEY_UNSUB_STOP] = unsub # Connect to motion gateway multicast = hass.data[DOMAIN][KEY_MULTICAST_LISTENER] @@ -205,10 +216,19 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> ) if unload_ok: + multicast = hass.data[DOMAIN][KEY_MULTICAST_LISTENER] + multicast.Unregister_motion_gateway(config_entry.data[CONF_HOST]) hass.data[DOMAIN].pop(config_entry.entry_id) - if len(hass.data[DOMAIN]) == 1: + loaded_entries = [ + entry + for entry in hass.config_entries.async_entries(DOMAIN) + if entry.state == ConfigEntryState.LOADED + ] + if len(loaded_entries) == 1: # No motion gateways left, stop Motion multicast + unsub_stop = hass.data[DOMAIN].pop(KEY_UNSUB_STOP) + unsub_stop() _LOGGER.debug("Shutting down Motion Listener") multicast = hass.data[DOMAIN].pop(KEY_MULTICAST_LISTENER) multicast.Stop_listen() diff --git a/homeassistant/components/motion_blinds/const.py b/homeassistant/components/motion_blinds/const.py index a35aeb6cd89..332a30a5e5f 100644 --- a/homeassistant/components/motion_blinds/const.py +++ b/homeassistant/components/motion_blinds/const.py @@ -16,6 +16,8 @@ KEY_GATEWAY = "gateway" KEY_API_LOCK = "api_lock" KEY_COORDINATOR = "coordinator" KEY_MULTICAST_LISTENER = "multicast_listener" +KEY_SETUP_LOCK = "setup_lock" +KEY_UNSUB_STOP = "unsub_stop" KEY_VERSION = "version" ATTR_WIDTH = "width" diff --git a/homeassistant/components/motion_blinds/cover.py b/homeassistant/components/motion_blinds/cover.py index ccd4043faee..7bac3a5fb20 100644 --- a/homeassistant/components/motion_blinds/cover.py +++ b/homeassistant/components/motion_blinds/cover.py @@ -9,6 +9,7 @@ from homeassistant.components.cover import ( ATTR_TILT_POSITION, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -64,6 +65,10 @@ TILT_DEVICE_MAP = { BlindType.VerticalBlindRight: CoverDeviceClass.BLIND, } +TILT_ONLY_DEVICE_MAP = { + BlindType.WoodShutter: CoverDeviceClass.BLIND, +} + TDBU_DEVICE_MAP = { BlindType.TopDownBottomUp: CoverDeviceClass.SHADE, } @@ -71,6 +76,7 @@ TDBU_DEVICE_MAP = { SET_ABSOLUTE_POSITION_SCHEMA = { vol.Required(ATTR_ABSOLUTE_POSITION): vol.All(cv.positive_int, vol.Range(max=100)), + vol.Optional(ATTR_TILT_POSITION): vol.All(cv.positive_int, vol.Range(max=100)), vol.Optional(ATTR_WIDTH): vol.All(cv.positive_int, vol.Range(max=100)), } @@ -107,6 +113,16 @@ async def async_setup_entry( ) ) + elif blind.type in TILT_ONLY_DEVICE_MAP: + entities.append( + MotionTiltOnlyDevice( + coordinator, + blind, + TILT_ONLY_DEVICE_MAP[blind.type], + sw_version, + ) + ) + elif blind.type in TDBU_DEVICE_MAP: entities.append( MotionTDBUDevice( @@ -163,6 +179,8 @@ async def async_setup_entry( class MotionPositionDevice(CoordinatorEntity, CoverEntity): """Representation of a Motion Blind Device.""" + _restore_tilt = False + def __init__(self, coordinator, blind, device_class, sw_version): """Initialize the blind.""" super().__init__(coordinator) @@ -179,7 +197,7 @@ class MotionPositionDevice(CoordinatorEntity, CoverEntity): else: via_device = (DOMAIN, blind._gateway.mac) connections = {} - name = f"{blind.blind_type}-{blind.mac[12:]}" + name = f"{blind.blind_type} {blind.mac[12:]}" sw_version = None self._attr_device_class = device_class @@ -287,16 +305,25 @@ class MotionPositionDevice(CoordinatorEntity, CoverEntity): position = kwargs[ATTR_POSITION] async with self._api_lock: await self.hass.async_add_executor_job( - self._blind.Set_position, 100 - position + self._blind.Set_position, + 100 - position, + None, + self._restore_tilt, ) await self.async_request_position_till_stop() async def async_set_absolute_position(self, **kwargs): """Move the cover to a specific absolute position (see TDBU).""" position = kwargs[ATTR_ABSOLUTE_POSITION] + angle = kwargs.get(ATTR_TILT_POSITION) + if angle is not None: + angle = angle * 180 / 100 async with self._api_lock: await self.hass.async_add_executor_job( - self._blind.Set_position, 100 - position + self._blind.Set_position, + 100 - position, + angle, + self._restore_tilt, ) await self.async_request_position_till_stop() @@ -309,6 +336,8 @@ class MotionPositionDevice(CoordinatorEntity, CoverEntity): class MotionTiltDevice(MotionPositionDevice): """Representation of a Motion Blind Device.""" + _restore_tilt = True + @property def current_cover_tilt_position(self): """ @@ -342,6 +371,49 @@ class MotionTiltDevice(MotionPositionDevice): await self.hass.async_add_executor_job(self._blind.Stop) +class MotionTiltOnlyDevice(MotionTiltDevice): + """Representation of a Motion Blind Device.""" + + _restore_tilt = False + + @property + def supported_features(self): + """Flag supported features.""" + supported_features = ( + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + ) + + if self.current_cover_tilt_position is not None: + supported_features |= CoverEntityFeature.SET_TILT_POSITION + + return supported_features + + @property + def current_cover_position(self): + """Return current position of cover.""" + return None + + @property + def is_closed(self): + """Return if the cover is closed or not.""" + if self._blind.angle is None: + return None + return self._blind.angle == 0 + + async def async_set_absolute_position(self, **kwargs): + """Move the cover to a specific absolute position (see TDBU).""" + angle = kwargs.get(ATTR_TILT_POSITION) + if angle is not None: + angle = angle * 180 / 100 + async with self._api_lock: + await self.hass.async_add_executor_job( + self._blind.Set_angle, + angle, + ) + + class MotionTDBUDevice(MotionPositionDevice): """Representation of a Motion Top Down Bottom Up blind Device.""" @@ -350,7 +422,7 @@ class MotionTDBUDevice(MotionPositionDevice): super().__init__(coordinator, blind, device_class, sw_version) self._motor = motor self._motor_key = motor[0] - self._attr_name = f"{blind.blind_type}-{motor}-{blind.mac[12:]}" + self._attr_name = f"{blind.blind_type} {blind.mac[12:]} {motor}" self._attr_unique_id = f"{blind.mac}-{motor}" if self._motor not in ["Bottom", "Top", "Combined"]: diff --git a/homeassistant/components/motion_blinds/gateway.py b/homeassistant/components/motion_blinds/gateway.py index 1775f7deb7d..218da9f625c 100644 --- a/homeassistant/components/motion_blinds/gateway.py +++ b/homeassistant/components/motion_blinds/gateway.py @@ -101,6 +101,8 @@ class ConnectMotionGateway: await check_multicast.Start_listen() except socket.gaierror: continue + except OSError: + continue # trigger test multicast self._gateway_device = MotionGateway( diff --git a/homeassistant/components/motion_blinds/manifest.json b/homeassistant/components/motion_blinds/manifest.json index 54aa5c97659..bc09d3e9e38 100644 --- a/homeassistant/components/motion_blinds/manifest.json +++ b/homeassistant/components/motion_blinds/manifest.json @@ -3,7 +3,7 @@ "name": "Motion Blinds", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/motion_blinds", - "requirements": ["motionblinds==0.6.5"], + "requirements": ["motionblinds==0.6.8"], "dependencies": ["network"], "dhcp": [ { "registered_devices": true }, @@ -19,5 +19,21 @@ ], "codeowners": ["@starkillerOG"], "iot_class": "local_push", - "loggers": ["motionblinds"] + "loggers": ["motionblinds"], + "supported_brands": { + "amp_motorization": "AMP Motorization", + "bliss_automation": "Bliss Automation", + "bloc_blinds": "Bloc Blinds", + "brel_home": "Brel Home", + "3_day_blinds": "3 Day Blinds", + "dooya": "Dooya", + "gaviota": "Gaviota", + "hurrican_shutters_wholesale": "Hurrican Shutters Wholesale", + "ismartwindow": "iSmartWindow", + "martec": "Martec", + "raven_rock_mfg": "Raven Rock MFG", + "smart_blinds": "Smart Blinds", + "smart_home": "Smart Home", + "uprise_smart_shades": "Uprise Smart Shades" + } } diff --git a/homeassistant/components/motion_blinds/sensor.py b/homeassistant/components/motion_blinds/sensor.py index 03e3a6e7618..ebaed95bcbf 100644 --- a/homeassistant/components/motion_blinds/sensor.py +++ b/homeassistant/components/motion_blinds/sensor.py @@ -55,9 +55,9 @@ class MotionBatterySensor(CoordinatorEntity, SensorEntity): super().__init__(coordinator) if blind.device_type in DEVICE_TYPES_WIFI: - name = f"{blind.blind_type}-battery" + name = f"{blind.blind_type} battery" else: - name = f"{blind.blind_type}-battery-{blind.mac[12:]}" + name = f"{blind.blind_type} {blind.mac[12:]} battery" self._blind = blind self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, blind.mac)}) @@ -104,9 +104,9 @@ class MotionTDBUBatterySensor(MotionBatterySensor): super().__init__(coordinator, blind) if blind.device_type in DEVICE_TYPES_WIFI: - name = f"{blind.blind_type}-{motor}-battery" + name = f"{blind.blind_type} {motor} battery" else: - name = f"{blind.blind_type}-{motor}-battery-{blind.mac[12:]}" + name = f"{blind.blind_type} {blind.mac[12:]} {motor} battery" self._motor = motor self._attr_unique_id = f"{blind.mac}-{motor}-battery" @@ -147,7 +147,7 @@ class MotionSignalStrengthSensor(CoordinatorEntity, SensorEntity): elif device.device_type in DEVICE_TYPES_WIFI: name = f"{device.blind_type} signal strength" else: - name = f"{device.blind_type} signal strength - {device.mac[12:]}" + name = f"{device.blind_type} {device.mac[12:]} signal strength" self._device = device self._device_type = device_type diff --git a/homeassistant/components/motion_blinds/services.yaml b/homeassistant/components/motion_blinds/services.yaml index 1ee60923332..d37d6bb5be8 100644 --- a/homeassistant/components/motion_blinds/services.yaml +++ b/homeassistant/components/motion_blinds/services.yaml @@ -14,8 +14,17 @@ set_absolute_position: required: true selector: number: - min: 1 + min: 0 max: 100 + unit_of_measurement: "%" + tilt_position: + name: Tilt position + description: Tilt position to move to. + selector: + number: + min: 0 + max: 100 + unit_of_measurement: "%" width: name: Width description: Specify the width that is covered, only for TDBU Combined entities. @@ -23,3 +32,4 @@ set_absolute_position: number: min: 1 max: 100 + unit_of_measurement: "%" diff --git a/homeassistant/components/motion_blinds/strings.json b/homeassistant/components/motion_blinds/strings.json index 13a4d117344..0b1482883aa 100644 --- a/homeassistant/components/motion_blinds/strings.json +++ b/homeassistant/components/motion_blinds/strings.json @@ -26,7 +26,7 @@ "discovery_error": "Failed to discover a Motion Gateway" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%], connection settings are updated", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "connection_error": "[%key:common::config_flow::error::cannot_connect%]" } diff --git a/homeassistant/components/motion_blinds/translations/bg.json b/homeassistant/components/motion_blinds/translations/bg.json index dad41cb1999..e78d7032040 100644 --- a/homeassistant/components/motion_blinds/translations/bg.json +++ b/homeassistant/components/motion_blinds/translations/bg.json @@ -4,9 +4,6 @@ "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", "connection_error": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, - "error": { - "invalid_interface": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d \u043c\u0440\u0435\u0436\u043e\u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441" - }, "step": { "connect": { "data": { @@ -20,7 +17,6 @@ }, "user": { "data": { - "api_key": "API \u043a\u043b\u044e\u0447", "host": "IP \u0430\u0434\u0440\u0435\u0441" } } diff --git a/homeassistant/components/motion_blinds/translations/ca.json b/homeassistant/components/motion_blinds/translations/ca.json index 18cc8f892d6..8de2711298b 100644 --- a/homeassistant/components/motion_blinds/translations/ca.json +++ b/homeassistant/components/motion_blinds/translations/ca.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat, la configuraci\u00f3 de connexi\u00f3 s'ha actualitzat", + "already_configured": "El dispositiu ja est\u00e0 configurat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "connection_error": "Ha fallat la connexi\u00f3" }, "error": { - "discovery_error": "No s'ha pogut descobrir cap Motion Gateway", - "invalid_interface": "Interf\u00edcie de xarxa no v\u00e0lida" + "discovery_error": "No s'ha pogut descobrir cap Motion Gateway" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "Clau API", - "interface": "Interf\u00edcie de xarxa a utilitzar" + "api_key": "Clau API" }, - "description": "Necessitar\u00e0s la clau API de 16 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", - "title": "Motion Blinds" + "description": "Necessitar\u00e0s la clau API de 16 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key." }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "Clau API", "host": "Adre\u00e7a IP" }, - "description": "Connecta el teu Motion Gateway, si no es configura l'adre\u00e7a IP, s'utilitza el descobriment autom\u00e0tic", - "title": "Motion Blinds" + "description": "Connecta el teu Motion Gateway, si no es configura l'adre\u00e7a IP, s'utilitza el descobriment autom\u00e0tic" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Espera l'entrada multidifusi\u00f3 en actualitzar" - }, - "description": "Especifica configuracions opcionals", - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/cs.json b/homeassistant/components/motion_blinds/translations/cs.json index 899f04d7cd4..be4dd0eb8bf 100644 --- a/homeassistant/components/motion_blinds/translations/cs.json +++ b/homeassistant/components/motion_blinds/translations/cs.json @@ -19,10 +19,8 @@ }, "user": { "data": { - "api_key": "Kl\u00ed\u010d API", "host": "IP adresa" - }, - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/de.json b/homeassistant/components/motion_blinds/translations/de.json index c5ced3ddd55..b483ea35dd5 100644 --- a/homeassistant/components/motion_blinds/translations/de.json +++ b/homeassistant/components/motion_blinds/translations/de.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert, Verbindungseinstellungen werden aktualisiert", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "connection_error": "Verbindung fehlgeschlagen" }, "error": { - "discovery_error": "Motion-Gateway konnte nicht gefunden werden", - "invalid_interface": "Ung\u00fcltige Netzwerkschnittstelle" + "discovery_error": "Motion-Gateway konnte nicht gefunden werden" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "API-Schl\u00fcssel", - "interface": "Die zu verwendende Netzwerkschnittstelle" + "api_key": "API-Schl\u00fcssel" }, - "description": "Ein 16-Zeichen-API-Schl\u00fcssel wird ben\u00f6tigt, siehe https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", - "title": "Motion Jalousien" + "description": "Ein 16-Zeichen-API-Schl\u00fcssel wird ben\u00f6tigt, siehe https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "API-Schl\u00fcssel", "host": "IP-Adresse" }, - "description": "Stelle eine Verbindung zu deinem Motion Gateway her. Wenn die IP-Adresse leer bleibt, wird die automatische Erkennung verwendet", - "title": "Jalousien" + "description": "Stelle eine Verbindung zu deinem Motion Gateway her. Wenn die IP-Adresse leer bleibt, wird die automatische Erkennung verwendet" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Warten auf Multicast-Push bei Aktualisierung" - }, - "description": "Optionale Einstellungen angeben", - "title": "Motion Jalousien" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/el.json b/homeassistant/components/motion_blinds/translations/el.json index 2914382e820..b9d67703c57 100644 --- a/homeassistant/components/motion_blinds/translations/el.json +++ b/homeassistant/components/motion_blinds/translations/el.json @@ -6,18 +6,15 @@ "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "error": { - "discovery_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2", - "invalid_interface": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" + "discovery_error": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2" }, "flow_title": "Motion Blinds", "step": { "connect": { "data": { - "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", - "interface": "\u0397 \u03b4\u03b9\u03b1\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af" + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" }, - "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API 16 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd, \u03b4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2.", - "title": "Motion Blinds" + "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API 16 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd, \u03b4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2." }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" }, - "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Motion Gateway, \u03b5\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7", - "title": "Motion Blinds" + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf Motion Gateway, \u03b5\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "\u0391\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae \u03b3\u03b9\u03b1 multicast push \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7" - }, - "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ce\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd", - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/en.json b/homeassistant/components/motion_blinds/translations/en.json index 92931ee27ab..33784e1d386 100644 --- a/homeassistant/components/motion_blinds/translations/en.json +++ b/homeassistant/components/motion_blinds/translations/en.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Device is already configured, connection settings are updated", + "already_configured": "Device is already configured", "already_in_progress": "Configuration flow is already in progress", "connection_error": "Failed to connect" }, "error": { - "discovery_error": "Failed to discover a Motion Gateway", - "invalid_interface": "Invalid network interface" + "discovery_error": "Failed to discover a Motion Gateway" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "API Key", - "interface": "The network interface to use" + "api_key": "API Key" }, - "description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions", - "title": "Motion Blinds" + "description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "API Key", "host": "IP Address" }, - "description": "Connect to your Motion Gateway, if the IP address is not set, auto-discovery is used", - "title": "Motion Blinds" + "description": "Connect to your Motion Gateway, if the IP address is not set, auto-discovery is used" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Wait for multicast push on update" - }, - "description": "Specify optional settings", - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/es.json b/homeassistant/components/motion_blinds/translations/es.json index 0c364f394ef..1a4312d4fe0 100644 --- a/homeassistant/components/motion_blinds/translations/es.json +++ b/homeassistant/components/motion_blinds/translations/es.json @@ -6,18 +6,15 @@ "connection_error": "No se pudo conectar" }, "error": { - "discovery_error": "No se pudo descubrir un detector de movimiento", - "invalid_interface": "Interfaz de red inv\u00e1lida" + "discovery_error": "No se pudo descubrir un detector de movimiento" }, "flow_title": "Motion Blinds", "step": { "connect": { "data": { - "api_key": "Clave API", - "interface": "La interfaz de la red a usar" + "api_key": "Clave API" }, - "description": "Necesitar\u00e1 la clave de API de 16 caracteres, consulte https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key para obtener instrucciones", - "title": "Estores motorizados" + "description": "Necesitar\u00e1 la clave de API de 16 caracteres, consulte https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key para obtener instrucciones" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "Clave API", "host": "Direcci\u00f3n IP" }, - "description": "Con\u00e9ctate a tu Motion Gateway, si la direcci\u00f3n IP no est\u00e1 establecida, se utilitzar\u00e1 la detecci\u00f3n autom\u00e1tica", - "title": "Motion Blinds" + "description": "Con\u00e9ctate a tu Motion Gateway, si la direcci\u00f3n IP no est\u00e1 establecida, se utilitzar\u00e1 la detecci\u00f3n autom\u00e1tica" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Espere a que se realice la actualizaci\u00f3n de multidifusi\u00f3n" - }, - "description": "Especifica los ajustes opcionales", - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/et.json b/homeassistant/components/motion_blinds/translations/et.json index 935e345ea3a..eb1fecf1118 100644 --- a/homeassistant/components/motion_blinds/translations/et.json +++ b/homeassistant/components/motion_blinds/translations/et.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Seade on juba h\u00e4\u00e4lestatud, \u00fchenduse s\u00e4tted on v\u00e4rskendatud", + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "already_in_progress": "Seadistamine on juba k\u00e4imas", "connection_error": "\u00dchendamine nurjus" }, "error": { - "discovery_error": "Motion Gateway avastamine nurjus", - "invalid_interface": "Sobimatu v\u00f5rguliides" + "discovery_error": "Motion Gateway avastamine nurjus" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "API v\u00f5ti", - "interface": "Kasutatav v\u00f5rguliides" + "api_key": "API v\u00f5ti" }, - "description": "On vaja 16-kohalist API-v\u00f5tit, juhiste saamiseks vaata https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", - "title": "" + "description": "On vaja 16-kohalist API-v\u00f5tit, juhiste saamiseks vaata https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "API v\u00f5ti", "host": "IP-aadress" }, - "description": "\u00dchenda oma Motion Gatewayga. Kui IP-aadress on m\u00e4\u00e4ramata kasutatakse automaatset avastamist", - "title": "" + "description": "\u00dchenda oma Motion Gatewayga. Kui IP-aadress on m\u00e4\u00e4ramata kasutatakse automaatset avastamist" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Oota multicast'i t\u00f5ukev\u00e4rskendust" - }, - "description": "Valikuliste s\u00e4tete m\u00e4\u00e4ramine", - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/fr.json b/homeassistant/components/motion_blinds/translations/fr.json index 09cf93fde60..c22ac830ebe 100644 --- a/homeassistant/components/motion_blinds/translations/fr.json +++ b/homeassistant/components/motion_blinds/translations/fr.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9, les param\u00e8tres de connexion ont \u00e9t\u00e9 mis \u00e0 jour", + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "connection_error": "\u00c9chec de connexion" }, "error": { - "discovery_error": "Impossible de d\u00e9couvrir une Motion Gateway", - "invalid_interface": "Interface r\u00e9seau non valide" + "discovery_error": "Impossible de d\u00e9couvrir une Motion Gateway" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "Cl\u00e9 d'API", - "interface": "Interface r\u00e9seau \u00e0 utiliser" + "api_key": "Cl\u00e9 d'API" }, - "description": "Vous aurez besoin de la cl\u00e9 API de 16 caract\u00e8res, voir https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key pour les instructions", - "title": "Stores de mouvement" + "description": "Vous aurez besoin de la cl\u00e9 API de 16 caract\u00e8res, voir https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key pour les instructions" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "Cl\u00e9 d'API", "host": "Adresse IP" }, - "description": "Connectez-vous \u00e0 votre Motion Gateway, si l'adresse IP n'est pas d\u00e9finie, la d\u00e9tection automatique est utilis\u00e9e", - "title": "Stores de mouvement" + "description": "Connectez-vous \u00e0 votre Motion Gateway, si l'adresse IP n'est pas d\u00e9finie, la d\u00e9tection automatique est utilis\u00e9e" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Attendre la mise \u00e0 jour de la diffusion group\u00e9e" - }, - "description": "Sp\u00e9cifiez les param\u00e8tres optionnels", - "title": "Store motoris\u00e9" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/he.json b/homeassistant/components/motion_blinds/translations/he.json index ce6f3d2918e..3cf199985e5 100644 --- a/homeassistant/components/motion_blinds/translations/he.json +++ b/homeassistant/components/motion_blinds/translations/he.json @@ -18,7 +18,6 @@ }, "user": { "data": { - "api_key": "\u05de\u05e4\u05ea\u05d7 API", "host": "\u05db\u05ea\u05d5\u05d1\u05ea IP" } } diff --git a/homeassistant/components/motion_blinds/translations/hu.json b/homeassistant/components/motion_blinds/translations/hu.json index 64334c54a28..cef4a0426da 100644 --- a/homeassistant/components/motion_blinds/translations/hu.json +++ b/homeassistant/components/motion_blinds/translations/hu.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van, a csatlakoz\u00e1si be\u00e1ll\u00edt\u00e1sai friss\u00edtve vannak", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "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" + "discovery_error": "Nem siker\u00fclt felfedezni a Motion Gateway-t" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "API kulcs", - "interface": "A haszn\u00e1lni k\u00edv\u00e1nt h\u00e1l\u00f3zati interf\u00e9sz" + "api_key": "API kulcs" }, - "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": "Red\u0151ny/rol\u00f3" + "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" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "API kulcs", "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": "Red\u0151ny/rol\u00f3" + "description": "Csatlakozzon a Motion Gateway-hez, ha az IP-c\u00edm nincs be\u00e1ll\u00edtva, akkor az automatikus felder\u00edt\u00e9st haszn\u00e1lja" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Multicast adatokra v\u00e1rakoz\u00e1s friss\u00edt\u00e9skor" - }, - "description": "Opcion\u00e1lis be\u00e1ll\u00edt\u00e1sok megad\u00e1sa", - "title": "Red\u0151ny/rol\u00f3" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/id.json b/homeassistant/components/motion_blinds/translations/id.json index d576ced8c0a..5892916aa0d 100644 --- a/homeassistant/components/motion_blinds/translations/id.json +++ b/homeassistant/components/motion_blinds/translations/id.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Perangkat sudah dikonfigurasi, pengaturan koneksi diperbarui", + "already_configured": "Perangkat sudah dikonfigurasi", "already_in_progress": "Alur konfigurasi sedang berlangsung", "connection_error": "Gagal terhubung" }, "error": { - "discovery_error": "Gagal menemukan Motion Gateway", - "invalid_interface": "Antarmuka jaringan tidak valid" + "discovery_error": "Gagal menemukan Motion Gateway" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "Kunci API", - "interface": "Antarmuka jaringan yang akan digunakan" + "api_key": "Kunci API" }, - "description": "Anda akan memerlukan Kunci API 16 karakter, baca petunjuknya di https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", - "title": "Motion Blinds" + "description": "Anda akan memerlukan Kunci API 16 karakter, baca petunjuknya di https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "Kunci API", "host": "Alamat IP" }, - "description": "Hubungkan ke Motion Gateway Anda, jika alamat IP tidak disetel, penemuan otomatis akan digunakan", - "title": "Motion Blinds" + "description": "Hubungkan ke Motion Gateway Anda, jika alamat IP tidak disetel, penemuan otomatis akan digunakan" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Tunggu push multicast pada pembaruan" - }, - "description": "Tentukan pengaturan opsional", - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/it.json b/homeassistant/components/motion_blinds/translations/it.json index 0871ce07bf8..35168c391b7 100644 --- a/homeassistant/components/motion_blinds/translations/it.json +++ b/homeassistant/components/motion_blinds/translations/it.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato, le impostazioni di connessione sono aggiornate", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "connection_error": "Impossibile connettersi" }, "error": { - "discovery_error": "Impossibile rilevare un Motion Gateway", - "invalid_interface": "Interfaccia di rete non valida" + "discovery_error": "Impossibile rilevare un Motion Gateway" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "Chiave API", - "interface": "L'interfaccia di rete da utilizzare" + "api_key": "Chiave API" }, - "description": "Avrai bisogno della chiave API di 16 caratteri, consulta https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key per le istruzioni", - "title": "Motion Blinds" + "description": "Avrai bisogno della chiave API di 16 caratteri, consulta https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key per le istruzioni" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "Chiave API", "host": "Indirizzo IP" }, - "description": "Connetti il tuo Motion Gateway, se l'indirizzo IP non \u00e8 impostato, sar\u00e0 utilizzato il rilevamento automatico", - "title": "Tende Motion" + "description": "Connetti il tuo Motion Gateway, se l'indirizzo IP non \u00e8 impostato, sar\u00e0 utilizzato il rilevamento automatico" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Attendi il push multicast all'aggiornamento" - }, - "description": "Specifica le impostazioni opzionali", - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/ja.json b/homeassistant/components/motion_blinds/translations/ja.json index b81cfa365c3..df0ba1f8e47 100644 --- a/homeassistant/components/motion_blinds/translations/ja.json +++ b/homeassistant/components/motion_blinds/translations/ja.json @@ -6,18 +6,15 @@ "connection_error": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" }, "error": { - "discovery_error": "Motion Gateway\u306e\u691c\u51fa\u306b\u5931\u6557\u3057\u307e\u3057\u305f", - "invalid_interface": "\u7121\u52b9\u306a\u30cd\u30c3\u30c8\u30ef\u30fc\u30af \u30a4\u30f3\u30bf\u30fc\u30d5\u30a7\u30a4\u30b9" + "discovery_error": "Motion Gateway\u306e\u691c\u51fa\u306b\u5931\u6557\u3057\u307e\u3057\u305f" }, "flow_title": "\u30e2\u30fc\u30b7\u30e7\u30f3\u30d6\u30e9\u30a4\u30f3\u30c9", "step": { "connect": { "data": { - "api_key": "API\u30ad\u30fc", - "interface": "\u4f7f\u7528\u3059\u308b\u30cd\u30c3\u30c8\u30ef\u30fc\u30af \u30a4\u30f3\u30bf\u30fc\u30d5\u30a7\u30a4\u30b9" + "api_key": "API\u30ad\u30fc" }, - "description": "16\u6587\u5b57\u306eAPI\u30ad\u30fc\u304c\u5fc5\u8981\u3067\u3059\u3002\u624b\u9806\u306f\u3001https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "\u30e2\u30fc\u30b7\u30e7\u30f3\u30d6\u30e9\u30a4\u30f3\u30c9" + "description": "16\u6587\u5b57\u306eAPI\u30ad\u30fc\u304c\u5fc5\u8981\u3067\u3059\u3002\u624b\u9806\u306f\u3001https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "API\u30ad\u30fc", "host": "IP\u30a2\u30c9\u30ec\u30b9" }, - "description": "Motion Gateway\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": "\u30e2\u30fc\u30b7\u30e7\u30f3\u30d6\u30e9\u30a4\u30f3\u30c9" + "description": "Motion Gateway\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" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "\u66f4\u65b0\u6642\u306b\u30de\u30eb\u30c1\u30ad\u30e3\u30b9\u30c8 \u30d7\u30c3\u30b7\u30e5\u3092\u5f85\u6a5f\u3059\u308b" - }, - "description": "\u30aa\u30d7\u30b7\u30e7\u30f3\u8a2d\u5b9a\u306e\u6307\u5b9a", - "title": "\u30e2\u30fc\u30b7\u30e7\u30f3\u30d6\u30e9\u30a4\u30f3\u30c9" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/ka.json b/homeassistant/components/motion_blinds/translations/ka.json index e8ea5e0deba..71223e44db4 100644 --- a/homeassistant/components/motion_blinds/translations/ka.json +++ b/homeassistant/components/motion_blinds/translations/ka.json @@ -9,11 +9,9 @@ "step": { "user": { "data": { - "api_key": "API Key", "host": "IP \u10db\u10d8\u10e1\u10d0\u10db\u10d0\u10e0\u10d7\u10d8" }, - "description": "\u10d7\u10e5\u10d5\u10d4\u10dc \u10d3\u10d0\u10d2\u10ed\u10d8\u10e0\u10d3\u10d4\u10d1\u10d0\u10d7 16 \u10d0\u10e1\u10dd\u10d8\u10d0\u10dc\u10d8 API key, \u10d8\u10dc\u10e1\u10e2\u10e0\u10e3\u10e5\u10ea\u10d8\u10d8\u10e1\u10d7\u10d5\u10d8\u10e1 \u10d8\u10ee\u10d8\u10da\u10d4\u10d7 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", - "title": "\u10db\u10dd\u10eb\u10e0\u10d0\u10d5\u10d8 \u10df\u10d0\u10da\u10e3\u10d6\u10d4\u10d1\u10d8" + "description": "\u10d7\u10e5\u10d5\u10d4\u10dc \u10d3\u10d0\u10d2\u10ed\u10d8\u10e0\u10d3\u10d4\u10d1\u10d0\u10d7 16 \u10d0\u10e1\u10dd\u10d8\u10d0\u10dc\u10d8 API key, \u10d8\u10dc\u10e1\u10e2\u10e0\u10e3\u10e5\u10ea\u10d8\u10d8\u10e1\u10d7\u10d5\u10d8\u10e1 \u10d8\u10ee\u10d8\u10da\u10d4\u10d7 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key" } } } diff --git a/homeassistant/components/motion_blinds/translations/ko.json b/homeassistant/components/motion_blinds/translations/ko.json index 69ed2cd7b35..19ad1c03da8 100644 --- a/homeassistant/components/motion_blinds/translations/ko.json +++ b/homeassistant/components/motion_blinds/translations/ko.json @@ -14,8 +14,7 @@ "data": { "api_key": "API \ud0a4" }, - "description": "16\uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API Key\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "title": "Motion Blinds" + "description": "16\uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API Key\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "select": { "data": { @@ -26,11 +25,9 @@ }, "user": { "data": { - "api_key": "API \ud0a4", "host": "IP \uc8fc\uc18c" }, - "description": "Motion \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \uc8fc\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", - "title": "Motion Blinds" + "description": "Motion \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \uc8fc\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4" } } } diff --git a/homeassistant/components/motion_blinds/translations/lb.json b/homeassistant/components/motion_blinds/translations/lb.json index 85caeea79e5..b8f301535bf 100644 --- a/homeassistant/components/motion_blinds/translations/lb.json +++ b/homeassistant/components/motion_blinds/translations/lb.json @@ -22,10 +22,8 @@ }, "user": { "data": { - "api_key": "API Schl\u00ebssel", "host": "IP Adresse" - }, - "title": "Steierbar Jalousien" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/nl.json b/homeassistant/components/motion_blinds/translations/nl.json index 6cb69b06b87..316e2077796 100644 --- a/homeassistant/components/motion_blinds/translations/nl.json +++ b/homeassistant/components/motion_blinds/translations/nl.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd, verbindingsinstellingen zijn bijgewerkt", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratie is momenteel al bezig", "connection_error": "Kan geen verbinding maken" }, "error": { - "discovery_error": "Kan geen Motion Gateway vinden", - "invalid_interface": "Ongeldige netwerkinterface" + "discovery_error": "Kan geen Motion Gateway vinden" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "API-sleutel", - "interface": "De te gebruiken netwerkinterface" + "api_key": "API-sleutel" }, - "description": "U hebt de API-sleutel van 16 tekens nodig, zie https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key voor instructies", - "title": "Motion Blinds" + "description": "U hebt de API-sleutel van 16 tekens nodig, zie https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key voor instructies" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "API-sleutel", "host": "IP-adres" }, - "description": "Maak verbinding met uw Motion Gateway, als het IP-adres niet is ingesteld, wordt auto-discovery gebruikt", - "title": "Motion Blinds" + "description": "Maak verbinding met uw Motion Gateway, als het IP-adres niet is ingesteld, wordt auto-discovery gebruikt" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Wacht op multicast push bij update" - }, - "description": "Optionele instellingen opgeven", - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/no.json b/homeassistant/components/motion_blinds/translations/no.json index d6c4708ea8c..3545a03a543 100644 --- a/homeassistant/components/motion_blinds/translations/no.json +++ b/homeassistant/components/motion_blinds/translations/no.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert , tilkoblingsinnstillingene er oppdatert", + "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "connection_error": "Tilkobling mislyktes" }, "error": { - "discovery_error": "Kunne ikke oppdage en Motion Gateway", - "invalid_interface": "Ugyldig nettverksgrensesnitt" + "discovery_error": "Kunne ikke oppdage en Motion Gateway" }, "flow_title": "{short_mac} ( {ip_address} )", "step": { "connect": { "data": { - "api_key": "API-n\u00f8kkel", - "interface": "Nettverksgrensesnittet som skal brukes" + "api_key": "API-n\u00f8kkel" }, - "description": "Du trenger API-n\u00f8kkelen med 16 tegn, se https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instruksjoner", - "title": "" + "description": "Du trenger API-n\u00f8kkelen med 16 tegn, se https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instruksjoner" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "API-n\u00f8kkel", "host": "IP adresse" }, - "description": "Koble til Motion Gateway. Hvis IP-adressen ikke er angitt, brukes automatisk oppdagelse", - "title": "Motion Blinds" + "description": "Koble til Motion Gateway. Hvis IP-adressen ikke er angitt, brukes automatisk oppdagelse" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Vent p\u00e5 multicast push p\u00e5 oppdateringen" - }, - "description": "Spesifiser valgfrie innstillinger", - "title": "Bevegelse Persienner" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/pl.json b/homeassistant/components/motion_blinds/translations/pl.json index 2a042859b88..6dfef34f4ab 100644 --- a/homeassistant/components/motion_blinds/translations/pl.json +++ b/homeassistant/components/motion_blinds/translations/pl.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane, ustawienia po\u0142\u0105czenia zosta\u0142y zaktualizowane", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "error": { - "discovery_error": "Nie uda\u0142o si\u0119 wykry\u0107 bramki ruchu", - "invalid_interface": "Nieprawid\u0142owy interfejs sieciowy" + "discovery_error": "Nie uda\u0142o si\u0119 wykry\u0107 bramki ruchu" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "Klucz API", - "interface": "Interfejs sieciowy" + "api_key": "Klucz API" }, - "description": "B\u0119dziesz potrzebowa\u0142 16-znakowego klucza API, instrukcje znajdziesz na https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", - "title": "Motion Blinds" + "description": "B\u0119dziesz potrzebowa\u0142 16-znakowego klucza API, instrukcje znajdziesz na https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "Klucz API", "host": "Adres IP" }, - "description": "Po\u0142\u0105cz si\u0119 z bram\u0105 ruchu. Je\u015bli adres IP nie jest ustawiony, u\u017cywane jest automatyczne wykrywanie", - "title": "Rolety Motion" + "description": "Po\u0142\u0105cz si\u0119 z bram\u0105 ruchu. Je\u015bli adres IP nie jest ustawiony, u\u017cywane jest automatyczne wykrywanie" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Poczekaj na aktualizacj\u0119 multicast push" - }, - "description": "Okre\u015bl opcjonalne ustawienia", - "title": "Rolety Motion" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/pt-BR.json b/homeassistant/components/motion_blinds/translations/pt-BR.json index 01658cea852..0a3b68357ee 100644 --- a/homeassistant/components/motion_blinds/translations/pt-BR.json +++ b/homeassistant/components/motion_blinds/translations/pt-BR.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado, as configura\u00e7\u00f5es de conex\u00e3o s\u00e3o atualizadas", + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "connection_error": "Falha ao conectar" }, "error": { - "discovery_error": "Falha ao descobrir um Motion Gateway", - "invalid_interface": "Interface de rede inv\u00e1lida" + "discovery_error": "Falha ao descobrir um Motion Gateway" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "Chave da API", - "interface": "A interface de rede a ser utilizada" + "api_key": "Chave da API" }, - "description": "Voc\u00ea precisar\u00e1 da chave de API de 16 caracteres, consulte https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key para obter instru\u00e7\u00f5es", - "title": "Cortinas de movimento" + "description": "Voc\u00ea precisar\u00e1 da chave de API de 16 caracteres, consulte https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key para obter instru\u00e7\u00f5es" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "Chave da API", "host": "Endere\u00e7o IP" }, - "description": "Conecte-se ao seu Motion Gateway, se o endere\u00e7o IP n\u00e3o estiver definido, a descoberta autom\u00e1tica ser\u00e1 usada", - "title": "Cortinas de movimento" + "description": "Conecte-se ao seu Motion Gateway, se o endere\u00e7o IP n\u00e3o estiver definido, a descoberta autom\u00e1tica ser\u00e1 usada" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "Aguarde o push multicast na atualiza\u00e7\u00e3o" - }, - "description": "Especifique as configura\u00e7\u00f5es opcionais", - "title": "Cortinas de movimento" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/pt.json b/homeassistant/components/motion_blinds/translations/pt.json index e43b71438db..7538043f1ce 100644 --- a/homeassistant/components/motion_blinds/translations/pt.json +++ b/homeassistant/components/motion_blinds/translations/pt.json @@ -10,8 +10,7 @@ "connect": { "data": { "api_key": "Chave da API" - }, - "title": "Cortinas Motion" + } }, "select": { "data": { @@ -20,10 +19,8 @@ }, "user": { "data": { - "api_key": "Chave da API", "host": "Endere\u00e7o IP" - }, - "title": "Cortinas Motion" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/ru.json b/homeassistant/components/motion_blinds/translations/ru.json index e7a4492bd54..afcde99b123 100644 --- a/homeassistant/components/motion_blinds/translations/ru.json +++ b/homeassistant/components/motion_blinds/translations/ru.json @@ -1,23 +1,20 @@ { "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. \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_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.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "error": { - "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." + "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." }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "\u041a\u043b\u044e\u0447 API", - "interface": "\u0421\u0435\u0442\u0435\u0432\u043e\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441" + "api_key": "\u041a\u043b\u044e\u0447 API" }, - "description": "\u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", - "title": "Motion Blinds" + "description": "\u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key." }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "\u041a\u043b\u044e\u0447 API", "host": "IP-\u0430\u0434\u0440\u0435\u0441" }, - "description": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0448\u043b\u044e\u0437\u0443 Motion. \u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\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\u0435 IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u0443\u0441\u0442\u044b\u043c.", - "title": "Motion Blinds" + "description": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0448\u043b\u044e\u0437\u0443 Motion. \u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\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\u0435 IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u0443\u0441\u0442\u044b\u043c." } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "\u041e\u0436\u0438\u0434\u0430\u0442\u044c \u043c\u043d\u043e\u0433\u043e\u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438 \u043e\u0431 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438" - }, - "description": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/sk.json b/homeassistant/components/motion_blinds/translations/sk.json index e58538162d7..6ea9c064f16 100644 --- a/homeassistant/components/motion_blinds/translations/sk.json +++ b/homeassistant/components/motion_blinds/translations/sk.json @@ -8,11 +8,6 @@ "data": { "api_key": "API k\u013e\u00fa\u010d" } - }, - "user": { - "data": { - "api_key": "API k\u013e\u00fa\u010d" - } } } } diff --git a/homeassistant/components/motion_blinds/translations/sl.json b/homeassistant/components/motion_blinds/translations/sl.json deleted file mode 100644 index 80ea8b24fbb..00000000000 --- a/homeassistant/components/motion_blinds/translations/sl.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "error": { - "invalid_interface": "Neveljaven omre\u017eni vmesnik" - }, - "step": { - "connect": { - "data": { - "interface": "Omre\u017eni vmesnik za uporabo" - } - }, - "user": { - "title": "Motion Blinds" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/tr.json b/homeassistant/components/motion_blinds/translations/tr.json index 824569ce173..5af004ae0cc 100644 --- a/homeassistant/components/motion_blinds/translations/tr.json +++ b/homeassistant/components/motion_blinds/translations/tr.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f , ba\u011flant\u0131 ayarlar\u0131 g\u00fcncellendi", + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "connection_error": "Ba\u011flanma hatas\u0131" }, "error": { - "discovery_error": "Motion Gateway bulunamad\u0131", - "invalid_interface": "Ge\u00e7ersiz a\u011f aray\u00fcz\u00fc" + "discovery_error": "Motion Gateway bulunamad\u0131" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "API Anahtar\u0131", - "interface": "Kullan\u0131lacak a\u011f aray\u00fcz\u00fc" + "api_key": "API Anahtar\u0131" }, - "description": "16 karakterlik API Anahtar\u0131na ihtiyac\u0131n\u0131z olacak, talimatlar i\u00e7in https://www.home-assistant.io/integrations/motion_blinds/#retriving-the-key adresine bak\u0131n.", - "title": "Hareketli Perdeler" + "description": "16 karakterlik API Anahtar\u0131na ihtiyac\u0131n\u0131z olacak, talimatlar i\u00e7in https://www.home-assistant.io/integrations/motion_blinds/#retriving-the-key adresine bak\u0131n." }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "API Anahtar\u0131", "host": "IP Adresi" }, - "description": "Motion Gateway'inize ba\u011flan\u0131n, IP adresi ayarlanmad\u0131ysa, otomatik ke\u015fif kullan\u0131l\u0131r", - "title": "Hareketli Perdeler" + "description": "Motion Gateway'inize ba\u011flan\u0131n, IP adresi ayarlanmad\u0131ysa, otomatik ke\u015fif kullan\u0131l\u0131r" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "G\u00fcncellemede \u00e7ok noktaya yay\u0131n i\u00e7in bekleyin" - }, - "description": "\u0130ste\u011fe ba\u011fl\u0131 ayarlar\u0131 belirtin", - "title": "Hareketli Panjurlar" + } } } } diff --git a/homeassistant/components/motion_blinds/translations/uk.json b/homeassistant/components/motion_blinds/translations/uk.json index 99ccb60dc6c..a3d8ee57c1f 100644 --- a/homeassistant/components/motion_blinds/translations/uk.json +++ b/homeassistant/components/motion_blinds/translations/uk.json @@ -14,8 +14,7 @@ "data": { "api_key": "\u041a\u043b\u044e\u0447 API" }, - "description": "\u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API, \u0434\u0438\u0432. https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439", - "title": "Motion Blinds" + "description": "\u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API, \u0434\u0438\u0432. https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439" }, "select": { "data": { @@ -26,11 +25,9 @@ }, "user": { "data": { - "api_key": "\u041a\u043b\u044e\u0447 API", "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" }, - "description": "\u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0457 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", - "title": "Motion Blinds" + "description": "\u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0457 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key." } } } diff --git a/homeassistant/components/motion_blinds/translations/zh-Hans.json b/homeassistant/components/motion_blinds/translations/zh-Hans.json index f8dac159488..d8f87406260 100644 --- a/homeassistant/components/motion_blinds/translations/zh-Hans.json +++ b/homeassistant/components/motion_blinds/translations/zh-Hans.json @@ -6,7 +6,6 @@ "step": { "user": { "data": { - "api_key": "API\u5bc6\u7801", "host": "IP\u5730\u5740" } } diff --git a/homeassistant/components/motion_blinds/translations/zh-Hant.json b/homeassistant/components/motion_blinds/translations/zh-Hant.json index e7fa565ba36..189f1474b22 100644 --- a/homeassistant/components/motion_blinds/translations/zh-Hant.json +++ b/homeassistant/components/motion_blinds/translations/zh-Hant.json @@ -1,23 +1,20 @@ { "config": { "abort": { - "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u9023\u7dda\u8a2d\u5b9a\u5df2\u66f4\u65b0", + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "connection_error": "\u9023\u7dda\u5931\u6557" }, "error": { - "discovery_error": "\u641c\u7d22 Motion \u9598\u9053\u5668\u5931\u6557", - "invalid_interface": "\u7db2\u8def\u4ecb\u9762\u7121\u6548" + "discovery_error": "\u641c\u7d22 Motion \u9598\u9053\u5668\u5931\u6557" }, "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { - "api_key": "API \u91d1\u9470", - "interface": "\u4f7f\u7528\u7684\u7db2\u8def\u4ecb\u9762" + "api_key": "API \u91d1\u9470" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 16 \u4f4d\u5b57\u5143 API \u91d1\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u4ee5\u7372\u5f97\u7372\u53d6\u91d1\u9470\u7684\u6559\u5b78\u3002", - "title": "Motion Blinds" + "description": "\u5c07\u9700\u8981\u8f38\u5165 16 \u4f4d\u5b57\u5143 API \u91d1\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u4ee5\u7372\u5f97\u7372\u53d6\u91d1\u9470\u7684\u6559\u5b78\u3002" }, "select": { "data": { @@ -28,11 +25,9 @@ }, "user": { "data": { - "api_key": "API \u91d1\u9470", "host": "IP \u4f4d\u5740" }, - "description": "\u9023\u7dda\u81f3 Motion \u9598\u9053\u5668\uff0c\u5047\u5982\u672a\u63d0\u4f9b IP \u4f4d\u5740\uff0c\u5c07\u4f7f\u7528\u81ea\u52d5\u641c\u7d22", - "title": "Motion Blinds" + "description": "\u9023\u7dda\u81f3 Motion \u9598\u9053\u5668\uff0c\u5047\u5982\u672a\u63d0\u4f9b IP \u4f4d\u5740\uff0c\u5c07\u4f7f\u7528\u81ea\u52d5\u641c\u7d22" } } }, @@ -41,9 +36,7 @@ "init": { "data": { "wait_for_push": "\u7b49\u5019 Multicast \u63a8\u9001\u901a\u77e5" - }, - "description": "\u6307\u5b9a\u9078\u9805\u8a2d\u5b9a", - "title": "Motion Blinds" + } } } } diff --git a/homeassistant/components/motioneye/__init__.py b/homeassistant/components/motioneye/__init__.py index 37a15931920..279cc8bde70 100644 --- a/homeassistant/components/motioneye/__init__.py +++ b/homeassistant/components/motioneye/__init__.py @@ -177,7 +177,7 @@ def async_generate_motioneye_webhook( except NoURLAvailableError: _LOGGER.warning( "Unable to get Home Assistant URL. Have you set the internal and/or " - "external URLs in Configuration -> General?" + "external URLs in Settings -> System -> Network?" ) return None diff --git a/homeassistant/components/motioneye/translations/nl.json b/homeassistant/components/motioneye/translations/nl.json index dce66fcb5d1..bd62a7037ac 100644 --- a/homeassistant/components/motioneye/translations/nl.json +++ b/homeassistant/components/motioneye/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "already_configured": "Dienst is al geconfigureerd", + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index d3262a0d5da..ecee057a653 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -453,7 +453,9 @@ class MpdDevice(MediaPlayerEntity): """Send the media player the command for playing a playlist.""" if media_source.is_media_source_id(media_id): media_type = MEDIA_TYPE_MUSIC - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = async_process_play_media_url(self.hass, play_item.url) if media_type == MEDIA_TYPE_PLAYLIST: diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 7c16da7f2aa..1728dd7f2c7 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -54,7 +54,6 @@ from homeassistant.helpers import config_validation as cv, event, template from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity -from homeassistant.helpers.frame import report from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.loader import bind_hass from homeassistant.util import dt as dt_util @@ -82,6 +81,8 @@ from .const import ( CONF_TLS_VERSION, CONF_TOPIC, CONF_WILL_MESSAGE, + CONFIG_ENTRY_IS_SETUP, + DATA_CONFIG_ENTRY_LOCK, DATA_MQTT_CONFIG, DATA_MQTT_RELOAD_NEEDED, DEFAULT_BIRTH, @@ -158,6 +159,7 @@ PLATFORMS = [ Platform.BUTTON, Platform.CAMERA, Platform.CLIMATE, + Platform.DEVICE_TRACKER, Platform.COVER, Platform.FAN, Platform.HUMIDIFIER, @@ -172,7 +174,6 @@ PLATFORMS = [ Platform.VACUUM, ] - CLIENT_KEY_AUTH_MSG = ( "client_key and client_cert must both be present in " "the MQTT broker configuration" @@ -188,7 +189,11 @@ MQTT_WILL_BIRTH_SCHEMA = vol.Schema( required=True, ) -CONFIG_SCHEMA_BASE = vol.Schema( +PLATFORM_CONFIG_SCHEMA_BASE = vol.Schema( + {vol.Optional(platform.value): cv.ensure_list for platform in PLATFORMS} +) + +CONFIG_SCHEMA_BASE = PLATFORM_CONFIG_SCHEMA_BASE.extend( { vol.Optional(CONF_CLIENT_ID): cv.string, vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All( @@ -254,10 +259,28 @@ SCHEMA_BASE = { vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string, } +MQTT_BASE_SCHEMA = vol.Schema(SCHEMA_BASE) + +# Will be removed when all platforms support a modern platform schema MQTT_BASE_PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(SCHEMA_BASE) +# Will be removed when all platforms support a modern platform schema +MQTT_RO_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_STATE_TOPIC): valid_subscribe_topic, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } +) +# Will be removed when all platforms support a modern platform schema +MQTT_RW_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, + vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, + vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic, + } +) # Sensor type platforms subscribe to MQTT events -MQTT_RO_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend( +MQTT_RO_SCHEMA = MQTT_BASE_SCHEMA.extend( { vol.Required(CONF_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, @@ -265,7 +288,7 @@ MQTT_RO_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend( ) # Switch type platforms publish to MQTT and may subscribe -MQTT_RW_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend( +MQTT_RW_SCHEMA = MQTT_BASE_SCHEMA.extend( { vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, @@ -419,20 +442,6 @@ class MqttServiceInfo(BaseServiceInfo): subscribed_topic: str timestamp: dt.datetime - def __getitem__(self, name: str) -> Any: - """ - Allow property access by name for compatibility reason. - - Deprecated, and will be removed in version 2022.6. - """ - report( - f"accessed discovery_info['{name}'] instead of discovery_info.{name}; " - "this will fail in version 2022.6", - exclude_integrations={DOMAIN}, - error_if_core=False, - ) - return getattr(self, name) - def publish( hass: HomeAssistant, @@ -676,14 +685,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # User has configuration.yaml config, warn about config entry overrides elif any(key in conf for key in entry.data): shared_keys = conf.keys() & entry.data.keys() - override = {k: entry.data[k] for k in shared_keys} + override = {k: entry.data[k] for k in shared_keys if conf[k] != entry.data[k]} if CONF_PASSWORD in override: override[CONF_PASSWORD] = "********" - _LOGGER.warning( - "Deprecated configuration settings found in configuration.yaml. " - "These settings from your configuration entry will override: %s", - override, - ) + if override: + _LOGGER.warning( + "Deprecated configuration settings found in configuration.yaml. " + "These settings from your configuration entry will override: %s", + override, + ) # Merge advanced configuration values from configuration.yaml conf = _merge_extended_config(entry, conf) @@ -789,6 +799,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ), ) + # setup platforms and discovery + hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock() + hass.data[CONFIG_ENTRY_IS_SETUP] = set() + + async def async_forward_entry_setup(): + """Forward the config entry setup to the platforms.""" + async with hass.data[DATA_CONFIG_ENTRY_LOCK]: + for component in PLATFORMS: + config_entries_key = f"{component}.mqtt" + if config_entries_key not in hass.data[CONFIG_ENTRY_IS_SETUP]: + hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key) + await hass.config_entries.async_forward_entry_setup( + entry, component + ) + + hass.async_create_task(async_forward_entry_setup()) + if conf.get(CONF_DISCOVERY): await _async_setup_discovery(hass, conf, entry) diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index a58e71e78d6..06c013ec744 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -1,6 +1,7 @@ """This platform enables the possibility to control a MQTT alarm.""" from __future__ import annotations +import asyncio import functools import logging import re @@ -44,8 +45,10 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) _LOGGER = logging.getLogger(__name__) @@ -82,7 +85,7 @@ DEFAULT_NAME = "MQTT Alarm" REMOTE_CODE = "REMOTE_CODE" REMOTE_CODE_TEXT = "REMOTE_CODE_TEXT" -PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_MODERN = mqtt.MQTT_BASE_SCHEMA.extend( { vol.Optional(CONF_CODE): cv.string, vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean, @@ -110,7 +113,13 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -DISCOVERY_SCHEMA = PLATFORM_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA) +# Configuring MQTT alarm control panels under the alarm_control_panel platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_MODERN.schema), + warn_for_legacy_schema(alarm.DOMAIN), +) + +DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA) async def async_setup_platform( @@ -119,7 +128,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT alarm control panel through configuration.yaml.""" + """Set up MQTT alarm control panel configured under the alarm_control_panel key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, alarm.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -130,7 +140,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT alarm control panel dynamically through MQTT discovery.""" + """Set up MQTT alarm control panel through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, alarm.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index 5d0da99d786..b9ab190cc9b 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -1,6 +1,7 @@ """Support for MQTT binary sensors.""" from __future__ import annotations +import asyncio from datetime import timedelta import functools import logging @@ -41,8 +42,10 @@ from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttAvailability, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) _LOGGER = logging.getLogger(__name__) @@ -54,7 +57,7 @@ DEFAULT_PAYLOAD_ON = "ON" DEFAULT_FORCE_UPDATE = False CONF_EXPIRE_AFTER = "expire_after" -PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_MODERN = mqtt.MQTT_RO_SCHEMA.extend( { vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, @@ -66,7 +69,13 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -DISCOVERY_SCHEMA = PLATFORM_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA) +# Configuring MQTT Binary sensors under the binary_sensor platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_MODERN.schema), + warn_for_legacy_schema(binary_sensor.DOMAIN), +) + +DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA) async def async_setup_platform( @@ -75,7 +84,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT binary sensor through configuration.yaml.""" + """Set up MQTT binary sensor configured under the fan platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, binary_sensor.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -86,7 +96,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT binary sensor dynamically through MQTT discovery.""" + """Set up MQTT binary sensor through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, binary_sensor.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/button.py b/homeassistant/components/mqtt/button.py index 22ee7b6d5ae..47e96ff3e1a 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -1,6 +1,7 @@ """Support for MQTT buttons.""" from __future__ import annotations +import asyncio import functools import voluptuous as vol @@ -26,15 +27,17 @@ from .const import ( from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) CONF_PAYLOAD_PRESS = "payload_press" DEFAULT_NAME = "MQTT Button" DEFAULT_PAYLOAD_PRESS = "PRESS" -PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_MODERN = mqtt.MQTT_BASE_SCHEMA.extend( { vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, @@ -45,7 +48,14 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -DISCOVERY_SCHEMA = PLATFORM_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA) +# Configuring MQTT Buttons under the button platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_MODERN.schema), + warn_for_legacy_schema(button.DOMAIN), +) + + +DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA) async def async_setup_platform( @@ -54,7 +64,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT button through configuration.yaml.""" + """Set up MQTT button configured under the fan platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, button.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -65,7 +76,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT button dynamically through MQTT discovery.""" + """Set up MQTT button through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, button.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 0e387023a39..2e5d95ebda4 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -1,6 +1,8 @@ """Camera that loads a picture from an MQTT topic.""" from __future__ import annotations +import asyncio +from base64 import b64decode import functools import voluptuous as vol @@ -16,13 +18,15 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import subscription from .. import mqtt -from .const import CONF_QOS, CONF_TOPIC +from .const import CONF_ENCODING, CONF_QOS, CONF_TOPIC from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) DEFAULT_NAME = "MQTT Camera" @@ -36,14 +40,20 @@ MQTT_CAMERA_ATTRIBUTES_BLOCKED = frozenset( } ) -PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_MODERN = mqtt.MQTT_BASE_SCHEMA.extend( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -DISCOVERY_SCHEMA = PLATFORM_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA) +# Configuring MQTT Camera under the camera platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_MODERN.schema), + warn_for_legacy_schema(camera.DOMAIN), +) + +DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA) async def async_setup_platform( @@ -52,7 +62,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT camera through configuration.yaml.""" + """Set up MQTT camera configured under the camera platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, camera.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -63,7 +74,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT camera dynamically through MQTT discovery.""" + """Set up MQTT camera through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, camera.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) @@ -102,7 +123,10 @@ class MqttCamera(MqttEntity, Camera): @log_messages(self.hass, self.entity_id) def message_received(msg): """Handle new MQTT messages.""" - self._last_image = msg.payload + if self._config[CONF_ENCODING] == "b64": + self._last_image = b64decode(msg.payload) + else: + self._last_image = msg.payload self._sub_state = subscription.async_prepare_subscribe_topics( self.hass, diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 5c53380bb71..52465bbba24 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -1,36 +1,31 @@ """Support for MQTT climate devices.""" from __future__ import annotations +import asyncio import functools import logging import voluptuous as vol from homeassistant.components import climate -from homeassistant.components.climate import ( - PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, - ClimateEntity, - ClimateEntityFeature, -) +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_ACTIONS, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, FAN_AUTO, 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_NONE, + SWING_OFF, + SWING_ON, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -43,27 +38,23 @@ from homeassistant.const import ( PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE, - STATE_ON, ) from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import ( - MQTT_BASE_PLATFORM_SCHEMA, - MqttCommandTemplate, - MqttValueTemplate, - subscription, -) +from . import MqttCommandTemplate, MqttValueTemplate, subscription from .. import mqtt from .const import CONF_ENCODING, CONF_QOS, CONF_RETAIN, PAYLOAD_NONE from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) _LOGGER = logging.getLogger(__name__) @@ -241,8 +232,7 @@ def valid_preset_mode_configuration(config): return config -SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema) -_PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend( +_PLATFORM_SCHEMA_BASE = mqtt.MQTT_BASE_SCHEMA.extend( { vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_AUX_STATE_TEMPLATE): cv.template, @@ -272,12 +262,12 @@ _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend( vol.Optional( CONF_MODE_LIST, default=[ - HVAC_MODE_AUTO, - HVAC_MODE_OFF, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, + HVACMode.AUTO, + HVACMode.OFF, + HVACMode.COOL, + HVACMode.HEAT, + HVACMode.DRY, + HVACMode.FAN_ONLY, ], ): cv.ensure_list, vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, @@ -309,7 +299,7 @@ _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend( vol.Optional(CONF_SWING_MODE_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional( - CONF_SWING_MODE_LIST, default=[STATE_ON, HVAC_MODE_OFF] + CONF_SWING_MODE_LIST, default=[SWING_ON, SWING_OFF] ): cv.ensure_list, vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, @@ -334,8 +324,14 @@ _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -PLATFORM_SCHEMA = vol.All( +PLATFORM_SCHEMA_MODERN = vol.All( _PLATFORM_SCHEMA_BASE, + valid_preset_mode_configuration, +) + +# Configuring MQTT Climate under the climate platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(_PLATFORM_SCHEMA_BASE.schema), # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 cv.deprecated(CONF_SEND_IF_OFF), # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 @@ -348,6 +344,7 @@ PLATFORM_SCHEMA = vol.All( cv.deprecated(CONF_HOLD_STATE_TOPIC), cv.deprecated(CONF_HOLD_LIST), valid_preset_mode_configuration, + warn_for_legacy_schema(climate.DOMAIN), ) _DISCOVERY_SCHEMA_BASE = _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA) @@ -375,7 +372,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT climate device through configuration.yaml.""" + """Set up MQTT climate configured under the fan platform key (deprecated).""" + # The use of PLATFORM_SCHEMA is deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, climate.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -386,7 +384,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT climate device dynamically through MQTT discovery.""" + """Set up MQTT climate device through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, climate.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) @@ -460,9 +468,9 @@ class MqttClimate(MqttEntity, ClimateEntity): if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None: self._current_fan_mode = FAN_LOW if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None: - self._current_swing_mode = HVAC_MODE_OFF + self._current_swing_mode = SWING_OFF if self._topic[CONF_MODE_STATE_TOPIC] is None: - self._current_operation = HVAC_MODE_OFF + self._current_operation = HVACMode.OFF self._feature_preset_mode = CONF_PRESET_MODE_COMMAND_TOPIC in config if self._feature_preset_mode: self._preset_modes = config[CONF_PRESET_MODES_LIST] @@ -532,21 +540,23 @@ class MqttClimate(MqttEntity, ClimateEntity): def handle_action_received(msg): """Handle receiving action via MQTT.""" payload = render_template(msg, CONF_ACTION_TEMPLATE) - if payload in CURRENT_HVAC_ACTIONS: - self._action = payload - self.async_write_ha_state() - elif not payload or payload == PAYLOAD_NONE: + if not payload or payload == PAYLOAD_NONE: _LOGGER.debug( "Invalid %s action: %s, ignoring", - CURRENT_HVAC_ACTIONS, + [e.value for e in HVACAction], payload, ) - else: + return + try: + self._action = HVACAction(payload) + except ValueError: _LOGGER.warning( "Invalid %s action: %s", - CURRENT_HVAC_ACTIONS, + [e.value for e in HVACAction], payload, ) + return + self.async_write_ha_state() add_subscription(topics, CONF_ACTION_TOPIC, handle_action_received) @@ -773,17 +783,17 @@ class MqttClimate(MqttEntity, ClimateEntity): return self._target_temp_high @property - def hvac_action(self): + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation if supported.""" return self._action @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current operation ie. heat, cool, idle.""" return self._current_operation @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available operation modes.""" return self._config[CONF_MODE_LIST] @@ -859,7 +869,7 @@ class MqttClimate(MqttEntity, ClimateEntity): setattr(self, attr, temp) # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 - if self._send_if_off or self._current_operation != HVAC_MODE_OFF: + if self._send_if_off or self._current_operation != HVACMode.OFF: payload = self._command_templates[cmnd_template](temp) await self._publish(cmnd_topic, payload) @@ -899,7 +909,7 @@ class MqttClimate(MqttEntity, ClimateEntity): async def async_set_swing_mode(self, swing_mode): """Set new swing mode.""" # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 - if self._send_if_off or self._current_operation != HVAC_MODE_OFF: + if self._send_if_off or self._current_operation != HVACMode.OFF: payload = self._command_templates[CONF_SWING_MODE_COMMAND_TEMPLATE]( swing_mode ) @@ -912,7 +922,7 @@ class MqttClimate(MqttEntity, ClimateEntity): async def async_set_fan_mode(self, fan_mode): """Set new target temperature.""" # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9 - if self._send_if_off or self._current_operation != HVAC_MODE_OFF: + if self._send_if_off or self._current_operation != HVACMode.OFF: payload = self._command_templates[CONF_FAN_MODE_COMMAND_TEMPLATE](fan_mode) await self._publish(CONF_FAN_MODE_COMMAND_TOPIC, payload) @@ -922,7 +932,7 @@ class MqttClimate(MqttEntity, ClimateEntity): async def async_set_hvac_mode(self, hvac_mode) -> None: """Set new operation mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self._publish( CONF_POWER_COMMAND_TOPIC, self._config[CONF_PAYLOAD_OFF] ) diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index 69865733763..106d0310158 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -28,6 +28,8 @@ CONF_CLIENT_CERT = "client_cert" CONF_TLS_INSECURE = "tls_insecure" CONF_TLS_VERSION = "tls_version" +CONFIG_ENTRY_IS_SETUP = "mqtt_config_entry_is_setup" +DATA_CONFIG_ENTRY_LOCK = "mqtt_config_entry_lock" DATA_MQTT_CONFIG = "mqtt_config" DATA_MQTT_RELOAD_NEEDED = "mqtt_reload_needed" diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 76f984f1bc9..8e36329946a 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -1,6 +1,7 @@ """Support for MQTT cover devices.""" from __future__ import annotations +import asyncio import functools from json import JSONDecodeError, loads as json_loads import logging @@ -45,8 +46,10 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) _LOGGER = logging.getLogger(__name__) @@ -149,7 +152,7 @@ def validate_options(value): return value -_PLATFORM_SCHEMA_BASE = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( +_PLATFORM_SCHEMA_BASE = mqtt.MQTT_BASE_SCHEMA.extend( { vol.Optional(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, @@ -194,11 +197,18 @@ _PLATFORM_SCHEMA_BASE = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -PLATFORM_SCHEMA = vol.All( +PLATFORM_SCHEMA_MODERN = vol.All( _PLATFORM_SCHEMA_BASE, validate_options, ) +# Configuring MQTT Covers under the cover platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(_PLATFORM_SCHEMA_BASE.schema), + validate_options, + warn_for_legacy_schema(cover.DOMAIN), +) + DISCOVERY_SCHEMA = vol.All( cv.removed("tilt_invert_state"), _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA), @@ -212,7 +222,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT cover through configuration.yaml.""" + """Set up MQTT covers configured under the fan platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, cover.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -223,7 +234,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT cover dynamically through MQTT discovery.""" + """Set up MQTT cover through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, cover.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/device_tracker/__init__.py b/homeassistant/components/mqtt/device_tracker/__init__.py index 03574e6554b..bcd5bbd4ee1 100644 --- a/homeassistant/components/mqtt/device_tracker/__init__.py +++ b/homeassistant/components/mqtt/device_tracker/__init__.py @@ -1,7 +1,15 @@ """Support for tracking MQTT enabled devices.""" +import voluptuous as vol + +from homeassistant.components import device_tracker + +from ..mixins import warn_for_legacy_schema from .schema_discovery import async_setup_entry_from_discovery from .schema_yaml import PLATFORM_SCHEMA_YAML, async_setup_scanner_from_yaml -PLATFORM_SCHEMA = PLATFORM_SCHEMA_YAML +# Configuring MQTT Device Trackers under the device_tracker platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + PLATFORM_SCHEMA_YAML, warn_for_legacy_schema(device_tracker.DOMAIN) +) async_setup_scanner = async_setup_scanner_from_yaml async_setup_entry = async_setup_entry_from_discovery diff --git a/homeassistant/components/mqtt/device_tracker/schema_discovery.py b/homeassistant/components/mqtt/device_tracker/schema_discovery.py index a7b597d0689..aa7506bd5e3 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -1,4 +1,5 @@ -"""Support for tracking MQTT enabled devices identified through discovery.""" +"""Support for tracking MQTT enabled devices.""" +import asyncio import functools import voluptuous as vol @@ -22,13 +23,18 @@ from .. import MqttValueTemplate, subscription from ... import mqtt from ..const import CONF_QOS, CONF_STATE_TOPIC from ..debug_info import log_messages -from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper +from ..mixins import ( + MQTT_ENTITY_COMMON_SCHEMA, + MqttEntity, + async_get_platform_config_from_yaml, + async_setup_entry_helper, +) CONF_PAYLOAD_HOME = "payload_home" CONF_PAYLOAD_NOT_HOME = "payload_not_home" CONF_SOURCE_TYPE = "source_type" -PLATFORM_SCHEMA_DISCOVERY = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_MODERN = mqtt.MQTT_RO_SCHEMA.extend( { vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_HOME, default=STATE_HOME): cv.string, @@ -37,11 +43,21 @@ PLATFORM_SCHEMA_DISCOVERY = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -DISCOVERY_SCHEMA = PLATFORM_SCHEMA_DISCOVERY.extend({}, extra=vol.REMOVE_EXTRA) +DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA) async def async_setup_entry_from_discovery(hass, config_entry, async_add_entities): - """Set up MQTT device tracker dynamically through MQTT discovery.""" + """Set up MQTT device tracker configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, device_tracker.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 56cfc3efc6b..42ffcee1644 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, cast +from typing import cast import attr import voluptuous as vol @@ -290,9 +290,9 @@ async def async_removed_from_device(hass: HomeAssistant, device_id: str) -> None async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for MQTT devices.""" - triggers: list[dict] = [] + triggers: list[dict[str, str]] = [] if DEVICE_TRIGGERS not in hass.data: return triggers diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index fae443dc411..8685c790fd2 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -27,6 +27,8 @@ from .const import ( ATTR_DISCOVERY_TOPIC, CONF_AVAILABILITY, CONF_TOPIC, + CONFIG_ENTRY_IS_SETUP, + DATA_CONFIG_ENTRY_LOCK, DOMAIN, ) @@ -62,8 +64,6 @@ SUPPORTED_COMPONENTS = [ ALREADY_DISCOVERED = "mqtt_discovered_components" PENDING_DISCOVERED = "mqtt_pending_components" -CONFIG_ENTRY_IS_SETUP = "mqtt_config_entry_is_setup" -DATA_CONFIG_ENTRY_LOCK = "mqtt_config_entry_lock" DATA_CONFIG_FLOW_LOCK = "mqtt_discovery_config_flow_lock" DISCOVERY_UNSUBSCRIBE = "mqtt_discovery_unsubscribe" INTEGRATION_UNSUBSCRIBE = "mqtt_integration_discovery_unsubscribe" @@ -258,9 +258,7 @@ async def async_start( # noqa: C901 hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None ) - hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock() hass.data[DATA_CONFIG_FLOW_LOCK] = asyncio.Lock() - hass.data[CONFIG_ENTRY_IS_SETUP] = set() hass.data[ALREADY_DISCOVERED] = {} hass.data[PENDING_DISCOVERED] = {} diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index ff2eab7a68f..f2b738cd2bb 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -1,6 +1,7 @@ """Support for MQTT fans.""" from __future__ import annotations +import asyncio import functools import logging import math @@ -49,8 +50,10 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) CONF_PERCENTAGE_STATE_TOPIC = "percentage_state_topic" @@ -122,7 +125,7 @@ def valid_preset_mode_configuration(config): return config -_PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( +_PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, @@ -172,7 +175,15 @@ _PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) +# Configuring MQTT Fans under the fan platform key is deprecated in HA Core 2022.6 PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(_PLATFORM_SCHEMA_BASE.schema), + valid_speed_range_configuration, + valid_preset_mode_configuration, + warn_for_legacy_schema(fan.DOMAIN), +) + +PLATFORM_SCHEMA_MODERN = vol.All( _PLATFORM_SCHEMA_BASE, valid_speed_range_configuration, valid_preset_mode_configuration, @@ -201,7 +212,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT fan through configuration.yaml.""" + """Set up MQTT fans configured under the fan platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, fan.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -212,7 +224,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT fan dynamically through MQTT discovery.""" + """Set up MQTT fan through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, fan.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index f5a8b372532..f6d4aa01dab 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -1,6 +1,7 @@ """Support for MQTT humidifiers.""" from __future__ import annotations +import asyncio import functools import logging @@ -45,8 +46,10 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) CONF_AVAILABLE_MODES_LIST = "modes" @@ -100,7 +103,7 @@ def valid_humidity_range_configuration(config): return config -_PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( +_PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_SCHEMA.extend( { # CONF_AVAIALABLE_MODES_LIST and CONF_MODE_COMMAND_TOPIC must be used together vol.Inclusive( @@ -140,7 +143,15 @@ _PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) +# Configuring MQTT Humidifiers under the humidifier platform key is deprecated in HA Core 2022.6 PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(_PLATFORM_SCHEMA_BASE.schema), + valid_humidity_range_configuration, + valid_mode_configuration, + warn_for_legacy_schema(humidifier.DOMAIN), +) + +PLATFORM_SCHEMA_MODERN = vol.All( _PLATFORM_SCHEMA_BASE, valid_humidity_range_configuration, valid_mode_configuration, @@ -159,7 +170,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT humidifier through configuration.yaml.""" + """Set up MQTT humidifier configured under the fan platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, humidifier.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -170,7 +182,16 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT humidifier dynamically through MQTT discovery.""" + """Set up MQTT humidifier through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, humidifier.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/light/__init__.py b/homeassistant/components/mqtt/light/__init__.py index d78cd5e7baa..ab2a3462615 100644 --- a/homeassistant/components/mqtt/light/__init__.py +++ b/homeassistant/components/mqtt/light/__init__.py @@ -1,36 +1,47 @@ """Support for MQTT lights.""" from __future__ import annotations +import asyncio import functools import voluptuous as vol from homeassistant.components import light +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from ..mixins import async_setup_entry_helper, async_setup_platform_helper +from ..mixins import ( + async_get_platform_config_from_yaml, + async_setup_entry_helper, + async_setup_platform_helper, + warn_for_legacy_schema, +) from .schema import CONF_SCHEMA, MQTT_LIGHT_SCHEMA_SCHEMA from .schema_basic import ( DISCOVERY_SCHEMA_BASIC, PLATFORM_SCHEMA_BASIC, + PLATFORM_SCHEMA_MODERN_BASIC, async_setup_entity_basic, ) from .schema_json import ( DISCOVERY_SCHEMA_JSON, PLATFORM_SCHEMA_JSON, + PLATFORM_SCHEMA_MODERN_JSON, async_setup_entity_json, ) from .schema_template import ( DISCOVERY_SCHEMA_TEMPLATE, + PLATFORM_SCHEMA_MODERN_TEMPLATE, PLATFORM_SCHEMA_TEMPLATE, async_setup_entity_template, ) def validate_mqtt_light_discovery(value): - """Validate MQTT light schema.""" + """Validate MQTT light schema for.""" schemas = { "basic": DISCOVERY_SCHEMA_BASIC, "json": DISCOVERY_SCHEMA_JSON, @@ -49,14 +60,31 @@ def validate_mqtt_light(value): return schemas[value[CONF_SCHEMA]](value) +def validate_mqtt_light_modern(value): + """Validate MQTT light schema.""" + schemas = { + "basic": PLATFORM_SCHEMA_MODERN_BASIC, + "json": PLATFORM_SCHEMA_MODERN_JSON, + "template": PLATFORM_SCHEMA_MODERN_TEMPLATE, + } + return schemas[value[CONF_SCHEMA]](value) + + DISCOVERY_SCHEMA = vol.All( MQTT_LIGHT_SCHEMA_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA), validate_mqtt_light_discovery, ) - +# Configuring MQTT Lights under the light platform key is deprecated in HA Core 2022.6 PLATFORM_SCHEMA = vol.All( - MQTT_LIGHT_SCHEMA_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA), validate_mqtt_light + cv.PLATFORM_SCHEMA.extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema, extra=vol.ALLOW_EXTRA), + validate_mqtt_light, + warn_for_legacy_schema(light.DOMAIN), +) + +PLATFORM_SCHEMA_MODERN = vol.All( + MQTT_LIGHT_SCHEMA_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA), + validate_mqtt_light_modern, ) @@ -66,14 +94,29 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT light through configuration.yaml.""" + """Set up MQTT light through configuration.yaml (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, light.DOMAIN, config, async_add_entities, _async_setup_entity ) -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up MQTT light dynamically through MQTT discovery.""" +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up MQTT lights configured under the light platform key (deprecated).""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, light.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index b56c06a43e0..eb4ec264981 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -23,10 +23,10 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, SUPPORT_WHITE_VALUE, ColorMode, LightEntity, + LightEntityFeature, valid_supported_color_modes, ) from homeassistant.const import ( @@ -156,7 +156,7 @@ VALUE_TEMPLATE_KEYS = [ ] _PLATFORM_SCHEMA_BASE = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + mqtt.MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_BRIGHTNESS_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_BRIGHTNESS_COMMAND_TOPIC): mqtt.valid_publish_topic, @@ -220,16 +220,29 @@ _PLATFORM_SCHEMA_BASE = ( .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) ) +# The use of PLATFORM_SCHEMA is deprecated in HA Core 2022.6 PLATFORM_SCHEMA_BASIC = vol.All( - _PLATFORM_SCHEMA_BASE, + # CONF_WHITE_VALUE_* is deprecated, support will be removed in release 2022.9 + cv.deprecated(CONF_WHITE_VALUE_COMMAND_TOPIC), + cv.deprecated(CONF_WHITE_VALUE_SCALE), + cv.deprecated(CONF_WHITE_VALUE_STATE_TOPIC), + cv.deprecated(CONF_WHITE_VALUE_TEMPLATE), + cv.PLATFORM_SCHEMA.extend(_PLATFORM_SCHEMA_BASE.schema), ) DISCOVERY_SCHEMA_BASIC = vol.All( # CONF_VALUE_TEMPLATE is no longer supported, support was removed in 2022.2 cv.removed(CONF_VALUE_TEMPLATE), + # CONF_WHITE_VALUE_* is deprecated, support will be removed in release 2022.9 + cv.deprecated(CONF_WHITE_VALUE_COMMAND_TOPIC), + cv.deprecated(CONF_WHITE_VALUE_SCALE), + cv.deprecated(CONF_WHITE_VALUE_STATE_TOPIC), + cv.deprecated(CONF_WHITE_VALUE_TEMPLATE), _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA), ) +PLATFORM_SCHEMA_MODERN_BASIC = _PLATFORM_SCHEMA_BASE + async def async_setup_entity_basic( hass, config, async_add_entities, config_entry, discovery_data=None @@ -789,7 +802,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): """Flag supported features.""" supported_features = 0 supported_features |= ( - self._topic[CONF_EFFECT_COMMAND_TOPIC] is not None and SUPPORT_EFFECT + self._topic[CONF_EFFECT_COMMAND_TOPIC] is not None + and LightEntityFeature.EFFECT ) if not self._legacy_mode: return supported_features diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 68cc7e8b36c..2049818ab31 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -24,13 +24,11 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, VALID_COLOR_MODES, ColorMode, LightEntity, + LightEntityFeature, legacy_supported_features, valid_supported_color_modes, ) @@ -105,7 +103,7 @@ def valid_color_configuration(config): _PLATFORM_SCHEMA_BASE = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + mqtt.MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_BRIGHTNESS, default=DEFAULT_BRIGHTNESS): cv.boolean, vol.Optional( @@ -148,16 +146,26 @@ _PLATFORM_SCHEMA_BASE = ( .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) ) +# Configuring MQTT Lights under the light platform key is deprecated in HA Core 2022.6 PLATFORM_SCHEMA_JSON = vol.All( - _PLATFORM_SCHEMA_BASE, + # CONF_WHITE_VALUE is deprecated, support will be removed in release 2022.9 + cv.deprecated(CONF_WHITE_VALUE), + cv.PLATFORM_SCHEMA.extend(_PLATFORM_SCHEMA_BASE.schema), valid_color_configuration, ) DISCOVERY_SCHEMA_JSON = vol.All( + # CONF_WHITE_VALUE is deprecated, support will be removed in release 2022.9 + cv.deprecated(CONF_WHITE_VALUE), _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA), valid_color_configuration, ) +PLATFORM_SCHEMA_MODERN_JSON = vol.All( + _PLATFORM_SCHEMA_BASE, + valid_color_configuration, +) + async def async_setup_entity_json( hass, config: ConfigType, async_add_entities, config_entry, discovery_data @@ -211,8 +219,10 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): for key in (CONF_FLASH_TIME_SHORT, CONF_FLASH_TIME_LONG) } - self._supported_features = SUPPORT_TRANSITION | SUPPORT_FLASH - self._supported_features |= config[CONF_EFFECT] and SUPPORT_EFFECT + self._supported_features = ( + LightEntityFeature.TRANSITION | LightEntityFeature.FLASH + ) + self._supported_features |= config[CONF_EFFECT] and LightEntityFeature.EFFECT if not self._config[CONF_COLOR_MODE]: self._supported_features |= config[CONF_BRIGHTNESS] and SUPPORT_BRIGHTNESS self._supported_features |= config[CONF_COLOR_TEMP] and SUPPORT_COLOR_TEMP @@ -351,7 +361,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): except ValueError: _LOGGER.warning("Invalid color temp value received") - if self._supported_features and SUPPORT_EFFECT: + if self._supported_features and LightEntityFeature.EFFECT: with suppress(KeyError): self._effect = values["effect"] diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 4f25bde928d..0165bfc8efa 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -15,11 +15,9 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, LightEntity, + LightEntityFeature, ) from homeassistant.const import ( CONF_NAME, @@ -68,8 +66,8 @@ CONF_MIN_MIREDS = "min_mireds" CONF_RED_TEMPLATE = "red_template" CONF_WHITE_VALUE_TEMPLATE = "white_value_template" -PLATFORM_SCHEMA_TEMPLATE = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( +_PLATFORM_SCHEMA_BASE = ( + mqtt.MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_BLUE_TEMPLATE): cv.template, vol.Optional(CONF_BRIGHTNESS_TEMPLATE): cv.template, @@ -92,7 +90,20 @@ PLATFORM_SCHEMA_TEMPLATE = ( .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) ) -DISCOVERY_SCHEMA_TEMPLATE = PLATFORM_SCHEMA_TEMPLATE.extend({}, extra=vol.REMOVE_EXTRA) +# Configuring MQTT Lights under the light platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA_TEMPLATE = vol.All( + # CONF_WHITE_VALUE_TEMPLATE is deprecated, support will be removed in release 2022.9 + cv.deprecated(CONF_WHITE_VALUE_TEMPLATE), + cv.PLATFORM_SCHEMA.extend(_PLATFORM_SCHEMA_BASE.schema), +) + +DISCOVERY_SCHEMA_TEMPLATE = vol.All( + # CONF_WHITE_VALUE_TEMPLATE is deprecated, support will be removed in release 2022.9 + cv.deprecated(CONF_WHITE_VALUE_TEMPLATE), + _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA), +) + +PLATFORM_SCHEMA_MODERN_TEMPLATE = _PLATFORM_SCHEMA_BASE async def async_setup_entity_template( @@ -432,7 +443,7 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): @property def supported_features(self): """Flag supported features.""" - features = SUPPORT_FLASH | SUPPORT_TRANSITION + features = LightEntityFeature.FLASH | LightEntityFeature.TRANSITION if self._templates[CONF_BRIGHTNESS_TEMPLATE] is not None: features = features | SUPPORT_BRIGHTNESS if ( @@ -442,7 +453,7 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): ): features = features | SUPPORT_COLOR | SUPPORT_BRIGHTNESS if self._config.get(CONF_EFFECT_LIST) is not None: - features = features | SUPPORT_EFFECT + features = features | LightEntityFeature.EFFECT if self._templates[CONF_COLOR_TEMP_TEMPLATE] is not None: features = features | SUPPORT_COLOR_TEMP if self._templates[CONF_WHITE_VALUE_TEMPLATE] is not None: diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 66efe9fece7..5dc0a974d26 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -1,6 +1,7 @@ """Support for MQTT locks.""" from __future__ import annotations +import asyncio import functools import voluptuous as vol @@ -27,8 +28,10 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) CONF_PAYLOAD_LOCK = "payload_lock" @@ -53,7 +56,7 @@ MQTT_LOCK_ATTRIBUTES_BLOCKED = frozenset( } ) -PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_MODERN = mqtt.MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, @@ -66,7 +69,13 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -DISCOVERY_SCHEMA = PLATFORM_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA) +# Configuring MQTT Locks under the lock platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_MODERN.schema), + warn_for_legacy_schema(lock.DOMAIN), +) + +DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA) async def async_setup_platform( @@ -75,7 +84,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT lock panel through configuration.yaml.""" + """Set up MQTT locks configured under the lock platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, lock.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -86,7 +96,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT lock dynamically through MQTT discovery.""" + """Set up MQTT lock through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, lock.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 43f75f08459..a46debeae54 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -9,6 +9,7 @@ from typing import Any, Protocol, cast, final import voluptuous as vol +from homeassistant.config import async_log_exception from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_CONFIGURATION_URL, @@ -64,6 +65,7 @@ from .const import ( CONF_ENCODING, CONF_QOS, CONF_TOPIC, + DATA_MQTT_CONFIG, DATA_MQTT_RELOAD_NEEDED, DEFAULT_ENCODING, DEFAULT_PAYLOAD_AVAILABLE, @@ -223,6 +225,31 @@ MQTT_ENTITY_COMMON_SCHEMA = MQTT_AVAILABILITY_SCHEMA.extend( ) +def warn_for_legacy_schema(domain: str) -> Callable: + """Warn once when a legacy platform schema is used.""" + warned = set() + + def validator(config: ConfigType) -> ConfigType: + """Return a validator.""" + nonlocal warned + + if domain in warned: + return config + + _LOGGER.warning( + "Manually configured MQTT %s(s) found under platform key '%s', " + "please move to the mqtt integration key, see " + "https://www.home-assistant.io/integrations/%s.mqtt/#new_format", + domain, + domain, + domain, + ) + warned.add(domain) + return config + + return validator + + class SetupEntity(Protocol): """Protocol type for async_setup_entities.""" @@ -237,6 +264,31 @@ class SetupEntity(Protocol): """Define setup_entities type.""" +async def async_get_platform_config_from_yaml( + hass: HomeAssistant, domain: str, schema: vol.Schema +) -> list[ConfigType]: + """Return a list of validated configurations for the domain.""" + + def async_validate_config( + hass: HomeAssistant, + config: list[ConfigType], + ) -> list[ConfigType]: + """Validate config.""" + validated_config = [] + for config_item in config: + try: + validated_config.append(schema(config_item)) + except vol.MultipleInvalid as err: + async_log_exception(err, domain, config_item, hass) + + return validated_config + + config_yaml: ConfigType = hass.data.get(DATA_MQTT_CONFIG, {}) + if not (platform_configs := config_yaml.get(domain)): + return [] + return async_validate_config(hass, platform_configs) + + async def async_setup_entry_helper(hass, domain, async_setup, schema): """Set up entity, automation or tag creation dynamically through MQTT discovery.""" diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index e13ae4ded84..001f9f4f668 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -1,6 +1,7 @@ """Configure number in a device through MQTT topic.""" from __future__ import annotations +import asyncio import functools import logging @@ -40,8 +41,10 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) _LOGGER = logging.getLogger(__name__) @@ -72,7 +75,7 @@ def validate_config(config): return config -_PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( +_PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_MAX, default=DEFAULT_MAX_VALUE): vol.Coerce(float), @@ -88,11 +91,18 @@ _PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( }, ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -PLATFORM_SCHEMA = vol.All( +PLATFORM_SCHEMA_MODERN = vol.All( _PLATFORM_SCHEMA_BASE, validate_config, ) +# Configuring MQTT Number under the number platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(_PLATFORM_SCHEMA_BASE.schema), + validate_config, + warn_for_legacy_schema(number.DOMAIN), +) + DISCOVERY_SCHEMA = vol.All( _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA), validate_config, @@ -105,7 +115,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT number through configuration.yaml.""" + """Set up MQTT number configured under the number platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, number.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -116,7 +127,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT number dynamically through MQTT discovery.""" + """Set up MQTT number through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, number.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/scene.py b/homeassistant/components/mqtt/scene.py index c9bcae2dac1..98c692ceaff 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -1,6 +1,7 @@ """Support for MQTT scenes.""" from __future__ import annotations +import asyncio import functools import voluptuous as vol @@ -21,14 +22,16 @@ from .mixins import ( CONF_OBJECT_ID, MQTT_AVAILABILITY_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) DEFAULT_NAME = "MQTT Scene" DEFAULT_RETAIN = False -PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_MODERN = mqtt.MQTT_BASE_SCHEMA.extend( { vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_ICON): cv.icon, @@ -42,7 +45,13 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( } ).extend(MQTT_AVAILABILITY_SCHEMA.schema) -DISCOVERY_SCHEMA = PLATFORM_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA) +# Configuring MQTT Scenes under the scene platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_MODERN.schema), + warn_for_legacy_schema(scene.DOMAIN), +) + +DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA) async def async_setup_platform( @@ -51,7 +60,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT scene through configuration.yaml.""" + """Set up MQTT scene configured under the scene platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, scene.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -62,7 +72,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT scene dynamically through MQTT discovery.""" + """Set up MQTT scene through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, scene.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/select.py b/homeassistant/components/mqtt/select.py index f873a32a5de..0765eb7f176 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -1,6 +1,7 @@ """Configure select in a device through MQTT topic.""" from __future__ import annotations +import asyncio import functools import logging @@ -30,8 +31,10 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) _LOGGER = logging.getLogger(__name__) @@ -48,7 +51,7 @@ MQTT_SELECT_ATTRIBUTES_BLOCKED = frozenset( ) -_PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_MODERN = mqtt.MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -58,9 +61,13 @@ _PLATFORM_SCHEMA_BASE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( }, ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -PLATFORM_SCHEMA = vol.All(_PLATFORM_SCHEMA_BASE) +# Configuring MQTT Select under the select platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_MODERN.schema), + warn_for_legacy_schema(select.DOMAIN), +) -DISCOVERY_SCHEMA = vol.All(_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA)) +DISCOVERY_SCHEMA = vol.All(PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA)) async def async_setup_platform( @@ -69,7 +76,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT select through configuration.yaml.""" + """Set up MQTT select configured under the select platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, select.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -80,7 +88,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT select dynamically through MQTT discovery.""" + """Set up MQTT select through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, select.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index a13f58f95ea..d865d90c4ee 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -1,6 +1,7 @@ """Support for MQTT sensors.""" from __future__ import annotations +import asyncio from datetime import timedelta import functools import logging @@ -41,8 +42,10 @@ from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttAvailability, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) _LOGGER = logging.getLogger(__name__) @@ -86,7 +89,7 @@ def validate_options(conf): return conf -_PLATFORM_SCHEMA_BASE = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( +_PLATFORM_SCHEMA_BASE = mqtt.MQTT_RO_SCHEMA.extend( { vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, @@ -99,12 +102,19 @@ _PLATFORM_SCHEMA_BASE = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -PLATFORM_SCHEMA = vol.All( - cv.deprecated(CONF_LAST_RESET_TOPIC), +PLATFORM_SCHEMA_MODERN = vol.All( _PLATFORM_SCHEMA_BASE, validate_options, ) +# Configuring MQTT Sensors under the sensor platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.deprecated(CONF_LAST_RESET_TOPIC), + cv.PLATFORM_SCHEMA.extend(_PLATFORM_SCHEMA_BASE.schema), + validate_options, + warn_for_legacy_schema(sensor.DOMAIN), +) + DISCOVERY_SCHEMA = vol.All( cv.deprecated(CONF_LAST_RESET_TOPIC), _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA), @@ -118,7 +128,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT sensors through configuration.yaml.""" + """Set up MQTT sensors configured under the fan platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, sensor.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -129,7 +140,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT sensors dynamically through MQTT discovery.""" + """Set up MQTT sensor through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, sensor.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index beed5b51e20..c3a41c3618e 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -1,6 +1,7 @@ """Support for MQTT sirens.""" from __future__ import annotations +import asyncio import copy import functools import json @@ -51,8 +52,10 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) DEFAULT_NAME = "MQTT Siren" @@ -71,7 +74,7 @@ CONF_SUPPORT_VOLUME_SET = "support_volume_set" STATE = "state" -PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_MODERN = mqtt.MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_AVAILABLE_TONES): cv.ensure_list, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, @@ -88,7 +91,13 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( }, ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -DISCOVERY_SCHEMA = vol.All(PLATFORM_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA)) +# Configuring MQTT Sirens under the siren platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_MODERN.schema), + warn_for_legacy_schema(siren.DOMAIN), +) + +DISCOVERY_SCHEMA = vol.All(PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA)) MQTT_SIREN_ATTRIBUTES_BLOCKED = frozenset( { @@ -116,7 +125,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT siren through configuration.yaml.""" + """Set up MQTT sirens configured under the fan platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, siren.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -127,7 +137,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT siren dynamically through MQTT discovery.""" + """Set up MQTT siren through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, siren.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index db54ae08953..f5f8363eb33 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -1,6 +1,7 @@ """Support for MQTT switches.""" from __future__ import annotations +import asyncio import functools import voluptuous as vol @@ -37,8 +38,10 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, + async_get_platform_config_from_yaml, async_setup_entry_helper, async_setup_platform_helper, + warn_for_legacy_schema, ) DEFAULT_NAME = "MQTT Switch" @@ -48,7 +51,7 @@ DEFAULT_OPTIMISTIC = False CONF_STATE_ON = "state_on" CONF_STATE_OFF = "state_off" -PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_MODERN = mqtt.MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, @@ -61,7 +64,13 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) -DISCOVERY_SCHEMA = PLATFORM_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA) +# Configuring MQTT Switches under the switch platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_MODERN.schema), + warn_for_legacy_schema(switch.DOMAIN), +) + +DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA) async def async_setup_platform( @@ -70,7 +79,8 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up MQTT switch through configuration.yaml.""" + """Set up MQTT switch configured under the fan platform key (deprecated).""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, switch.DOMAIN, config, async_add_entities, _async_setup_entity ) @@ -81,7 +91,17 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up MQTT switch dynamically through MQTT discovery.""" + """Set up MQTT switch through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, switch.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/translations/ko.json b/homeassistant/components/mqtt/translations/ko.json index dccd49b2ef3..454b1e0368f 100644 --- a/homeassistant/components/mqtt/translations/ko.json +++ b/homeassistant/components/mqtt/translations/ko.json @@ -62,7 +62,8 @@ "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "MQTT \ube0c\ub85c\ucee4\uc640\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." + "description": "MQTT \ube0c\ub85c\ucee4\uc640\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\ube0c\ub85c\ucee4 \uc635\uc158" }, "options": { "data": { @@ -78,7 +79,8 @@ "will_retain": "Will \uba54\uc2dc\uc9c0 \ub9ac\ud14c\uc778", "will_topic": "Will \uba54\uc2dc\uc9c0 \ud1a0\ud53d" }, - "description": "MQTT \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694." + "description": "MQTT \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "title": "MQTT \uc635\uc158" } } } diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index 40448942a9f..155cf0bf07a 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Dienst is al geconfigureerd", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "cannot_connect": "Kan geen verbinding maken" @@ -53,7 +53,7 @@ "error": { "bad_birth": "Ongeldig birth topic", "bad_will": "Ongeldig will topic", - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "step": { "broker": { diff --git a/homeassistant/components/mqtt/trigger.py b/homeassistant/components/mqtt/trigger.py index db366010bb2..7d1f93d30eb 100644 --- a/homeassistant/components/mqtt/trigger.py +++ b/homeassistant/components/mqtt/trigger.py @@ -5,9 +5,14 @@ import logging import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import CONF_PAYLOAD, CONF_PLATFORM, CONF_VALUE_TEMPLATE -from homeassistant.core import HassJob, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, template +from homeassistant.helpers.typing import ConfigType from .. import mqtt from .const import CONF_ENCODING, CONF_QOS, CONF_TOPIC, DEFAULT_ENCODING, DEFAULT_QOS @@ -31,7 +36,12 @@ TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( _LOGGER = logging.getLogger(__name__) -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" trigger_data = automation_info["trigger_data"] topic = config[CONF_TOPIC] diff --git a/homeassistant/components/mqtt/vacuum/__init__.py b/homeassistant/components/mqtt/vacuum/__init__.py index f64a67820d4..34205ab7780 100644 --- a/homeassistant/components/mqtt/vacuum/__init__.py +++ b/homeassistant/components/mqtt/vacuum/__init__.py @@ -1,20 +1,33 @@ """Support for MQTT vacuums.""" +from __future__ import annotations + +import asyncio import functools import voluptuous as vol from homeassistant.components import vacuum +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 -from ..mixins import async_setup_entry_helper, async_setup_platform_helper +from ..mixins import ( + async_get_platform_config_from_yaml, + async_setup_entry_helper, + async_setup_platform_helper, +) from .schema import CONF_SCHEMA, LEGACY, MQTT_VACUUM_SCHEMA, STATE from .schema_legacy import ( DISCOVERY_SCHEMA_LEGACY, PLATFORM_SCHEMA_LEGACY, + PLATFORM_SCHEMA_LEGACY_MODERN, async_setup_entity_legacy, ) from .schema_state import ( DISCOVERY_SCHEMA_STATE, PLATFORM_SCHEMA_STATE, + PLATFORM_SCHEMA_STATE_MODERN, async_setup_entity_state, ) @@ -25,30 +38,65 @@ def validate_mqtt_vacuum_discovery(value): return schemas[value[CONF_SCHEMA]](value) +# Configuring MQTT Vacuums under the vacuum platform key is deprecated in HA Core 2022.6 def validate_mqtt_vacuum(value): - """Validate MQTT vacuum schema.""" + """Validate MQTT vacuum schema (deprecated).""" schemas = {LEGACY: PLATFORM_SCHEMA_LEGACY, STATE: PLATFORM_SCHEMA_STATE} return schemas[value[CONF_SCHEMA]](value) +def validate_mqtt_vacuum_modern(value): + """Validate MQTT vacuum modern schema.""" + schemas = { + LEGACY: PLATFORM_SCHEMA_LEGACY_MODERN, + STATE: PLATFORM_SCHEMA_STATE_MODERN, + } + return schemas[value[CONF_SCHEMA]](value) + + DISCOVERY_SCHEMA = vol.All( MQTT_VACUUM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA), validate_mqtt_vacuum_discovery ) +# Configuring MQTT Vacuums under the vacuum platform key is deprecated in HA Core 2022.6 PLATFORM_SCHEMA = vol.All( MQTT_VACUUM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA), validate_mqtt_vacuum ) +PLATFORM_SCHEMA_MODERN = vol.All( + MQTT_VACUUM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA), validate_mqtt_vacuum_modern +) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up MQTT vacuum through configuration.yaml.""" + # Deprecated in HA Core 2022.6 await async_setup_platform_helper( hass, vacuum.DOMAIN, config, async_add_entities, _async_setup_entity ) -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up MQTT vacuum dynamically through MQTT discovery.""" +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up MQTT vacuum through configuration.yaml and dynamically through MQTT discovery.""" + # load and initialize platform config from configuration.yaml + await asyncio.gather( + *( + _async_setup_entity(hass, async_add_entities, config, config_entry) + for config in await async_get_platform_config_from_yaml( + hass, vacuum.DOMAIN, PLATFORM_SCHEMA_MODERN + ) + ) + ) + # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index 41d0c9faf17..eb5e01b6251 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -5,6 +5,7 @@ import voluptuous as vol from homeassistant.components.vacuum import ( ATTR_STATUS, + DOMAIN as VACUUM_DOMAIN, ENTITY_ID_FORMAT, VacuumEntity, VacuumEntityFeature, @@ -18,7 +19,7 @@ from .. import MqttValueTemplate, subscription from ... import mqtt from ..const import CONF_COMMAND_TOPIC, CONF_ENCODING, CONF_QOS, CONF_RETAIN from ..debug_info import log_messages -from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, warn_for_legacy_schema from .const import MQTT_VACUUM_ATTRIBUTES_BLOCKED from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services @@ -94,8 +95,8 @@ MQTT_LEGACY_VACUUM_ATTRIBUTES_BLOCKED = MQTT_VACUUM_ATTRIBUTES_BLOCKED | frozens {ATTR_STATUS} ) -PLATFORM_SCHEMA_LEGACY = ( - mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_LEGACY_MODERN = ( + mqtt.MQTT_BASE_SCHEMA.extend( { vol.Inclusive(CONF_BATTERY_LEVEL_TEMPLATE, "battery"): cv.template, vol.Inclusive( @@ -147,7 +148,15 @@ PLATFORM_SCHEMA_LEGACY = ( .extend(MQTT_VACUUM_SCHEMA.schema) ) -DISCOVERY_SCHEMA_LEGACY = PLATFORM_SCHEMA_LEGACY.extend({}, extra=vol.REMOVE_EXTRA) +# Configuring MQTT Vacuums under the vacuum platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA_LEGACY = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_LEGACY_MODERN.schema), + warn_for_legacy_schema(VACUUM_DOMAIN), +) + +DISCOVERY_SCHEMA_LEGACY = PLATFORM_SCHEMA_LEGACY_MODERN.extend( + {}, extra=vol.REMOVE_EXTRA +) async def async_setup_entity_legacy( diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index 7a5424d7cbf..7aa7be07797 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -4,6 +4,7 @@ import json import voluptuous as vol from homeassistant.components.vacuum import ( + DOMAIN as VACUUM_DOMAIN, ENTITY_ID_FORMAT, STATE_CLEANING, STATE_DOCKED, @@ -31,7 +32,7 @@ from ..const import ( CONF_STATE_TOPIC, ) from ..debug_info import log_messages -from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, warn_for_legacy_schema from .const import MQTT_VACUUM_ATTRIBUTES_BLOCKED from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services @@ -103,8 +104,8 @@ DEFAULT_PAYLOAD_LOCATE = "locate" DEFAULT_PAYLOAD_START = "start" DEFAULT_PAYLOAD_PAUSE = "pause" -PLATFORM_SCHEMA_STATE = ( - mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA_STATE_MODERN = ( + mqtt.MQTT_BASE_SCHEMA.extend( { vol.Optional(CONF_FAN_SPEED_LIST, default=[]): vol.All( cv.ensure_list, [cv.string] @@ -136,8 +137,13 @@ PLATFORM_SCHEMA_STATE = ( .extend(MQTT_VACUUM_SCHEMA.schema) ) +# Configuring MQTT Vacuums under the vacuum platform key is deprecated in HA Core 2022.6 +PLATFORM_SCHEMA_STATE = vol.All( + cv.PLATFORM_SCHEMA.extend(PLATFORM_SCHEMA_STATE_MODERN.schema), + warn_for_legacy_schema(VACUUM_DOMAIN), +) -DISCOVERY_SCHEMA_STATE = PLATFORM_SCHEMA_STATE.extend({}, extra=vol.REMOVE_EXTRA) +DISCOVERY_SCHEMA_STATE = PLATFORM_SCHEMA_STATE_MODERN.extend({}, extra=vol.REMOVE_EXTRA) async def async_setup_entity_state( diff --git a/homeassistant/components/myq/translations/es.json b/homeassistant/components/myq/translations/es.json index 08d2ccbee73..98d02b56280 100644 --- a/homeassistant/components/myq/translations/es.json +++ b/homeassistant/components/myq/translations/es.json @@ -6,7 +6,7 @@ }, "error": { "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.", - "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", "unknown": "Error inesperado" }, "step": { diff --git a/homeassistant/components/myq/translations/ko.json b/homeassistant/components/myq/translations/ko.json index 23ba2eecea7..1218ce0e4f4 100644 --- a/homeassistant/components/myq/translations/ko.json +++ b/homeassistant/components/myq/translations/ko.json @@ -9,6 +9,10 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "reauth_confirm": { + "description": "{username} \uc758 \ube44\ubc00\ubc88\ud638\uac00 \ub354 \uc774\uc0c1 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "title": "MyQ \uacc4\uc815 \uc7ac\uc778\uc99d" + }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", diff --git a/homeassistant/components/myq/translations/nl.json b/homeassistant/components/myq/translations/nl.json index 09a36665414..ea41554ff75 100644 --- a/homeassistant/components/myq/translations/nl.json +++ b/homeassistant/components/myq/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "already_configured": "Dienst is al geconfigureerd", + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/mysensors/translations/ca.json b/homeassistant/components/mysensors/translations/ca.json index 6e20f0bcbee..99cf16939ad 100644 --- a/homeassistant/components/mysensors/translations/ca.json +++ b/homeassistant/components/mysensors/translations/ca.json @@ -75,6 +75,5 @@ "description": "Tria el m\u00e8tode de connexi\u00f3 a la passarel\u00b7la" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/cs.json b/homeassistant/components/mysensors/translations/cs.json index abe47f046ff..3434cfe82ec 100644 --- a/homeassistant/components/mysensors/translations/cs.json +++ b/homeassistant/components/mysensors/translations/cs.json @@ -12,6 +12,5 @@ "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/de.json b/homeassistant/components/mysensors/translations/de.json index cfef6f7d363..0b90165fa5a 100644 --- a/homeassistant/components/mysensors/translations/de.json +++ b/homeassistant/components/mysensors/translations/de.json @@ -75,6 +75,5 @@ "description": "Verbindungsmethode zum Gateway w\u00e4hlen" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/el.json b/homeassistant/components/mysensors/translations/el.json index 17bf83158b7..81ba72eea7e 100644 --- a/homeassistant/components/mysensors/translations/el.json +++ b/homeassistant/components/mysensors/translations/el.json @@ -75,6 +75,5 @@ "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/en.json b/homeassistant/components/mysensors/translations/en.json index 7ca3516e50d..5ec81c22186 100644 --- a/homeassistant/components/mysensors/translations/en.json +++ b/homeassistant/components/mysensors/translations/en.json @@ -75,6 +75,5 @@ "description": "Choose connection method to the gateway" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/es.json b/homeassistant/components/mysensors/translations/es.json index 411501db49d..91f2b7f0d1e 100644 --- a/homeassistant/components/mysensors/translations/es.json +++ b/homeassistant/components/mysensors/translations/es.json @@ -75,6 +75,5 @@ "description": "Elija el m\u00e9todo de conexi\u00f3n con la pasarela" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/et.json b/homeassistant/components/mysensors/translations/et.json index 7aff6b1c3da..00614e57252 100644 --- a/homeassistant/components/mysensors/translations/et.json +++ b/homeassistant/components/mysensors/translations/et.json @@ -75,6 +75,5 @@ "description": "Vali l\u00fc\u00fcsi \u00fchendusviis" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/fr.json b/homeassistant/components/mysensors/translations/fr.json index c9d64ee73e6..722bf639111 100644 --- a/homeassistant/components/mysensors/translations/fr.json +++ b/homeassistant/components/mysensors/translations/fr.json @@ -75,6 +75,5 @@ "description": "Choisissez la m\u00e9thode de connexion \u00e0 la passerelle" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/hu.json b/homeassistant/components/mysensors/translations/hu.json index a52ac3cd289..433714de477 100644 --- a/homeassistant/components/mysensors/translations/hu.json +++ b/homeassistant/components/mysensors/translations/hu.json @@ -75,6 +75,5 @@ "description": "V\u00e1lassza ki az \u00e1tj\u00e1r\u00f3hoz val\u00f3 csatlakoz\u00e1si m\u00f3dot" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/id.json b/homeassistant/components/mysensors/translations/id.json index 93b10bb8c2c..e6256bd8757 100644 --- a/homeassistant/components/mysensors/translations/id.json +++ b/homeassistant/components/mysensors/translations/id.json @@ -75,6 +75,5 @@ "description": "Pilih metode koneksi ke gateway" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/it.json b/homeassistant/components/mysensors/translations/it.json index 56ce395aa4c..0a16a4f045c 100644 --- a/homeassistant/components/mysensors/translations/it.json +++ b/homeassistant/components/mysensors/translations/it.json @@ -75,6 +75,5 @@ "description": "Scegli il metodo di connessione al gateway" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/ja.json b/homeassistant/components/mysensors/translations/ja.json index 9842d241ad1..da0354fe3c7 100644 --- a/homeassistant/components/mysensors/translations/ja.json +++ b/homeassistant/components/mysensors/translations/ja.json @@ -42,7 +42,7 @@ "step": { "gw_mqtt": { "data": { - "persistence_file": "\u6c38\u7d9a(persistence)\u30d5\u30a1\u30a4\u30eb(\u7a7a\u306b\u3059\u308b\u3068\u81ea\u52d5\u751f\u6210\u3055\u308c\u307e\u3059)", + "persistence_file": "\u6c38\u7d9a(persistence)\u30d5\u30a1\u30a4\u30eb(\u7a7a\u767d\u306b\u3059\u308b\u3068\u81ea\u52d5\u751f\u6210\u3055\u308c\u307e\u3059)", "retain": "mqtt retain(\u4fdd\u6301)", "topic_in_prefix": "\u30a4\u30f3\u30d7\u30c3\u30c8 \u30c8\u30d4\u30c3\u30af\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9(topic_in_prefix)", "topic_out_prefix": "\u30a2\u30a6\u30c8\u30d7\u30c3\u30c8 \u30c8\u30d4\u30c3\u30af\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9(topic_out_prefix)", @@ -54,7 +54,7 @@ "data": { "baud_rate": "\u30dc\u30fc\u30ec\u30fc\u30c8", "device": "\u30b7\u30ea\u30a2\u30eb\u30dd\u30fc\u30c8", - "persistence_file": "\u6c38\u7d9a(persistence)\u30d5\u30a1\u30a4\u30eb(\u7a7a\u306b\u3059\u308b\u3068\u81ea\u52d5\u751f\u6210\u3055\u308c\u307e\u3059)", + "persistence_file": "\u6c38\u7d9a(persistence)\u30d5\u30a1\u30a4\u30eb(\u7a7a\u767d\u306b\u3059\u308b\u3068\u81ea\u52d5\u751f\u6210\u3055\u308c\u307e\u3059)", "version": "MySensors\u306e\u30d0\u30fc\u30b8\u30e7\u30f3" }, "description": "\u30b7\u30ea\u30a2\u30eb\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" @@ -62,7 +62,7 @@ "gw_tcp": { "data": { "device": "\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4\u306eIP\u30a2\u30c9\u30ec\u30b9", - "persistence_file": "\u6c38\u7d9a(persistence)\u30d5\u30a1\u30a4\u30eb(\u7a7a\u306b\u3059\u308b\u3068\u81ea\u52d5\u751f\u6210\u3055\u308c\u307e\u3059)", + "persistence_file": "\u6c38\u7d9a(persistence)\u30d5\u30a1\u30a4\u30eb(\u7a7a\u767d\u306b\u3059\u308b\u3068\u81ea\u52d5\u751f\u6210\u3055\u308c\u307e\u3059)", "tcp_port": "\u30dd\u30fc\u30c8", "version": "MySensors\u306e\u30d0\u30fc\u30b8\u30e7\u30f3" }, @@ -75,6 +75,5 @@ "description": "\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4\u3078\u306e\u63a5\u7d9a\u65b9\u6cd5\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/ko.json b/homeassistant/components/mysensors/translations/ko.json index e57f60aafbf..4d5be0f44e3 100644 --- a/homeassistant/components/mysensors/translations/ko.json +++ b/homeassistant/components/mysensors/translations/ko.json @@ -33,6 +33,7 @@ "invalid_serial": "\uc2dc\ub9ac\uc5bc \ud3ec\ud2b8\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_subscribe_topic": "\uad6c\ub3c5 \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_version": "MySensors \ubc84\uc804\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "mqtt_required": "MQTT \ud1b5\ud569\uad6c\uc131\uc694\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", "not_a_number": "\uc22b\uc790\ub85c \uc785\ub825\ud574\uc8fc\uc138\uc694", "port_out_of_range": "\ud3ec\ud2b8 \ubc88\ud638\ub294 1 \uc774\uc0c1 65535 \uc774\ud558\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4", "same_topic": "\uad6c\ub3c5 \ubc0f \ubc1c\ud589 \ud1a0\ud53d\uc740 \ub3d9\uc77c\ud569\ub2c8\ub2e4", @@ -74,6 +75,5 @@ "description": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc5f0\uacb0 \ubc29\ubc95\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/nl.json b/homeassistant/components/mysensors/translations/nl.json index 14055639f60..8293e815d8c 100644 --- a/homeassistant/components/mysensors/translations/nl.json +++ b/homeassistant/components/mysensors/translations/nl.json @@ -75,6 +75,5 @@ "description": "Kies de verbindingsmethode met de gateway" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/no.json b/homeassistant/components/mysensors/translations/no.json index f0e307a1ab2..14fb39910fe 100644 --- a/homeassistant/components/mysensors/translations/no.json +++ b/homeassistant/components/mysensors/translations/no.json @@ -75,6 +75,5 @@ "description": "Velg tilkoblingsmetode til gatewayen" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/pl.json b/homeassistant/components/mysensors/translations/pl.json index f3233a01d50..3c4bed1ee86 100644 --- a/homeassistant/components/mysensors/translations/pl.json +++ b/homeassistant/components/mysensors/translations/pl.json @@ -75,6 +75,5 @@ "description": "Wybierz metod\u0119 po\u0142\u0105czenia z bramk\u0105" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/pt-BR.json b/homeassistant/components/mysensors/translations/pt-BR.json index 468acf70588..2c0f312da72 100644 --- a/homeassistant/components/mysensors/translations/pt-BR.json +++ b/homeassistant/components/mysensors/translations/pt-BR.json @@ -75,6 +75,5 @@ "description": "Escolha o m\u00e9todo de conex\u00e3o com o gateway" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/ru.json b/homeassistant/components/mysensors/translations/ru.json index 16f23f6efff..2801b8e8c00 100644 --- a/homeassistant/components/mysensors/translations/ru.json +++ b/homeassistant/components/mysensors/translations/ru.json @@ -75,6 +75,5 @@ "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0448\u043b\u044e\u0437\u0443" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/tr.json b/homeassistant/components/mysensors/translations/tr.json index 59f7a319e07..9f99525c7b2 100644 --- a/homeassistant/components/mysensors/translations/tr.json +++ b/homeassistant/components/mysensors/translations/tr.json @@ -75,6 +75,5 @@ "description": "A\u011f ge\u00e7idine ba\u011flant\u0131 y\u00f6ntemini se\u00e7in" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/zh-Hant.json b/homeassistant/components/mysensors/translations/zh-Hant.json index 5774df64b78..a3ec7ba2dd7 100644 --- a/homeassistant/components/mysensors/translations/zh-Hant.json +++ b/homeassistant/components/mysensors/translations/zh-Hant.json @@ -75,6 +75,5 @@ "description": "\u9078\u64c7\u9598\u9053\u5668\u9023\u7dda\u65b9\u5f0f" } } - }, - "title": "MySensors" + } } \ No newline at end of file diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py index 2bcd7958de9..f031175a321 100644 --- a/homeassistant/components/nad/media_player.py +++ b/homeassistant/components/nad/media_player.py @@ -45,7 +45,8 @@ CONF_MAX_VOLUME = "max_volume" CONF_VOLUME_STEP = "volume_step" # for NADReceiverTCP CONF_SOURCE_DICT = "sources" # for NADReceiver -SOURCE_DICT_SCHEMA = vol.Schema({vol.Range(min=1, max=10): cv.string}) +# Max value based on a C658 with an MDC HDM-2 card installed +SOURCE_DICT_SCHEMA = vol.Schema({vol.Range(min=1, max=12): cv.string}) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { diff --git a/homeassistant/components/nam/translations/es.json b/homeassistant/components/nam/translations/es.json index 81e83c4376b..feeff2015f9 100644 --- a/homeassistant/components/nam/translations/es.json +++ b/homeassistant/components/nam/translations/es.json @@ -14,7 +14,7 @@ "flow_title": "{host}", "step": { "confirm_discovery": { - "description": "\u00bfQuieres configurar Nettigo Air Monitor en {host} ?" + "description": "\u00bfQuieres configurar Nettigo Air Monitor en {host}?" }, "credentials": { "data": { diff --git a/homeassistant/components/nam/translations/ko.json b/homeassistant/components/nam/translations/ko.json new file mode 100644 index 00000000000..8112e2968c5 --- /dev/null +++ b/homeassistant/components/nam/translations/ko.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_unsuccessful": "\uc7ac\uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131\uc744 \uc81c\uac70\ud558\uace0 \ub2e4\uc2dc \uc124\uc815\ud558\uc2ed\uc2dc\uc624." + }, + "step": { + "reauth_confirm": { + "description": "\ud638\uc2a4\ud2b8\uc5d0 \ub300\ud55c \uc62c\ubc14\ub978 \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \uc554\ud638\ub97c \uc785\ub825\ud558\uc2ed\uc2dc\uc624: {host}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nam/translations/nl.json b/homeassistant/components/nam/translations/nl.json index f900dccc295..a3d7925479f 100644 --- a/homeassistant/components/nam/translations/nl.json +++ b/homeassistant/components/nam/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "device_unsupported": "Het apparaat wordt niet ondersteund.", - "reauth_successful": "Herauthenticatie was succesvol", + "reauth_successful": "Herauthenticatie geslaagd", "reauth_unsuccessful": "Herauthenticatie is mislukt, verwijder de integratie en stel het opnieuw in." }, "error": { diff --git a/homeassistant/components/nanoleaf/__init__.py b/homeassistant/components/nanoleaf/__init__.py index 9e9cf1d6ca4..f6fb2f8112b 100644 --- a/homeassistant/components/nanoleaf/__init__.py +++ b/homeassistant/components/nanoleaf/__init__.py @@ -85,9 +85,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Receive touch event.""" gesture_type = TOUCH_GESTURE_TRIGGER_MAP.get(event.gesture_id) if gesture_type is None: - _LOGGER.debug("Received unknown touch gesture ID %s", event.gesture_id) + _LOGGER.warning( + "Received unknown touch gesture ID %s", event.gesture_id + ) return - _LOGGER.warning("Received touch gesture %s", gesture_type) + _LOGGER.debug("Received touch gesture %s", gesture_type) hass.bus.async_fire( NANOLEAF_EVENT, {CONF_DEVICE_ID: device_entry.id, CONF_TYPE: gesture_type}, diff --git a/homeassistant/components/nanoleaf/device_trigger.py b/homeassistant/components/nanoleaf/device_trigger.py index 311d12506ba..68dc6326719 100644 --- a/homeassistant/components/nanoleaf/device_trigger.py +++ b/homeassistant/components/nanoleaf/device_trigger.py @@ -1,8 +1,6 @@ """Provides device triggers for Nanoleaf.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -38,7 +36,7 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Nanoleaf devices.""" device_registry = dr.async_get(hass) device_entry = device_registry.async_get(device_id) diff --git a/homeassistant/components/nanoleaf/translations/es.json b/homeassistant/components/nanoleaf/translations/es.json index 605997c6439..187517ed478 100644 --- a/homeassistant/components/nanoleaf/translations/es.json +++ b/homeassistant/components/nanoleaf/translations/es.json @@ -24,5 +24,10 @@ } } } + }, + "device_automation": { + "trigger_type": { + "swipe_down": "Desliza hacia abajo" + } } } \ No newline at end of file diff --git a/homeassistant/components/nanoleaf/translations/ko.json b/homeassistant/components/nanoleaf/translations/ko.json new file mode 100644 index 00000000000..e540d903647 --- /dev/null +++ b/homeassistant/components/nanoleaf/translations/ko.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "\ub514\ubc14\uc774\uc2a4\uac00 \uc774\ubbf8 \uc124\uc815\ub418\uc5b4 \uc788\uc74c", + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_token": "\uc798\ubabb\ub41c \uc811\uc18d \ud1a0\ud070", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nanoleaf/translations/nl.json b/homeassistant/components/nanoleaf/translations/nl.json index 76c471769e3..292eb066bf3 100644 --- a/homeassistant/components/nanoleaf/translations/nl.json +++ b/homeassistant/components/nanoleaf/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", "invalid_token": "Ongeldig toegangstoken", - "reauth_successful": "Herauthenticatie was succesvol", + "reauth_successful": "Herauthenticatie geslaagd", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/nanoleaf/translations/sk.json b/homeassistant/components/nanoleaf/translations/sk.json index c2f015fe339..c34ad96714a 100644 --- a/homeassistant/components/nanoleaf/translations/sk.json +++ b/homeassistant/components/nanoleaf/translations/sk.json @@ -3,5 +3,10 @@ "abort": { "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" } + }, + "device_automation": { + "trigger_type": { + "swipe_right": "Potiahnite prstom doprava" + } } } \ No newline at end of file diff --git a/homeassistant/components/neato/__init__.py b/homeassistant/components/neato/__init__.py index c1e193b7406..cbfd860a0b1 100644 --- a/homeassistant/components/neato/__init__.py +++ b/homeassistant/components/neato/__init__.py @@ -2,10 +2,14 @@ import logging import aiohttp -from pybotvac import Account, Neato +from pybotvac import Account from pybotvac.exceptions import NeatoException import voluptuous as vol +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_TOKEN, Platform from homeassistant.core import HomeAssistant @@ -13,7 +17,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv from homeassistant.helpers.typing import ConfigType -from . import api, config_flow +from . import api from .const import NEATO_CONFIG, NEATO_DOMAIN, NEATO_LOGIN from .hub import NeatoHub @@ -21,14 +25,17 @@ _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( - { - NEATO_DOMAIN: vol.Schema( - { - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_CLIENT_SECRET): cv.string, - } - ) - }, + vol.All( + cv.deprecated(NEATO_DOMAIN), + { + NEATO_DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) @@ -43,18 +50,21 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True hass.data[NEATO_CONFIG] = config[NEATO_DOMAIN] - vendor = Neato() - config_flow.OAuth2FlowHandler.async_register_implementation( + await async_import_client_credential( hass, - api.NeatoImplementation( - hass, - NEATO_DOMAIN, + NEATO_DOMAIN, + ClientCredential( config[NEATO_DOMAIN][CONF_CLIENT_ID], config[NEATO_DOMAIN][CONF_CLIENT_SECRET], - vendor.auth_endpoint, - vendor.token_endpoint, ), ) + _LOGGER.warning( + "Configuration of Neato integration in YAML is deprecated and " + "will be removed in a future release; Your existing OAuth " + "Application Credentials have been imported into the UI " + "automatically and can be safely removed from your " + "configuration.yaml file" + ) return True diff --git a/homeassistant/components/neato/api.py b/homeassistant/components/neato/api.py index cd26b009040..f3a4324b7ed 100644 --- a/homeassistant/components/neato/api.py +++ b/homeassistant/components/neato/api.py @@ -7,6 +7,7 @@ from typing import Any import pybotvac from homeassistant import config_entries, core +from homeassistant.components.application_credentials import AuthImplementation from homeassistant.helpers import config_entry_oauth2_flow @@ -35,7 +36,7 @@ class ConfigEntryAuth(pybotvac.OAuthSession): # type: ignore[misc] return self.session.token["access_token"] # type: ignore[no-any-return] -class NeatoImplementation(config_entry_oauth2_flow.LocalOAuth2Implementation): +class NeatoImplementation(AuthImplementation): """Neato implementation of LocalOAuth2Implementation. We need this class because we have to add client_secret and scope to the authorization request. diff --git a/homeassistant/components/neato/application_credentials.py b/homeassistant/components/neato/application_credentials.py new file mode 100644 index 00000000000..2abdb6bcc81 --- /dev/null +++ b/homeassistant/components/neato/application_credentials.py @@ -0,0 +1,28 @@ +"""Application credentials platform for neato.""" + +from pybotvac import Neato + +from homeassistant.components.application_credentials import ( + AuthorizationServer, + ClientCredential, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow + +from . import api + + +async def async_get_auth_implementation( + hass: HomeAssistant, auth_domain: str, credential: ClientCredential +) -> config_entry_oauth2_flow.AbstractOAuth2Implementation: + """Return auth implementation for a custom auth implementation.""" + vendor = Neato() + return api.NeatoImplementation( + hass, + auth_domain, + credential, + AuthorizationServer( + authorize_url=vendor.auth_endpoint, + token_url=vendor.token_endpoint, + ), + ) diff --git a/homeassistant/components/neato/manifest.json b/homeassistant/components/neato/manifest.json index f12f79c77a8..64c10abec06 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": ["auth"], + "dependencies": ["application_credentials"], "iot_class": "cloud_polling", "loggers": ["pybotvac"] } diff --git a/homeassistant/components/neato/translations/ca.json b/homeassistant/components/neato/translations/ca.json index ab8210afb2f..e45c8a28e5f 100644 --- a/homeassistant/components/neato/translations/ca.json +++ b/homeassistant/components/neato/translations/ca.json @@ -18,6 +18,5 @@ "title": "Vols comen\u00e7ar la configuraci\u00f3?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/cs.json b/homeassistant/components/neato/translations/cs.json index 6cf7e314c4b..4618be9d933 100644 --- a/homeassistant/components/neato/translations/cs.json +++ b/homeassistant/components/neato/translations/cs.json @@ -18,6 +18,5 @@ "title": "Chcete za\u010d\u00edt nastavovat?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/de.json b/homeassistant/components/neato/translations/de.json index c7fd239c585..03ad20e3caf 100644 --- a/homeassistant/components/neato/translations/de.json +++ b/homeassistant/components/neato/translations/de.json @@ -18,6 +18,5 @@ "title": "M\u00f6chtest Du mit der Einrichtung beginnen?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/el.json b/homeassistant/components/neato/translations/el.json index 5e57ddd8b8b..a1a6232cfdb 100644 --- a/homeassistant/components/neato/translations/el.json +++ b/homeassistant/components/neato/translations/el.json @@ -18,6 +18,5 @@ "title": "\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;" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/en.json b/homeassistant/components/neato/translations/en.json index 684d667678b..7c435c3749b 100644 --- a/homeassistant/components/neato/translations/en.json +++ b/homeassistant/components/neato/translations/en.json @@ -18,6 +18,5 @@ "title": "Do you want to start set up?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/es.json b/homeassistant/components/neato/translations/es.json index 64c58279614..7eeecb4dde0 100644 --- a/homeassistant/components/neato/translations/es.json +++ b/homeassistant/components/neato/translations/es.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El dispositivo ya est\u00e1 configurado", - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n.", "no_url_available": "No hay URL disponible. Para obtener informaci\u00f3n sobre este error, [consulta la secci\u00f3n de ayuda]({docs_url})", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" @@ -18,6 +18,5 @@ "title": "\u00bfQuieres iniciar la configuraci\u00f3n?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/et.json b/homeassistant/components/neato/translations/et.json index 40e601dfe9d..eff32d5b877 100644 --- a/homeassistant/components/neato/translations/et.json +++ b/homeassistant/components/neato/translations/et.json @@ -18,6 +18,5 @@ "title": "Kas soovid alustada seadistamist?" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/fr.json b/homeassistant/components/neato/translations/fr.json index df805121f6b..4334a2180bb 100644 --- a/homeassistant/components/neato/translations/fr.json +++ b/homeassistant/components/neato/translations/fr.json @@ -18,6 +18,5 @@ "title": "Voulez-vous commencer la configuration\u00a0?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/hu.json b/homeassistant/components/neato/translations/hu.json index 255378eda35..3415d966717 100644 --- a/homeassistant/components/neato/translations/hu.json +++ b/homeassistant/components/neato/translations/hu.json @@ -18,6 +18,5 @@ "title": "El szeretn\u00e9 kezdeni a be\u00e1ll\u00edt\u00e1st?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/id.json b/homeassistant/components/neato/translations/id.json index b1811fe298e..e38096f47aa 100644 --- a/homeassistant/components/neato/translations/id.json +++ b/homeassistant/components/neato/translations/id.json @@ -18,6 +18,5 @@ "title": "Ingin memulai penyiapan?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/it.json b/homeassistant/components/neato/translations/it.json index 000625a0294..cda062ceff0 100644 --- a/homeassistant/components/neato/translations/it.json +++ b/homeassistant/components/neato/translations/it.json @@ -18,6 +18,5 @@ "title": "Vuoi iniziare la configurazione?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/ja.json b/homeassistant/components/neato/translations/ja.json index c5f4605159c..39eeedddc91 100644 --- a/homeassistant/components/neato/translations/ja.json +++ b/homeassistant/components/neato/translations/ja.json @@ -18,6 +18,5 @@ "title": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u958b\u59cb\u3057\u307e\u3059\u304b\uff1f" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/ko.json b/homeassistant/components/neato/translations/ko.json index dc651939665..faaea344741 100644 --- a/homeassistant/components/neato/translations/ko.json +++ b/homeassistant/components/neato/translations/ko.json @@ -18,6 +18,5 @@ "title": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/lb.json b/homeassistant/components/neato/translations/lb.json index d54443e6671..31a2b8d2f73 100644 --- a/homeassistant/components/neato/translations/lb.json +++ b/homeassistant/components/neato/translations/lb.json @@ -17,6 +17,5 @@ "title": "Soll den Ariichtungs Prozess gestart ginn?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/nl.json b/homeassistant/components/neato/translations/nl.json index 2e9ab212fa9..53cf562a748 100644 --- a/homeassistant/components/neato/translations/nl.json +++ b/homeassistant/components/neato/translations/nl.json @@ -2,22 +2,21 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "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})", - "reauth_successful": "Herauthenticatie was succesvol" + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", + "reauth_successful": "Herauthenticatie geslaagd" }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "step": { "pick_implementation": { "title": "Kies een authenticatie methode" }, "reauth_confirm": { - "title": "Wilt u beginnen met instellen?" + "title": "Wil je beginnen met instellen?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/no.json b/homeassistant/components/neato/translations/no.json index fc50308eda8..d62c8e5d1f1 100644 --- a/homeassistant/components/neato/translations/no.json +++ b/homeassistant/components/neato/translations/no.json @@ -18,6 +18,5 @@ "title": "Vil du starte oppsettet?" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/pl.json b/homeassistant/components/neato/translations/pl.json index 34f1d42bda5..a97184b8b35 100644 --- a/homeassistant/components/neato/translations/pl.json +++ b/homeassistant/components/neato/translations/pl.json @@ -18,6 +18,5 @@ "title": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/pt-BR.json b/homeassistant/components/neato/translations/pt-BR.json index 684f7cdff22..41bc8c5e4a6 100644 --- a/homeassistant/components/neato/translations/pt-BR.json +++ b/homeassistant/components/neato/translations/pt-BR.json @@ -18,6 +18,5 @@ "title": "Deseja iniciar a configura\u00e7\u00e3o?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/ru.json b/homeassistant/components/neato/translations/ru.json index 29201df669d..9c12f8c962c 100644 --- a/homeassistant/components/neato/translations/ru.json +++ b/homeassistant/components/neato/translations/ru.json @@ -18,6 +18,5 @@ "title": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/sl.json b/homeassistant/components/neato/translations/sl.json index 1aaae76ada8..a82510d9b95 100644 --- a/homeassistant/components/neato/translations/sl.json +++ b/homeassistant/components/neato/translations/sl.json @@ -18,6 +18,5 @@ "title": "Bi radi zagnali namestitev?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/tr.json b/homeassistant/components/neato/translations/tr.json index a461d5b0c2f..4c93fdc78b0 100644 --- a/homeassistant/components/neato/translations/tr.json +++ b/homeassistant/components/neato/translations/tr.json @@ -18,6 +18,5 @@ "title": "Kuruluma ba\u015flamak ister misiniz?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/uk.json b/homeassistant/components/neato/translations/uk.json index 353005546cf..532808c1fb6 100644 --- a/homeassistant/components/neato/translations/uk.json +++ b/homeassistant/components/neato/translations/uk.json @@ -18,6 +18,5 @@ "title": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/zh-Hant.json b/homeassistant/components/neato/translations/zh-Hant.json index 781ee1f952b..1252e80f4b7 100644 --- a/homeassistant/components/neato/translations/zh-Hant.json +++ b/homeassistant/components/neato/translations/zh-Hant.json @@ -18,6 +18,5 @@ "title": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" } } - }, - "title": "Neato Botvac" + } } \ No newline at end of file diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index 3a4096055bc..4d402fbb8bb 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -94,6 +94,7 @@ async def async_setup_entry( class NeatoConnectedVacuum(StateVacuumEntity): """Representation of a Neato Connected Vacuum.""" + _attr_icon = "mdi:robot-vacuum-variant" _attr_supported_features = ( VacuumEntityFeature.BATTERY | VacuumEntityFeature.PAUSE @@ -115,12 +116,13 @@ class NeatoConnectedVacuum(StateVacuumEntity): ) -> None: """Initialize the Neato Connected Vacuum.""" self.robot = robot - self._available: bool = neato is not None + self._attr_available: bool = neato is not None self._mapdata = mapdata - self._name: str = f"{self.robot.name}" + self._attr_name: str = self.robot.name self._robot_has_map: bool = self.robot.has_persistent_maps self._robot_maps = persistent_maps self._robot_serial: str = self.robot.serial + self._attr_unique_id: str = self.robot.serial self._status_state: str | None = None self._clean_state: str | None = None self._state: dict[str, Any] | None = None @@ -134,7 +136,6 @@ class NeatoConnectedVacuum(StateVacuumEntity): self._clean_pause_time: int | None = None self._clean_error_time: int | None = None self._launched_from: str | None = None - self._battery_level: int | None = None self._robot_boundaries: list = [] self._robot_stats: dict[str, Any] | None = None @@ -150,17 +151,17 @@ class NeatoConnectedVacuum(StateVacuumEntity): try: self._state = self.robot.state except NeatoRobotException as ex: - if self._available: # print only once when available + if self._attr_available: # print only once when available _LOGGER.error( "Neato vacuum connection error for '%s': %s", self.entity_id, ex ) self._state = None - self._available = False + self._attr_available = False return if self._state is None: return - self._available = True + self._attr_available = True _LOGGER.debug("self._state=%s", self._state) if "alert" in self._state: robot_alert = ALERTS.get(self._state["alert"]) @@ -205,7 +206,7 @@ class NeatoConnectedVacuum(StateVacuumEntity): self._clean_state = STATE_ERROR self._status_state = ERRORS.get(self._state["error"]) - self._battery_level = self._state["details"]["charge"] + self._attr_battery_level = self._state["details"]["charge"] if self._mapdata is None or not self._mapdata.get(self._robot_serial, {}).get( "maps", [] @@ -260,41 +261,11 @@ class NeatoConnectedVacuum(StateVacuumEntity): self._robot_boundaries, ) - @property - def name(self) -> str: - """Return the name of the device.""" - return self._name - - @property - def supported_features(self) -> int: - """Flag vacuum cleaner robot features that are supported.""" - return self._attr_supported_features - - @property - def battery_level(self) -> int | None: - """Return the battery level of the vacuum cleaner.""" - return self._battery_level - - @property - def available(self) -> bool: - """Return if the robot is available.""" - return self._available - - @property - def icon(self) -> str: - """Return neato specific icon.""" - return "mdi:robot-vacuum-variant" - @property def state(self) -> str | None: """Return the status of the vacuum cleaner.""" return self._clean_state - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self._robot_serial - @property def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the vacuum cleaner.""" @@ -333,7 +304,7 @@ class NeatoConnectedVacuum(StateVacuumEntity): identifiers={(NEATO_DOMAIN, self._robot_serial)}, manufacturer=stats["battery"]["vendor"] if stats else None, model=stats["model"] if stats else None, - name=self._name, + name=self._attr_name, sw_version=stats["firmware"] if stats else None, ) diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 076b9a58814..0920b37e6ef 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -42,7 +42,11 @@ from homeassistant.exceptions import ( HomeAssistantError, Unauthorized, ) -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + entity_registry as er, +) from homeassistant.helpers.entity_registry import async_entries_for_device from homeassistant.helpers.typing import ConfigType @@ -50,6 +54,7 @@ from . import api, config_flow from .const import ( CONF_PROJECT_ID, CONF_SUBSCRIBER_ID, + DATA_DEVICE_MANAGER, DATA_NEST_CONFIG, DATA_SDM, DATA_SUBSCRIBER, @@ -59,8 +64,8 @@ from .events import EVENT_NAME_MAP, NEST_EVENT from .legacy import async_setup_legacy, async_setup_legacy_entry from .media_source import ( async_get_media_event_store, + async_get_media_source_devices, async_get_transcoder, - get_media_source_devices, ) _LOGGER = logging.getLogger(__name__) @@ -145,7 +150,7 @@ class SignalUpdateCallback: if not (events := event_message.resource_update_events): return _LOGGER.debug("Event Update %s", events.keys()) - device_registry = await self._hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(self._hass) device_entry = device_registry.async_get_device({(DOMAIN, device_id)}) if not device_entry: return @@ -201,7 +206,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryNotReady from err try: - await subscriber.async_get_device_manager() + device_manager = await subscriber.async_get_device_manager() except ApiException as err: if DATA_NEST_UNAVAILABLE not in hass.data[DOMAIN]: _LOGGER.error("Device manager error: %s", err) @@ -211,6 +216,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(DATA_NEST_UNAVAILABLE, None) hass.data[DOMAIN][DATA_SUBSCRIBER] = subscriber + hass.data[DOMAIN][DATA_DEVICE_MANAGER] = device_manager hass.config_entries.async_setup_platforms(entry, PLATFORMS) @@ -228,6 +234,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: hass.data[DOMAIN].pop(DATA_SUBSCRIBER) + hass.data[DOMAIN].pop(DATA_DEVICE_MANAGER) hass.data[DOMAIN].pop(DATA_NEST_UNAVAILABLE, None) return unload_ok @@ -266,12 +273,12 @@ class NestEventViewBase(HomeAssistantView, ABC): ) -> web.StreamResponse: """Start a GET request.""" user = request[KEY_HASS_USER] - entity_registry = await self.hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(self.hass) for entry in async_entries_for_device(entity_registry, device_id): if not user.permissions.check_entity(entry.entity_id, POLICY_READ): raise Unauthorized(entity_id=entry.entity_id) - devices = await get_media_source_devices(self.hass) + devices = async_get_media_source_devices(self.hass) if not (nest_device := devices.get(device_id)): return self._json_error( f"No Nest Device found for '{device_id}'", HTTPStatus.NOT_FOUND diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index a46af2979f4..6e14100e881 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -15,19 +15,20 @@ from google_nest_sdm.camera_traits import ( StreamingProtocol, ) from google_nest_sdm.device import Device +from google_nest_sdm.device_manager import DeviceManager from google_nest_sdm.exceptions import ApiException 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 +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util.dt import utcnow -from .const import DATA_SUBSCRIBER, DOMAIN +from .const import DATA_DEVICE_MANAGER, DOMAIN from .device_info import NestDeviceInfo _LOGGER = logging.getLogger(__name__) @@ -43,14 +44,7 @@ async def async_setup_sdm_entry( ) -> None: """Set up the cameras.""" - subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] - try: - device_manager = await subscriber.async_get_device_manager() - except ApiException as err: - raise PlatformNotReady from err - - # Fetch initial data so we have data when entities subscribe. - + device_manager: DeviceManager = hass.data[DOMAIN][DATA_DEVICE_MANAGER] entities = [] for device in device_manager.devices.values(): if ( diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index 89048b9f624..8a56f78028b 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import Any, cast from google_nest_sdm.device import Device +from google_nest_sdm.device_manager import DeviceManager from google_nest_sdm.device_traits import FanTrait, TemperatureTrait from google_nest_sdm.exceptions import ApiException from google_nest_sdm.thermostat_traits import ( @@ -30,11 +31,11 @@ from homeassistant.components.climate.const import ( from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant -from homeassistant.exceptions import PlatformNotReady +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DATA_SUBSCRIBER, DOMAIN +from .const import DATA_DEVICE_MANAGER, DOMAIN from .device_info import NestDeviceInfo # Mapping for sdm.devices.traits.ThermostatMode mode field @@ -80,12 +81,7 @@ async def async_setup_sdm_entry( ) -> None: """Set up the client entities.""" - subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] - try: - device_manager = await subscriber.async_get_device_manager() - except ApiException as err: - raise PlatformNotReady from err - + device_manager: DeviceManager = hass.data[DOMAIN][DATA_DEVICE_MANAGER] entities = [] for device in device_manager.devices.values(): if ThermostatHvacTrait.NAME in device.traits: @@ -300,7 +296,10 @@ class ThermostatEntity(ClimateEntity): hvac_mode = HVACMode.OFF api_mode = THERMOSTAT_INV_MODE_MAP[hvac_mode] trait = self._device.traits[ThermostatModeTrait.NAME] - await trait.set_mode(api_mode) + try: + await trait.set_mode(api_mode) + except ApiException as err: + raise HomeAssistantError(f"Error setting HVAC mode: {err}") from err async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" @@ -314,20 +313,26 @@ 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 == HVACMode.HEAT_COOL: - if low_temp and high_temp: - await trait.set_range(low_temp, high_temp) - elif hvac_mode == HVACMode.COOL and temp: - await trait.set_cool(temp) - elif hvac_mode == HVACMode.HEAT and temp: - await trait.set_heat(temp) + try: + 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 == HVACMode.COOL and temp: + await trait.set_cool(temp) + elif hvac_mode == HVACMode.HEAT and temp: + await trait.set_heat(temp) + except ApiException as err: + raise HomeAssistantError(f"Error setting HVAC mode: {err}") from err async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new target preset mode.""" if preset_mode not in self.preset_modes: raise ValueError(f"Unsupported preset_mode '{preset_mode}'") trait = self._device.traits[ThermostatEcoTrait.NAME] - await trait.set_mode(PRESET_INV_MODE_MAP[preset_mode]) + try: + await trait.set_mode(PRESET_INV_MODE_MAP[preset_mode]) + except ApiException as err: + raise HomeAssistantError(f"Error setting HVAC mode: {err}") from err async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" @@ -337,4 +342,7 @@ class ThermostatEntity(ClimateEntity): duration = None if fan_mode != FAN_OFF: duration = MAX_FAN_DURATION - await trait.set_timer(FAN_INV_MODE_MAP[fan_mode], duration=duration) + try: + await trait.set_timer(FAN_INV_MODE_MAP[fan_mode], duration=duration) + except ApiException as err: + raise HomeAssistantError(f"Error setting HVAC mode: {err}") from err diff --git a/homeassistant/components/nest/const.py b/homeassistant/components/nest/const.py index 7aabcfe8d77..bd951756eae 100644 --- a/homeassistant/components/nest/const.py +++ b/homeassistant/components/nest/const.py @@ -3,6 +3,7 @@ DOMAIN = "nest" DATA_SDM = "sdm" DATA_SUBSCRIBER = "subscriber" +DATA_DEVICE_MANAGER = "device_manager" DATA_NEST_CONFIG = "nest_config" WEB_AUTH_DOMAIN = DOMAIN diff --git a/homeassistant/components/nest/device_trigger.py b/homeassistant/components/nest/device_trigger.py index 28eb444b91a..05769a407f2 100644 --- a/homeassistant/components/nest/device_trigger.py +++ b/homeassistant/components/nest/device_trigger.py @@ -1,8 +1,7 @@ """Provides device automations for Nest.""" from __future__ import annotations -from typing import Any - +from google_nest_sdm.device_manager import DeviceManager import voluptuous as vol from homeassistant.components.automation import ( @@ -15,11 +14,11 @@ from homeassistant.components.device_automation.exceptions import ( ) from homeassistant.components.homeassistant.triggers import event as event_trigger from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE -from homeassistant.core import CALLBACK_TYPE, HomeAssistant -from homeassistant.helpers.device_registry import DeviceRegistry +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.typing import ConfigType -from .const import DATA_SUBSCRIBER, DOMAIN +from .const import DATA_DEVICE_MANAGER, DOMAIN from .events import DEVICE_TRAIT_TRIGGER_MAP, NEST_EVENT DEVICE = "device" @@ -33,11 +32,10 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_nest_device_id(hass: HomeAssistant, device_id: str) -> str | None: +@callback +def async_get_nest_device_id(hass: HomeAssistant, device_id: str) -> str | None: """Get the nest API device_id from the HomeAssistant device_id.""" - device_registry: DeviceRegistry = ( - await hass.helpers.device_registry.async_get_registry() - ) + device_registry = dr.async_get(hass) if device := device_registry.async_get(device_id): for (domain, unique_id) in device.identifiers: if domain == DOMAIN: @@ -45,14 +43,12 @@ async def async_get_nest_device_id(hass: HomeAssistant, device_id: str) -> str | return None -async def async_get_device_trigger_types( +@callback +def async_get_device_trigger_types( hass: HomeAssistant, nest_device_id: str ) -> list[str]: """List event triggers supported for a Nest device.""" - # All devices should have already been loaded so any failures here are - # "shouldn't happen" cases - subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] - device_manager = await subscriber.async_get_device_manager() + device_manager: DeviceManager = hass.data[DOMAIN][DATA_DEVICE_MANAGER] if not (nest_device := device_manager.devices.get(nest_device_id)): raise InvalidDeviceAutomationConfig(f"Nest device not found {nest_device_id}") @@ -67,12 +63,12 @@ async def async_get_device_trigger_types( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for a Nest device.""" - nest_device_id = await async_get_nest_device_id(hass, device_id) + nest_device_id = async_get_nest_device_id(hass, device_id) if not nest_device_id: raise InvalidDeviceAutomationConfig(f"Device not found {device_id}") - trigger_types = await async_get_device_trigger_types(hass, nest_device_id) + trigger_types = async_get_device_trigger_types(hass, nest_device_id) return [ { CONF_PLATFORM: DEVICE, diff --git a/homeassistant/components/nest/diagnostics.py b/homeassistant/components/nest/diagnostics.py index d178d52393e..c21842d5939 100644 --- a/homeassistant/components/nest/diagnostics.py +++ b/homeassistant/components/nest/diagnostics.py @@ -6,43 +6,39 @@ from typing import Any from google_nest_sdm import diagnostics from google_nest_sdm.device import Device +from google_nest_sdm.device_manager import DeviceManager 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.core import HomeAssistant, callback from homeassistant.helpers.device_registry import DeviceEntry -from .const import DATA_SDM, DATA_SUBSCRIBER, DOMAIN +from .const import DATA_DEVICE_MANAGER, DATA_SDM, DOMAIN REDACT_DEVICE_TRAITS = {InfoTrait.NAME} -async def _get_nest_devices( +@callback +def _async_get_nest_devices( hass: HomeAssistant, config_entry: ConfigEntry ) -> dict[str, Device]: """Return dict of available devices.""" if DATA_SDM not in config_entry.data: return {} - if DATA_SUBSCRIBER not in hass.data[DOMAIN]: + if DATA_DEVICE_MANAGER not in hass.data[DOMAIN]: return {} - subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] - device_manager = await subscriber.async_get_device_manager() - devices: dict[str, Device] = device_manager.devices - return devices + device_manager: DeviceManager = hass.data[DOMAIN][DATA_DEVICE_MANAGER] + return device_manager.devices async def async_get_config_entry_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry ) -> dict: """Return diagnostics for a config entry.""" - try: - nest_devices = await _get_nest_devices(hass, config_entry) - except ApiException as err: - return {"error": str(err)} + nest_devices = _async_get_nest_devices(hass, config_entry) if not nest_devices: return {} data: dict[str, Any] = { @@ -65,10 +61,7 @@ async def async_get_device_diagnostics( device: DeviceEntry, ) -> dict: """Return diagnostics for a device.""" - try: - nest_devices = await _get_nest_devices(hass, config_entry) - except ApiException as err: - return {"error": str(err)} + nest_devices = _async_get_nest_devices(hass, config_entry) nest_device_id = next(iter(device.identifiers))[1] nest_device = nest_devices.get(nest_device_id) return nest_device.get_diagnostics() if nest_device else {} diff --git a/homeassistant/components/nest/media_source.py b/homeassistant/components/nest/media_source.py index 7a8ae49bdbc..e4e26153b3a 100644 --- a/homeassistant/components/nest/media_source.py +++ b/homeassistant/components/nest/media_source.py @@ -25,6 +25,7 @@ import os from google_nest_sdm.camera_traits import CameraClipPreviewTrait, CameraEventImageTrait from google_nest_sdm.device import Device +from google_nest_sdm.device_manager import DeviceManager from google_nest_sdm.event import EventImageType, ImageEventBase from google_nest_sdm.event_media import ( ClipPreviewSession, @@ -50,13 +51,13 @@ from homeassistant.components.media_source.models import ( MediaSourceItem, PlayMedia, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.storage import Store from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.util import dt as dt_util -from .const import DATA_SUBSCRIBER, DOMAIN +from .const import DATA_DEVICE_MANAGER, DOMAIN from .device_info import NestDeviceInfo from .events import EVENT_NAME_MAP, MEDIA_SOURCE_EVENT_TITLE_MAP @@ -267,13 +268,13 @@ async def async_get_media_source(hass: HomeAssistant) -> MediaSource: return NestMediaSource(hass) -async def get_media_source_devices(hass: HomeAssistant) -> Mapping[str, Device]: +@callback +def async_get_media_source_devices(hass: HomeAssistant) -> Mapping[str, Device]: """Return a mapping of device id to eligible Nest event media devices.""" - if DATA_SUBSCRIBER not in hass.data[DOMAIN]: + if DATA_DEVICE_MANAGER not in hass.data[DOMAIN]: # Integration unloaded, or is legacy nest integration return {} - subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] - device_manager = await subscriber.async_get_device_manager() + device_manager: DeviceManager = hass.data[DOMAIN][DATA_DEVICE_MANAGER] device_registry = dr.async_get(hass) devices = {} for device in device_manager.devices.values(): @@ -339,7 +340,7 @@ class NestMediaSource(MediaSource): media_id: MediaId | None = parse_media_id(item.identifier) if not media_id: raise Unresolvable("No identifier specified for MediaSourceItem") - devices = await self.devices() + devices = async_get_media_source_devices(self.hass) if not (device := devices.get(media_id.device_id)): raise Unresolvable( "Unable to find device with identifier: %s" % item.identifier @@ -376,7 +377,7 @@ class NestMediaSource(MediaSource): _LOGGER.debug( "Browsing media for identifier=%s, media_id=%s", item.identifier, media_id ) - devices = await self.devices() + devices = async_get_media_source_devices(self.hass) if media_id is None: # Browse the root and return child devices browse_root = _browse_root() @@ -443,10 +444,6 @@ class NestMediaSource(MediaSource): ) return _browse_image_event(media_id, device, single_image) - async def devices(self) -> Mapping[str, Device]: - """Return all event media related devices.""" - return await get_media_source_devices(self.hass) - async def _async_get_clip_preview_sessions( device: Device, diff --git a/homeassistant/components/nest/sensor_sdm.py b/homeassistant/components/nest/sensor_sdm.py index b9c13f6b9c7..d33aa3eff8b 100644 --- a/homeassistant/components/nest/sensor_sdm.py +++ b/homeassistant/components/nest/sensor_sdm.py @@ -4,8 +4,8 @@ from __future__ import annotations import logging from google_nest_sdm.device import Device +from google_nest_sdm.device_manager import DeviceManager from google_nest_sdm.device_traits import HumidityTrait, TemperatureTrait -from google_nest_sdm.exceptions import ApiException from homeassistant.components.sensor import ( SensorDeviceClass, @@ -15,10 +15,9 @@ from homeassistant.components.sensor import ( from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TEMP_CELSIUS from homeassistant.core import HomeAssistant -from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DATA_SUBSCRIBER, DOMAIN +from .const import DATA_DEVICE_MANAGER, DOMAIN from .device_info import NestDeviceInfo _LOGGER = logging.getLogger(__name__) @@ -37,13 +36,7 @@ async def async_setup_sdm_entry( ) -> None: """Set up the sensors.""" - subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] - try: - device_manager = await subscriber.async_get_device_manager() - except ApiException as err: - _LOGGER.warning("Failed to get devices: %s", err) - raise PlatformNotReady from err - + device_manager: DeviceManager = hass.data[DOMAIN][DATA_DEVICE_MANAGER] entities: list[SensorEntity] = [] for device in device_manager.devices.values(): if TemperatureTrait.NAME in device.traits: diff --git a/homeassistant/components/nest/translations/es.json b/homeassistant/components/nest/translations/es.json index 2921b0272f8..ea65d2fc78a 100644 --- a/homeassistant/components/nest/translations/es.json +++ b/homeassistant/components/nest/translations/es.json @@ -18,7 +18,7 @@ "invalid_pin": "C\u00f3digo PIN no v\u00e1lido", "subscriber_error": "Error de abonado desconocido, ver registros", "timeout": "Tiempo de espera agotado validando el c\u00f3digo", - "unknown": "Error desconocido validando el c\u00f3digo", + "unknown": "Error inesperado", "wrong_project_id": "Por favor, introduzca un ID de proyecto Cloud v\u00e1lido (encontr\u00f3 el ID de proyecto de acceso al dispositivo)" }, "step": { @@ -33,7 +33,7 @@ "data": { "flow_impl": "Proveedor" }, - "description": "Elija a trav\u00e9s de qu\u00e9 proveedor de autenticaci\u00f3n desea autenticarse con Nest.", + "description": "Selecciona el m\u00e9todo de autenticaci\u00f3n", "title": "Proveedor de autenticaci\u00f3n" }, "link": { diff --git a/homeassistant/components/nest/translations/ja.json b/homeassistant/components/nest/translations/ja.json index 752e847336f..c2994de0532 100644 --- a/homeassistant/components/nest/translations/ja.json +++ b/homeassistant/components/nest/translations/ja.json @@ -54,7 +54,7 @@ "title": "Google Cloud\u306e\u8a2d\u5b9a" }, "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", + "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", "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" } } diff --git a/homeassistant/components/nest/translations/ko.json b/homeassistant/components/nest/translations/ko.json index 048a0c7d100..1b4563c800f 100644 --- a/homeassistant/components/nest/translations/ko.json +++ b/homeassistant/components/nest/translations/ko.json @@ -35,6 +35,10 @@ "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, + "pubsub": { + "description": "[Cloud Console]( {url} )\uc744 \ubc29\ubb38\ud558\uc5ec Google Cloud \ud504\ub85c\uc81d\ud2b8 ID\ub97c \ucc3e\uc73c\uc138\uc694.", + "title": "\uad6c\uae00 \ud074\ub77c\uc6b0\ub4dc \uad6c\uc131" + }, "reauth_confirm": { "description": "Nest \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4", "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" diff --git a/homeassistant/components/nest/translations/nl.json b/homeassistant/components/nest/translations/nl.json index 0e2984972af..a2f8ba77d78 100644 --- a/homeassistant/components/nest/translations/nl.json +++ b/homeassistant/components/nest/translations/nl.json @@ -1,21 +1,21 @@ { "config": { "abort": { - "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", "invalid_access_token": "Ongeldig toegangstoken", - "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})", - "reauth_successful": "Herauthenticatie was succesvol", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", + "reauth_successful": "Herauthenticatie geslaagd", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "error": { "bad_project_id": "Voer een geldige Cloud Project ID in (controleer Cloud Console)", "internal_error": "Interne foutvalidatiecode", - "invalid_pin": "Ongeldige PIN-code", + "invalid_pin": "Ongeldige Pincode", "subscriber_error": "Onbekende abonneefout, zie logs", "timeout": "Time-out validatie van code", "unknown": "Onverwachte fout", @@ -38,7 +38,7 @@ }, "link": { "data": { - "code": "PIN-code" + "code": "Pincode" }, "description": "Als je je Nest-account wilt koppelen, [autoriseer je account] ( {url} ). \n\nNa autorisatie, kopieer en plak de voorziene pincode hieronder.", "title": "Koppel Nest-account" @@ -55,7 +55,7 @@ }, "reauth_confirm": { "description": "De Nest-integratie moet uw account opnieuw verifi\u00ebren", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" } } }, diff --git a/homeassistant/components/nest/translations/sk.json b/homeassistant/components/nest/translations/sk.json index 029432864bf..43b57c320d4 100644 --- a/homeassistant/components/nest/translations/sk.json +++ b/homeassistant/components/nest/translations/sk.json @@ -6,6 +6,9 @@ "create_entry": { "default": "\u00daspe\u0161ne overen\u00e9" }, + "error": { + "invalid_pin": "Nespr\u00e1vny PIN" + }, "step": { "auth": { "data": { diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index f6e43b29653..a3a9d22e017 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -11,6 +11,10 @@ import pyatmo import voluptuous as vol from homeassistant.components import cloud +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.components.webhook import ( async_generate_url as webhook_generate_url, async_register as webhook_register, @@ -35,7 +39,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later from homeassistant.helpers.typing import ConfigType -from . import api, config_flow +from . import api from .const import ( AUTH, CONF_CLOUDHOOK_URL, @@ -48,8 +52,6 @@ from .const import ( DATA_SCHEDULES, DOMAIN, NETATMO_SCOPES, - OAUTH2_AUTHORIZE, - OAUTH2_TOKEN, PLATFORMS, WEBHOOK_DEACTIVATION, WEBHOOK_PUSH_TYPE, @@ -60,14 +62,17 @@ from .webhook import async_handle_webhook _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, - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) @@ -88,17 +93,21 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if DOMAIN not in config: return True - config_flow.NetatmoFlowHandler.async_register_implementation( + await async_import_client_credential( hass, - config_entry_oauth2_flow.LocalOAuth2Implementation( - hass, - DOMAIN, + DOMAIN, + ClientCredential( config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET], - OAUTH2_AUTHORIZE, - OAUTH2_TOKEN, ), ) + _LOGGER.warning( + "Configuration of Netatmo integration in YAML is deprecated and " + "will be removed in a future release; Your existing configuration " + "(including OAuth Application Credentials) have been imported into " + "the UI automatically and can be safely removed from your " + "configuration.yaml file" + ) return True diff --git a/homeassistant/components/netatmo/application_credentials.py b/homeassistant/components/netatmo/application_credentials.py new file mode 100644 index 00000000000..5536343ebe6 --- /dev/null +++ b/homeassistant/components/netatmo/application_credentials.py @@ -0,0 +1,14 @@ +"""Application credentials platform for Netatmo.""" + +from homeassistant.components.application_credentials import AuthorizationServer +from homeassistant.core import HomeAssistant + +from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN + + +async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer: + """Return authorization server.""" + return AuthorizationServer( + authorize_url=OAUTH2_AUTHORIZE, + token_url=OAUTH2_TOKEN, + ) diff --git a/homeassistant/components/netatmo/device_trigger.py b/homeassistant/components/netatmo/device_trigger.py index e3173fde160..25f76307b5f 100644 --- a/homeassistant/components/netatmo/device_trigger.py +++ b/homeassistant/components/netatmo/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for Netatmo.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -23,7 +21,11 @@ from homeassistant.const import ( CONF_TYPE, ) from homeassistant.core import CALLBACK_TYPE, HomeAssistant -from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + entity_registry, +) from homeassistant.helpers.typing import ConfigType from .climate import STATE_NETATMO_AWAY, STATE_NETATMO_HG, STATE_NETATMO_SCHEDULE @@ -74,9 +76,14 @@ async def async_validate_trigger_config( """Validate config.""" config = TRIGGER_SCHEMA(config) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(config[CONF_DEVICE_ID]) + if not device: + raise InvalidDeviceAutomationConfig( + f"Trigger invalid, device with ID {config[CONF_DEVICE_ID]} not found" + ) + trigger = config[CONF_TYPE] if ( @@ -91,14 +98,17 @@ async def async_validate_trigger_config( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Netatmo devices.""" - registry = await entity_registry.async_get_registry(hass) - device_registry = await hass.helpers.device_registry.async_get_registry() + registry = entity_registry.async_get(hass) + device_registry = dr.async_get(hass) triggers = [] for entry in entity_registry.async_entries_for_device(registry, device_id): - device = device_registry.async_get(device_id) + if ( + device := device_registry.async_get(device_id) + ) is None or device.model is None: + continue for trigger in DEVICES.get(device.model, []): if trigger in SUBTYPES: @@ -134,7 +144,7 @@ async def async_attach_trigger( automation_info: AutomationTriggerInfo, ) -> CALLBACK_TYPE: """Attach a trigger.""" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(config[CONF_DEVICE_ID]) if not device: diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index 58b1a0d4f43..7e078153a8a 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -6,7 +6,7 @@ from typing import Any, cast import pyatmo -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.exceptions import PlatformNotReady @@ -62,6 +62,9 @@ async def async_setup_entry( class NetatmoLight(NetatmoBase, LightEntity): """Representation of a Netatmo Presence camera light.""" + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + def __init__( self, data_handler: NetatmoDataHandler, diff --git a/homeassistant/components/netatmo/manifest.json b/homeassistant/components/netatmo/manifest.json index 565bd42594a..2081f9bd274 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": ["auth", "webhook"], + "dependencies": ["application_credentials", "webhook"], "codeowners": ["@cgtobi"], "config_flow": true, "homekit": { diff --git a/homeassistant/components/netatmo/netatmo_entity_base.py b/homeassistant/components/netatmo/netatmo_entity_base.py index 56f25e04906..decedbbdfbd 100644 --- a/homeassistant/components/netatmo/netatmo_entity_base.py +++ b/homeassistant/components/netatmo/netatmo_entity_base.py @@ -3,6 +3,7 @@ from __future__ import annotations from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import callback +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import DeviceInfo, Entity from .const import ( @@ -65,9 +66,9 @@ class NetatmoBase(Entity): if sub is None: await self.data_handler.unregister_data_class(signal_name, None) - registry = await self.hass.helpers.device_registry.async_get_registry() - device = registry.async_get_device({(DOMAIN, self._id)}) - self.hass.data[DOMAIN][DATA_DEVICE_IDS][self._id] = device.id + registry = dr.async_get(self.hass) + if device := registry.async_get_device({(DOMAIN, self._id)}): + self.hass.data[DOMAIN][DATA_DEVICE_IDS][self._id] = device.id self.async_update_callback() diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 41ae27b2992..217b2146cc9 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -29,7 +29,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.device_registry import async_entries_for_config_entry +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -393,13 +393,13 @@ async def async_setup_entry( async_add_entities(await find_entities(data_class_name), True) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) async def add_public_entities(update: bool = True) -> None: """Retrieve Netatmo public weather entities.""" entities = { device.name: device.id - for device in async_entries_for_config_entry( + for device in dr.async_entries_for_config_entry( device_registry, entry.entry_id ) if device.model == "Public Weather stations" diff --git a/homeassistant/components/netatmo/translations/es.json b/homeassistant/components/netatmo/translations/es.json index 15b67d4b32b..dd9fcf18d60 100644 --- a/homeassistant/components/netatmo/translations/es.json +++ b/homeassistant/components/netatmo/translations/es.json @@ -47,9 +47,9 @@ "public_weather": { "data": { "area_name": "Nombre del \u00e1rea", - "lat_ne": "Latitud esquina Noreste", - "lat_sw": "Latitud esquina Suroeste", - "lon_ne": "Longitud esquina Noreste", + "lat_ne": "Longitud esquina noreste", + "lat_sw": "Latitud esquina suroeste", + "lon_ne": "Longitud esquina noreste", "lon_sw": "Longitud esquina Suroeste", "mode": "C\u00e1lculo", "show_on_map": "Mostrar en el mapa" diff --git a/homeassistant/components/netatmo/translations/ja.json b/homeassistant/components/netatmo/translations/ja.json index ecb21ffe039..b9f2f17960a 100644 --- a/homeassistant/components/netatmo/translations/ja.json +++ b/homeassistant/components/netatmo/translations/ja.json @@ -15,7 +15,7 @@ "title": "\u8a8d\u8a3c\u65b9\u6cd5\u306e\u9078\u629e" }, "reauth_confirm": { - "description": "Netatmo\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", + "description": "Netatmo\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", "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" } } diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index 1a740f9d5b1..80e3379b321 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -1,22 +1,22 @@ { "config": { "abort": { - "authorize_url_timeout": "Time-out genereren autorisatie-URL.", - "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", - "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", - "reauth_successful": "Herauthenticatie was succesvol", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", + "reauth_successful": "Herauthenticatie geslaagd", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "step": { "pick_implementation": { - "title": "Kies de verificatiemethode" + "title": "Kies een authenticatie methode" }, "reauth_confirm": { "description": "De Netatmo-integratie moet uw account opnieuw verifi\u00ebren", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" } } }, @@ -59,7 +59,7 @@ }, "public_weather_areas": { "data": { - "new_area": "Naam van het gebied", + "new_area": "Naam van ruimte", "weather_areas": "Weersgebieden" }, "description": "Configureer openbare weersensoren.", diff --git a/homeassistant/components/netatmo/translations/pt-BR.json b/homeassistant/components/netatmo/translations/pt-BR.json index b47c0ea3646..9f438868d43 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": "[%key:component::iss::config::step::user::data::show_on_map%]" }, "description": "Configure um sensor meteorol\u00f3gico p\u00fablico para uma \u00e1rea.", "title": "Sensor meteorol\u00f3gico p\u00fablico Netatmo" diff --git a/homeassistant/components/netatmo/translations/tr.json b/homeassistant/components/netatmo/translations/tr.json index d40de61e36a..807497e9d60 100644 --- a/homeassistant/components/netatmo/translations/tr.json +++ b/homeassistant/components/netatmo/translations/tr.json @@ -60,7 +60,7 @@ "public_weather_areas": { "data": { "new_area": "Alan ad\u0131", - "weather_areas": "Hava alanlar\u0131" + "weather_areas": "Hava b\u00f6lgeleri" }, "description": "Genel hava durumu sens\u00f6rlerini yap\u0131land\u0131r\u0131n.", "title": "Netatmo genel hava durumu sens\u00f6r\u00fc" diff --git a/homeassistant/components/netgear/__init__.py b/homeassistant/components/netgear/__init__.py index 72a56427e17..7a4d0e7a8cd 100644 --- a/homeassistant/components/netgear/__init__.py +++ b/homeassistant/components/netgear/__init__.py @@ -9,14 +9,16 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( DOMAIN, KEY_COORDINATOR, + KEY_COORDINATOR_SPEED, KEY_COORDINATOR_TRAFFIC, KEY_ROUTER, + MODE_ROUTER, PLATFORMS, ) from .errors import CannotLoginException @@ -25,6 +27,7 @@ from .router import NetgearRouter _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=30) +SPEED_TEST_INTERVAL = timedelta(seconds=1800) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -69,12 +72,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_update_devices() -> bool: """Fetch data from the router.""" - return await router.async_update_device_trackers() + if router.mode == MODE_ROUTER: + return await router.async_update_device_trackers() + return False async def async_update_traffic_meter() -> dict[str, Any] | None: """Fetch data from the router.""" return await router.async_get_traffic_meter() + async def async_update_speed_test() -> dict[str, Any] | None: + """Fetch data from the router.""" + return await router.async_get_speed_test() + # Create update coordinators coordinator = DataUpdateCoordinator( hass, @@ -90,14 +99,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: update_method=async_update_traffic_meter, update_interval=SCAN_INTERVAL, ) + coordinator_speed_test = DataUpdateCoordinator( + hass, + _LOGGER, + name=f"{router.device_name} Speed test", + update_method=async_update_speed_test, + update_interval=SPEED_TEST_INTERVAL, + ) - await coordinator.async_config_entry_first_refresh() + if router.mode == MODE_ROUTER: + await coordinator.async_config_entry_first_refresh() await coordinator_traffic_meter.async_config_entry_first_refresh() hass.data[DOMAIN][entry.entry_id] = { KEY_ROUTER: router, KEY_COORDINATOR: coordinator, KEY_COORDINATOR_TRAFFIC: coordinator_traffic_meter, + KEY_COORDINATOR_SPEED: coordinator_speed_test, } hass.config_entries.async_setup_platforms(entry, PLATFORMS) @@ -109,11 +127,32 @@ 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) + router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER] + if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) + if router.mode != MODE_ROUTER: + router_id = None + # Remove devices that are no longer tracked + device_registry = dr.async_get(hass) + devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id) + for device_entry in devices: + if device_entry.via_device_id is None: + router_id = device_entry.id + continue # do not remove the router itself + device_registry.async_update_device( + device_entry.id, remove_config_entry_id=entry.entry_id + ) + # Remove entities that are no longer tracked + entity_registry = er.async_get(hass) + entries = er.async_entries_for_config_entry(entity_registry, entry.entry_id) + for entity_entry in entries: + if entity_entry.device_id is not router_id: + entity_registry.async_remove(entity_entry.entity_id) + return unload_ok diff --git a/homeassistant/components/netgear/config_flow.py b/homeassistant/components/netgear/config_flow.py index 79053c712fc..9024d0510dd 100644 --- a/homeassistant/components/netgear/config_flow.py +++ b/homeassistant/components/netgear/config_flow.py @@ -191,11 +191,6 @@ class NetgearFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if errors: return await self._show_setup_form(user_input, errors) - # Check if already configured - info = await self.hass.async_add_executor_job(api.get_info) - await self.async_set_unique_id(info["SerialNumber"], raise_on_progress=False) - self._abort_if_unique_id_configured() - config_data = { CONF_USERNAME: username, CONF_PASSWORD: password, @@ -204,6 +199,11 @@ class NetgearFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONF_SSL: api.ssl, } + # Check if already configured + info = await self.hass.async_add_executor_job(api.get_info) + await self.async_set_unique_id(info["SerialNumber"], raise_on_progress=False) + self._abort_if_unique_id_configured(updates=config_data) + if info.get("ModelName") is not None and info.get("DeviceName") is not None: name = f"{info['ModelName']} - {info['DeviceName']}" else: diff --git a/homeassistant/components/netgear/const.py b/homeassistant/components/netgear/const.py index f2e0263a4e4..936777a7961 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -12,10 +12,14 @@ CONF_CONSIDER_HOME = "consider_home" KEY_ROUTER = "router" KEY_COORDINATOR = "coordinator" KEY_COORDINATOR_TRAFFIC = "coordinator_traffic" +KEY_COORDINATOR_SPEED = "coordinator_speed" DEFAULT_CONSIDER_HOME = timedelta(seconds=180) DEFAULT_NAME = "Netgear router" +MODE_ROUTER = "0" +MODE_AP = "1" + # models using port 80 instead of 5000 MODELS_PORT_80 = [ "Orbi", diff --git a/homeassistant/components/netgear/router.py b/homeassistant/components/netgear/router.py index f543ba8a5f3..301906f22b6 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -32,6 +32,7 @@ from .const import ( DEFAULT_CONSIDER_HOME, DEFAULT_NAME, DOMAIN, + MODE_ROUTER, MODELS_V2, ) from .errors import CannotLoginException @@ -73,6 +74,7 @@ class NetgearRouter: self._info = None self.model = "" + self.mode = MODE_ROUTER self.device_name = "" self.firmware_version = "" self.hardware_version = "" @@ -108,12 +110,13 @@ class NetgearRouter: self.firmware_version = self._info.get("Firmwareversion") self.hardware_version = self._info.get("Hardwareversion") self.serial_number = self._info["SerialNumber"] + self.mode = self._info.get("DeviceMode", MODE_ROUTER) for model in MODELS_V2: if self.model.startswith(model): self.method_version = 2 - if self.method_version == 2: + if self.method_version == 2 and self.mode == MODE_ROUTER: if not self._api.get_attached_devices_2(): _LOGGER.error( "Netgear Model '%s' in MODELS_V2 list, but failed to get attached devices using V2", @@ -130,28 +133,29 @@ class NetgearRouter: return False # set already known devices to away instead of unavailable - device_registry = dr.async_get(self.hass) - devices = dr.async_entries_for_config_entry(device_registry, self.entry_id) - for device_entry in devices: - if device_entry.via_device_id is None: - continue # do not add the router itself + if self.mode == MODE_ROUTER: + device_registry = dr.async_get(self.hass) + devices = dr.async_entries_for_config_entry(device_registry, self.entry_id) + for device_entry in devices: + if device_entry.via_device_id is None: + continue # do not add the router itself - device_mac = dict(device_entry.connections)[dr.CONNECTION_NETWORK_MAC] - self.devices[device_mac] = { - "mac": device_mac, - "name": device_entry.name, - "active": False, - "last_seen": dt_util.utcnow() - timedelta(days=365), - "device_model": None, - "device_type": None, - "type": None, - "link_rate": None, - "signal": None, - "ip": None, - "ssid": None, - "conn_ap_mac": None, - "allow_or_block": None, - } + device_mac = dict(device_entry.connections)[dr.CONNECTION_NETWORK_MAC] + self.devices[device_mac] = { + "mac": device_mac, + "name": device_entry.name, + "active": False, + "last_seen": dt_util.utcnow() - timedelta(days=365), + "device_model": None, + "device_type": None, + "type": None, + "link_rate": None, + "signal": None, + "ip": None, + "ssid": None, + "conn_ap_mac": None, + "allow_or_block": None, + } return True @@ -204,6 +208,13 @@ class NetgearRouter: async with self._api_lock: return await self.hass.async_add_executor_job(self._api.get_traffic_meter) + async def async_get_speed_test(self) -> dict[str, Any] | None: + """Perform a speed test and get the results from the router.""" + async with self._api_lock: + return await self.hass.async_add_executor_job( + self._api.get_new_speed_test_result + ) + async def async_allow_block_device(self, mac: str, allow_block: str) -> None: """Allow or block a device connected to the router.""" async with self._api_lock: diff --git a/homeassistant/components/netgear/sensor.py b/homeassistant/components/netgear/sensor.py index 16c63a8cdcb..9dec1ab3390 100644 --- a/homeassistant/components/netgear/sensor.py +++ b/homeassistant/components/netgear/sensor.py @@ -1,20 +1,37 @@ """Support for Netgear routers.""" +from __future__ import annotations + from collections.abc import Callable from dataclasses import dataclass +from datetime import date, datetime +from decimal import Decimal from homeassistant.components.sensor import ( + RestoreSensor, SensorDeviceClass, SensorEntity, SensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import DATA_MEGABYTES, PERCENTAGE +from homeassistant.const import ( + DATA_MEGABYTES, + DATA_RATE_MEGABITS_PER_SECOND, + PERCENTAGE, + TIME_MILLISECONDS, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DOMAIN, KEY_COORDINATOR, KEY_COORDINATOR_TRAFFIC, KEY_ROUTER +from .const import ( + DOMAIN, + KEY_COORDINATOR, + KEY_COORDINATOR_SPEED, + KEY_COORDINATOR_TRAFFIC, + KEY_ROUTER, +) from .router import NetgearDeviceEntity, NetgearRouter, NetgearRouterEntity SENSOR_TYPES = { @@ -200,6 +217,30 @@ SENSOR_TRAFFIC_TYPES = [ ), ] +SENSOR_SPEED_TYPES = [ + NetgearSensorEntityDescription( + key="NewOOKLAUplinkBandwidth", + name="Uplink Bandwidth", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_RATE_MEGABITS_PER_SECOND, + icon="mdi:upload", + ), + NetgearSensorEntityDescription( + key="NewOOKLADownlinkBandwidth", + name="Downlink Bandwidth", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=DATA_RATE_MEGABITS_PER_SECOND, + icon="mdi:download", + ), + NetgearSensorEntityDescription( + key="AveragePing", + name="Average Ping", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=TIME_MILLISECONDS, + icon="mdi:wan", + ), +] + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -208,6 +249,7 @@ async def async_setup_entry( router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER] coordinator = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR] coordinator_traffic = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR_TRAFFIC] + coordinator_speed = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR_SPEED] # Router entities router_entities = [] @@ -217,6 +259,11 @@ async def async_setup_entry( NetgearRouterSensorEntity(coordinator_traffic, router, description) ) + for description in SENSOR_SPEED_TYPES: + router_entities.append( + NetgearRouterSensorEntity(coordinator_speed, router, description) + ) + async_add_entities(router_entities) # Entities per network device @@ -288,7 +335,7 @@ class NetgearSensorEntity(NetgearDeviceEntity, SensorEntity): self._state = self._device[self._attribute] -class NetgearRouterSensorEntity(NetgearRouterEntity, SensorEntity): +class NetgearRouterSensorEntity(NetgearRouterEntity, RestoreSensor): """Representation of a device connected to a Netgear router.""" _attr_entity_registry_enabled_default = False @@ -306,7 +353,7 @@ class NetgearRouterSensorEntity(NetgearRouterEntity, SensorEntity): self._name = f"{router.device_name} {entity_description.name}" self._unique_id = f"{router.serial_number}-{entity_description.key}-{entity_description.index}" - self._value = None + self._value: StateType | date | datetime | Decimal = None self.async_update_device() @property @@ -314,6 +361,14 @@ class NetgearRouterSensorEntity(NetgearRouterEntity, SensorEntity): """Return the state of the sensor.""" return self._value + async def async_added_to_hass(self) -> None: + """Handle entity which will be added.""" + await super().async_added_to_hass() + if self.coordinator.data is None: + sensor_data = await self.async_get_last_sensor_data() + if sensor_data is not None: + self._value = sensor_data.native_value + @callback def async_update_device(self) -> None: """Update the Netgear device.""" diff --git a/homeassistant/components/netgear/translations/bg.json b/homeassistant/components/netgear/translations/bg.json index dd5e4fa2f38..17bc7cd1666 100644 --- a/homeassistant/components/netgear/translations/bg.json +++ b/homeassistant/components/netgear/translations/bg.json @@ -11,18 +11,9 @@ "data": { "host": "\u0425\u043e\u0441\u0442 (\u043f\u043e \u0438\u0437\u0431\u043e\u0440)", "password": "\u041f\u0430\u0440\u043e\u043b\u0430", - "port": "\u041f\u043e\u0440\u0442 (\u043f\u043e \u0438\u0437\u0431\u043e\u0440)", "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435 (\u043f\u043e \u0438\u0437\u0431\u043e\u0440)" }, - "description": "\u0425\u043e\u0441\u0442 \u043f\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043d\u0435: {host}\n\u041f\u043e\u0440\u0442 \u043f\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043d\u0435: {port}\n\u041f\u043e\u0442\u0440. \u0438\u043c\u0435 \u043f\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043d\u0435: {username}", - "title": "Netgear" - } - } - }, - "options": { - "step": { - "init": { - "title": "Netgear" + "description": "\u0425\u043e\u0441\u0442 \u043f\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043d\u0435: {host}\n\u041f\u043e\u0440\u0442 \u043f\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043d\u0435: {port}\n\u041f\u043e\u0442\u0440. \u0438\u043c\u0435 \u043f\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043d\u0435: {username}" } } } diff --git a/homeassistant/components/netgear/translations/ca.json b/homeassistant/components/netgear/translations/ca.json index 6d5edbd8e1f..f89e68cdfce 100644 --- a/homeassistant/components/netgear/translations/ca.json +++ b/homeassistant/components/netgear/translations/ca.json @@ -11,12 +11,9 @@ "data": { "host": "Amfitri\u00f3 (opcional)", "password": "Contrasenya", - "port": "Port (opcional)", - "ssl": "Utilitza un certificat SSL", "username": "Nom d'usuari (opcional)" }, - "description": "Amfitri\u00f3 predeterminat: {host}\nNom d'usuari predeterminat: {username}", - "title": "Netgear" + "description": "Amfitri\u00f3 predeterminat: {host}\nNom d'usuari predeterminat: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Temps per considerar 'a casa' (segons)" }, - "description": "Especifica les configuracions opcional", - "title": "Netgear" + "description": "Especifica les configuracions opcional" } } } diff --git a/homeassistant/components/netgear/translations/cs.json b/homeassistant/components/netgear/translations/cs.json index 6d942ff2ff4..67e2611aa87 100644 --- a/homeassistant/components/netgear/translations/cs.json +++ b/homeassistant/components/netgear/translations/cs.json @@ -8,8 +8,6 @@ "data": { "host": "Hostitel (nepovinn\u00fd)", "password": "Heslo", - "port": "Port (nepovinn\u00fd)", - "ssl": "Pou\u017e\u00edv\u00e1 SSL certifik\u00e1t", "username": "U\u017eivatelsk\u00e9 jm\u00e9no (nepovinn\u00e9)" } } diff --git a/homeassistant/components/netgear/translations/de.json b/homeassistant/components/netgear/translations/de.json index b742fb36ec5..bbfadbb8254 100644 --- a/homeassistant/components/netgear/translations/de.json +++ b/homeassistant/components/netgear/translations/de.json @@ -11,12 +11,9 @@ "data": { "host": "Host (Optional)", "password": "Passwort", - "port": "Port (Optional)", - "ssl": "Verwendet ein SSL-Zertifikat", "username": "Benutzername (Optional)" }, - "description": "Standardhost: {host}\nStandardbenutzername: {username}", - "title": "Netgear" + "description": "Standardhost: {host}\nStandardbenutzername: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Zu Hause Zeit (Sekunden)" }, - "description": "Optionale Einstellungen angeben", - "title": "Netgear" + "description": "Optionale Einstellungen angeben" } } } diff --git a/homeassistant/components/netgear/translations/el.json b/homeassistant/components/netgear/translations/el.json index 0ac5cb328bc..51f982da008 100644 --- a/homeassistant/components/netgear/translations/el.json +++ b/homeassistant/components/netgear/translations/el.json @@ -11,12 +11,9 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "port": "\u0398\u03cd\u03c1\u03b1 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", - "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" }, - "description": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2: {host}\n \u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7: {username}", - "title": "Netgear" + "description": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2: {host}\n \u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "\u0395\u03be\u03b5\u03c4\u03ac\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ce\u03c1\u03b1 \u03c3\u03c4\u03bf \u03c3\u03c0\u03af\u03c4\u03b9 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" }, - "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ad\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2", - "title": "Netgear" + "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ad\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2" } } } diff --git a/homeassistant/components/netgear/translations/en.json b/homeassistant/components/netgear/translations/en.json index 42b014d9ed3..82712b7c738 100644 --- a/homeassistant/components/netgear/translations/en.json +++ b/homeassistant/components/netgear/translations/en.json @@ -11,12 +11,9 @@ "data": { "host": "Host (Optional)", "password": "Password", - "port": "Port (Optional)", - "ssl": "Uses an SSL certificate", "username": "Username (Optional)" }, - "description": "Default host: {host}\nDefault username: {username}", - "title": "Netgear" + "description": "Default host: {host}\nDefault username: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Consider home time (seconds)" }, - "description": "Specify optional settings", - "title": "Netgear" + "description": "Specify optional settings" } } } diff --git a/homeassistant/components/netgear/translations/es.json b/homeassistant/components/netgear/translations/es.json index 7aadc51efb1..9edbd1101d0 100644 --- a/homeassistant/components/netgear/translations/es.json +++ b/homeassistant/components/netgear/translations/es.json @@ -11,12 +11,9 @@ "data": { "host": "Host (Opcional)", "password": "Contrase\u00f1a", - "port": "Puerto (Opcional)", - "ssl": "Utiliza un certificado SSL", "username": "Usuario (Opcional)" }, - "description": "Host predeterminado: {host}\nPuerto predeterminado: {port}\nNombre de usuario predeterminado: {username}", - "title": "Netgear" + "description": "Host predeterminado: {host} \nNombre de usuario predeterminado: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Considere el tiempo en casa (segundos)" }, - "description": "Especifica los ajustes opcionales", - "title": "Netgear" + "description": "Especifica los ajustes opcionales" } } } diff --git a/homeassistant/components/netgear/translations/et.json b/homeassistant/components/netgear/translations/et.json index dcbaf4bcb2e..56b999fdbef 100644 --- a/homeassistant/components/netgear/translations/et.json +++ b/homeassistant/components/netgear/translations/et.json @@ -11,12 +11,9 @@ "data": { "host": "Host (valikuline)", "password": "Salas\u00f5na", - "port": "Port (valikuline)", - "ssl": "Kasutusel on SSL sert", "username": "Kasutajanimi (valikuline)" }, - "description": "Vaikimisi host: {host}\nVaikimisi kasutajanimi: {username}", - "title": "Netgear" + "description": "Vaikimisi host: {host}\nVaikimisi kasutajanimi: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Kohaloleku m\u00e4\u00e4ramise aeg (sekundites)" }, - "description": "Valikuliste s\u00e4tete m\u00e4\u00e4ramine", - "title": "Netgear" + "description": "Valikuliste s\u00e4tete m\u00e4\u00e4ramine" } } } diff --git a/homeassistant/components/netgear/translations/fr.json b/homeassistant/components/netgear/translations/fr.json index fe507462e8d..a86df352ff6 100644 --- a/homeassistant/components/netgear/translations/fr.json +++ b/homeassistant/components/netgear/translations/fr.json @@ -11,12 +11,9 @@ "data": { "host": "H\u00f4te (facultatif)", "password": "Mot de passe", - "port": "Port (facultatif)", - "ssl": "Utilise un certificat SSL", "username": "Nom d'utilisateur (facultatif)" }, - "description": "H\u00f4te par d\u00e9faut\u00a0: {host}\nNom d'utilisateur par d\u00e9faut\u00a0: {username}", - "title": "Netgear" + "description": "H\u00f4te par d\u00e9faut\u00a0: {host}\nNom d'utilisateur par d\u00e9faut\u00a0: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Consid\u00e9rez le temps pass\u00e9 \u00e0 la maison (secondes)" }, - "description": "Sp\u00e9cifiez les param\u00e8tres optionnels", - "title": "Netgear" + "description": "Sp\u00e9cifiez les param\u00e8tres optionnels" } } } diff --git a/homeassistant/components/netgear/translations/he.json b/homeassistant/components/netgear/translations/he.json index f1f42b6c771..e1aa083fcb8 100644 --- a/homeassistant/components/netgear/translations/he.json +++ b/homeassistant/components/netgear/translations/he.json @@ -8,18 +8,8 @@ "data": { "host": "\u05de\u05d0\u05e8\u05d7 (\u05d0\u05d5\u05e4\u05e6\u05d9\u05d5\u05e0\u05dc\u05d9)", "password": "\u05e1\u05d9\u05e1\u05de\u05d4", - "port": "\u05e4\u05ea\u05d7\u05d4 (\u05d0\u05d5\u05e4\u05e6\u05d9\u05d5\u05e0\u05dc\u05d9)", - "ssl": "\u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05d0\u05d9\u05e9\u05d5\u05e8 SSL", "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9 (\u05d0\u05d5\u05e4\u05e6\u05d9\u05d5\u05e0\u05dc\u05d9)" - }, - "title": "Netgear" - } - } - }, - "options": { - "step": { - "init": { - "title": "Netgear" + } } } } diff --git a/homeassistant/components/netgear/translations/hu.json b/homeassistant/components/netgear/translations/hu.json index cf8d31ec389..4432e08a508 100644 --- a/homeassistant/components/netgear/translations/hu.json +++ b/homeassistant/components/netgear/translations/hu.json @@ -11,12 +11,9 @@ "data": { "host": "C\u00edm (nem k\u00f6telez\u0151)", "password": "Jelsz\u00f3", - "port": "Port (nem k\u00f6telez\u0151)", - "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", "username": "Felhaszn\u00e1l\u00f3n\u00e9v (nem k\u00f6telez\u0151)" }, - "description": "Alap\u00e9rtelmezett c\u00edm: {host}\nAlap\u00e9rtelmezett felhaszn\u00e1l\u00f3n\u00e9v: {username}", - "title": "Netgear" + "description": "Alap\u00e9rtelmezett c\u00edm: {host}\nAlap\u00e9rtelmezett felhaszn\u00e1l\u00f3n\u00e9v: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Otthoni \u00e1llapotnak tekint\u00e9s (m\u00e1sodperc)" }, - "description": "Opcion\u00e1lis be\u00e1ll\u00edt\u00e1sok megad\u00e1sa", - "title": "Netgear" + "description": "Opcion\u00e1lis be\u00e1ll\u00edt\u00e1sok megad\u00e1sa" } } } diff --git a/homeassistant/components/netgear/translations/id.json b/homeassistant/components/netgear/translations/id.json index 7f3eb3a0796..589459e180b 100644 --- a/homeassistant/components/netgear/translations/id.json +++ b/homeassistant/components/netgear/translations/id.json @@ -11,12 +11,9 @@ "data": { "host": "Host (Opsional)", "password": "Kata Sandi", - "port": "Port (Opsional)", - "ssl": "Menggunakan sertifikat SSL", "username": "Nama Pengguna (Opsional)" }, - "description": "Host default: {host}\nNama pengguna default: {username}", - "title": "Netgear" + "description": "Host default: {host}\nNama pengguna default: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Pertimbangan waktu sebagai di rumah (detik)" }, - "description": "Tentukan pengaturan opsional", - "title": "Netgear" + "description": "Tentukan pengaturan opsional" } } } diff --git a/homeassistant/components/netgear/translations/it.json b/homeassistant/components/netgear/translations/it.json index 15237e3a16a..28dc49326d2 100644 --- a/homeassistant/components/netgear/translations/it.json +++ b/homeassistant/components/netgear/translations/it.json @@ -11,12 +11,9 @@ "data": { "host": "Host (Facoltativo)", "password": "Password", - "port": "Porta (Facoltativo)", - "ssl": "Utilizza un certificato SSL", "username": "Nome utente (Facoltativo)" }, - "description": "Host predefinito: {host}\nNome utente predefinito: {username}", - "title": "Netgear" + "description": "Host predefinito: {host}\nNome utente predefinito: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Considera il tempo in casa (secondi)" }, - "description": "Specifica le impostazioni opzionali", - "title": "Netgear" + "description": "Specifica le impostazioni opzionali" } } } diff --git a/homeassistant/components/netgear/translations/ja.json b/homeassistant/components/netgear/translations/ja.json index a1d795bca9f..4402afcb92d 100644 --- a/homeassistant/components/netgear/translations/ja.json +++ b/homeassistant/components/netgear/translations/ja.json @@ -11,12 +11,9 @@ "data": { "host": "\u30db\u30b9\u30c8(\u30aa\u30d7\u30b7\u30e7\u30f3)", "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", - "port": "\u30dd\u30fc\u30c8(\u30aa\u30d7\u30b7\u30e7\u30f3)", - "ssl": "SSL\u8a3c\u660e\u66f8\u3092\u4f7f\u7528\u3059\u308b", "username": "\u30e6\u30fc\u30b6\u30fc\u540d(\u30aa\u30d7\u30b7\u30e7\u30f3)" }, - "description": "\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u30db\u30b9\u30c8: {host}\n\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u30dd\u30fc\u30c8: {port}\n\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u30e6\u30fc\u30b6\u30fc\u540d: {username}", - "title": "Netgear" + "description": "\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u30db\u30b9\u30c8: {host}\n\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u30dd\u30fc\u30c8: {port}\n\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u30e6\u30fc\u30b6\u30fc\u540d: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "\u30db\u30fc\u30e0\u30bf\u30a4\u30e0\u3092\u8003\u616e\u3059\u308b(\u79d2)" }, - "description": "\u30aa\u30d7\u30b7\u30e7\u30f3\u8a2d\u5b9a\u306e\u6307\u5b9a", - "title": "Netgear" + "description": "\u30aa\u30d7\u30b7\u30e7\u30f3\u8a2d\u5b9a\u306e\u6307\u5b9a" } } } diff --git a/homeassistant/components/netgear/translations/nl.json b/homeassistant/components/netgear/translations/nl.json index 1d0ae357a8f..8a613738ffd 100644 --- a/homeassistant/components/netgear/translations/nl.json +++ b/homeassistant/components/netgear/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Apparaat is al ingesteld" + "already_configured": "Apparaat is al geconfigureerd" }, "error": { "config": "Verbindings- of inlogfout; controleer uw configuratie" @@ -11,12 +11,9 @@ "data": { "host": "Host (optioneel)", "password": "Wachtwoord", - "port": "Poort (optioneel)", - "ssl": "Gebruikt een SSL certificaat", "username": "Gebruikersnaam (optioneel)" }, - "description": "Standaardhost: {host}\n Standaard gebruikersnaam: {username}", - "title": "Netgear" + "description": "Standaardhost: {host}\n Standaard gebruikersnaam: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Aantal seconden dat wordt gewacht voordat een apparaat als afwezig wordt beschouwd" }, - "description": "Optionele instellingen opgeven", - "title": "Netgear" + "description": "Optionele instellingen opgeven" } } } diff --git a/homeassistant/components/netgear/translations/no.json b/homeassistant/components/netgear/translations/no.json index 73735f3e91a..65b90889a20 100644 --- a/homeassistant/components/netgear/translations/no.json +++ b/homeassistant/components/netgear/translations/no.json @@ -11,12 +11,9 @@ "data": { "host": "Vert (valgfritt)", "password": "Passord", - "port": "Port (valgfritt)", - "ssl": "Bruker et SSL-sertifikat", "username": "Brukernavn (Valgfritt)" }, - "description": "Standard vert: {host}\n Standard brukernavn: {username}", - "title": "Netgear" + "description": "Standard vert: {host}\n Standard brukernavn: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Vurder hjemmetid (sekunder)" }, - "description": "Spesifiser valgfrie innstillinger", - "title": "Netgear" + "description": "Spesifiser valgfrie innstillinger" } } } diff --git a/homeassistant/components/netgear/translations/pl.json b/homeassistant/components/netgear/translations/pl.json index 0f3a6ad3cde..a3ec916efcc 100644 --- a/homeassistant/components/netgear/translations/pl.json +++ b/homeassistant/components/netgear/translations/pl.json @@ -11,12 +11,9 @@ "data": { "host": "Nazwa hosta lub adres IP (opcjonalnie)", "password": "Has\u0142o", - "port": "Port (opcjonalnie)", - "ssl": "Certyfikat SSL", "username": "Nazwa u\u017cytkownika (opcjonalnie)" }, - "description": "Domy\u015blne IP lub nazwa hosta: {host}\nDomy\u015blna nazwa u\u017cytkownika: {username}", - "title": "Netgear" + "description": "Domy\u015blne IP lub nazwa hosta: {host}\nDomy\u015blna nazwa u\u017cytkownika: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Czas przed oznaczeniem \"w domu\" (w sekundach)" }, - "description": "Okre\u015bl opcjonalne ustawienia", - "title": "Netgear" + "description": "Okre\u015bl opcjonalne ustawienia" } } } diff --git a/homeassistant/components/netgear/translations/pt-BR.json b/homeassistant/components/netgear/translations/pt-BR.json index 66ffd692374..ac52b1d18c3 100644 --- a/homeassistant/components/netgear/translations/pt-BR.json +++ b/homeassistant/components/netgear/translations/pt-BR.json @@ -11,12 +11,9 @@ "data": { "host": "Nome do host (Opcional)", "password": "Senha", - "port": "Porta (Opcional)", - "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio (Opcional)" }, - "description": "Host padr\u00e3o: {host}\n Nome de usu\u00e1rio padr\u00e3o: {username}", - "title": "Netgear" + "description": "Host padr\u00e3o: {host}\n Nome de usu\u00e1rio padr\u00e3o: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Considere o tempo de casa (segundos)" }, - "description": "Especifique configura\u00e7\u00f5es opcionais", - "title": "Netgear" + "description": "Especifique configura\u00e7\u00f5es opcionais" } } } diff --git a/homeassistant/components/netgear/translations/ru.json b/homeassistant/components/netgear/translations/ru.json index e6e5f9a008a..d2fd1f500f5 100644 --- a/homeassistant/components/netgear/translations/ru.json +++ b/homeassistant/components/netgear/translations/ru.json @@ -11,12 +11,9 @@ "data": { "host": "\u0425\u043e\u0441\u0442 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "port": "\u041f\u043e\u0440\u0442 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", - "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)" }, - "description": "\u0425\u043e\u0441\u0442 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: {host}\n\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: {username}", - "title": "Netgear" + "description": "\u0425\u043e\u0441\u0442 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: {host}\n\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "\u0412\u0440\u0435\u043c\u044f, \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0447\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0434\u043e\u043c\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" }, - "description": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", - "title": "Netgear" + "description": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438" } } } diff --git a/homeassistant/components/netgear/translations/tr.json b/homeassistant/components/netgear/translations/tr.json index b29a9f075aa..2f49affd564 100644 --- a/homeassistant/components/netgear/translations/tr.json +++ b/homeassistant/components/netgear/translations/tr.json @@ -11,12 +11,9 @@ "data": { "host": "Sunucu (\u0130ste\u011fe ba\u011fl\u0131)", "password": "Parola", - "port": "Port (\u0130ste\u011fe ba\u011fl\u0131)", - "ssl": "SSL sertifikas\u0131 kullan\u0131r", "username": "Kullan\u0131c\u0131 Ad\u0131 (\u0130ste\u011fe ba\u011fl\u0131)" }, - "description": "Varsay\u0131lan sunucu: {host}\nVarsay\u0131lan kullan\u0131c\u0131 ad\u0131: {username}", - "title": "Netgear" + "description": "Varsay\u0131lan sunucu: {host}\nVarsay\u0131lan kullan\u0131c\u0131 ad\u0131: {username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "Ev s\u00fcresini g\u00f6z \u00f6n\u00fcnde bulundurun (saniye)" }, - "description": "\u0130ste\u011fe ba\u011fl\u0131 ayarlar\u0131 belirtin", - "title": "Netgear" + "description": "\u0130ste\u011fe ba\u011fl\u0131 ayarlar\u0131 belirtin" } } } diff --git a/homeassistant/components/netgear/translations/zh-Hans.json b/homeassistant/components/netgear/translations/zh-Hans.json index dd7b165d2d4..4048634787b 100644 --- a/homeassistant/components/netgear/translations/zh-Hans.json +++ b/homeassistant/components/netgear/translations/zh-Hans.json @@ -11,20 +11,16 @@ "data": { "host": "\u4e3b\u673a\u5730\u5740 (\u53ef\u9009)", "password": "\u5bc6\u7801", - "port": "\u7aef\u53e3 (\u53ef\u9009)", - "ssl": "\u4f7f\u7528 SSL \u51ed\u8bc1\u767b\u5f55", "username": "\u7528\u6237\u540d (\u53ef\u9009)" }, - "description": "\u9ed8\u8ba4\u4e3b\u673a\u5730\u5740: {host}\n\u9ed8\u8ba4\u7528\u6237\u540d: {username}", - "title": "\u7f51\u4ef6\u8def\u7531\u5668" + "description": "\u9ed8\u8ba4\u4e3b\u673a\u5730\u5740: {host}\n\u9ed8\u8ba4\u7528\u6237\u540d: {username}" } } }, "options": { "step": { "init": { - "description": "\u6307\u5b9a\u53ef\u9009\u8bbe\u7f6e", - "title": "\u7f51\u4ef6\u8def\u7531\u5668" + "description": "\u6307\u5b9a\u53ef\u9009\u8bbe\u7f6e" } } } diff --git a/homeassistant/components/netgear/translations/zh-Hant.json b/homeassistant/components/netgear/translations/zh-Hant.json index 39b481f85b9..6a2866bc22d 100644 --- a/homeassistant/components/netgear/translations/zh-Hant.json +++ b/homeassistant/components/netgear/translations/zh-Hant.json @@ -11,12 +11,9 @@ "data": { "host": "\u4e3b\u6a5f\u7aef\uff08\u9078\u9805\uff09", "password": "\u5bc6\u78bc", - "port": "\u901a\u8a0a\u57e0\uff08\u9078\u9805\uff09", - "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "username": "\u4f7f\u7528\u8005\u540d\u7a31\uff08\u9078\u9805\uff09" }, - "description": "\u9810\u8a2d\u4e3b\u6a5f\u7aef\uff1a{host}\n\u9810\u8a2d\u4f7f\u7528\u8005\u540d\u7a31\uff1a{username}", - "title": "Netgear" + "description": "\u9810\u8a2d\u4e3b\u6a5f\u7aef\uff1a{host}\n\u9810\u8a2d\u4f7f\u7528\u8005\u540d\u7a31\uff1a{username}" } } }, @@ -26,8 +23,7 @@ "data": { "consider_home": "\u5224\u5b9a\u5728\u5bb6\u6642\u9593\uff08\u79d2\uff09" }, - "description": "\u6307\u5b9a\u9078\u9805\u8a2d\u5b9a", - "title": "Netgear" + "description": "\u6307\u5b9a\u9078\u9805\u8a2d\u5b9a" } } } diff --git a/homeassistant/components/network/network.py b/homeassistant/components/network/network.py index 6ec9941da3c..b2caf6438bd 100644 --- a/homeassistant/components/network/network.py +++ b/homeassistant/components/network/network.py @@ -6,6 +6,7 @@ from typing import Any, cast from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.singleton import singleton +from homeassistant.helpers.storage import Store from .const import ( ATTR_CONFIGURED_ADAPTERS, @@ -37,9 +38,7 @@ class Network: def __init__(self, hass: HomeAssistant) -> None: """Initialize the Network class.""" - self._store = hass.helpers.storage.Store( - STORAGE_VERSION, STORAGE_KEY, atomic_writes=True - ) + self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY, atomic_writes=True) self._data: dict[str, Any] = {} self.adapters: list[Adapter] = [] diff --git a/homeassistant/components/nexia/__init__.py b/homeassistant/components/nexia/__init__.py index 634f69b49d3..ab491c0a271 100644 --- a/homeassistant/components/nexia/__init__.py +++ b/homeassistant/components/nexia/__init__.py @@ -1,15 +1,16 @@ """Support for Nexia / Trane XL Thermostats.""" -from functools import partial +import asyncio import logging +import aiohttp from nexia.const import BRAND_NEXIA from nexia.home import NexiaHome -from requests.exceptions import ConnectTimeout, HTTPError from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from .const import CONF_BRAND, DOMAIN, PLATFORMS @@ -30,31 +31,32 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: brand = conf.get(CONF_BRAND, BRAND_NEXIA) state_file = hass.config.path(f"nexia_config_{username}.conf") + session = async_get_clientsession(hass) + nexia_home = NexiaHome( + session, + username=username, + password=password, + device_name=hass.config.location_name, + state_file=state_file, + brand=brand, + ) try: - nexia_home = await hass.async_add_executor_job( - partial( - NexiaHome, - username=username, - password=password, - device_name=hass.config.location_name, - state_file=state_file, - brand=brand, - ) - ) - except ConnectTimeout as ex: - _LOGGER.error("Unable to connect to Nexia service: %s", ex) - raise ConfigEntryNotReady from ex - except HTTPError as http_ex: - if is_invalid_auth_code(http_ex.response.status_code): + await nexia_home.login() + except asyncio.TimeoutError as ex: + raise ConfigEntryNotReady( + f"Timed out trying to connect to Nexia service: {ex}" + ) from ex + except aiohttp.ClientResponseError as http_ex: + if is_invalid_auth_code(http_ex.status): _LOGGER.error( "Access error from Nexia service, please check credentials: %s", http_ex ) return False - _LOGGER.error("HTTP error from Nexia service: %s", http_ex) - raise ConfigEntryNotReady from http_ex + raise ConfigEntryNotReady(f"Error from Nexia service: {http_ex}") from http_ex coordinator = NexiaDataUpdateCoordinator(hass, nexia_home) + await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator hass.config_entries.async_setup_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index fc85f72e503..20fcf5c6b85 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -38,11 +38,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( ATTR_AIRCLEANER_MODE, ATTR_DEHUMIDIFY_SETPOINT, - ATTR_DEHUMIDIFY_SUPPORTED, ATTR_HUMIDIFY_SETPOINT, - ATTR_HUMIDIFY_SUPPORTED, ATTR_RUN_MODE, - ATTR_ZONE_STATUS, DOMAIN, ) from .coordinator import NexiaDataUpdateCoordinator @@ -125,13 +122,17 @@ async def async_setup_entry( platform.async_register_entity_service( SERVICE_SET_HUMIDIFY_SETPOINT, SET_HUMIDITY_SCHEMA, - SERVICE_SET_HUMIDIFY_SETPOINT, + f"async_{SERVICE_SET_HUMIDIFY_SETPOINT}", ) platform.async_register_entity_service( - SERVICE_SET_AIRCLEANER_MODE, SET_AIRCLEANER_SCHEMA, SERVICE_SET_AIRCLEANER_MODE + SERVICE_SET_AIRCLEANER_MODE, + SET_AIRCLEANER_SCHEMA, + f"async_{SERVICE_SET_AIRCLEANER_MODE}", ) platform.async_register_entity_service( - SERVICE_SET_HVAC_RUN_MODE, SET_HVAC_RUN_MODE_SCHEMA, SERVICE_SET_HVAC_RUN_MODE + SERVICE_SET_HVAC_RUN_MODE, + SET_HVAC_RUN_MODE_SCHEMA, + f"async_{SERVICE_SET_HVAC_RUN_MODE}", ) entities: list[NexiaZone] = [] @@ -194,20 +195,20 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): """Return the fan setting.""" return self._thermostat.get_fan_mode() - def set_fan_mode(self, fan_mode): + async def async_set_fan_mode(self, fan_mode): """Set new target fan mode.""" - self._thermostat.set_fan_mode(fan_mode) + await self._thermostat.set_fan_mode(fan_mode) self._signal_thermostat_update() - def set_hvac_run_mode(self, run_mode, hvac_mode): + async def async_set_hvac_run_mode(self, run_mode, hvac_mode): """Set the hvac run mode.""" if run_mode is not None: if run_mode == HOLD_PERMANENT: - self._zone.call_permanent_hold() + await self._zone.call_permanent_hold() else: - self._zone.call_return_to_schedule() + await self._zone.call_return_to_schedule() if hvac_mode is not None: - self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode]) + await self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode]) self._signal_thermostat_update() @property @@ -215,12 +216,12 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): """Preset that is active.""" return self._zone.get_preset() - def set_humidity(self, humidity): + async def async_set_humidity(self, humidity): """Dehumidify target.""" if self._thermostat.has_dehumidify_support(): - self.set_dehumidify_setpoint(humidity) + await self.async_set_dehumidify_setpoint(humidity) else: - self.set_humidify_setpoint(humidity) + await self.async_set_humidify_setpoint(humidity) self._signal_thermostat_update() @property @@ -302,7 +303,7 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): return NEXIA_TO_HA_HVAC_MODE_MAP[mode] - def set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs): """Set target temperature.""" new_heat_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) new_cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) @@ -334,7 +335,7 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): ): new_heat_temp = new_cool_temp - deadband - self._zone.set_heat_cool_temp( + await self._zone.set_heat_cool_temp( heat_temperature=new_heat_temp, cool_temperature=new_cool_temp, set_temperature=set_temp, @@ -342,96 +343,84 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): self._signal_zone_update() @property - def is_aux_heat(self): + def is_aux_heat(self) -> bool: """Emergency heat state.""" return self._thermostat.is_emergency_heat_active() @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, str] | None: """Return the device specific state attributes.""" - data = super().extra_state_attributes - - data[ATTR_ZONE_STATUS] = self._zone.get_status() - if not self._has_relative_humidity: - return data - - data.update( - { - ATTR_DEHUMIDIFY_SUPPORTED: self._has_dehumidify_support, - ATTR_HUMIDIFY_SUPPORTED: self._has_humidify_support, - } - ) + return None + attrs = {} if self._has_dehumidify_support: dehumdify_setpoint = percent_conv( self._thermostat.get_dehumidify_setpoint() ) - data[ATTR_DEHUMIDIFY_SETPOINT] = dehumdify_setpoint - + attrs[ATTR_DEHUMIDIFY_SETPOINT] = dehumdify_setpoint if self._has_humidify_support: humdify_setpoint = percent_conv(self._thermostat.get_humidify_setpoint()) - data[ATTR_HUMIDIFY_SETPOINT] = humdify_setpoint + attrs[ATTR_HUMIDIFY_SETPOINT] = humdify_setpoint + return attrs - return data - - def set_preset_mode(self, preset_mode: str): + async def async_set_preset_mode(self, preset_mode: str): """Set the preset mode.""" - self._zone.set_preset(preset_mode) + await self._zone.set_preset(preset_mode) self._signal_zone_update() - def turn_aux_heat_off(self): - """Turn. Aux Heat off.""" - self._thermostat.set_emergency_heat(False) + async def async_turn_aux_heat_off(self): + """Turn Aux Heat off.""" + await self._thermostat.set_emergency_heat(False) self._signal_thermostat_update() - def turn_aux_heat_on(self): - """Turn. Aux Heat on.""" + async def async_turn_aux_heat_on(self): + """Turn Aux Heat on.""" self._thermostat.set_emergency_heat(True) self._signal_thermostat_update() - def turn_off(self): - """Turn. off the zone.""" - self.set_hvac_mode(OPERATION_MODE_OFF) + async def async_turn_off(self): + """Turn off the zone.""" + await self.async_set_hvac_mode(OPERATION_MODE_OFF) self._signal_zone_update() - def turn_on(self): - """Turn. on the zone.""" - self.set_hvac_mode(OPERATION_MODE_AUTO) + async def async_turn_on(self): + """Turn on the zone.""" + await self.async_set_hvac_mode(OPERATION_MODE_AUTO) self._signal_zone_update() - def set_hvac_mode(self, hvac_mode: HVACMode) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set the system mode (Auto, Heat_Cool, Cool, Heat, etc).""" if hvac_mode == HVACMode.AUTO: - self._zone.call_return_to_schedule() - self._zone.set_mode(mode=OPERATION_MODE_AUTO) + await self._zone.call_return_to_schedule() + await self._zone.set_mode(mode=OPERATION_MODE_AUTO) else: - self._zone.call_permanent_hold() - self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode]) + await self._zone.call_permanent_hold() + await self._zone.set_mode(mode=HA_TO_NEXIA_HVAC_MODE_MAP[hvac_mode]) self._signal_zone_update() - def set_aircleaner_mode(self, aircleaner_mode): + async def async_set_aircleaner_mode(self, aircleaner_mode): """Set the aircleaner mode.""" - self._thermostat.set_air_cleaner(aircleaner_mode) + await self._thermostat.set_air_cleaner(aircleaner_mode) self._signal_thermostat_update() - def set_humidify_setpoint(self, humidity): + async def async_set_humidify_setpoint(self, humidity): """Set the humidify setpoint.""" target_humidity = find_humidity_setpoint(humidity / 100.0) if self._thermostat.get_humidify_setpoint() == target_humidity: # Trying to set the humidify setpoint to the # same value will cause the api to timeout return - self._thermostat.set_humidify_setpoint(target_humidity) + await self._thermostat.set_humidify_setpoint(target_humidity) self._signal_thermostat_update() - def set_dehumidify_setpoint(self, humidity): + async def async_set_dehumidify_setpoint(self, humidity): """Set the dehumidify setpoint.""" target_humidity = find_humidity_setpoint(humidity / 100.0) if self._thermostat.get_dehumidify_setpoint() == target_humidity: # Trying to set the dehumidify setpoint to the # same value will cause the api to timeout return - self._thermostat.set_dehumidify_setpoint(target_humidity) + await self._thermostat.set_dehumidify_setpoint(target_humidity) self._signal_thermostat_update() diff --git a/homeassistant/components/nexia/config_flow.py b/homeassistant/components/nexia/config_flow.py index 4e48123a5de..de5640beef7 100644 --- a/homeassistant/components/nexia/config_flow.py +++ b/homeassistant/components/nexia/config_flow.py @@ -1,13 +1,15 @@ """Config flow for Nexia integration.""" +import asyncio import logging +import aiohttp from nexia.const import BRAND_ASAIR, BRAND_NEXIA, BRAND_TRANE from nexia.home import NexiaHome -from requests.exceptions import ConnectTimeout, HTTPError import voluptuous as vol from homeassistant import config_entries, core, exceptions from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( BRAND_ASAIR_NAME, @@ -44,23 +46,23 @@ async def validate_input(hass: core.HomeAssistant, data): state_file = hass.config.path( f"{data[CONF_BRAND]}_config_{data[CONF_USERNAME]}.conf" ) + session = async_get_clientsession(hass) + nexia_home = NexiaHome( + session, + username=data[CONF_USERNAME], + password=data[CONF_PASSWORD], + brand=data[CONF_BRAND], + device_name=hass.config.location_name, + state_file=state_file, + ) try: - nexia_home = NexiaHome( - username=data[CONF_USERNAME], - password=data[CONF_PASSWORD], - brand=data[CONF_BRAND], - auto_login=False, - auto_update=False, - device_name=hass.config.location_name, - state_file=state_file, - ) - await hass.async_add_executor_job(nexia_home.login) - except ConnectTimeout as ex: + await nexia_home.login() + except asyncio.TimeoutError as ex: _LOGGER.error("Unable to connect to Nexia service: %s", ex) raise CannotConnect from ex - except HTTPError as http_ex: + except aiohttp.ClientResponseError as http_ex: _LOGGER.error("HTTP error from Nexia service: %s", http_ex) - if is_invalid_auth_code(http_ex.response.status_code): + if is_invalid_auth_code(http_ex.status): raise InvalidAuth from http_ex raise CannotConnect from http_ex diff --git a/homeassistant/components/nexia/const.py b/homeassistant/components/nexia/const.py index 4fa3cb022f8..493fdd8a403 100644 --- a/homeassistant/components/nexia/const.py +++ b/homeassistant/components/nexia/const.py @@ -9,17 +9,11 @@ PLATFORMS = [ Platform.SWITCH, ] -ATTRIBUTION = "Data provided by mynexia.com" - -NOTIFICATION_ID = "nexia_notification" -NOTIFICATION_TITLE = "Nexia Setup" +ATTRIBUTION = "Data provided by Trane Technologies" CONF_BRAND = "brand" -NEXIA_SCAN_INTERVAL = "scan_interval" - DOMAIN = "nexia" -DEFAULT_ENTITY_NAMESPACE = "nexia" ATTR_DESCRIPTION = "description" @@ -27,9 +21,6 @@ ATTR_AIRCLEANER_MODE = "aircleaner_mode" ATTR_RUN_MODE = "run_mode" -ATTR_ZONE_STATUS = "zone_status" -ATTR_HUMIDIFY_SUPPORTED = "humidify_supported" -ATTR_DEHUMIDIFY_SUPPORTED = "dehumidify_supported" ATTR_HUMIDIFY_SETPOINT = "humidify_setpoint" ATTR_DEHUMIDIFY_SETPOINT = "dehumidify_setpoint" diff --git a/homeassistant/components/nexia/coordinator.py b/homeassistant/components/nexia/coordinator.py index 7f92ca9354b..d44e1827f5a 100644 --- a/homeassistant/components/nexia/coordinator.py +++ b/homeassistant/components/nexia/coordinator.py @@ -1,4 +1,4 @@ -"""Component to embed TP-Link smart home devices.""" +"""Component to embed nexia devices.""" from __future__ import annotations from datetime import timedelta @@ -33,4 +33,4 @@ class NexiaDataUpdateCoordinator(DataUpdateCoordinator): async def _async_update_data(self) -> None: """Fetch data from API endpoint.""" - return await self.hass.async_add_executor_job(self.nexia_home.update) + return await self.nexia_home.update() diff --git a/homeassistant/components/nexia/entity.py b/homeassistant/components/nexia/entity.py index 0be5c05396d..4f806d03eda 100644 --- a/homeassistant/components/nexia/entity.py +++ b/homeassistant/components/nexia/entity.py @@ -1,9 +1,18 @@ """The nexia integration base entity.""" + from nexia.thermostat import NexiaThermostat from nexia.zone import NexiaThermostatZone -from homeassistant.const import ATTR_ATTRIBUTION -from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send +from homeassistant.const import ( + ATTR_IDENTIFIERS, + ATTR_NAME, + ATTR_SUGGESTED_AREA, + ATTR_VIA_DEVICE, +) +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -17,31 +26,18 @@ from .const import ( from .coordinator import NexiaDataUpdateCoordinator -class NexiaEntity(CoordinatorEntity): +class NexiaEntity(CoordinatorEntity[NexiaDataUpdateCoordinator]): """Base class for nexia entities.""" - def __init__(self, coordinator, name, unique_id): + _attr_attribution = ATTRIBUTION + + def __init__( + self, coordinator: NexiaDataUpdateCoordinator, name: str, unique_id: str + ) -> None: """Initialize the entity.""" super().__init__(coordinator) - self._unique_id = unique_id - self._name = name - - @property - def unique_id(self): - """Return the unique id.""" - return self._unique_id - - @property - def name(self): - """Return the name.""" - return self._name - - @property - def extra_state_attributes(self): - """Return the device specific state attributes.""" - return { - ATTR_ATTRIBUTION: ATTRIBUTION, - } + self._attr_unique_id = unique_id + self._attr_name = name class NexiaThermostatEntity(NexiaEntity): @@ -51,12 +47,7 @@ class NexiaThermostatEntity(NexiaEntity): """Initialize the entity.""" super().__init__(coordinator, name, unique_id) self._thermostat: NexiaThermostat = thermostat - - @property - def device_info(self) -> DeviceInfo: - """Return the device_info of the device.""" - assert isinstance(self.coordinator, NexiaDataUpdateCoordinator) - return DeviceInfo( + self._attr_device_info = DeviceInfo( configuration_url=self.coordinator.nexia_home.root_url, identifiers={(DOMAIN, self._thermostat.thermostat_id)}, manufacturer=MANUFACTURER, @@ -85,7 +76,7 @@ class NexiaThermostatEntity(NexiaEntity): Update all the zones on the thermostat. """ - dispatcher_send( + async_dispatcher_send( self.hass, f"{SIGNAL_THERMOSTAT_UPDATE}-{self._thermostat.thermostat_id}" ) @@ -97,21 +88,13 @@ class NexiaThermostatZoneEntity(NexiaThermostatEntity): """Initialize the entity.""" super().__init__(coordinator, zone.thermostat, name, unique_id) self._zone: NexiaThermostatZone = zone - - @property - def device_info(self): - """Return the device_info of the device.""" - data = super().device_info zone_name = self._zone.get_name() - data.update( - { - "identifiers": {(DOMAIN, self._zone.zone_id)}, - "name": zone_name, - "suggested_area": zone_name, - "via_device": (DOMAIN, self._zone.thermostat.thermostat_id), - } - ) - return data + self._attr_device_info |= { + ATTR_IDENTIFIERS: {(DOMAIN, self._zone.zone_id)}, + ATTR_NAME: zone_name, + ATTR_SUGGESTED_AREA: zone_name, + ATTR_VIA_DEVICE: (DOMAIN, self._zone.thermostat.thermostat_id), + } async def async_added_to_hass(self): """Listen for signals for services.""" @@ -132,4 +115,4 @@ class NexiaThermostatZoneEntity(NexiaThermostatEntity): Update a single zone. """ - dispatcher_send(self.hass, f"{SIGNAL_ZONE_UPDATE}-{self._zone.zone_id}") + async_dispatcher_send(self.hass, f"{SIGNAL_ZONE_UPDATE}-{self._zone.zone_id}") diff --git a/homeassistant/components/nexia/manifest.json b/homeassistant/components/nexia/manifest.json index 29b80fb00e9..f9ca21d9e0b 100644 --- a/homeassistant/components/nexia/manifest.json +++ b/homeassistant/components/nexia/manifest.json @@ -1,7 +1,7 @@ { "domain": "nexia", "name": "Nexia/American Standard/Trane", - "requirements": ["nexia==0.9.13"], + "requirements": ["nexia==1.0.1"], "codeowners": ["@bdraco"], "documentation": "https://www.home-assistant.io/integrations/nexia", "config_flow": true, diff --git a/homeassistant/components/nexia/scene.py b/homeassistant/components/nexia/scene.py index 3dd90b07ca2..941785f8221 100644 --- a/homeassistant/components/nexia/scene.py +++ b/homeassistant/components/nexia/scene.py @@ -1,6 +1,8 @@ """Support for Nexia Automations.""" from typing import Any +from nexia.automation import NexiaAutomation + from homeassistant.components.scene import Scene from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -22,45 +24,34 @@ async def async_setup_entry( """Set up automations for a Nexia device.""" coordinator: NexiaDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] nexia_home = coordinator.nexia_home - - entities = [] - - # Automation switches - for automation_id in nexia_home.get_automation_ids(): - automation = nexia_home.get_automation_by_id(automation_id) - - entities.append(NexiaAutomationScene(coordinator, automation)) - - async_add_entities(entities) + async_add_entities( + NexiaAutomationScene( + coordinator, nexia_home.get_automation_by_id(automation_id) + ) + for automation_id in nexia_home.get_automation_ids() + ) class NexiaAutomationScene(NexiaEntity, Scene): """Provides Nexia automation support.""" - def __init__(self, coordinator, automation): + _attr_icon = "mdi:script-text-outline" + + def __init__( + self, coordinator: NexiaDataUpdateCoordinator, automation: NexiaAutomation + ) -> None: """Initialize the automation scene.""" super().__init__( coordinator, name=automation.name, unique_id=automation.automation_id, ) - self._automation = automation - - @property - def extra_state_attributes(self): - """Return the scene specific state attributes.""" - data = super().extra_state_attributes - data[ATTR_DESCRIPTION] = self._automation.description - return data - - @property - def icon(self): - """Return the icon of the automation scene.""" - return "mdi:script-text-outline" + self._automation: NexiaAutomation = automation + self._attr_extra_state_attributes = {ATTR_DESCRIPTION: automation.description} async def async_activate(self, **kwargs: Any) -> None: """Activate an automation scene.""" - await self.hass.async_add_executor_job(self._automation.activate) + await self._automation.activate() async def refresh_callback(_): await self.coordinator.async_refresh() diff --git a/homeassistant/components/nexia/sensor.py b/homeassistant/components/nexia/sensor.py index 8137da8e5cc..3f280581ee7 100644 --- a/homeassistant/components/nexia/sensor.py +++ b/homeassistant/components/nexia/sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations from nexia.const import UNIT_CELSIUS +from nexia.thermostat import NexiaThermostat from homeassistant.components.sensor import ( SensorDeviceClass, @@ -32,7 +33,7 @@ async def async_setup_entry( # Thermostat / System Sensors for thermostat_id in nexia_home.get_thermostat_ids(): - thermostat = nexia_home.get_thermostat_by_id(thermostat_id) + thermostat: NexiaThermostat = nexia_home.get_thermostat_by_id(thermostat_id) entities.append( NexiaThermostatSensor( diff --git a/homeassistant/components/nexia/switch.py b/homeassistant/components/nexia/switch.py index 09bc8a3852e..380fea8c4a0 100644 --- a/homeassistant/components/nexia/switch.py +++ b/homeassistant/components/nexia/switch.py @@ -56,12 +56,12 @@ class NexiaHoldSwitch(NexiaThermostatZoneEntity, SwitchEntity): """Return the icon for the switch.""" return "mdi:timer-off" if self._zone.is_in_permanent_hold() else "mdi:timer" - def turn_on(self, **kwargs: Any) -> None: + async def async_turn_on(self, **kwargs: Any) -> None: """Enable permanent hold.""" - self._zone.call_permanent_hold() + await self._zone.call_permanent_hold() self._signal_zone_update() - def turn_off(self, **kwargs: Any) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Disable permanent hold.""" - self._zone.call_return_to_schedule() + await self._zone.call_return_to_schedule() self._signal_zone_update() diff --git a/homeassistant/components/nexia/translations/bg.json b/homeassistant/components/nexia/translations/bg.json index 0c8cf4f44a6..78264e2adbd 100644 --- a/homeassistant/components/nexia/translations/bg.json +++ b/homeassistant/components/nexia/translations/bg.json @@ -4,8 +4,7 @@ "user": { "data": { "brand": "\u041c\u0430\u0440\u043a\u0430" - }, - "title": "\u0421\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/ca.json b/homeassistant/components/nexia/translations/ca.json index e0f0c87f60f..f369d64bde9 100644 --- a/homeassistant/components/nexia/translations/ca.json +++ b/homeassistant/components/nexia/translations/ca.json @@ -14,8 +14,7 @@ "brand": "Marca", "password": "Contrasenya", "username": "Nom d'usuari" - }, - "title": "Connexi\u00f3 amb mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/cs.json b/homeassistant/components/nexia/translations/cs.json index 0196d0d03ed..dc27752e935 100644 --- a/homeassistant/components/nexia/translations/cs.json +++ b/homeassistant/components/nexia/translations/cs.json @@ -13,8 +13,7 @@ "data": { "password": "Heslo", "username": "U\u017eivatelsk\u00e9 jm\u00e9no" - }, - "title": "P\u0159ipojen\u00ed k mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/de.json b/homeassistant/components/nexia/translations/de.json index 94094f08037..b2eeebb2f17 100644 --- a/homeassistant/components/nexia/translations/de.json +++ b/homeassistant/components/nexia/translations/de.json @@ -14,8 +14,7 @@ "brand": "Marke", "password": "Passwort", "username": "Benutzername" - }, - "title": "Stelle eine Verbindung zu mynexia.com her" + } } } } diff --git a/homeassistant/components/nexia/translations/el.json b/homeassistant/components/nexia/translations/el.json index 47d2a624cd3..4db4ad008ed 100644 --- a/homeassistant/components/nexia/translations/el.json +++ b/homeassistant/components/nexia/translations/el.json @@ -14,8 +14,7 @@ "brand": "\u039c\u03ac\u03c1\u03ba\u03b1", "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" - }, - "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03b5 mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/en.json b/homeassistant/components/nexia/translations/en.json index 20b0a137970..050db24e0cf 100644 --- a/homeassistant/components/nexia/translations/en.json +++ b/homeassistant/components/nexia/translations/en.json @@ -14,8 +14,7 @@ "brand": "Brand", "password": "Password", "username": "Username" - }, - "title": "Connect to mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/es-419.json b/homeassistant/components/nexia/translations/es-419.json index e2f04c7d4b4..ea277d080cc 100644 --- a/homeassistant/components/nexia/translations/es-419.json +++ b/homeassistant/components/nexia/translations/es-419.json @@ -13,8 +13,7 @@ "data": { "password": "Contrase\u00f1a", "username": "Nombre de usuario" - }, - "title": "Con\u00e9ctese a mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/es.json b/homeassistant/components/nexia/translations/es.json index 1698b8db1d1..ee89d9db6f5 100644 --- a/homeassistant/components/nexia/translations/es.json +++ b/homeassistant/components/nexia/translations/es.json @@ -4,7 +4,7 @@ "already_configured": "Este nexia home ya est\u00e1 configurado" }, "error": { - "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.", + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "unknown": "Error inesperado" }, @@ -14,8 +14,7 @@ "brand": "Marca", "password": "Contrase\u00f1a", "username": "Usuario" - }, - "title": "Conectar con mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/et.json b/homeassistant/components/nexia/translations/et.json index 2f9348c1eed..7e24abee556 100644 --- a/homeassistant/components/nexia/translations/et.json +++ b/homeassistant/components/nexia/translations/et.json @@ -14,8 +14,7 @@ "brand": "Tootja", "password": "Salas\u00f5na", "username": "Kasutajanimi" - }, - "title": "\u00dchendu mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/fr.json b/homeassistant/components/nexia/translations/fr.json index 3955d07ff7a..be31f08787b 100644 --- a/homeassistant/components/nexia/translations/fr.json +++ b/homeassistant/components/nexia/translations/fr.json @@ -14,8 +14,7 @@ "brand": "Marque", "password": "Mot de passe", "username": "Nom d'utilisateur" - }, - "title": "Se connecter \u00e0 mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/he.json b/homeassistant/components/nexia/translations/he.json index 7f9efa5b24e..d27edd983c2 100644 --- a/homeassistant/components/nexia/translations/he.json +++ b/homeassistant/components/nexia/translations/he.json @@ -14,8 +14,7 @@ "brand": "\u05de\u05d5\u05ea\u05d2", "password": "\u05e1\u05d9\u05e1\u05de\u05d4", "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" - }, - "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc-mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/hu.json b/homeassistant/components/nexia/translations/hu.json index ac85fec6456..f9178aafa07 100644 --- a/homeassistant/components/nexia/translations/hu.json +++ b/homeassistant/components/nexia/translations/hu.json @@ -14,8 +14,7 @@ "brand": "M\u00e1rka", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - }, - "title": "Csatlakoz\u00e1s a mynexia.com-hoz" + } } } } diff --git a/homeassistant/components/nexia/translations/id.json b/homeassistant/components/nexia/translations/id.json index 0600315fc78..1dba99e3984 100644 --- a/homeassistant/components/nexia/translations/id.json +++ b/homeassistant/components/nexia/translations/id.json @@ -14,8 +14,7 @@ "brand": "Merek", "password": "Kata Sandi", "username": "Nama Pengguna" - }, - "title": "Hubungkan ke mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/it.json b/homeassistant/components/nexia/translations/it.json index 65d06c3c8f0..dad681085de 100644 --- a/homeassistant/components/nexia/translations/it.json +++ b/homeassistant/components/nexia/translations/it.json @@ -14,8 +14,7 @@ "brand": "Marca", "password": "Password", "username": "Nome utente" - }, - "title": "Connettersi a mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/ja.json b/homeassistant/components/nexia/translations/ja.json index f06358ef78c..b80fe6c7cd2 100644 --- a/homeassistant/components/nexia/translations/ja.json +++ b/homeassistant/components/nexia/translations/ja.json @@ -14,8 +14,7 @@ "brand": "\u30d6\u30e9\u30f3\u30c9", "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", "username": "\u30e6\u30fc\u30b6\u30fc\u540d" - }, - "title": "mynexia.com\u306b\u63a5\u7d9a" + } } } } diff --git a/homeassistant/components/nexia/translations/ko.json b/homeassistant/components/nexia/translations/ko.json index 4bd1589e4e8..94261de9637 100644 --- a/homeassistant/components/nexia/translations/ko.json +++ b/homeassistant/components/nexia/translations/ko.json @@ -13,8 +13,7 @@ "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - }, - "title": "mynexia.com\uc5d0 \uc5f0\uacb0\ud558\uae30" + } } } } diff --git a/homeassistant/components/nexia/translations/lb.json b/homeassistant/components/nexia/translations/lb.json index fa9b186ccbf..60b1bb6f305 100644 --- a/homeassistant/components/nexia/translations/lb.json +++ b/homeassistant/components/nexia/translations/lb.json @@ -13,8 +13,7 @@ "data": { "password": "Passwuert", "username": "Benotzernumm" - }, - "title": "Mat mynexia.com verbannen" + } } } } diff --git a/homeassistant/components/nexia/translations/nl.json b/homeassistant/components/nexia/translations/nl.json index 14efdfcd221..106dcc85cc7 100644 --- a/homeassistant/components/nexia/translations/nl.json +++ b/homeassistant/components/nexia/translations/nl.json @@ -14,8 +14,7 @@ "brand": "Brand", "password": "Wachtwoord", "username": "Gebruikersnaam" - }, - "title": "Verbinding maken met mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/no.json b/homeassistant/components/nexia/translations/no.json index 4533b94e48e..6cb06d0c827 100644 --- a/homeassistant/components/nexia/translations/no.json +++ b/homeassistant/components/nexia/translations/no.json @@ -14,8 +14,7 @@ "brand": "Merke", "password": "Passord", "username": "Brukernavn" - }, - "title": "Koble til mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/pl.json b/homeassistant/components/nexia/translations/pl.json index 6e40e21a7f0..8f4021ec9d8 100644 --- a/homeassistant/components/nexia/translations/pl.json +++ b/homeassistant/components/nexia/translations/pl.json @@ -14,8 +14,7 @@ "brand": "Marka", "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" - }, - "title": "Po\u0142\u0105czenie z mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/pt-BR.json b/homeassistant/components/nexia/translations/pt-BR.json index e9cce64aef9..01a830027fa 100644 --- a/homeassistant/components/nexia/translations/pt-BR.json +++ b/homeassistant/components/nexia/translations/pt-BR.json @@ -14,8 +14,7 @@ "brand": "Marca", "password": "Senha", "username": "Usu\u00e1rio" - }, - "title": "Conecte-se a mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/ru.json b/homeassistant/components/nexia/translations/ru.json index b5572c9f2da..e72388f6c9f 100644 --- a/homeassistant/components/nexia/translations/ru.json +++ b/homeassistant/components/nexia/translations/ru.json @@ -14,8 +14,7 @@ "brand": "\u041c\u0430\u0440\u043a\u0430", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" - }, - "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/sl.json b/homeassistant/components/nexia/translations/sl.json index 55c234fbde3..a1e926075f7 100644 --- a/homeassistant/components/nexia/translations/sl.json +++ b/homeassistant/components/nexia/translations/sl.json @@ -13,8 +13,7 @@ "data": { "password": "Geslo", "username": "Uporabni\u0161ko ime" - }, - "title": "Pove\u017eite se z mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/sv.json b/homeassistant/components/nexia/translations/sv.json index 60044361f65..9cfd620ac73 100644 --- a/homeassistant/components/nexia/translations/sv.json +++ b/homeassistant/components/nexia/translations/sv.json @@ -10,8 +10,7 @@ "data": { "password": "L\u00f6senord", "username": "Anv\u00e4ndarnamn" - }, - "title": "Anslut till mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/tr.json b/homeassistant/components/nexia/translations/tr.json index 8918bcd8f5e..11bacdf69df 100644 --- a/homeassistant/components/nexia/translations/tr.json +++ b/homeassistant/components/nexia/translations/tr.json @@ -14,8 +14,7 @@ "brand": "Marka", "password": "Parola", "username": "Kullan\u0131c\u0131 Ad\u0131" - }, - "title": "Mynexia.com'a ba\u011flan\u0131n" + } } } } diff --git a/homeassistant/components/nexia/translations/uk.json b/homeassistant/components/nexia/translations/uk.json index 8cb2aec836a..49bceaa3f6e 100644 --- a/homeassistant/components/nexia/translations/uk.json +++ b/homeassistant/components/nexia/translations/uk.json @@ -13,8 +13,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" - }, - "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e mynexia.com" + } } } } diff --git a/homeassistant/components/nexia/translations/zh-Hant.json b/homeassistant/components/nexia/translations/zh-Hant.json index c066a433d1b..0924ebf7259 100644 --- a/homeassistant/components/nexia/translations/zh-Hant.json +++ b/homeassistant/components/nexia/translations/zh-Hant.json @@ -14,8 +14,7 @@ "brand": "\u54c1\u724c", "password": "\u5bc6\u78bc", "username": "\u4f7f\u7528\u8005\u540d\u7a31" - }, - "title": "\u9023\u7dda\u81f3 mynexia.com" + } } } } diff --git a/homeassistant/components/nfandroidtv/translations/bg.json b/homeassistant/components/nfandroidtv/translations/bg.json index 484dd2b98e3..5b58777a0e2 100644 --- a/homeassistant/components/nfandroidtv/translations/bg.json +++ b/homeassistant/components/nfandroidtv/translations/bg.json @@ -12,8 +12,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "name": "\u0418\u043c\u0435" - }, - "title": "\u0418\u0437\u0432\u0435\u0441\u0442\u0438\u044f \u0437\u0430 Android TV / Fire TV" + } } } } diff --git a/homeassistant/components/nfandroidtv/translations/ca.json b/homeassistant/components/nfandroidtv/translations/ca.json index 0eda4938d9d..90b910617f9 100644 --- a/homeassistant/components/nfandroidtv/translations/ca.json +++ b/homeassistant/components/nfandroidtv/translations/ca.json @@ -13,8 +13,7 @@ "host": "Amfitri\u00f3", "name": "Nom" }, - "description": "Consulta la documentaci\u00f3 per assegurar-te que compleixes tots els requisits.", - "title": "Notificacions per a Android TV / Fire TV" + "description": "Consulta la documentaci\u00f3 per assegurar-te que compleixes tots els requisits." } } } diff --git a/homeassistant/components/nfandroidtv/translations/de.json b/homeassistant/components/nfandroidtv/translations/de.json index d9df9058d13..fef64459261 100644 --- a/homeassistant/components/nfandroidtv/translations/de.json +++ b/homeassistant/components/nfandroidtv/translations/de.json @@ -13,8 +13,7 @@ "host": "Host", "name": "Name" }, - "description": "Bitte lies die Dokumentation, um sicherzustellen, dass alle Anforderungen erf\u00fcllt sind.", - "title": "Benachrichtigungen f\u00fcr Android TV / Fire TV" + "description": "Bitte lies die Dokumentation, um sicherzustellen, dass alle Anforderungen erf\u00fcllt sind." } } } diff --git a/homeassistant/components/nfandroidtv/translations/el.json b/homeassistant/components/nfandroidtv/translations/el.json index b95ec27a3bf..8512834e2b8 100644 --- a/homeassistant/components/nfandroidtv/translations/el.json +++ b/homeassistant/components/nfandroidtv/translations/el.json @@ -13,8 +13,7 @@ "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": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 Android TV. \n\n \u0393\u03b9\u03b1 Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\n \u0393\u03b9\u03b1 Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\n \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 DHCP \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2 (\u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03b3\u03c7\u03b5\u03b9\u03c1\u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2) \u03b5\u03af\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c4\u03b1\u03c4\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0395\u03ac\u03bd \u03cc\u03c7\u03b9, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b8\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03b5\u03af \u03c4\u03b5\u03bb\u03b9\u03ba\u03ac \u03bc\u03b7 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7.", - "title": "\u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 Android TV / Fire TV" + "description": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u0395\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 Android TV. \n\n \u0393\u03b9\u03b1 Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\n \u0393\u03b9\u03b1 Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\n \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03c1\u03ac\u03c4\u03b7\u03c3\u03b7 DHCP \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2 (\u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b5\u03b3\u03c7\u03b5\u03b9\u03c1\u03af\u03b4\u03b9\u03bf \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2) \u03b5\u03af\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c4\u03b1\u03c4\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae. \u0395\u03ac\u03bd \u03cc\u03c7\u03b9, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b8\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03b5\u03af \u03c4\u03b5\u03bb\u03b9\u03ba\u03ac \u03bc\u03b7 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7." } } } diff --git a/homeassistant/components/nfandroidtv/translations/en.json b/homeassistant/components/nfandroidtv/translations/en.json index 3c1383b91b8..4164a968c81 100644 --- a/homeassistant/components/nfandroidtv/translations/en.json +++ b/homeassistant/components/nfandroidtv/translations/en.json @@ -13,8 +13,7 @@ "host": "Host", "name": "Name" }, - "description": "Please refer to the documentation to make sure all requirements are met.", - "title": "Notifications for Android TV / Fire TV" + "description": "Please refer to the documentation to make sure all requirements are met." } } } diff --git a/homeassistant/components/nfandroidtv/translations/es.json b/homeassistant/components/nfandroidtv/translations/es.json index 880835cfb1e..e382855479f 100644 --- a/homeassistant/components/nfandroidtv/translations/es.json +++ b/homeassistant/components/nfandroidtv/translations/es.json @@ -13,8 +13,7 @@ "host": "Host", "name": "Nombre" }, - "description": "Esta integraci\u00f3n requiere la aplicaci\u00f3n de Notificaciones para 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\nDebes configurar una reserva DHCP en su router (consulta el manual de usuario de tu router) o una direcci\u00f3n IP est\u00e1tica en el dispositivo. Si no, el dispositivo acabar\u00e1 por no estar disponible.", - "title": "Notificaciones para Android TV / Fire TV" + "description": "Esta integraci\u00f3n requiere la aplicaci\u00f3n de Notificaciones para 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\nDebes configurar una reserva DHCP en su router (consulta el manual de usuario de tu router) o una direcci\u00f3n IP est\u00e1tica en el dispositivo. Si no, el dispositivo acabar\u00e1 por no estar disponible." } } } diff --git a/homeassistant/components/nfandroidtv/translations/et.json b/homeassistant/components/nfandroidtv/translations/et.json index f567795b608..4e5ae85ca00 100644 --- a/homeassistant/components/nfandroidtv/translations/et.json +++ b/homeassistant/components/nfandroidtv/translations/et.json @@ -13,8 +13,7 @@ "host": "Host", "name": "Nimi" }, - "description": "Vaata dokumentatsiooni, et veenduda, et k\u00f5ik n\u00f5uded on t\u00e4idetud.", - "title": "Android TV / Fire TV teavitused" + "description": "Vaata dokumentatsiooni, et veenduda, et k\u00f5ik n\u00f5uded on t\u00e4idetud." } } } diff --git a/homeassistant/components/nfandroidtv/translations/fr.json b/homeassistant/components/nfandroidtv/translations/fr.json index 7c69bbc6ac0..30d592c6edf 100644 --- a/homeassistant/components/nfandroidtv/translations/fr.json +++ b/homeassistant/components/nfandroidtv/translations/fr.json @@ -13,8 +13,7 @@ "host": "H\u00f4te", "name": "Nom" }, - "description": "Veuillez consulter la documentation afin de vous assurer que toutes les conditions sont respect\u00e9es.", - "title": "Notifications pour Android TV / Fire TV" + "description": "Veuillez consulter la documentation afin de vous assurer que toutes les conditions sont respect\u00e9es." } } } diff --git a/homeassistant/components/nfandroidtv/translations/he.json b/homeassistant/components/nfandroidtv/translations/he.json index 65eefcd05b4..cad2cfc04a3 100644 --- a/homeassistant/components/nfandroidtv/translations/he.json +++ b/homeassistant/components/nfandroidtv/translations/he.json @@ -13,8 +13,7 @@ "host": "\u05de\u05d0\u05e8\u05d7", "name": "\u05e9\u05dd" }, - "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" + "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." } } } diff --git a/homeassistant/components/nfandroidtv/translations/hu.json b/homeassistant/components/nfandroidtv/translations/hu.json index 0c8d0567483..65cd2bebc01 100644 --- a/homeassistant/components/nfandroidtv/translations/hu.json +++ b/homeassistant/components/nfandroidtv/translations/hu.json @@ -13,8 +13,7 @@ "host": "C\u00edm", "name": "Elnevez\u00e9s" }, - "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" + "description": "K\u00e9rj\u00fck, olvassa el a dokument\u00e1ci\u00f3t, hogy megbizonyosodjon arr\u00f3l, hogy minden k\u00f6vetelm\u00e9ny teljes\u00fcl." } } } diff --git a/homeassistant/components/nfandroidtv/translations/id.json b/homeassistant/components/nfandroidtv/translations/id.json index de4ded1e98a..4495908ef84 100644 --- a/homeassistant/components/nfandroidtv/translations/id.json +++ b/homeassistant/components/nfandroidtv/translations/id.json @@ -13,8 +13,7 @@ "host": "Host", "name": "Nama" }, - "description": "Rujuk ke dokumentasi untuk memastikan semua persyaratan terpenuhi.", - "title": "Notifikasi untuk Android TV/Fire TV" + "description": "Rujuk ke dokumentasi untuk memastikan semua persyaratan terpenuhi." } } } diff --git a/homeassistant/components/nfandroidtv/translations/it.json b/homeassistant/components/nfandroidtv/translations/it.json index 83eea49b419..4fb88f80726 100644 --- a/homeassistant/components/nfandroidtv/translations/it.json +++ b/homeassistant/components/nfandroidtv/translations/it.json @@ -13,8 +13,7 @@ "host": "Host", "name": "Nome" }, - "description": "Fai riferimento alla documentazione per assicurarti che tutti i requisiti siano soddisfatti.", - "title": "Notifiche per Android TV / Fire TV" + "description": "Fai riferimento alla documentazione per assicurarti che tutti i requisiti siano soddisfatti." } } } diff --git a/homeassistant/components/nfandroidtv/translations/ja.json b/homeassistant/components/nfandroidtv/translations/ja.json index c3cd4fee235..fff28117234 100644 --- a/homeassistant/components/nfandroidtv/translations/ja.json +++ b/homeassistant/components/nfandroidtv/translations/ja.json @@ -13,8 +13,7 @@ "host": "\u30db\u30b9\u30c8", "name": "\u540d\u524d" }, - "description": "\u3053\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306b\u306f\u3001AndroidTV\u30a2\u30d7\u30ea\u306e\u901a\u77e5\u304c\u5fc5\u8981\u3067\u3059\u3002 \n\nAndroid TV\u306e\u5834\u5408: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nFire TV\u306e\u5834\u5408: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\n\u30eb\u30fc\u30bf\u30fc\u306eDHCP\u4e88\u7d04((DHCP reservation)\u30eb\u30fc\u30bf\u30fc\u306e\u30e6\u30fc\u30b6\u30fc\u30de\u30cb\u30e5\u30a2\u30eb\u3092\u53c2\u7167))\u307e\u305f\u306f\u3001\u30c7\u30d0\u30a4\u30b9\u306b\u9759\u7684IP\u30a2\u30c9\u30ec\u30b9\u3092\u8a2d\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\u305d\u3046\u3067\u306a\u3044\u5834\u5408\u3001\u30c7\u30d0\u30a4\u30b9\u306f\u6700\u7d42\u7684\u306b\u4f7f\u7528\u3067\u304d\u306a\u304f\u306a\u308a\u307e\u3059\u3002", - "title": "Android TV / Fire TV\u306e\u901a\u77e5" + "description": "\u3053\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306b\u306f\u3001AndroidTV\u30a2\u30d7\u30ea\u306e\u901a\u77e5\u304c\u5fc5\u8981\u3067\u3059\u3002 \n\nAndroid TV\u306e\u5834\u5408: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nFire TV\u306e\u5834\u5408: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\n\u30eb\u30fc\u30bf\u30fc\u306eDHCP\u4e88\u7d04((DHCP reservation)\u30eb\u30fc\u30bf\u30fc\u306e\u30e6\u30fc\u30b6\u30fc\u30de\u30cb\u30e5\u30a2\u30eb\u3092\u53c2\u7167))\u307e\u305f\u306f\u3001\u30c7\u30d0\u30a4\u30b9\u306b\u9759\u7684IP\u30a2\u30c9\u30ec\u30b9\u3092\u8a2d\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\u305d\u3046\u3067\u306a\u3044\u5834\u5408\u3001\u30c7\u30d0\u30a4\u30b9\u306f\u6700\u7d42\u7684\u306b\u4f7f\u7528\u3067\u304d\u306a\u304f\u306a\u308a\u307e\u3059\u3002" } } } diff --git a/homeassistant/components/nfandroidtv/translations/nl.json b/homeassistant/components/nfandroidtv/translations/nl.json index 10c9f44a94a..5a2342b740d 100644 --- a/homeassistant/components/nfandroidtv/translations/nl.json +++ b/homeassistant/components/nfandroidtv/translations/nl.json @@ -13,8 +13,7 @@ "host": "Host", "name": "Naam" }, - "description": "Raadpleeg de documentatie om er zeker van te zijn dat aan alle vereisten is voldaan.", - "title": "Meldingen voor Android TV / Fire TV" + "description": "Raadpleeg de documentatie om er zeker van te zijn dat aan alle vereisten is voldaan." } } } diff --git a/homeassistant/components/nfandroidtv/translations/no.json b/homeassistant/components/nfandroidtv/translations/no.json index 8d59ca40b0d..73c1a98538f 100644 --- a/homeassistant/components/nfandroidtv/translations/no.json +++ b/homeassistant/components/nfandroidtv/translations/no.json @@ -13,8 +13,7 @@ "host": "Vert", "name": "Navn" }, - "description": "Se dokumentasjonen for \u00e5 sikre at alle krav er oppfylt.", - "title": "Varsler for Android TV / Fire TV" + "description": "Se dokumentasjonen for \u00e5 sikre at alle krav er oppfylt." } } } diff --git a/homeassistant/components/nfandroidtv/translations/pl.json b/homeassistant/components/nfandroidtv/translations/pl.json index a42335ceb10..a050ee07a90 100644 --- a/homeassistant/components/nfandroidtv/translations/pl.json +++ b/homeassistant/components/nfandroidtv/translations/pl.json @@ -13,8 +13,7 @@ "host": "Nazwa hosta lub adres IP", "name": "Nazwa" }, - "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" + "description": "Zapoznaj si\u0119 z dokumentacj\u0105, aby upewni\u0107 si\u0119, \u017ce wszystkie wymagania s\u0105 spe\u0142nione." } } } diff --git a/homeassistant/components/nfandroidtv/translations/pt-BR.json b/homeassistant/components/nfandroidtv/translations/pt-BR.json index f4488408b42..255c67527e9 100644 --- a/homeassistant/components/nfandroidtv/translations/pt-BR.json +++ b/homeassistant/components/nfandroidtv/translations/pt-BR.json @@ -13,8 +13,7 @@ "host": "Nome do host", "name": "Nome" }, - "description": "Consulte a documenta\u00e7\u00e3o para garantir que todos os requisitos sejam atendidos.", - "title": "Notifica\u00e7\u00f5es para Android TV / Fire TV" + "description": "Consulte a documenta\u00e7\u00e3o para garantir que todos os requisitos sejam atendidos." } } } diff --git a/homeassistant/components/nfandroidtv/translations/ru.json b/homeassistant/components/nfandroidtv/translations/ru.json index 12451d4735b..3cd5b134813 100644 --- a/homeassistant/components/nfandroidtv/translations/ru.json +++ b/homeassistant/components/nfandroidtv/translations/ru.json @@ -13,8 +13,7 @@ "host": "\u0425\u043e\u0441\u0442", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "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" + "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." } } } diff --git a/homeassistant/components/nfandroidtv/translations/tr.json b/homeassistant/components/nfandroidtv/translations/tr.json index 835e61eea66..efb33dafb9c 100644 --- a/homeassistant/components/nfandroidtv/translations/tr.json +++ b/homeassistant/components/nfandroidtv/translations/tr.json @@ -13,8 +13,7 @@ "host": "Sunucu", "name": "Ad" }, - "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" + "description": "T\u00fcm gereksinimlerin kar\u015f\u0131land\u0131\u011f\u0131ndan emin olmak i\u00e7in l\u00fctfen belgelere bak\u0131n." } } } diff --git a/homeassistant/components/nfandroidtv/translations/zh-Hant.json b/homeassistant/components/nfandroidtv/translations/zh-Hant.json index 755ccdfeec8..f84b329d74f 100644 --- a/homeassistant/components/nfandroidtv/translations/zh-Hant.json +++ b/homeassistant/components/nfandroidtv/translations/zh-Hant.json @@ -13,8 +13,7 @@ "host": "\u4e3b\u6a5f\u7aef", "name": "\u540d\u7a31" }, - "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" + "description": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u6240\u6709\u5fc5\u8981\u9700\u6c42\u3002" } } } diff --git a/homeassistant/components/nightscout/translations/ca.json b/homeassistant/components/nightscout/translations/ca.json index 21a472680b4..911c75a19de 100644 --- a/homeassistant/components/nightscout/translations/ca.json +++ b/homeassistant/components/nightscout/translations/ca.json @@ -8,7 +8,6 @@ "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/cs.json b/homeassistant/components/nightscout/translations/cs.json index dac121c7fb1..3a4f8bf2b0e 100644 --- a/homeassistant/components/nightscout/translations/cs.json +++ b/homeassistant/components/nightscout/translations/cs.json @@ -8,7 +8,6 @@ "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/de.json b/homeassistant/components/nightscout/translations/de.json index 32bc6653af1..93524c79689 100644 --- a/homeassistant/components/nightscout/translations/de.json +++ b/homeassistant/components/nightscout/translations/de.json @@ -8,7 +8,6 @@ "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/el.json b/homeassistant/components/nightscout/translations/el.json index c484d715e76..c2359686297 100644 --- a/homeassistant/components/nightscout/translations/el.json +++ b/homeassistant/components/nightscout/translations/el.json @@ -8,7 +8,6 @@ "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" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/en.json b/homeassistant/components/nightscout/translations/en.json index baec475fc2d..97ce458c8c0 100644 --- a/homeassistant/components/nightscout/translations/en.json +++ b/homeassistant/components/nightscout/translations/en.json @@ -8,7 +8,6 @@ "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/es.json b/homeassistant/components/nightscout/translations/es.json index 1731cd7f48c..d98b8e345b6 100644 --- a/homeassistant/components/nightscout/translations/es.json +++ b/homeassistant/components/nightscout/translations/es.json @@ -8,7 +8,6 @@ "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "unknown": "Error inesperado" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/et.json b/homeassistant/components/nightscout/translations/et.json index 0d00cebb6a5..7521ee633c5 100644 --- a/homeassistant/components/nightscout/translations/et.json +++ b/homeassistant/components/nightscout/translations/et.json @@ -8,7 +8,6 @@ "invalid_auth": "Tuvastamise viga", "unknown": "Tundmatu viga" }, - "flow_title": "", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/fr.json b/homeassistant/components/nightscout/translations/fr.json index 0b99785652d..9c9cd110990 100644 --- a/homeassistant/components/nightscout/translations/fr.json +++ b/homeassistant/components/nightscout/translations/fr.json @@ -8,7 +8,6 @@ "invalid_auth": "Authentification non valide", "unknown": "Erreur inattendue" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/hu.json b/homeassistant/components/nightscout/translations/hu.json index 569a4f3aca9..85f0976d017 100644 --- a/homeassistant/components/nightscout/translations/hu.json +++ b/homeassistant/components/nightscout/translations/hu.json @@ -8,7 +8,6 @@ "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/id.json b/homeassistant/components/nightscout/translations/id.json index 147c3131213..d9a113784da 100644 --- a/homeassistant/components/nightscout/translations/id.json +++ b/homeassistant/components/nightscout/translations/id.json @@ -8,7 +8,6 @@ "invalid_auth": "Autentikasi tidak valid", "unknown": "Kesalahan yang tidak diharapkan" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/it.json b/homeassistant/components/nightscout/translations/it.json index 8a1bfc826e2..c0438787915 100644 --- a/homeassistant/components/nightscout/translations/it.json +++ b/homeassistant/components/nightscout/translations/it.json @@ -8,7 +8,6 @@ "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/ja.json b/homeassistant/components/nightscout/translations/ja.json index d0a88be1985..211113d943c 100644 --- a/homeassistant/components/nightscout/translations/ja.json +++ b/homeassistant/components/nightscout/translations/ja.json @@ -8,7 +8,6 @@ "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/ko.json b/homeassistant/components/nightscout/translations/ko.json index 1146e6926e3..7574e1f6d47 100644 --- a/homeassistant/components/nightscout/translations/ko.json +++ b/homeassistant/components/nightscout/translations/ko.json @@ -8,7 +8,6 @@ "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/lb.json b/homeassistant/components/nightscout/translations/lb.json index cc1f048e84e..b592e209dd8 100644 --- a/homeassistant/components/nightscout/translations/lb.json +++ b/homeassistant/components/nightscout/translations/lb.json @@ -8,7 +8,6 @@ "invalid_auth": "Ong\u00eblteg Authentifikatioun", "unknown": "Onerwaarte Feeler" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/nl.json b/homeassistant/components/nightscout/translations/nl.json index a9b81e9403e..397150acbde 100644 --- a/homeassistant/components/nightscout/translations/nl.json +++ b/homeassistant/components/nightscout/translations/nl.json @@ -8,7 +8,6 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/no.json b/homeassistant/components/nightscout/translations/no.json index 6a21b13aca7..08f1ae09fee 100644 --- a/homeassistant/components/nightscout/translations/no.json +++ b/homeassistant/components/nightscout/translations/no.json @@ -8,7 +8,6 @@ "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, - "flow_title": "", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/pl.json b/homeassistant/components/nightscout/translations/pl.json index bc70aab3bf8..fe0da7ee321 100644 --- a/homeassistant/components/nightscout/translations/pl.json +++ b/homeassistant/components/nightscout/translations/pl.json @@ -8,7 +8,6 @@ "invalid_auth": "Niepoprawne uwierzytelnienie", "unknown": "Nieoczekiwany b\u0142\u0105d" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/pt-BR.json b/homeassistant/components/nightscout/translations/pt-BR.json index 4c31da85534..b8ebefa1f08 100644 --- a/homeassistant/components/nightscout/translations/pt-BR.json +++ b/homeassistant/components/nightscout/translations/pt-BR.json @@ -8,7 +8,6 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/ru.json b/homeassistant/components/nightscout/translations/ru.json index c7688973c1b..7e24a9cf1f2 100644 --- a/homeassistant/components/nightscout/translations/ru.json +++ b/homeassistant/components/nightscout/translations/ru.json @@ -8,7 +8,6 @@ "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." }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/tr.json b/homeassistant/components/nightscout/translations/tr.json index 8a27b28aa6e..dc134ac4f40 100644 --- a/homeassistant/components/nightscout/translations/tr.json +++ b/homeassistant/components/nightscout/translations/tr.json @@ -8,7 +8,6 @@ "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "unknown": "Beklenmeyen hata" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/uk.json b/homeassistant/components/nightscout/translations/uk.json index 6504b00eb88..f23c457f553 100644 --- a/homeassistant/components/nightscout/translations/uk.json +++ b/homeassistant/components/nightscout/translations/uk.json @@ -8,7 +8,6 @@ "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/nightscout/translations/zh-Hant.json b/homeassistant/components/nightscout/translations/zh-Hant.json index 2ad1c4fde39..cbf1a33cde5 100644 --- a/homeassistant/components/nightscout/translations/zh-Hant.json +++ b/homeassistant/components/nightscout/translations/zh-Hant.json @@ -8,7 +8,6 @@ "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, - "flow_title": "Nightscout", "step": { "user": { "data": { diff --git a/homeassistant/components/niko_home_control/light.py b/homeassistant/components/niko_home_control/light.py index ef3efec9b71..f4d62fd6dc2 100644 --- a/homeassistant/components/niko_home_control/light.py +++ b/homeassistant/components/niko_home_control/light.py @@ -8,7 +8,7 @@ import nikohomecontrol import voluptuous as vol # Import the device class from the component that you want to support -from homeassistant.components.light import ATTR_BRIGHTNESS, PLATFORM_SCHEMA, LightEntity +from homeassistant.components.light import PLATFORM_SCHEMA, ColorMode, LightEntity from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady @@ -51,6 +51,9 @@ async def async_setup_platform( class NikoHomeControlLight(LightEntity): """Representation of an Niko Light.""" + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + def __init__(self, light, data): """Set up the Niko Home Control light platform.""" self._data = data @@ -58,7 +61,6 @@ class NikoHomeControlLight(LightEntity): self._unique_id = f"light-{light.id}" self._name = light.name self._state = light.is_on - self._brightness = None @property def unique_id(self): @@ -70,11 +72,6 @@ class NikoHomeControlLight(LightEntity): """Return the display name of this light.""" return self._name - @property - def brightness(self): - """Return the brightness of the light.""" - return self._brightness - @property def is_on(self): """Return true if light is on.""" @@ -82,7 +79,6 @@ class NikoHomeControlLight(LightEntity): def turn_on(self, **kwargs): """Instruct the light to turn on.""" - self._light.brightness = kwargs.get(ATTR_BRIGHTNESS, 255) _LOGGER.debug("Turn on: %s", self.name) self._light.turn_on() diff --git a/homeassistant/components/nina/translations/nl.json b/homeassistant/components/nina/translations/nl.json index 3531cd8bb72..4b0100f9f07 100644 --- a/homeassistant/components/nina/translations/nl.json +++ b/homeassistant/components/nina/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/nmap_tracker/translations/ar.json b/homeassistant/components/nmap_tracker/translations/ar.json index 9f223966416..620adf5b41c 100644 --- a/homeassistant/components/nmap_tracker/translations/ar.json +++ b/homeassistant/components/nmap_tracker/translations/ar.json @@ -16,8 +16,7 @@ "step": { "init": { "data": { - "interval_seconds": "\u0641\u062a\u0631\u0629 \u062a\u0643\u0631\u0627\u0631 \u0627\u0644\u0628\u062d\u062b", - "track_new_devices": "\u062a\u062a\u0628\u0639 \u0627\u0644\u0623\u062c\u0647\u0632\u0629 \u0627\u0644\u062c\u062f\u064a\u062f\u0629" + "interval_seconds": "\u0641\u062a\u0631\u0629 \u062a\u0643\u0631\u0627\u0631 \u0627\u0644\u0628\u062d\u062b" } } } diff --git a/homeassistant/components/nmap_tracker/translations/ca.json b/homeassistant/components/nmap_tracker/translations/ca.json index e72c03cc63a..fd0cbadf71a 100644 --- a/homeassistant/components/nmap_tracker/translations/ca.json +++ b/homeassistant/components/nmap_tracker/translations/ca.json @@ -30,8 +30,7 @@ "home_interval": "Nombre m\u00ednim de minuts entre escanejos de dispositius actius (conserva la bateria)", "hosts": "Adreces de xarxa a escanejar (separades per comes)", "interval_seconds": "Interval d'escaneig", - "scan_options": "Opcions de configuraci\u00f3 d'escaneig d'Nmap en brut", - "track_new_devices": "Segueix dispositius nous" + "scan_options": "Opcions de configuraci\u00f3 d'escaneig d'Nmap en brut" }, "description": "Configura els amfitrions a explorar per Nmap. L'adre\u00e7a de xarxa i les exclusions poden ser adreces IP (192.168.1.1), xarxes IP (192.168.0.0/24) o intervals IP (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/cs.json b/homeassistant/components/nmap_tracker/translations/cs.json index ac5f913d8e6..95f0bfc3ae8 100644 --- a/homeassistant/components/nmap_tracker/translations/cs.json +++ b/homeassistant/components/nmap_tracker/translations/cs.json @@ -8,8 +8,7 @@ "step": { "init": { "data": { - "interval_seconds": "Interval skenov\u00e1n\u00ed", - "track_new_devices": "Sledovat nov\u00e1 za\u0159\u00edzen\u00ed" + "interval_seconds": "Interval skenov\u00e1n\u00ed" } } } diff --git a/homeassistant/components/nmap_tracker/translations/de.json b/homeassistant/components/nmap_tracker/translations/de.json index 48f06bf320b..cfab5b828fd 100644 --- a/homeassistant/components/nmap_tracker/translations/de.json +++ b/homeassistant/components/nmap_tracker/translations/de.json @@ -30,8 +30,7 @@ "home_interval": "Mindestanzahl von Minuten zwischen den Scans aktiver Ger\u00e4te (Batterie schonen)", "hosts": "Netzwerkadressen (kommagetrennt) zum Scannen", "interval_seconds": "Scanintervall", - "scan_options": "Raw konfigurierbare Scan-Optionen f\u00fcr Nmap", - "track_new_devices": "Neue Ger\u00e4te verfolgen" + "scan_options": "Raw konfigurierbare Scan-Optionen f\u00fcr Nmap" }, "description": "Konfiguriere die Hosts, die von Nmap gescannt werden sollen. Netzwerkadresse und Ausschl\u00fcsse k\u00f6nnen IP-Adressen (192.168.1.1), IP-Netzwerke (192.168.0.0/24) oder IP-Bereiche (192.168.1.0-32) sein." } diff --git a/homeassistant/components/nmap_tracker/translations/el.json b/homeassistant/components/nmap_tracker/translations/el.json index 202bc8e0722..fe1a448a8be 100644 --- a/homeassistant/components/nmap_tracker/translations/el.json +++ b/homeassistant/components/nmap_tracker/translations/el.json @@ -30,8 +30,7 @@ "home_interval": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03c9\u03bd \u03c3\u03b1\u03c1\u03ce\u03c3\u03b5\u03c9\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03ce\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd (\u03b4\u03b9\u03b1\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1\u03c2)", "hosts": "\u0394\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 (\u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1) \u03b3\u03b9\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7", "interval_seconds": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2", - "scan_options": "\u0391\u03ba\u03b1\u03c4\u03ad\u03c1\u03b3\u03b1\u03c3\u03c4\u03b5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b9\u03bc\u03b5\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Nmap", - "track_new_devices": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03bd\u03ad\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd" + "scan_options": "\u0391\u03ba\u03b1\u03c4\u03ad\u03c1\u03b3\u03b1\u03c3\u03c4\u03b5\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b9\u03bc\u03b5\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Nmap" }, "description": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03ce\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf Nmap. \u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03ba\u03b1\u03b9 \u03bf\u03b9 \u03b5\u03be\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03b9\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u0394\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 IP (192.168.1.1), \u0394\u03af\u03ba\u03c4\u03c5\u03b1 IP (192.168.0.0/24) \u03ae \u0395\u03cd\u03c1\u03bf\u03c2 IP (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/en.json b/homeassistant/components/nmap_tracker/translations/en.json index ee615b87e91..ae4175e0f14 100644 --- a/homeassistant/components/nmap_tracker/translations/en.json +++ b/homeassistant/components/nmap_tracker/translations/en.json @@ -30,8 +30,7 @@ "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", - "track_new_devices": "Track new devices" + "scan_options": "Raw configurable scan options for Nmap" }, "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/es.json b/homeassistant/components/nmap_tracker/translations/es.json index 212b56a9606..9b943edb310 100644 --- a/homeassistant/components/nmap_tracker/translations/es.json +++ b/homeassistant/components/nmap_tracker/translations/es.json @@ -11,7 +11,7 @@ "data": { "exclude": "Direcciones de red (separadas por comas) para excluir del escaneo", "home_interval": "N\u00famero m\u00ednimo de minutos entre los escaneos de los dispositivos activos (preservar la bater\u00eda)", - "hosts": "Direcciones de red (separadas por comas) para escanear", + "hosts": "Direcciones de red a escanear (separadas por comas)", "scan_options": "Opciones de escaneo configurables sin procesar para Nmap" }, "description": "Configure los hosts que ser\u00e1n escaneados por Nmap. Las direcciones de red y los excluidos pueden ser direcciones IP (192.168.1.1), redes IP (192.168.0.0/24) o rangos IP (192.168.1.0-32)." @@ -28,10 +28,9 @@ "consider_home": "Segundos de espera hasta que se marca un dispositivo de seguimiento como no en casa despu\u00e9s de no ser visto.", "exclude": "Direcciones de red (separadas por comas) para excluir del escaneo", "home_interval": "N\u00famero m\u00ednimo de minutos entre los escaneos de los dispositivos activos (preservar la bater\u00eda)", - "hosts": "Direcciones de red (separadas por comas) para escanear", + "hosts": "Direcciones de red a escanear (separadas por comas)", "interval_seconds": "Intervalo de exploraci\u00f3n", - "scan_options": "Opciones de escaneo configurables sin procesar para Nmap", - "track_new_devices": "Seguimiento de nuevos dispositivos" + "scan_options": "Opciones de escaneo configurables sin procesar para Nmap" }, "description": "Configure los hosts que ser\u00e1n escaneados por Nmap. Las direcciones de red y los excluidos pueden ser direcciones IP (192.168.1.1), redes IP (192.168.0.0/24) o rangos IP (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/et.json b/homeassistant/components/nmap_tracker/translations/et.json index ac23cb7004f..e0b922fd007 100644 --- a/homeassistant/components/nmap_tracker/translations/et.json +++ b/homeassistant/components/nmap_tracker/translations/et.json @@ -30,8 +30,7 @@ "home_interval": "Minimaalne sk\u00e4nnimiste intervall minutites (eeldus on aku s\u00e4\u00e4stmine)", "hosts": "V\u00f5rguaadresside vahemik (komaga eraldatud)", "interval_seconds": "P\u00e4ringute intervall", - "scan_options": "Vaikimisi Nmap sk\u00e4nnimise valikud", - "track_new_devices": "Uute seadmete j\u00e4lgimine" + "scan_options": "Vaikimisi Nmap sk\u00e4nnimise valikud" }, "description": "Vali Nmap poolt sk\u00e4nnitavad hostid. Valikuks on IP aadressid (192.168.1.1), v\u00f5rgud (192.168.0.0/24) v\u00f5i IP vahemikud (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/fr.json b/homeassistant/components/nmap_tracker/translations/fr.json index 0a4e75a9813..b0362217d33 100644 --- a/homeassistant/components/nmap_tracker/translations/fr.json +++ b/homeassistant/components/nmap_tracker/translations/fr.json @@ -30,8 +30,7 @@ "home_interval": "Nombre minimum de minutes entre les analyses des appareils actifs (pr\u00e9server la batterie)", "hosts": "Adresses r\u00e9seau (s\u00e9par\u00e9es par des virgules) \u00e0 analyser", "interval_seconds": "Intervalle d\u2019analyse", - "scan_options": "Options d'analyse brutes configurables pour Nmap", - "track_new_devices": "Suivre les nouveaux appareils" + "scan_options": "Options d'analyse brutes configurables pour Nmap" }, "description": "Configurer les h\u00f4tes \u00e0 analyser par Nmap. L'adresse r\u00e9seau et les exclusions peuvent \u00eatre des adresses IP (192.168.1.1), des r\u00e9seaux IP (192.168.0.0/24) ou des plages IP (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/he.json b/homeassistant/components/nmap_tracker/translations/he.json index d57ca363944..22bc8fa7080 100644 --- a/homeassistant/components/nmap_tracker/translations/he.json +++ b/homeassistant/components/nmap_tracker/translations/he.json @@ -29,8 +29,7 @@ "home_interval": "\u05de\u05e1\u05e4\u05e8 \u05de\u05d9\u05e0\u05d9\u05de\u05dc\u05d9 \u05e9\u05dc \u05d3\u05e7\u05d5\u05ea \u05d1\u05d9\u05df \u05e1\u05e8\u05d9\u05e7\u05d5\u05ea \u05e9\u05dc \u05d4\u05ea\u05e7\u05e0\u05d9\u05dd \u05e4\u05e2\u05d9\u05dc\u05d9\u05dd (\u05e9\u05d9\u05de\u05d5\u05e8 \u05e1\u05d5\u05dc\u05dc\u05d4)", "hosts": "\u05db\u05ea\u05d5\u05d1\u05d5\u05ea \u05e8\u05e9\u05ea (\u05de\u05d5\u05e4\u05e8\u05d3\u05d5\u05ea \u05d1\u05e4\u05e1\u05d9\u05e7) \u05dc\u05e1\u05e8\u05d9\u05e7\u05d4", "interval_seconds": "\u05de\u05e8\u05d5\u05d5\u05d7 \u05d6\u05de\u05df \u05dc\u05e1\u05e8\u05d9\u05e7\u05d4", - "scan_options": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e1\u05e8\u05d9\u05e7\u05d4 \u05d2\u05d5\u05dc\u05de\u05d9\u05d5\u05ea \u05d4\u05e0\u05d9\u05ea\u05e0\u05d5\u05ea \u05dc\u05d4\u05d2\u05d3\u05e8\u05d4 \u05e2\u05d1\u05d5\u05e8 Nmap", - "track_new_devices": "\u05de\u05e2\u05e7\u05d1 \u05d0\u05d7\u05e8 \u05d4\u05ea\u05e7\u05e0\u05d9\u05dd \u05d7\u05d3\u05e9\u05d9\u05dd" + "scan_options": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e1\u05e8\u05d9\u05e7\u05d4 \u05d2\u05d5\u05dc\u05de\u05d9\u05d5\u05ea \u05d4\u05e0\u05d9\u05ea\u05e0\u05d5\u05ea \u05dc\u05d4\u05d2\u05d3\u05e8\u05d4 \u05e2\u05d1\u05d5\u05e8 Nmap" }, "description": "\u05e7\u05d1\u05e2 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc \u05de\u05d0\u05e8\u05d7\u05d9\u05dd \u05e9\u05d9\u05e1\u05e8\u05e7\u05d5 \u05e2\u05dc \u05d9\u05d3\u05d9 Nmap. \u05db\u05ea\u05d5\u05d1\u05ea \u05e8\u05e9\u05ea \u05d5\u05d0\u05d9 \u05d4\u05db\u05dc\u05dc\u05d4 \u05d9\u05db\u05d5\u05dc \u05dc\u05d4\u05d9\u05d5\u05ea \u05db\u05ea\u05d5\u05d1\u05d5\u05ea IP (192.168.1.1), \u05e8\u05e9\u05ea\u05d5\u05ea IP (192.168.0.0/24) \u05d0\u05d5 \u05d8\u05d5\u05d5\u05d7\u05d9 IP (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/hu.json b/homeassistant/components/nmap_tracker/translations/hu.json index f54cf208e92..6c802e6d25c 100644 --- a/homeassistant/components/nmap_tracker/translations/hu.json +++ b/homeassistant/components/nmap_tracker/translations/hu.json @@ -30,8 +30,7 @@ "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", "interval_seconds": "Szkennel\u00e9si intervallum", - "scan_options": "Nyersen konfigur\u00e1lhat\u00f3 beolvas\u00e1si lehet\u0151s\u00e9gek az Nmap sz\u00e1m\u00e1ra", - "track_new_devices": "\u00daj eszk\u00f6z\u00f6k nyomon k\u00f6vet\u00e9se" + "scan_options": "Nyersen konfigur\u00e1lhat\u00f3 beolvas\u00e1si 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/id.json b/homeassistant/components/nmap_tracker/translations/id.json index e1894aff49b..fae1fdfe1bd 100644 --- a/homeassistant/components/nmap_tracker/translations/id.json +++ b/homeassistant/components/nmap_tracker/translations/id.json @@ -30,8 +30,7 @@ "home_interval": "Jumlah menit minimum antara pemindaian perangkat aktif (menghemat baterai)", "hosts": "Alamat jaringan (dipisahkan koma) untuk pemindaian", "interval_seconds": "Interval pindai", - "scan_options": "Opsi pemindaian mentah yang dapat dikonfigurasi untuk Nmap", - "track_new_devices": "Lacak perangkat baru" + "scan_options": "Opsi pemindaian mentah yang dapat dikonfigurasi untuk Nmap" }, "description": "Konfigurasikan host untuk dipindai oleh Nmap. Alamat jaringan dan pengecualian dapat berupa alamat IP (192.168.1.1), jaringan IP (192.168.0.0/24), atau rentang IP (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/it.json b/homeassistant/components/nmap_tracker/translations/it.json index 8a7de165778..e6d5a41656f 100644 --- a/homeassistant/components/nmap_tracker/translations/it.json +++ b/homeassistant/components/nmap_tracker/translations/it.json @@ -30,8 +30,7 @@ "home_interval": "Numero minimo di minuti tra le scansioni dei dispositivi attivi (preserva la batteria)", "hosts": "Indirizzi di rete (separati da virgole) da scansionare", "interval_seconds": "Intervallo di scansione", - "scan_options": "Opzioni di scansione configurabili non elaborate per Nmap", - "track_new_devices": "Traccia nuovi dispositivi" + "scan_options": "Opzioni di scansione configurabili non elaborate per Nmap" }, "description": "Configura gli host da scansionare con Nmap. L'indirizzo di rete e le esclusioni possono essere indirizzi IP (192.168.1.1), reti IP (192.168.0.0/24) o intervalli IP (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/ja.json b/homeassistant/components/nmap_tracker/translations/ja.json index 2e60a814468..60de8477859 100644 --- a/homeassistant/components/nmap_tracker/translations/ja.json +++ b/homeassistant/components/nmap_tracker/translations/ja.json @@ -30,8 +30,7 @@ "home_interval": "\u30a2\u30af\u30c6\u30a3\u30d6\u306a\u30c7\u30d0\u30a4\u30b9\u306e\u30b9\u30ad\u30e3\u30f3\u9593\u9694(\u5206)\u306e\u6700\u5c0f\u6642\u9593(\u30d0\u30c3\u30c6\u30ea\u30fc\u3092\u7bc0\u7d04)", "hosts": "\u30b9\u30ad\u30e3\u30f3\u3059\u308b\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30a2\u30c9\u30ec\u30b9(\u30b3\u30f3\u30de\u533a\u5207\u308a)", "interval_seconds": "\u30b9\u30ad\u30e3\u30f3\u9593\u9694", - "scan_options": "Nmap\u306b\u672a\u52a0\u5de5\u3067\u305d\u306e\u307e\u307e\u6e21\u3055\u308c\u308b\u30b9\u30ad\u30e3\u30f3\u8a2d\u5b9a\u306e\u30aa\u30d7\u30b7\u30e7\u30f3", - "track_new_devices": "\u65b0\u3057\u3044\u30c7\u30d0\u30a4\u30b9\u306e\u8ffd\u8de1" + "scan_options": "Nmap\u306b\u672a\u52a0\u5de5\u3067\u305d\u306e\u307e\u307e\u6e21\u3055\u308c\u308b\u30b9\u30ad\u30e3\u30f3\u8a2d\u5b9a\u306e\u30aa\u30d7\u30b7\u30e7\u30f3" }, "description": "Nmap\u3067\u30b9\u30ad\u30e3\u30f3\u3055\u308c\u308b\u30db\u30b9\u30c8\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u3002\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30a2\u30c9\u30ec\u30b9\u304a\u3088\u3073\u9664\u5916\u5bfe\u8c61\u306f\u3001IP\u30a2\u30c9\u30ec\u30b9(192.168.1.1)\u3001IP\u30cd\u30c3\u30c8\u30ef\u30fc\u30af(192.168.0.0/24)\u3001\u307e\u305f\u306f\u3001IP\u7bc4\u56f2(192.168.1.0-32)\u3067\u3059\u3002" } diff --git a/homeassistant/components/nmap_tracker/translations/nl.json b/homeassistant/components/nmap_tracker/translations/nl.json index 8385aca1ffe..6221162035a 100644 --- a/homeassistant/components/nmap_tracker/translations/nl.json +++ b/homeassistant/components/nmap_tracker/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { "invalid_hosts": "Ongeldige hosts" @@ -30,8 +30,7 @@ "home_interval": "Minimum aantal minuten tussen scans van actieve apparaten (batterij sparen)", "hosts": "Netwerkadressen (gescheiden door komma's) om te scannen", "interval_seconds": "Scaninterval", - "scan_options": "Ruwe configureerbare scanopties voor Nmap", - "track_new_devices": "Volg nieuwe apparaten" + "scan_options": "Ruwe configureerbare scanopties voor Nmap" }, "description": "Configureer hosts die moeten worden gescand door Nmap. Netwerkadres en uitsluitingen kunnen IP-adressen (192.168.1.1), IP-netwerken (192.168.0.0/24) of IP-bereiken (192.168.1.0-32) zijn." } diff --git a/homeassistant/components/nmap_tracker/translations/no.json b/homeassistant/components/nmap_tracker/translations/no.json index 1de32bed121..de8ca5898aa 100644 --- a/homeassistant/components/nmap_tracker/translations/no.json +++ b/homeassistant/components/nmap_tracker/translations/no.json @@ -30,8 +30,7 @@ "home_interval": "Minimum antall minutter mellom skanninger av aktive enheter (lagre batteri)", "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" + "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)." } diff --git a/homeassistant/components/nmap_tracker/translations/pl.json b/homeassistant/components/nmap_tracker/translations/pl.json index 98f27dcdd4b..98b16043d4d 100644 --- a/homeassistant/components/nmap_tracker/translations/pl.json +++ b/homeassistant/components/nmap_tracker/translations/pl.json @@ -30,8 +30,7 @@ "home_interval": "Minimalna liczba minut mi\u0119dzy skanami aktywnych urz\u0105dze\u0144 (oszcz\u0119dzanie baterii)", "hosts": "Adresy sieciowe (oddzielone przecinkami) do skanowania", "interval_seconds": "Cz\u0119stotliwo\u015b\u0107 skanowania", - "scan_options": "Surowe konfigurowalne opcje skanowania dla Nmap", - "track_new_devices": "\u015aled\u017a nowe urz\u0105dzenia" + "scan_options": "Surowe konfigurowalne opcje skanowania dla Nmap" }, "description": "Skonfiguruj hosta do skanowania przez Nmap. Adresy sieciowe i te wykluczone mog\u0105 by\u0107 adresami IP (192.168.1.1), sieciami IP (192.168.0.0/24) lub zakresami IP (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/pt-BR.json b/homeassistant/components/nmap_tracker/translations/pt-BR.json index a80213ce5d6..786d5d4df0b 100644 --- a/homeassistant/components/nmap_tracker/translations/pt-BR.json +++ b/homeassistant/components/nmap_tracker/translations/pt-BR.json @@ -30,8 +30,7 @@ "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 digitalizar", "interval_seconds": "Intervalo de varredura", - "scan_options": "Op\u00e7\u00f5es de escaneamento bruto configur\u00e1veis para Nmap", - "track_new_devices": "Rastrear novos dispositivos" + "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)." } diff --git a/homeassistant/components/nmap_tracker/translations/ru.json b/homeassistant/components/nmap_tracker/translations/ru.json index ba143e20d01..125f394fc23 100644 --- a/homeassistant/components/nmap_tracker/translations/ru.json +++ b/homeassistant/components/nmap_tracker/translations/ru.json @@ -30,8 +30,7 @@ "home_interval": "\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043c\u0438\u043d\u0443\u0442 \u043c\u0435\u0436\u0434\u0443 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043c\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 (\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u044f \u0437\u0430\u0440\u044f\u0434\u0430 \u0431\u0430\u0442\u0430\u0440\u0435\u0438)", "hosts": "\u0421\u0435\u0442\u0435\u0432\u044b\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u0434\u043b\u044f \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f (\u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u044f\u0442\u0443\u044e)", "interval_seconds": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f", - "scan_options": "\u041d\u0435\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f Nmap", - "track_new_devices": "\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" + "scan_options": "\u041d\u0435\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f Nmap" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0445\u043e\u0441\u0442\u043e\u0432 \u0434\u043b\u044f \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f Nmap. \u0421\u0435\u0442\u0435\u0432\u044b\u043c \u0430\u0434\u0440\u0435\u0441\u043e\u043c \u0438 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441\u0430 (192.168.1.1), IP-\u0441\u0435\u0442\u0438 (192.168.0.0/24) \u0438\u043b\u0438 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u044b IP-\u0430\u0434\u0440\u0435\u0441\u043e\u0432 (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/tr.json b/homeassistant/components/nmap_tracker/translations/tr.json index 8e5dadb8100..f4d984f2bdb 100644 --- a/homeassistant/components/nmap_tracker/translations/tr.json +++ b/homeassistant/components/nmap_tracker/translations/tr.json @@ -30,8 +30,7 @@ "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", - "scan_options": "Nmap i\u00e7in ham yap\u0131land\u0131r\u0131labilir tarama se\u00e7enekleri", - "track_new_devices": "Yeni cihazlar\u0131 izle" + "scan_options": "Nmap i\u00e7in ham yap\u0131land\u0131r\u0131labilir tarama se\u00e7enekleri" }, "description": "Nmap taraf\u0131ndan taranacak ana bilgisayarlar\u0131 yap\u0131land\u0131r\u0131n. A\u011f adresi ve hari\u00e7 tutulanlar, IP Adresleri (192.168.1.1), IP A\u011flar\u0131 (192.168.0.0/24) veya IP Aral\u0131klar\u0131 (192.168.1.0-32) olabilir." } diff --git a/homeassistant/components/nmap_tracker/translations/zh-Hans.json b/homeassistant/components/nmap_tracker/translations/zh-Hans.json index 5b1be2f497d..31f4568a848 100644 --- a/homeassistant/components/nmap_tracker/translations/zh-Hans.json +++ b/homeassistant/components/nmap_tracker/translations/zh-Hans.json @@ -27,8 +27,7 @@ "home_interval": "\u626b\u63cf\u8bbe\u5907\u7684\u6700\u5c0f\u95f4\u9694\u5206\u949f\u6570\uff08\u7528\u4e8e\u8282\u7701\u7535\u91cf\uff09", "hosts": "\u8981\u626b\u63cf\u7684\u7f51\u7edc\u5730\u5740\uff08\u4ee5\u9017\u53f7\u5206\u9694\uff09", "interval_seconds": "\u626b\u63cf\u95f4\u9694\uff08\u79d2\uff09", - "scan_options": "Nmap \u7684\u539f\u59cb\u53ef\u914d\u7f6e\u626b\u63cf\u9009\u9879", - "track_new_devices": "\u8ddf\u8e2a\u65b0\u8bbe\u5907" + "scan_options": "Nmap \u7684\u539f\u59cb\u53ef\u914d\u7f6e\u626b\u63cf\u9009\u9879" }, "description": "\u914d\u7f6e\u901a\u8fc7 Nmap \u626b\u63cf\u7684\u4e3b\u673a\u3002\u7f51\u7edc\u5730\u5740\u548c\u6392\u9664\u9879\u53ef\u4ee5\u662f IP \u5730\u5740 (192.168.1.1)\u3001IP \u5730\u5740\u5757 (192.168.0.0/24) \u6216 IP \u8303\u56f4 (192.168.1.0-32)\u3002" } diff --git a/homeassistant/components/nmap_tracker/translations/zh-Hant.json b/homeassistant/components/nmap_tracker/translations/zh-Hant.json index 65b0b7afed4..acee979e8b8 100644 --- a/homeassistant/components/nmap_tracker/translations/zh-Hant.json +++ b/homeassistant/components/nmap_tracker/translations/zh-Hant.json @@ -30,8 +30,7 @@ "home_interval": "\u6383\u63cf\u6d3b\u52d5\u88dd\u7f6e\u7684\u6700\u4f4e\u9593\u9694\u5206\u9418\uff08\u8003\u91cf\u7701\u96fb\uff09", "hosts": "\u6240\u8981\u6383\u63cf\u7684\u7db2\u8def\u4f4d\u5740\uff08\u4ee5\u9017\u865f\u5206\u9694\uff09", "interval_seconds": "\u6383\u63cf\u9593\u8ddd", - "scan_options": "Nmap \u539f\u59cb\u8a2d\u5b9a\u6383\u63cf\u9078\u9805", - "track_new_devices": "\u8ffd\u8e64\u65b0\u88dd\u7f6e" + "scan_options": "Nmap \u539f\u59cb\u8a2d\u5b9a\u6383\u63cf\u9078\u9805" }, "description": "\u8a2d\u5b9a Nmap \u6383\u63cf\u4e3b\u6a5f\u3002\u7db2\u8def\u4f4d\u5740\u8207\u6392\u9664\u4f4d\u5740\u53ef\u4ee5\u662f IP \u4f4d\u5740\uff08192.168.1.1\uff09\u3001IP \u7db2\u8def\uff08192.168.0.0/24\uff09\u6216 IP \u7bc4\u570d\uff08192.168.1.0-32\uff09\u3002" } diff --git a/homeassistant/components/no_ip/__init__.py b/homeassistant/components/no_ip/__init__.py index 5340e926e62..a903b1af5b6 100644 --- a/homeassistant/components/no_ip/__init__.py +++ b/homeassistant/components/no_ip/__init__.py @@ -16,6 +16,7 @@ from homeassistant.helpers.aiohttp_client import ( async_get_clientsession, ) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -76,7 +77,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Update the NO-IP entry.""" await _update_no_ip(hass, session, domain, auth_str, timeout) - hass.helpers.event.async_track_time_interval(update_domain_interval, INTERVAL) + async_track_time_interval(hass, update_domain_interval, INTERVAL) return True diff --git a/homeassistant/components/notion/translations/bg.json b/homeassistant/components/notion/translations/bg.json index ef4a919401e..1df2ad13a33 100644 --- a/homeassistant/components/notion/translations/bg.json +++ b/homeassistant/components/notion/translations/bg.json @@ -4,7 +4,6 @@ "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": { - "no_devices": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043f\u0440\u043e\u0444\u0438\u043b\u0430", "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, "step": { diff --git a/homeassistant/components/notion/translations/ca.json b/homeassistant/components/notion/translations/ca.json index 51ca461f854..4047a5dbd30 100644 --- a/homeassistant/components/notion/translations/ca.json +++ b/homeassistant/components/notion/translations/ca.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", - "no_devices": "No s'han trobat dispositius al compte", "unknown": "Error inesperat" }, "step": { diff --git a/homeassistant/components/notion/translations/cs.json b/homeassistant/components/notion/translations/cs.json index 53cae8ab73b..57ce0a06b95 100644 --- a/homeassistant/components/notion/translations/cs.json +++ b/homeassistant/components/notion/translations/cs.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", - "no_devices": "V \u00fa\u010dtu nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "step": { diff --git a/homeassistant/components/notion/translations/da.json b/homeassistant/components/notion/translations/da.json index e8a1d35417a..add31a51324 100644 --- a/homeassistant/components/notion/translations/da.json +++ b/homeassistant/components/notion/translations/da.json @@ -3,9 +3,6 @@ "abort": { "already_configured": "Dette brugernavn er allerede i brug." }, - "error": { - "no_devices": "Ingen enheder fundet i konto" - }, "step": { "user": { "data": { diff --git a/homeassistant/components/notion/translations/de.json b/homeassistant/components/notion/translations/de.json index 59ab1fdc1be..6ee08a8a732 100644 --- a/homeassistant/components/notion/translations/de.json +++ b/homeassistant/components/notion/translations/de.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Ung\u00fcltige Authentifizierung", - "no_devices": "Keine Ger\u00e4te im Konto gefunden", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/notion/translations/el.json b/homeassistant/components/notion/translations/el.json index ee39e67ae73..89c175337ce 100644 --- a/homeassistant/components/notion/translations/el.json +++ b/homeassistant/components/notion/translations/el.json @@ -6,7 +6,6 @@ }, "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_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { diff --git a/homeassistant/components/notion/translations/en.json b/homeassistant/components/notion/translations/en.json index afd58a4d404..0eb4689bce6 100644 --- a/homeassistant/components/notion/translations/en.json +++ b/homeassistant/components/notion/translations/en.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Invalid authentication", - "no_devices": "No devices found in account", "unknown": "Unexpected error" }, "step": { diff --git a/homeassistant/components/notion/translations/es-419.json b/homeassistant/components/notion/translations/es-419.json index 489df80f736..ec520bd89df 100644 --- a/homeassistant/components/notion/translations/es-419.json +++ b/homeassistant/components/notion/translations/es-419.json @@ -3,9 +3,6 @@ "abort": { "already_configured": "Este nombre de usuario ya est\u00e1 en uso." }, - "error": { - "no_devices": "No se han encontrado dispositivos en la cuenta." - }, "step": { "user": { "data": { diff --git a/homeassistant/components/notion/translations/es.json b/homeassistant/components/notion/translations/es.json index b2012363f63..62a21b06022 100644 --- a/homeassistant/components/notion/translations/es.json +++ b/homeassistant/components/notion/translations/es.json @@ -1,12 +1,11 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", - "no_devices": "No se han encontrado dispositivos en la cuenta", "unknown": "Error inesperado" }, "step": { diff --git a/homeassistant/components/notion/translations/et.json b/homeassistant/components/notion/translations/et.json index 7639901201d..488e66c57a3 100644 --- a/homeassistant/components/notion/translations/et.json +++ b/homeassistant/components/notion/translations/et.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Tuvastamise viga", - "no_devices": "Kontolt ei leitud \u00fchtegi seadet", "unknown": "Ootamatu t\u00f5rge" }, "step": { diff --git a/homeassistant/components/notion/translations/fr.json b/homeassistant/components/notion/translations/fr.json index c5c0145e972..2340ad7620e 100644 --- a/homeassistant/components/notion/translations/fr.json +++ b/homeassistant/components/notion/translations/fr.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Authentification non valide", - "no_devices": "Aucun appareil trouv\u00e9 sur le compte", "unknown": "Erreur inattendue" }, "step": { diff --git a/homeassistant/components/notion/translations/hr.json b/homeassistant/components/notion/translations/hr.json index 237845502dd..75f9880c4e0 100644 --- a/homeassistant/components/notion/translations/hr.json +++ b/homeassistant/components/notion/translations/hr.json @@ -1,8 +1,5 @@ { "config": { - "error": { - "no_devices": "Nisu prona\u0111eni ure\u0111aji na ra\u010dunu" - }, "step": { "user": { "data": { diff --git a/homeassistant/components/notion/translations/hu.json b/homeassistant/components/notion/translations/hu.json index ef10a451a84..82e7df49eed 100644 --- a/homeassistant/components/notion/translations/hu.json +++ b/homeassistant/components/notion/translations/hu.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", - "no_devices": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a fi\u00f3kban", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { diff --git a/homeassistant/components/notion/translations/id.json b/homeassistant/components/notion/translations/id.json index bf8cb8e1186..8191e9bc85a 100644 --- a/homeassistant/components/notion/translations/id.json +++ b/homeassistant/components/notion/translations/id.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Autentikasi tidak valid", - "no_devices": "Tidak ada perangkat yang ditemukan di akun", "unknown": "Kesalahan yang tidak diharapkan" }, "step": { diff --git a/homeassistant/components/notion/translations/it.json b/homeassistant/components/notion/translations/it.json index 811884dddb4..48cda247e03 100644 --- a/homeassistant/components/notion/translations/it.json +++ b/homeassistant/components/notion/translations/it.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Autenticazione non valida", - "no_devices": "Nessun dispositivo trovato nell'account", "unknown": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/notion/translations/ja.json b/homeassistant/components/notion/translations/ja.json index fb764116ecf..8cd2a58e1e0 100644 --- a/homeassistant/components/notion/translations/ja.json +++ b/homeassistant/components/notion/translations/ja.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", - "no_devices": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { diff --git a/homeassistant/components/notion/translations/ko.json b/homeassistant/components/notion/translations/ko.json index b5c7cadbe9b..991553a1531 100644 --- a/homeassistant/components/notion/translations/ko.json +++ b/homeassistant/components/notion/translations/ko.json @@ -4,10 +4,12 @@ "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "no_devices": "\uacc4\uc815\uc5d0 \ub4f1\ub85d\ub41c \uae30\uae30\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { + "reauth_confirm": { + "description": "{username} \uc758 \ube44\ubc00\ubc88\ud638\ub97c \ub2e4\uc2dc \uc785\ub825\ud558\uc138\uc694." + }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", diff --git a/homeassistant/components/notion/translations/lb.json b/homeassistant/components/notion/translations/lb.json index a1bc1601e00..9a7ca2855de 100644 --- a/homeassistant/components/notion/translations/lb.json +++ b/homeassistant/components/notion/translations/lb.json @@ -4,8 +4,7 @@ "already_configured": "Kont ass scho konfigur\u00e9iert" }, "error": { - "invalid_auth": "Ong\u00eblteg Authentifikatioun", - "no_devices": "Keng Apparater am Kont fonnt" + "invalid_auth": "Ong\u00eblteg Authentifikatioun" }, "step": { "user": { diff --git a/homeassistant/components/notion/translations/nl.json b/homeassistant/components/notion/translations/nl.json index 81da85f6240..fbc4b6ac7be 100644 --- a/homeassistant/components/notion/translations/nl.json +++ b/homeassistant/components/notion/translations/nl.json @@ -2,11 +2,10 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie", - "no_devices": "Geen apparaten gevonden in account", "unknown": "Onverwachte fout" }, "step": { @@ -15,7 +14,7 @@ "password": "Wachtwoord" }, "description": "Voer het wachtwoord voor {username} opnieuw in.", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { diff --git a/homeassistant/components/notion/translations/no.json b/homeassistant/components/notion/translations/no.json index 0bbbeb9c0dd..b8ffb36e040 100644 --- a/homeassistant/components/notion/translations/no.json +++ b/homeassistant/components/notion/translations/no.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Ugyldig godkjenning", - "no_devices": "Ingen enheter funnet i kontoen", "unknown": "Uventet feil" }, "step": { diff --git a/homeassistant/components/notion/translations/pl.json b/homeassistant/components/notion/translations/pl.json index edd7b608b3d..bda5f89b4e1 100644 --- a/homeassistant/components/notion/translations/pl.json +++ b/homeassistant/components/notion/translations/pl.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Niepoprawne uwierzytelnienie", - "no_devices": "Nie znaleziono urz\u0105dze\u0144 na koncie", "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { diff --git a/homeassistant/components/notion/translations/pt-BR.json b/homeassistant/components/notion/translations/pt-BR.json index d778a301ee1..d7d15016852 100644 --- a/homeassistant/components/notion/translations/pt-BR.json +++ b/homeassistant/components/notion/translations/pt-BR.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", - "no_devices": "Nenhum dispositivo encontrado na conta", "unknown": "Erro inesperado" }, "step": { diff --git a/homeassistant/components/notion/translations/ru.json b/homeassistant/components/notion/translations/ru.json index bebd8a66e0e..9ad8a0a8626 100644 --- a/homeassistant/components/notion/translations/ru.json +++ b/homeassistant/components/notion/translations/ru.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", - "no_devices": "\u041d\u0435\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u044c\u044e.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { diff --git a/homeassistant/components/notion/translations/sl.json b/homeassistant/components/notion/translations/sl.json index 9e4a4c8d77e..8b9dc856382 100644 --- a/homeassistant/components/notion/translations/sl.json +++ b/homeassistant/components/notion/translations/sl.json @@ -3,9 +3,6 @@ "abort": { "already_configured": "To uporabni\u0161ko ime je \u017ee v uporabi." }, - "error": { - "no_devices": "V ra\u010dunu ni najdene nobene naprave" - }, "step": { "user": { "data": { diff --git a/homeassistant/components/notion/translations/sv.json b/homeassistant/components/notion/translations/sv.json index 0cbf4a937b9..71836f79877 100644 --- a/homeassistant/components/notion/translations/sv.json +++ b/homeassistant/components/notion/translations/sv.json @@ -3,9 +3,6 @@ "abort": { "already_configured": "Det h\u00e4r anv\u00e4ndarnamnet anv\u00e4nds redan." }, - "error": { - "no_devices": "Inga enheter hittades p\u00e5 kontot" - }, "step": { "user": { "data": { diff --git a/homeassistant/components/notion/translations/tr.json b/homeassistant/components/notion/translations/tr.json index cd95e502c8d..51ba57e9fa6 100644 --- a/homeassistant/components/notion/translations/tr.json +++ b/homeassistant/components/notion/translations/tr.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", - "no_devices": "Hesapta cihaz bulunamad\u0131", "unknown": "Beklenmeyen hata" }, "step": { diff --git a/homeassistant/components/notion/translations/uk.json b/homeassistant/components/notion/translations/uk.json index 6dc969c3609..1dd26aaf2a7 100644 --- a/homeassistant/components/notion/translations/uk.json +++ b/homeassistant/components/notion/translations/uk.json @@ -4,8 +4,7 @@ "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." }, "error": { - "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", - "no_devices": "\u041d\u0435\u043c\u0430\u0454 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432, \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0445 \u0437 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u043c \u0437\u0430\u043f\u0438\u0441\u043e\u043c." + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." }, "step": { "user": { diff --git a/homeassistant/components/notion/translations/zh-Hans.json b/homeassistant/components/notion/translations/zh-Hans.json index 2d670456235..0b54dcf7771 100644 --- a/homeassistant/components/notion/translations/zh-Hans.json +++ b/homeassistant/components/notion/translations/zh-Hans.json @@ -1,8 +1,5 @@ { "config": { - "error": { - "no_devices": "\u5e10\u6237\u4e2d\u627e\u4e0d\u5230\u8bbe\u5907" - }, "step": { "user": { "data": { diff --git a/homeassistant/components/notion/translations/zh-Hant.json b/homeassistant/components/notion/translations/zh-Hant.json index 951ec07c8ad..7feae143a22 100644 --- a/homeassistant/components/notion/translations/zh-Hant.json +++ b/homeassistant/components/notion/translations/zh-Hant.json @@ -6,7 +6,6 @@ }, "error": { "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", - "no_devices": "\u5e33\u865f\u4e2d\u627e\u4e0d\u5230\u4efb\u4f55\u88dd\u7f6e", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/nuki/translations/ko.json b/homeassistant/components/nuki/translations/ko.json index 3015596e7d4..75e9ab53e84 100644 --- a/homeassistant/components/nuki/translations/ko.json +++ b/homeassistant/components/nuki/translations/ko.json @@ -13,6 +13,7 @@ "data": { "token": "\uc561\uc138\uc2a4 \ud1a0\ud070" }, + "description": "Nuki \ud1b5\ud569\uad6c\uc131\uc694\uc18c\ub294 \ube0c\ub9ac\uc9c0\ub85c \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" }, "user": { diff --git a/homeassistant/components/nuki/translations/nl.json b/homeassistant/components/nuki/translations/nl.json index 4157d21ffd3..3577fad5a01 100644 --- a/homeassistant/components/nuki/translations/nl.json +++ b/homeassistant/components/nuki/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -14,7 +14,7 @@ "token": "Toegangstoken" }, "description": "De Nuki integratie moet opnieuw authenticeren met uw bridge.", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { diff --git a/homeassistant/components/number/device_action.py b/homeassistant/components/number/device_action.py index 917bcdce74f..e4311f50dd2 100644 --- a/homeassistant/components/number/device_action.py +++ b/homeassistant/components/number/device_action.py @@ -13,7 +13,7 @@ from homeassistant.const import ( from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from .const import ATTR_VALUE, DOMAIN, SERVICE_SET_VALUE @@ -32,7 +32,7 @@ async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Number.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) actions: list[dict[str, str]] = [] # Get all the integrations entities for this device @@ -53,7 +53,10 @@ async def async_get_actions( async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" await hass.services.async_call( diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 775c512f946..28c41ccda3a 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -1,4 +1,7 @@ """The nut component.""" +from __future__ import annotations + +from dataclasses import dataclass from datetime import timedelta import logging @@ -7,9 +10,6 @@ from pynut2.nut2 import PyNUTClient, PyNUTError from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - ATTR_MANUFACTURER, - ATTR_MODEL, - ATTR_SW_VERSION, CONF_ALIAS, CONF_HOST, CONF_PASSWORD, @@ -59,7 +59,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: data = PyNUTData(host, port, alias, username, password) - async def async_update_data(): + async def async_update_data() -> dict[str, str]: """Fetch data from NUT.""" async with async_timeout.timeout(10): await hass.async_add_executor_job(data.update) @@ -98,9 +98,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: config_entry_id=entry.entry_id, identifiers={(DOMAIN, unique_id)}, name=data.name.title(), - manufacturer=data.device_info.get(ATTR_MANUFACTURER), - model=data.device_info.get(ATTR_MODEL), - sw_version=data.device_info.get(ATTR_SW_VERSION), + manufacturer=data.device_info.manufacturer, + model=data.device_info.model, + sw_version=data.device_info.firmware, ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) @@ -120,7 +120,7 @@ async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> Non await hass.config_entries.async_reload(entry.entry_id) -def _manufacturer_from_status(status): +def _manufacturer_from_status(status: dict[str, str]) -> str | None: """Find the best manufacturer value from the status.""" return ( status.get("device.mfr") @@ -130,7 +130,7 @@ def _manufacturer_from_status(status): ) -def _model_from_status(status): +def _model_from_status(status: dict[str, str]) -> str | None: """Find the best model value from the status.""" return ( status.get("device.model") @@ -139,12 +139,12 @@ def _model_from_status(status): ) -def _firmware_from_status(status): +def _firmware_from_status(status: dict[str, str]) -> str | None: """Find the best firmware value from the status.""" return status.get("ups.firmware") or status.get("ups.firmware.aux") -def _serial_from_status(status): +def _serial_from_status(status: dict[str, str]) -> str | None: """Find the best serialvalue from the status.""" serial = status.get("device.serial") or status.get("ups.serial") if serial and ( @@ -154,7 +154,7 @@ def _serial_from_status(status): return serial -def _unique_id_from_status(status): +def _unique_id_from_status(status: dict[str, str]) -> str | None: """Find the best unique id value from the status.""" serial = _serial_from_status(status) # We must have a serial for this to be unique @@ -174,6 +174,15 @@ def _unique_id_from_status(status): return "_".join(unique_id_group) +@dataclass +class NUTDeviceInfo: + """Device information for NUT.""" + + manufacturer: str | None = None + model: str | None = None + firmware: str | None = None + + class PyNUTData: """Stores the data retrieved from NUT. @@ -181,7 +190,14 @@ class PyNUTData: updates from the server. """ - def __init__(self, host, port, alias, username, password): + def __init__( + self, + host: str, + port: int, + alias: str | None, + username: str | None, + password: str | None, + ) -> None: """Initialize the data object.""" self._host = host @@ -190,29 +206,29 @@ class PyNUTData: # Establish client with persistent=False to open/close connection on # each update call. This is more reliable with async. self._client = PyNUTClient(self._host, port, username, password, 5, False) - self.ups_list = None - self._status = None - self._device_info = None + self.ups_list: dict[str, str] | None = None + self._status: dict[str, str] | None = None + self._device_info: NUTDeviceInfo | None = None @property - def status(self): + def status(self) -> dict[str, str] | None: """Get latest update if throttle allows. Return status.""" return self._status @property - def name(self): + def name(self) -> str: """Return the name of the ups.""" - return self._alias + return self._alias or f"Nut-{self._host}" @property - def device_info(self): + def device_info(self) -> NUTDeviceInfo: """Return the device info for the ups.""" - return self._device_info or {} + return self._device_info or NUTDeviceInfo() - def _get_alias(self): + def _get_alias(self) -> str | None: """Get the ups alias from NUT.""" try: - ups_list = self._client.list_ups() + ups_list: dict[str, str] = self._client.list_ups() except PyNUTError as err: _LOGGER.error("Failure getting NUT ups alias, %s", err) return None @@ -224,7 +240,7 @@ class PyNUTData: self.ups_list = ups_list return list(ups_list)[0] - def _get_device_info(self): + def _get_device_info(self) -> NUTDeviceInfo | None: """Get the ups device info from NUT.""" if not self._status: return None @@ -232,27 +248,24 @@ class PyNUTData: manufacturer = _manufacturer_from_status(self._status) model = _model_from_status(self._status) firmware = _firmware_from_status(self._status) - device_info = {} - if model: - device_info[ATTR_MODEL] = model - if manufacturer: - device_info[ATTR_MANUFACTURER] = manufacturer - if firmware: - device_info[ATTR_SW_VERSION] = firmware + device_info = NUTDeviceInfo(manufacturer, model, firmware) + return device_info - def _get_status(self): + def _get_status(self) -> dict[str, str] | None: """Get the ups status from NUT.""" if self._alias is None: self._alias = self._get_alias() try: - return self._client.list_vars(self._alias) + status: dict[str, str] = self._client.list_vars(self._alias) except (PyNUTError, ConnectionResetError) as err: _LOGGER.debug("Error getting NUT vars for host %s: %s", self._host, err) return None - def update(self): + return status + + def update(self) -> None: """Fetch the latest status from NUT.""" self._status = self._get_status() if self._device_info is None: diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index fb0a2210a69..917f004ce32 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -1,12 +1,15 @@ """Config flow for Network UPS Tools (NUT) integration.""" from __future__ import annotations +from collections.abc import Mapping import logging +from typing import Any import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant import exceptions from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import ( CONF_ALIAS, CONF_BASE, @@ -16,7 +19,7 @@ from homeassistant.const import ( CONF_SCAN_INTERVAL, CONF_USERNAME, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from . import PyNUTData @@ -42,12 +45,12 @@ def _base_schema(discovery_info: zeroconf.ZeroconfServiceInfo | None) -> vol.Sch return vol.Schema(base_schema) -def _ups_schema(ups_list): +def _ups_schema(ups_list: dict[str, str]) -> vol.Schema: """UPS selection schema.""" return vol.Schema({vol.Required(CONF_ALIAS): vol.In(ups_list)}) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from _base_schema with values provided by the user. @@ -59,15 +62,15 @@ async def validate_input(hass: core.HomeAssistant, data): username = data.get(CONF_USERNAME) password = data.get(CONF_PASSWORD) - data = PyNUTData(host, port, alias, username, password) - await hass.async_add_executor_job(data.update) - if not (status := data.status): + nut_data = PyNUTData(host, port, alias, username, password) + await hass.async_add_executor_job(nut_data.update) + if not (status := nut_data.status): raise CannotConnect - return {"ups_list": data.ups_list, "available_resources": status} + return {"ups_list": nut_data.ups_list, "available_resources": status} -def _format_host_port_alias(user_input): +def _format_host_port_alias(user_input: Mapping[str, Any]) -> str: """Format a host, port, and alias so it can be used for comparison or display.""" host = user_input[CONF_HOST] port = user_input[CONF_PORT] @@ -77,17 +80,17 @@ def _format_host_port_alias(user_input): return f"{host}:{port}" -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NutConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Network UPS Tools (NUT).""" VERSION = 1 - def __init__(self): + def __init__(self) -> None: """Initialize the nut config flow.""" - self.nut_config = {} + self.nut_config: dict[str, Any] = {} self.discovery_info: zeroconf.ZeroconfServiceInfo | None = None - self.ups_list = None - self.title = None + self.ups_list: dict[str, str] | None = None + self.title: str | None = None async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo @@ -101,9 +104,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): } return await self.async_step_user() - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the user input.""" - errors = {} + errors: dict[str, str] = {} if user_input is not None: if self.discovery_info: user_input.update( @@ -129,9 +134,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=_base_schema(self.discovery_info), errors=errors ) - async def async_step_ups(self, user_input=None): + async def async_step_ups( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the picking the ups.""" - errors = {} + errors: dict[str, str] = {} if user_input is not None: self.nut_config.update(user_input) @@ -144,11 +151,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id="ups", - data_schema=_ups_schema(self.ups_list), + data_schema=_ups_schema(self.ups_list or {}), errors=errors, ) - def _host_port_alias_already_configured(self, user_input): + def _host_port_alias_already_configured(self, user_input: dict[str, Any]) -> bool: """See if we already have a nut entry matching user input configured.""" existing_host_port_aliases = { _format_host_port_alias(entry.data) @@ -157,7 +164,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): } return _format_host_port_alias(user_input) in existing_host_port_aliases - async def _async_validate_or_error(self, config): + async def _async_validate_or_error( + self, config: dict[str, Any] + ) -> tuple[dict[str, Any], dict[str, str]]: errors = {} info = {} try: @@ -171,19 +180,21 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @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 nut.""" - 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/nut/sensor.py b/homeassistant/components/nut/sensor.py index 1992ba49635..6c68f2b6c6b 100644 --- a/homeassistant/components/nut/sensor.py +++ b/homeassistant/components/nut/sensor.py @@ -1,11 +1,18 @@ """Provides a sensor to track various status aspects of a UPS.""" from __future__ import annotations +from dataclasses import asdict import logging +from typing import cast from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.config_entries import ConfigEntry -from homeassistant.const import STATE_UNKNOWN +from homeassistant.const import ( + ATTR_MANUFACTURER, + ATTR_MODEL, + ATTR_SW_VERSION, + STATE_UNKNOWN, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -26,9 +33,27 @@ from .const import ( STATE_TYPES, ) +NUT_DEV_INFO_TO_DEV_INFO: dict[str, str] = { + "manufacturer": ATTR_MANUFACTURER, + "model": ATTR_MODEL, + "firmware": ATTR_SW_VERSION, +} + _LOGGER = logging.getLogger(__name__) +def _get_nut_device_info(data: PyNUTData) -> DeviceInfo: + """Return a DeviceInfo object filled with NUT device info.""" + nut_dev_infos = asdict(data.device_info) + nut_infos = { + info_key: nut_dev_infos[nut_key] + for nut_key, info_key in NUT_DEV_INFO_TO_DEV_INFO.items() + if nut_dev_infos[nut_key] is not None + } + + return cast(DeviceInfo, nut_infos) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -61,12 +86,12 @@ async def async_setup_entry( async_add_entities(entities, True) -class NUTSensor(CoordinatorEntity, SensorEntity): +class NUTSensor(CoordinatorEntity[DataUpdateCoordinator[dict[str, str]]], SensorEntity): """Representation of a sensor entity for NUT status values.""" def __init__( self, - coordinator: DataUpdateCoordinator, + coordinator: DataUpdateCoordinator[dict[str, str]], sensor_description: SensorEntityDescription, data: PyNUTData, unique_id: str, @@ -82,10 +107,10 @@ class NUTSensor(CoordinatorEntity, SensorEntity): identifiers={(DOMAIN, unique_id)}, name=device_name, ) - self._attr_device_info.update(data.device_info) + self._attr_device_info.update(_get_nut_device_info(data)) @property - def native_value(self): + def native_value(self) -> str | None: """Return entity state from ups.""" status = self.coordinator.data if self.entity_description.key == KEY_STATUS_DISPLAY: @@ -93,7 +118,7 @@ class NUTSensor(CoordinatorEntity, SensorEntity): return status.get(self.entity_description.key) -def _format_display_state(status): +def _format_display_state(status: dict[str, str]) -> str: """Return UPS display state.""" try: return " ".join(STATE_TYPES[state] for state in status[KEY_STATUS].split()) diff --git a/homeassistant/components/nut/translations/bg.json b/homeassistant/components/nut/translations/bg.json index 1413a7f101c..4983c9a14b2 100644 --- a/homeassistant/components/nut/translations/bg.json +++ b/homeassistant/components/nut/translations/bg.json @@ -1,16 +1,6 @@ { "config": { "step": { - "resources": { - "data": { - "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438" - } - }, - "ups": { - "data": { - "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438" - } - }, "user": { "data": { "port": "\u041f\u043e\u0440\u0442" diff --git a/homeassistant/components/nut/translations/ca.json b/homeassistant/components/nut/translations/ca.json index fc8b5b719a0..c90f6fa8a12 100644 --- a/homeassistant/components/nut/translations/ca.json +++ b/homeassistant/components/nut/translations/ca.json @@ -8,16 +8,9 @@ "unknown": "Error inesperat" }, "step": { - "resources": { - "data": { - "resources": "Recursos" - }, - "title": "Selecciona els recursos a monitoritzar" - }, "ups": { "data": { - "alias": "\u00c0lies", - "resources": "Recursos" + "alias": "\u00c0lies" }, "title": "Selecciona el SAI (UPS) a monitoritzar" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Ha fallat la connexi\u00f3", - "unknown": "Error inesperat" - }, "step": { "init": { "data": { - "resources": "Recursos", "scan_interval": "Interval d'escaneig (segons)" - }, - "description": "Selecciona els recursos del sensor." + } } } } diff --git a/homeassistant/components/nut/translations/cs.json b/homeassistant/components/nut/translations/cs.json index 37d73391ecf..839b01d622d 100644 --- a/homeassistant/components/nut/translations/cs.json +++ b/homeassistant/components/nut/translations/cs.json @@ -8,16 +8,9 @@ "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "step": { - "resources": { - "data": { - "resources": "Zdroje" - }, - "title": "Vyberte zdroje, kter\u00e9 chcete sledovat" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Zdroje" + "alias": "Alias" }, "title": "Vyberte UPS, kterou chcete sledovat" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" - }, "step": { "init": { "data": { - "resources": "Zdroje", "scan_interval": "Interval sledov\u00e1n\u00ed (v sekund\u00e1ch)" - }, - "description": "Zvolte zdroje senzoru." + } } } } diff --git a/homeassistant/components/nut/translations/de.json b/homeassistant/components/nut/translations/de.json index e82dca2506b..2e2d16a94d7 100644 --- a/homeassistant/components/nut/translations/de.json +++ b/homeassistant/components/nut/translations/de.json @@ -8,16 +8,9 @@ "unknown": "Unerwarteter Fehler" }, "step": { - "resources": { - "data": { - "resources": "Ressourcen" - }, - "title": "W\u00e4hle die zu \u00fcberwachenden Ressourcen aus" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Ressourcen" + "alias": "Alias" }, "title": "W\u00e4hle die zu \u00fcberwachende USV" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Verbindung fehlgeschlagen", - "unknown": "Unerwarteter Fehler" - }, "step": { "init": { "data": { - "resources": "Ressourcen", "scan_interval": "Scan-Intervall (Sekunden)" - }, - "description": "W\u00e4hle Sensorressourcen." + } } } } diff --git a/homeassistant/components/nut/translations/el.json b/homeassistant/components/nut/translations/el.json index 971d6476a23..a42670f70d8 100644 --- a/homeassistant/components/nut/translations/el.json +++ b/homeassistant/components/nut/translations/el.json @@ -8,16 +8,9 @@ "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { - "resources": { - "data": { - "resources": "\u03a0\u03cc\u03c1\u03bf\u03b9" - }, - "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c0\u03cc\u03c1\u03bf\u03c5\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7" - }, "ups": { "data": { - "alias": "\u03a8\u03b5\u03c5\u03b4\u03ce\u03bd\u03c5\u03bc\u03bf", - "resources": "\u03a0\u03cc\u03c1\u03bf\u03b9" + "alias": "\u03a8\u03b5\u03c5\u03b4\u03ce\u03bd\u03c5\u03bc\u03bf" }, "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf UPS \u03b3\u03b9\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" - }, "step": { "init": { "data": { - "resources": "\u03a0\u03cc\u03c1\u03bf\u03b9", "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c0\u03cc\u03c1\u03bf\u03c5\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd." + } } } } diff --git a/homeassistant/components/nut/translations/en.json b/homeassistant/components/nut/translations/en.json index 3d57189f7a5..fb8e548902b 100644 --- a/homeassistant/components/nut/translations/en.json +++ b/homeassistant/components/nut/translations/en.json @@ -8,16 +8,9 @@ "unknown": "Unexpected error" }, "step": { - "resources": { - "data": { - "resources": "Resources" - }, - "title": "Choose the Resources to Monitor" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Resources" + "alias": "Alias" }, "title": "Choose the UPS to Monitor" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Failed to connect", - "unknown": "Unexpected error" - }, "step": { "init": { "data": { - "resources": "Resources", "scan_interval": "Scan Interval (seconds)" - }, - "description": "Choose Sensor Resources." + } } } } diff --git a/homeassistant/components/nut/translations/es-419.json b/homeassistant/components/nut/translations/es-419.json index b33bc854b23..992abf2d7c3 100644 --- a/homeassistant/components/nut/translations/es-419.json +++ b/homeassistant/components/nut/translations/es-419.json @@ -8,16 +8,9 @@ "unknown": "Error inesperado" }, "step": { - "resources": { - "data": { - "resources": "Recursos" - }, - "title": "Seleccione los recursos para monitorear" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Recursos" + "alias": "Alias" }, "title": "Seleccione el UPS para monitorear" }, @@ -36,10 +29,8 @@ "step": { "init": { "data": { - "resources": "Recursos", "scan_interval": "Intervalo de escaneo (segundos)" - }, - "description": "Seleccione los Recursos del sensor." + } } } } diff --git a/homeassistant/components/nut/translations/es.json b/homeassistant/components/nut/translations/es.json index 234f34082e1..898bf1f027d 100644 --- a/homeassistant/components/nut/translations/es.json +++ b/homeassistant/components/nut/translations/es.json @@ -8,16 +8,9 @@ "unknown": "Error inesperado" }, "step": { - "resources": { - "data": { - "resources": "Recursos" - }, - "title": "Selecciona los recursos a monitorizar" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Recursos" + "alias": "Alias" }, "title": "Selecciona el UPS a monitorizar" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "No se pudo conectar", - "unknown": "Error inesperado" - }, "step": { "init": { "data": { - "resources": "Recursos", "scan_interval": "Intervalo de escaneo (segundos)" - }, - "description": "Elige los recursos del sensor." + } } } } diff --git a/homeassistant/components/nut/translations/et.json b/homeassistant/components/nut/translations/et.json index 83d793eb22c..3b52b9856c7 100644 --- a/homeassistant/components/nut/translations/et.json +++ b/homeassistant/components/nut/translations/et.json @@ -8,16 +8,9 @@ "unknown": "Tundmatu viga" }, "step": { - "resources": { - "data": { - "resources": "Ressursid" - }, - "title": "Vali j\u00e4lgitavad ressursid" - }, "ups": { "data": { - "alias": "", - "resources": "Ressursid" + "alias": "" }, "title": "Vali j\u00e4lgitava UPS" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "\u00dchendus nurjus", - "unknown": "Tundmatu viga" - }, "step": { "init": { "data": { - "resources": "Ressursid", "scan_interval": "P\u00e4ringute intervall (sekundites)" - }, - "description": "Vali anduri ressursid." + } } } } diff --git a/homeassistant/components/nut/translations/fr.json b/homeassistant/components/nut/translations/fr.json index 3e1b345a8f1..18bde05e550 100644 --- a/homeassistant/components/nut/translations/fr.json +++ b/homeassistant/components/nut/translations/fr.json @@ -8,16 +8,9 @@ "unknown": "Erreur inattendue" }, "step": { - "resources": { - "data": { - "resources": "Ressources" - }, - "title": "Choisissez les ressources \u00e0 surveiller" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Ressources" + "alias": "Alias" }, "title": "Choisir l'UPS \u00e0 surveiller" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "\u00c9chec de connexion", - "unknown": "Erreur inattendue" - }, "step": { "init": { "data": { - "resources": "Ressources", "scan_interval": "Intervalle de balayage (secondes)" - }, - "description": "Choisissez les ressources des capteurs." + } } } } diff --git a/homeassistant/components/nut/translations/he.json b/homeassistant/components/nut/translations/he.json index 77c12630351..b3fb785d55e 100644 --- a/homeassistant/components/nut/translations/he.json +++ b/homeassistant/components/nut/translations/he.json @@ -17,11 +17,5 @@ } } } - }, - "options": { - "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" - } } } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/hu.json b/homeassistant/components/nut/translations/hu.json index aa8f7c37105..498c12e2fcd 100644 --- a/homeassistant/components/nut/translations/hu.json +++ b/homeassistant/components/nut/translations/hu.json @@ -8,16 +8,9 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { - "resources": { - "data": { - "resources": "Forr\u00e1sok" - }, - "title": "V\u00e1lassza ki a nyomon k\u00f6vetend\u0151 er\u0151forr\u00e1sokat" - }, "ups": { "data": { - "alias": "\u00c1ln\u00e9v", - "resources": "Forr\u00e1sok" + "alias": "\u00c1ln\u00e9v" }, "title": "V\u00e1lassza ki a fel\u00fcgyelni k\u00edv\u00e1nt UPS-t" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" - }, "step": { "init": { "data": { - "resources": "Forr\u00e1sok", "scan_interval": "Szkennel\u00e9si intervallum (m\u00e1sodperc)" - }, - "description": "V\u00e1lassza az \u00c9rz\u00e9kel\u0151 er\u0151forr\u00e1sokat." + } } } } diff --git a/homeassistant/components/nut/translations/id.json b/homeassistant/components/nut/translations/id.json index fc23e34fe8e..709256b2bc2 100644 --- a/homeassistant/components/nut/translations/id.json +++ b/homeassistant/components/nut/translations/id.json @@ -8,16 +8,9 @@ "unknown": "Kesalahan yang tidak diharapkan" }, "step": { - "resources": { - "data": { - "resources": "Sumber Daya" - }, - "title": "Pilih Sumber Daya untuk Dipantau" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Sumber Daya" + "alias": "Alias" }, "title": "Pilih UPS untuk Dipantau" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Gagal terhubung", - "unknown": "Kesalahan yang tidak diharapkan" - }, "step": { "init": { "data": { - "resources": "Sumber Daya", "scan_interval": "Interval Pindai (detik)" - }, - "description": "Pilih Sumber Daya Sensor." + } } } } diff --git a/homeassistant/components/nut/translations/it.json b/homeassistant/components/nut/translations/it.json index 6b29911b28a..976c675a8b6 100644 --- a/homeassistant/components/nut/translations/it.json +++ b/homeassistant/components/nut/translations/it.json @@ -8,16 +8,9 @@ "unknown": "Errore imprevisto" }, "step": { - "resources": { - "data": { - "resources": "Risorse" - }, - "title": "Scegli le risorse da monitorare" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Risorse" + "alias": "Alias" }, "title": "Scegli l'UPS da monitorare" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Impossibile connettersi", - "unknown": "Errore imprevisto" - }, "step": { "init": { "data": { - "resources": "Risorse", "scan_interval": "Intervallo di scansione (secondi)" - }, - "description": "Scegli le risorse del sensore." + } } } } diff --git a/homeassistant/components/nut/translations/ja.json b/homeassistant/components/nut/translations/ja.json index 4c52cc74ed2..91f5aafb20d 100644 --- a/homeassistant/components/nut/translations/ja.json +++ b/homeassistant/components/nut/translations/ja.json @@ -8,16 +8,9 @@ "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { - "resources": { - "data": { - "resources": "\u30ea\u30bd\u30fc\u30b9" - }, - "title": "\u76e3\u8996\u3059\u308b\u30ea\u30bd\u30fc\u30b9\u3092\u9078\u629e" - }, "ups": { "data": { - "alias": "\u30a8\u30a4\u30ea\u30a2\u30b9", - "resources": "\u30ea\u30bd\u30fc\u30b9" + "alias": "\u30a8\u30a4\u30ea\u30a2\u30b9" }, "title": "\u76e3\u8996\u3059\u308bUPS\u3092\u9078\u629e" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", - "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" - }, "step": { "init": { "data": { - "resources": "\u30ea\u30bd\u30fc\u30b9", "scan_interval": "\u30b9\u30ad\u30e3\u30f3\u30a4\u30f3\u30bf\u30fc\u30d0\u30eb(\u79d2)" - }, - "description": "\u30bb\u30f3\u30b5\u30fc\u30ea\u30bd\u30fc\u30b9\u3092\u9078\u629e\u3057\u307e\u3059\u3002" + } } } } diff --git a/homeassistant/components/nut/translations/ko.json b/homeassistant/components/nut/translations/ko.json index a5680f4ed48..89bd39e35b5 100644 --- a/homeassistant/components/nut/translations/ko.json +++ b/homeassistant/components/nut/translations/ko.json @@ -8,16 +8,9 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { - "resources": { - "data": { - "resources": "\ub9ac\uc18c\uc2a4" - }, - "title": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \ub9ac\uc18c\uc2a4 \uc120\ud0dd\ud558\uae30" - }, "ups": { "data": { - "alias": "\ubcc4\uba85", - "resources": "\ub9ac\uc18c\uc2a4" + "alias": "\ubcc4\uba85" }, "title": "\ubaa8\ub2c8\ud130\ub9c1\ud560 UPS \uc120\ud0dd\ud558\uae30" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" - }, "step": { "init": { "data": { - "resources": "\ub9ac\uc18c\uc2a4", "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" - }, - "description": "\uc13c\uc11c \ub9ac\uc18c\uc2a4\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694." + } } } } diff --git a/homeassistant/components/nut/translations/lb.json b/homeassistant/components/nut/translations/lb.json index da4840653c1..40a44ef19eb 100644 --- a/homeassistant/components/nut/translations/lb.json +++ b/homeassistant/components/nut/translations/lb.json @@ -8,16 +8,9 @@ "unknown": "Onerwaarte Feeler" }, "step": { - "resources": { - "data": { - "resources": "Ressourcen" - }, - "title": "Ressourcen auswielen fir z'iwwerwaachen" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Ressourcen" + "alias": "Alias" }, "title": "UPS fir z'iwwerwaachen auswielen" }, @@ -36,10 +29,8 @@ "step": { "init": { "data": { - "resources": "Ressourcen", "scan_interval": "Scan Intervall (sekonnen)" - }, - "description": "Sensor Ressourcen auswielen" + } } } } diff --git a/homeassistant/components/nut/translations/nl.json b/homeassistant/components/nut/translations/nl.json index d90b75b4bcc..ae8c91f75f9 100644 --- a/homeassistant/components/nut/translations/nl.json +++ b/homeassistant/components/nut/translations/nl.json @@ -8,16 +8,9 @@ "unknown": "Onverwachte fout" }, "step": { - "resources": { - "data": { - "resources": "Bronnen" - }, - "title": "Kies de te controleren bronnen" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Bronnen" + "alias": "Alias" }, "title": "Kies een UPS om uit te lezen" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Verbinden mislukt", - "unknown": "Onverwachte fout" - }, "step": { "init": { "data": { - "resources": "Bronnen", "scan_interval": "Scaninterval (seconden)" - }, - "description": "Kies Sensorbronnen." + } } } } diff --git a/homeassistant/components/nut/translations/no.json b/homeassistant/components/nut/translations/no.json index 887d3b6d30a..d657b67fd07 100644 --- a/homeassistant/components/nut/translations/no.json +++ b/homeassistant/components/nut/translations/no.json @@ -8,16 +8,9 @@ "unknown": "Uventet feil" }, "step": { - "resources": { - "data": { - "resources": "Ressurser" - }, - "title": "Velg ressurser som skal overv\u00e5kes" - }, "ups": { "data": { - "alias": "", - "resources": "Ressurser" + "alias": "" }, "title": "Velg UPS som skal overv\u00e5kes" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Tilkobling mislyktes", - "unknown": "Uventet feil" - }, "step": { "init": { "data": { - "resources": "Ressurser", "scan_interval": "Skanneintervall (sekunder)" - }, - "description": "Velg Sensor Ressurser." + } } } } diff --git a/homeassistant/components/nut/translations/pl.json b/homeassistant/components/nut/translations/pl.json index 2686a98e697..3f9d3c89b41 100644 --- a/homeassistant/components/nut/translations/pl.json +++ b/homeassistant/components/nut/translations/pl.json @@ -8,16 +8,9 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { - "resources": { - "data": { - "resources": "Zasoby" - }, - "title": "Wybierz zasoby do monitorowania" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Zasoby" + "alias": "Alias" }, "title": "Wybierz UPS do monitorowania" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "unknown": "Nieoczekiwany b\u0142\u0105d" - }, "step": { "init": { "data": { - "resources": "Zasoby", "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (sekundy)" - }, - "description": "Wybierz zasoby sensor\u00f3w." + } } } } diff --git a/homeassistant/components/nut/translations/pt-BR.json b/homeassistant/components/nut/translations/pt-BR.json index d6be7b41044..ee2c84a7777 100644 --- a/homeassistant/components/nut/translations/pt-BR.json +++ b/homeassistant/components/nut/translations/pt-BR.json @@ -8,16 +8,9 @@ "unknown": "Erro inesperado" }, "step": { - "resources": { - "data": { - "resources": "Recursos" - }, - "title": "Escolha os recursos para monitorar" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Recursos" + "alias": "Alias" }, "title": "Escolha o no-break (UPS) para monitorar" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Falha ao conectar", - "unknown": "Erro inesperado" - }, "step": { "init": { "data": { - "resources": "Recursos", "scan_interval": "Intervalo de escaneamento (segundos)" - }, - "description": "Escolha os recursos dos sensores." + } } } } diff --git a/homeassistant/components/nut/translations/pt.json b/homeassistant/components/nut/translations/pt.json index f5e8690e383..9f6c04e78a3 100644 --- a/homeassistant/components/nut/translations/pt.json +++ b/homeassistant/components/nut/translations/pt.json @@ -8,11 +8,6 @@ "unknown": "Erro inesperado" }, "step": { - "ups": { - "data": { - "resources": "Recursos" - } - }, "user": { "data": { "host": "Servidor", @@ -22,11 +17,5 @@ } } } - }, - "options": { - "error": { - "cannot_connect": "Falha na liga\u00e7\u00e3o", - "unknown": "Erro inesperado" - } } } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/ru.json b/homeassistant/components/nut/translations/ru.json index 071a7d0f09c..230c4d3d869 100644 --- a/homeassistant/components/nut/translations/ru.json +++ b/homeassistant/components/nut/translations/ru.json @@ -8,16 +8,9 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { - "resources": { - "data": { - "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u044b" - }, - "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430" - }, "ups": { "data": { - "alias": "\u041f\u0441\u0435\u0432\u0434\u043e\u043d\u0438\u043c", - "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u044b" + "alias": "\u041f\u0441\u0435\u0432\u0434\u043e\u043d\u0438\u043c" }, "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0418\u0411\u041f \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", - "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." - }, "step": { "init": { "data": { - "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u044b", "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" - }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u0441\u0435\u043d\u0441\u043e\u0440\u043e\u0432." + } } } } diff --git a/homeassistant/components/nut/translations/sk.json b/homeassistant/components/nut/translations/sk.json index 434ad4a26b2..d00d818716f 100644 --- a/homeassistant/components/nut/translations/sk.json +++ b/homeassistant/components/nut/translations/sk.json @@ -8,16 +8,9 @@ "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" }, "step": { - "resources": { - "data": { - "resources": "Prostriedky" - }, - "title": "Vyberte prostriedky, ktor\u00e9 chcete monitorova\u0165" - }, "ups": { "data": { - "alias": "Alias", - "resources": "Prostriedky" + "alias": "Alias" }, "title": "Vyberte UPS, ktor\u00fa chcete monitorova\u0165" }, @@ -32,17 +25,11 @@ } }, "options": { - "error": { - "cannot_connect": "Nepodarilo sa pripoji\u0165", - "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" - }, "step": { "init": { "data": { - "resources": "Prostriedky", "scan_interval": "Skenovac\u00ed interval (v sekund\u00e1ch)" - }, - "description": "Vyberte senzory." + } } } } diff --git a/homeassistant/components/nut/translations/sl.json b/homeassistant/components/nut/translations/sl.json index 1f0b269562d..156d3dc7d92 100644 --- a/homeassistant/components/nut/translations/sl.json +++ b/homeassistant/components/nut/translations/sl.json @@ -8,16 +8,9 @@ "unknown": "Nepri\u010dakovana napaka" }, "step": { - "resources": { - "data": { - "resources": "Viri" - }, - "title": "Izberite vire za spremljanje" - }, "ups": { "data": { - "alias": "Vzdevek", - "resources": "Viri" + "alias": "Vzdevek" }, "title": "Izberite UPS za spremljanje" }, @@ -36,10 +29,8 @@ "step": { "init": { "data": { - "resources": "Viri", "scan_interval": "Interval skeniranja (sekunde)" - }, - "description": "Izberite vire senzorja." + } } } } diff --git a/homeassistant/components/nut/translations/sv.json b/homeassistant/components/nut/translations/sv.json index 45832197f68..9a3cb690744 100644 --- a/homeassistant/components/nut/translations/sv.json +++ b/homeassistant/components/nut/translations/sv.json @@ -8,12 +8,6 @@ "unknown": "Ov\u00e4ntat fel" }, "step": { - "resources": { - "data": { - "resources": "Resurser" - }, - "title": "V\u00e4lj resurser som ska \u00f6vervakas" - }, "ups": { "data": { "alias": "Alias" @@ -31,10 +25,6 @@ } }, "options": { - "error": { - "cannot_connect": "Kunde inte ansluta", - "unknown": "Ov\u00e4ntat fel" - }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/tr.json b/homeassistant/components/nut/translations/tr.json index 7802c451fd0..e24f982268c 100644 --- a/homeassistant/components/nut/translations/tr.json +++ b/homeassistant/components/nut/translations/tr.json @@ -8,16 +8,9 @@ "unknown": "Beklenmeyen hata" }, "step": { - "resources": { - "data": { - "resources": "Kaynaklar" - }, - "title": "\u0130zlenecek Kaynaklar\u0131 Se\u00e7in" - }, "ups": { "data": { - "alias": "Takma ad", - "resources": "Kaynaklar" + "alias": "Takma ad" }, "title": "\u0130zlenecek UPS'i Se\u00e7in" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "Ba\u011flanma hatas\u0131", - "unknown": "Beklenmeyen hata" - }, "step": { "init": { "data": { - "resources": "Kaynaklar", "scan_interval": "Tarama Aral\u0131\u011f\u0131 (saniye)" - }, - "description": "Sens\u00f6r Kaynaklar\u0131'n\u0131 se\u00e7in." + } } } } diff --git a/homeassistant/components/nut/translations/uk.json b/homeassistant/components/nut/translations/uk.json index b25fe854560..64c8a44d219 100644 --- a/homeassistant/components/nut/translations/uk.json +++ b/homeassistant/components/nut/translations/uk.json @@ -8,16 +8,9 @@ "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" }, "step": { - "resources": { - "data": { - "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438" - }, - "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0440\u0435\u0441\u0443\u0440\u0441\u0438 \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443" - }, "ups": { "data": { - "alias": "\u041f\u0441\u0435\u0432\u0434\u043e\u043d\u0456\u043c", - "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438" + "alias": "\u041f\u0441\u0435\u0432\u0434\u043e\u043d\u0456\u043c" }, "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c UPS \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443" }, @@ -36,10 +29,8 @@ "step": { "init": { "data": { - "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438", "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" - }, - "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0440\u0435\u0441\u0443\u0440\u0441\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432." + } } } } diff --git a/homeassistant/components/nut/translations/zh-Hans.json b/homeassistant/components/nut/translations/zh-Hans.json index 4afd1ff0031..deb09194b47 100644 --- a/homeassistant/components/nut/translations/zh-Hans.json +++ b/homeassistant/components/nut/translations/zh-Hans.json @@ -8,16 +8,9 @@ "unknown": "\u672a\u77e5\u9519\u8bef" }, "step": { - "resources": { - "data": { - "resources": "\u8d44\u6e90" - }, - "title": "\u9009\u62e9\u8981\u76d1\u89c6\u7684\u8d44\u6e90" - }, "ups": { "data": { - "alias": "\u522b\u540d", - "resources": "\u8d44\u6e90" + "alias": "\u522b\u540d" }, "title": "\u9009\u62e9\u8981\u76d1\u63a7\u7684 UPS" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "\u8fde\u63a5\u5931\u8d25", - "unknown": "\u4e0d\u5728\u9884\u671f\u5185\u7684\u9519\u8bef" - }, "step": { "init": { "data": { - "resources": "\u8d44\u6e90", "scan_interval": "\u626b\u63cf\u95f4\u9694\uff08\u79d2\uff09" - }, - "description": "\u9009\u62e9\u8981\u76d1\u89c6\u7684\u8d44\u6e90" + } } } } diff --git a/homeassistant/components/nut/translations/zh-Hant.json b/homeassistant/components/nut/translations/zh-Hant.json index 3b1daf88bf1..71dc2f78402 100644 --- a/homeassistant/components/nut/translations/zh-Hant.json +++ b/homeassistant/components/nut/translations/zh-Hant.json @@ -8,16 +8,9 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { - "resources": { - "data": { - "resources": "\u8cc7\u6e90" - }, - "title": "\u9078\u64c7\u6240\u8981\u76e3\u8996\u7684\u8cc7\u6e90" - }, "ups": { "data": { - "alias": "\u5225\u540d", - "resources": "\u8cc7\u6e90" + "alias": "\u5225\u540d" }, "title": "\u9078\u64c7\u6240\u8981\u76e3\u8996\u7684 UPS" }, @@ -33,17 +26,11 @@ } }, "options": { - "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557", - "unknown": "\u672a\u9810\u671f\u932f\u8aa4" - }, "step": { "init": { "data": { - "resources": "\u8cc7\u6e90", "scan_interval": "\u6383\u63cf\u9593\u8ddd\uff08\u79d2\uff09" - }, - "description": "\u9078\u64c7\u611f\u6e2c\u5668\u8cc7\u6e90\u3002" + } } } } diff --git a/homeassistant/components/nws/translations/id.json b/homeassistant/components/nws/translations/id.json index 3c92ebaed64..e4e73efe685 100644 --- a/homeassistant/components/nws/translations/id.json +++ b/homeassistant/components/nws/translations/id.json @@ -15,7 +15,7 @@ "longitude": "Bujur", "station": "Kode stasiun METAR" }, - "description": "Jika kode stasiun METAR tidak ditentukan, informasi lintang dan bujur akan digunakan untuk menemukan stasiun terdekat. Untuk saat ini, Kunci API bisa berupa nilai sebarang. Disarankan untuk menggunakan alamat email yang valid.", + "description": "Jika kode stasiun METAR tidak ditentukan, informasi lintang dan bujur akan digunakan untuk menemukan stasiun terdekat. Untuk saat ini, Kunci API bisa berupa nilai sebarang. Disarankan untuk menggunakan alamat email yang valid.fsilak", "title": "Hubungkan ke National Weather Service" } } diff --git a/homeassistant/components/nws/translations/nl.json b/homeassistant/components/nws/translations/nl.json index 5332f43f4c7..4aa958f385b 100644 --- a/homeassistant/components/nws/translations/nl.json +++ b/homeassistant/components/nws/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/nzbget/sensor.py b/homeassistant/components/nzbget/sensor.py index 9e5bd6e4ac9..a1097389020 100644 --- a/homeassistant/components/nzbget/sensor.py +++ b/homeassistant/components/nzbget/sensor.py @@ -1,7 +1,7 @@ """Monitor the NZBGet API.""" from __future__ import annotations -from datetime import timedelta +from datetime import datetime, timedelta import logging from homeassistant.components.sensor import ( @@ -105,15 +105,16 @@ class NZBGetSensor(NZBGetEntity, SensorEntity): description: SensorEntityDescription, ) -> None: """Initialize a new NZBGet sensor.""" - self.entity_description = description - self._attr_unique_id = f"{entry_id}_{description.key}" - super().__init__( coordinator=coordinator, entry_id=entry_id, name=f"{entry_name} {description.name}", ) + self.entity_description = description + self._attr_unique_id = f"{entry_id}_{description.key}" + self._native_value: datetime | None = None + @property def native_value(self): """Return the state of the sensor.""" @@ -122,14 +123,17 @@ class NZBGetSensor(NZBGetEntity, SensorEntity): if value is None: _LOGGER.warning("Unable to locate value for %s", sensor_type) - return None - - if "DownloadRate" in sensor_type and value > 0: + self._native_value = None + elif "DownloadRate" in sensor_type and value > 0: # Convert download rate from Bytes/s to MBytes/s - return round(value / 2**20, 2) + self._native_value = round(value / 2**20, 2) + elif "UpTimeSec" in sensor_type and value > 0: + uptime = utcnow().replace(microsecond=0) - timedelta(seconds=value) + if not isinstance(self._attr_native_value, datetime) or abs( + uptime - self._attr_native_value + ) > timedelta(seconds=5): + self._native_value = uptime + else: + self._native_value = value - if "UpTimeSec" in sensor_type and value > 0: - uptime = utcnow() - timedelta(seconds=value) - return uptime.replace(microsecond=0) - - return value + return self._native_value diff --git a/homeassistant/components/nzbget/translations/es.json b/homeassistant/components/nzbget/translations/es.json index cc89bdd2469..e3b1a595c90 100644 --- a/homeassistant/components/nzbget/translations/es.json +++ b/homeassistant/components/nzbget/translations/es.json @@ -7,7 +7,7 @@ "error": { "cannot_connect": "No se pudo conectar" }, - "flow_title": "NZBGet: {name}", + "flow_title": "{name}", "step": { "user": { "data": { diff --git a/homeassistant/components/nzbget/translations/nl.json b/homeassistant/components/nzbget/translations/nl.json index 5f98d7435ec..80c96a509f2 100644 --- a/homeassistant/components/nzbget/translations/nl.json +++ b/homeassistant/components/nzbget/translations/nl.json @@ -5,7 +5,7 @@ "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "flow_title": "{name}", "step": { diff --git a/homeassistant/components/ombi/__init__.py b/homeassistant/components/ombi/__init__.py index bdf8334ff02..b67b097dfbf 100644 --- a/homeassistant/components/ombi/__init__.py +++ b/homeassistant/components/ombi/__init__.py @@ -16,6 +16,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, ServiceCall import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.typing import ConfigType from .const import ( @@ -152,6 +153,6 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: submit_tv_request, schema=SUBMIT_TV_REQUEST_SERVICE_SCHEMA, ) - hass.helpers.discovery.load_platform(Platform.SENSOR, DOMAIN, {}, config) + load_platform(hass, Platform.SENSOR, DOMAIN, {}, config) return True diff --git a/homeassistant/components/omnilogic/translations/nl.json b/homeassistant/components/omnilogic/translations/nl.json index b94599f93f4..ca6bd42b2b3 100644 --- a/homeassistant/components/omnilogic/translations/nl.json +++ b/homeassistant/components/omnilogic/translations/nl.json @@ -5,14 +5,14 @@ }, "error": { "cannot_connect": "Kan geen verbinding maken", - "invalid_auth": "Onjuiste gebruikersgegevens", + "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { "password": "Wachtwoord", - "username": "Benutzername" + "username": "Gebruikersnaam" } } } diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index b277bd97edf..7f40ad87e84 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -14,7 +14,9 @@ from homeassistant.components.http.const import KEY_HASS_REFRESH_TOKEN_ID from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.view import HomeAssistantView from homeassistant.core import callback +from homeassistant.helpers import area_registry as ar from homeassistant.helpers.system_info import async_get_system_info +from homeassistant.helpers.translation import async_get_translations from .const import ( DEFAULT_AREAS, @@ -147,11 +149,11 @@ class UserOnboardingView(_BaseOnboardingView): await person.async_create_person(hass, data["name"], user_id=user.id) # Create default areas using the users supplied language. - translations = await hass.helpers.translation.async_get_translations( - data["language"], "area", DOMAIN + translations = await async_get_translations( + hass, data["language"], "area", {DOMAIN} ) - area_registry = await hass.helpers.area_registry.async_get_registry() + area_registry = ar.async_get(hass) for area in DEFAULT_AREAS: area_registry.async_create( diff --git a/homeassistant/components/oncue/translations/sk.json b/homeassistant/components/oncue/translations/sk.json index 5ada995aa6e..82a91905e7c 100644 --- a/homeassistant/components/oncue/translations/sk.json +++ b/homeassistant/components/oncue/translations/sk.json @@ -1,7 +1,8 @@ { "config": { "error": { - "invalid_auth": "Neplatn\u00e9 overenie" + "invalid_auth": "Neplatn\u00e9 overenie", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" } } } \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/es.json b/homeassistant/components/ondilo_ico/translations/es.json index db8d744d176..1de8399aced 100644 --- a/homeassistant/components/ondilo_ico/translations/es.json +++ b/homeassistant/components/ondilo_ico/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n." }, "create_entry": { diff --git a/homeassistant/components/ondilo_ico/translations/nl.json b/homeassistant/components/ondilo_ico/translations/nl.json index 0613d559fce..bf5ad718617 100644 --- a/homeassistant/components/ondilo_ico/translations/nl.json +++ b/homeassistant/components/ondilo_ico/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "De component is niet geconfigureerd. Gelieve de documentatie volgen." + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie." }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/onewire/binary_sensor.py b/homeassistant/components/onewire/binary_sensor.py index 0235b46baa6..307f38ceea0 100644 --- a/homeassistant/components/onewire/binary_sensor.py +++ b/homeassistant/components/onewire/binary_sensor.py @@ -3,7 +3,6 @@ from __future__ import annotations from dataclasses import dataclass import os -from typing import TYPE_CHECKING from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, @@ -11,21 +10,18 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_TYPE from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( - CONF_TYPE_OWSERVER, DEVICE_KEYS_0_3, DEVICE_KEYS_0_7, DEVICE_KEYS_A_B, DOMAIN, READ_MODE_BOOL, ) -from .model import OWServerDeviceDescription -from .onewire_entities import OneWireEntityDescription, OneWireProxyEntity +from .onewire_entities import OneWireEntity, OneWireEntityDescription from .onewirehub import OneWireHub @@ -98,23 +94,19 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up 1-Wire platform.""" - # Only OWServer implementation works with binary sensors - if config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER: - onewirehub = hass.data[DOMAIN][config_entry.entry_id] + onewirehub = hass.data[DOMAIN][config_entry.entry_id] - entities = await hass.async_add_executor_job(get_entities, onewirehub) - async_add_entities(entities, True) + entities = await hass.async_add_executor_job(get_entities, onewirehub) + async_add_entities(entities, True) -def get_entities(onewirehub: OneWireHub) -> list[BinarySensorEntity]: +def get_entities(onewirehub: OneWireHub) -> list[OneWireBinarySensor]: """Get a list of entities.""" if not onewirehub.devices: return [] - entities: list[BinarySensorEntity] = [] + entities: list[OneWireBinarySensor] = [] for device in onewirehub.devices: - if TYPE_CHECKING: - assert isinstance(device, OWServerDeviceDescription) family = device.family device_id = device.id device_type = device.type @@ -130,7 +122,7 @@ def get_entities(onewirehub: OneWireHub) -> list[BinarySensorEntity]: device_file = os.path.join(os.path.split(device.path)[0], description.key) name = f"{device_id} {description.name}" entities.append( - OneWireProxyBinarySensor( + OneWireBinarySensor( description=description, device_id=device_id, device_file=device_file, @@ -143,7 +135,7 @@ def get_entities(onewirehub: OneWireHub) -> list[BinarySensorEntity]: return entities -class OneWireProxyBinarySensor(OneWireProxyEntity, BinarySensorEntity): +class OneWireBinarySensor(OneWireEntity, BinarySensorEntity): """Implementation of a 1-Wire binary sensor.""" entity_description: OneWireBinarySensorEntityDescription diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py index 25604473bcf..5876bcfd206 100644 --- a/homeassistant/components/onewire/config_flow.py +++ b/homeassistant/components/onewire/config_flow.py @@ -6,19 +6,15 @@ from typing import Any import voluptuous as vol from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow -from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.device_registry import DeviceRegistry from .const import ( - CONF_MOUNT_DIR, - CONF_TYPE_OWSERVER, - CONF_TYPE_SYSBUS, - DEFAULT_OWSERVER_HOST, - DEFAULT_OWSERVER_PORT, - DEFAULT_SYSBUS_MOUNT_DIR, + DEFAULT_HOST, + DEFAULT_PORT, DEVICE_SUPPORT_OPTIONS, DOMAIN, INPUT_ENTRY_CLEAR_OPTIONS, @@ -27,31 +23,21 @@ from .const import ( OPTION_ENTRY_SENSOR_PRECISION, PRECISION_MAPPING_FAMILY_28, ) -from .model import OWServerDeviceDescription -from .onewirehub import CannotConnect, InvalidPath, OneWireHub +from .model import OWDeviceDescription +from .onewirehub import CannotConnect, OneWireHub -DATA_SCHEMA_USER = vol.Schema( - {vol.Required(CONF_TYPE): vol.In([CONF_TYPE_OWSERVER, CONF_TYPE_SYSBUS])} -) -DATA_SCHEMA_OWSERVER = vol.Schema( +DATA_SCHEMA = vol.Schema( { - vol.Required(CONF_HOST, default=DEFAULT_OWSERVER_HOST): str, - vol.Required(CONF_PORT, default=DEFAULT_OWSERVER_PORT): int, - } -) -DATA_SCHEMA_MOUNTDIR = vol.Schema( - { - vol.Required(CONF_MOUNT_DIR, default=DEFAULT_SYSBUS_MOUNT_DIR): str, + vol.Required(CONF_HOST, default=DEFAULT_HOST): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, } ) -async def validate_input_owserver( - hass: HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows us to connect. - Data has the keys from DATA_SCHEMA_OWSERVER with values provided by the user. + Data has the keys from DATA_SCHEMA with values provided by the user. """ hub = OneWireHub(hass) @@ -65,24 +51,6 @@ async def validate_input_owserver( return {"title": host} -async def validate_input_mount_dir( - hass: HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: - """Validate the user input allows us to connect. - - Data has the keys from DATA_SCHEMA_MOUNTDIR with values provided by the user. - """ - hub = OneWireHub(hass) - - mount_dir = data[CONF_MOUNT_DIR] - - # Raises InvalidDir exception on failure - await hub.check_mount_dir(mount_dir) - - # Return info that you want to store in the config entry. - return {"title": mount_dir} - - class OneWireFlowHandler(ConfigFlow, domain=DOMAIN): """Handle 1-Wire config flow.""" @@ -100,29 +68,10 @@ class OneWireFlowHandler(ConfigFlow, domain=DOMAIN): Let user manually input configuration. """ errors: dict[str, str] = {} - if user_input is not None: - self.onewire_config.update(user_input) - if CONF_TYPE_OWSERVER == user_input[CONF_TYPE]: - return await self.async_step_owserver() - if CONF_TYPE_SYSBUS == user_input[CONF_TYPE]: - return await self.async_step_mount_dir() - - return self.async_show_form( - step_id="user", - data_schema=DATA_SCHEMA_USER, - errors=errors, - ) - - async def async_step_owserver( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - """Handle OWServer configuration.""" - errors = {} if user_input: # Prevent duplicate entries self._async_abort_entries_match( { - CONF_TYPE: CONF_TYPE_OWSERVER, CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT], } @@ -131,7 +80,7 @@ class OneWireFlowHandler(ConfigFlow, domain=DOMAIN): self.onewire_config.update(user_input) try: - info = await validate_input_owserver(self.hass, user_input) + info = await validate_input(self.hass, user_input) except CannotConnect: errors["base"] = "cannot_connect" else: @@ -140,37 +89,8 @@ class OneWireFlowHandler(ConfigFlow, domain=DOMAIN): ) return self.async_show_form( - step_id="owserver", - data_schema=DATA_SCHEMA_OWSERVER, - errors=errors, - ) - - async def async_step_mount_dir( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - """Handle SysBus configuration.""" - errors = {} - if user_input: - # Prevent duplicate entries - await self.async_set_unique_id( - f"{CONF_TYPE_SYSBUS}:{user_input[CONF_MOUNT_DIR]}" - ) - self._abort_if_unique_id_configured() - - self.onewire_config.update(user_input) - - try: - info = await validate_input_mount_dir(self.hass, user_input) - except InvalidPath: - errors["base"] = "invalid_path" - else: - return self.async_create_entry( - title=info["title"], data=self.onewire_config - ) - - return self.async_show_form( - step_id="mount_dir", - data_schema=DATA_SCHEMA_MOUNTDIR, + step_id="user", + data_schema=DATA_SCHEMA, errors=errors, ) @@ -188,8 +108,8 @@ class OnewireOptionsFlowHandler(OptionsFlow): """Initialize OneWire Network options flow.""" self.entry_id = config_entry.entry_id self.options = dict(config_entry.options) - self.configurable_devices: dict[str, OWServerDeviceDescription] = {} - self.devices_to_configure: dict[str, OWServerDeviceDescription] = {} + self.configurable_devices: dict[str, OWDeviceDescription] = {} + self.devices_to_configure: dict[str, OWDeviceDescription] = {} self.current_device: str = "" async def async_step_init( @@ -197,12 +117,7 @@ class OnewireOptionsFlowHandler(OptionsFlow): ) -> FlowResult: """Manage the options.""" controller: OneWireHub = self.hass.data[DOMAIN][self.entry_id] - if controller.type == CONF_TYPE_SYSBUS: - return self.async_abort( - reason="SysBus setup does not have any config options." - ) - - all_devices: list[OWServerDeviceDescription] = controller.devices # type: ignore[assignment] + all_devices: list[OWDeviceDescription] = controller.devices # type: ignore[assignment] if not all_devices: return self.async_abort(reason="No configurable devices found.") @@ -224,7 +139,7 @@ class OnewireOptionsFlowHandler(OptionsFlow): if user_input.get(INPUT_ENTRY_CLEAR_OPTIONS): # Reset all options self.options = {} - return await self._update_options() + return self._async_update_options() selected_devices: list[str] = ( user_input.get(INPUT_ENTRY_DEVICE_SELECTION) or [] @@ -266,7 +181,7 @@ class OnewireOptionsFlowHandler(OptionsFlow): self._update_device_options(user_input) if self.devices_to_configure: return await self.async_step_configure_device(user_input=None) - return await self._update_options() + return self._async_update_options() self.current_device, description = self.devices_to_configure.popitem() data_schema = vol.Schema( @@ -286,7 +201,8 @@ class OnewireOptionsFlowHandler(OptionsFlow): description_placeholders={"sensor_id": self.current_device}, ) - async def _update_options(self) -> FlowResult: + @callback + def _async_update_options(self) -> FlowResult: """Update config entry options.""" return self.async_create_entry(title="", data=self.options) diff --git a/homeassistant/components/onewire/const.py b/homeassistant/components/onewire/const.py index 7fce90cc012..ed744caee17 100644 --- a/homeassistant/components/onewire/const.py +++ b/homeassistant/components/onewire/const.py @@ -3,14 +3,8 @@ from __future__ import annotations from homeassistant.const import Platform -CONF_MOUNT_DIR = "mount_dir" - -CONF_TYPE_OWSERVER = "OWServer" -CONF_TYPE_SYSBUS = "SysBus" - -DEFAULT_OWSERVER_HOST = "localhost" -DEFAULT_OWSERVER_PORT = 4304 -DEFAULT_SYSBUS_MOUNT_DIR = "/sys/bus/w1/devices/" +DEFAULT_HOST = "localhost" +DEFAULT_PORT = 4304 DOMAIN = "onewire" @@ -18,7 +12,7 @@ DEVICE_KEYS_0_3 = range(4) DEVICE_KEYS_0_7 = range(8) DEVICE_KEYS_A_B = ("A", "B") -DEVICE_SUPPORT_OWSERVER = { +DEVICE_SUPPORT = { "05": (), "10": (), "12": (), @@ -35,7 +29,6 @@ DEVICE_SUPPORT_OWSERVER = { "7E": ("EDS0066", "EDS0068"), "EF": ("HB_HUB", "HB_MOISTURE_METER", "HobbyBoards_EF"), } -DEVICE_SUPPORT_SYSBUS = ["10", "22", "28", "3B", "42"] DEVICE_SUPPORT_OPTIONS = ["28"] diff --git a/homeassistant/components/onewire/manifest.json b/homeassistant/components/onewire/manifest.json index d7b301f9c23..87adc23ae18 100644 --- a/homeassistant/components/onewire/manifest.json +++ b/homeassistant/components/onewire/manifest.json @@ -3,8 +3,8 @@ "name": "1-Wire", "documentation": "https://www.home-assistant.io/integrations/onewire", "config_flow": true, - "requirements": ["pyownet==0.10.0.post1", "pi1wire==0.1.0"], + "requirements": ["pyownet==0.10.0.post1"], "codeowners": ["@garbled1", "@epenet"], "iot_class": "local_polling", - "loggers": ["pi1wire", "pyownet"] + "loggers": ["pyownet"] } diff --git a/homeassistant/components/onewire/model.py b/homeassistant/components/onewire/model.py index 370b26c2530..d3fb2f22f14 100644 --- a/homeassistant/components/onewire/model.py +++ b/homeassistant/components/onewire/model.py @@ -3,29 +3,15 @@ from __future__ import annotations from dataclasses import dataclass -from pi1wire import OneWireInterface - from homeassistant.helpers.entity import DeviceInfo @dataclass class OWDeviceDescription: - """OWDeviceDescription device description class.""" + """1-Wire device description class.""" device_info: DeviceInfo - -@dataclass -class OWDirectDeviceDescription(OWDeviceDescription): - """SysBus device description class.""" - - interface: OneWireInterface - - -@dataclass -class OWServerDeviceDescription(OWDeviceDescription): - """OWServer device description class.""" - family: str id: str path: str diff --git a/homeassistant/components/onewire/onewire_entities.py b/homeassistant/components/onewire/onewire_entities.py index e00733ae387..98287c01ce1 100644 --- a/homeassistant/components/onewire/onewire_entities.py +++ b/homeassistant/components/onewire/onewire_entities.py @@ -23,7 +23,7 @@ class OneWireEntityDescription(EntityDescription): _LOGGER = logging.getLogger(__name__) -class OneWireBaseEntity(Entity): +class OneWireEntity(Entity): """Implementation of a 1-Wire entity.""" entity_description: OneWireEntityDescription @@ -35,6 +35,7 @@ class OneWireBaseEntity(Entity): device_info: DeviceInfo, device_file: str, name: str, + owproxy: protocol._Proxy, ) -> None: """Initialize the entity.""" self.entity_description = description @@ -44,6 +45,7 @@ class OneWireBaseEntity(Entity): self._device_file = device_file self._state: StateType = None self._value_raw: float | None = None + self._owproxy = owproxy @property def extra_state_attributes(self) -> dict[str, Any] | None: @@ -53,44 +55,21 @@ class OneWireBaseEntity(Entity): "raw_value": self._value_raw, } - -class OneWireProxyEntity(OneWireBaseEntity): - """Implementation of a 1-Wire entity connected through owserver.""" - - def __init__( - self, - description: OneWireEntityDescription, - device_id: str, - device_info: DeviceInfo, - device_file: str, - name: str, - owproxy: protocol._Proxy, - ) -> None: - """Initialize the sensor.""" - super().__init__( - description=description, - device_id=device_id, - device_info=device_info, - device_file=device_file, - name=name, - ) - self._owproxy = owproxy - - def _read_value_ownet(self) -> str: - """Read a value from the owserver.""" + def _read_value(self) -> str: + """Read a value from the server.""" read_bytes: bytes = self._owproxy.read(self._device_file) return read_bytes.decode().lstrip() - def _write_value_ownet(self, value: bytes) -> None: - """Write a value to the owserver.""" + def _write_value(self, value: bytes) -> None: + """Write a value to the server.""" self._owproxy.write(self._device_file, value) def update(self) -> None: """Get the latest data from the device.""" try: - self._value_raw = float(self._read_value_ownet()) + self._value_raw = float(self._read_value()) except protocol.Error as exc: - _LOGGER.error("Owserver failure in read(), got: %s", exc) + _LOGGER.error("Failure to read server value, got: %s", exc) self._state = None else: if self.entity_description.read_mode == READ_MODE_INT: diff --git a/homeassistant/components/onewire/onewirehub.py b/homeassistant/components/onewire/onewirehub.py index 63ed3e8cef9..a412f87deaa 100644 --- a/homeassistant/components/onewire/onewirehub.py +++ b/homeassistant/components/onewire/onewirehub.py @@ -5,7 +5,6 @@ import logging import os from typing import TYPE_CHECKING -from pi1wire import Pi1Wire from pyownet import protocol from homeassistant.config_entries import ConfigEntry @@ -17,7 +16,6 @@ from homeassistant.const import ( ATTR_VIA_DEVICE, CONF_HOST, CONF_PORT, - CONF_TYPE, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -25,21 +23,13 @@ from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import DeviceInfo from .const import ( - CONF_MOUNT_DIR, - CONF_TYPE_OWSERVER, - CONF_TYPE_SYSBUS, - DEVICE_SUPPORT_OWSERVER, - DEVICE_SUPPORT_SYSBUS, + DEVICE_SUPPORT, DOMAIN, MANUFACTURER_EDS, MANUFACTURER_HOBBYBOARDS, MANUFACTURER_MAXIM, ) -from .model import ( - OWDeviceDescription, - OWDirectDeviceDescription, - OWServerDeviceDescription, -) +from .model import OWDeviceDescription DEVICE_COUPLERS = { # Family : [branches] @@ -54,26 +44,24 @@ DEVICE_MANUFACTURER = { _LOGGER = logging.getLogger(__name__) -def _is_known_owserver_device(device_family: str, device_type: str) -> bool: +def _is_known_device(device_family: str, device_type: str) -> bool: """Check if device family/type is known to the library.""" if device_family in ("7E", "EF"): # EDS or HobbyBoard - return device_type in DEVICE_SUPPORT_OWSERVER[device_family] - return device_family in DEVICE_SUPPORT_OWSERVER + return device_type in DEVICE_SUPPORT[device_family] + return device_family in DEVICE_SUPPORT class OneWireHub: - """Hub to communicate with SysBus or OWServer.""" + """Hub to communicate with server.""" def __init__(self, hass: HomeAssistant) -> None: """Initialize.""" self.hass = hass - self.type: str | None = None - self.pi1proxy: Pi1Wire | None = None self.owproxy: protocol._Proxy | None = None self.devices: list[OWDeviceDescription] | None = None async def connect(self, host: str, port: int) -> None: - """Connect to the owserver host.""" + """Connect to the server.""" try: self.owproxy = await self.hass.async_add_executor_job( protocol.proxy, host, port @@ -81,32 +69,12 @@ class OneWireHub: except protocol.ConnError as exc: raise CannotConnect from exc - async def check_mount_dir(self, mount_dir: str) -> None: - """Test that the mount_dir is a valid path.""" - if not await self.hass.async_add_executor_job(os.path.isdir, mount_dir): - raise InvalidPath - self.pi1proxy = Pi1Wire(mount_dir) - async def initialize(self, config_entry: ConfigEntry) -> None: """Initialize a config entry.""" - self.type = config_entry.data[CONF_TYPE] - if self.type == CONF_TYPE_SYSBUS: - mount_dir = config_entry.data[CONF_MOUNT_DIR] - _LOGGER.debug("Initializing using SysBus %s", mount_dir) - _LOGGER.warning( - "Using the 1-Wire integration via SysBus is deprecated and will be removed " - "in Home Assistant Core 2022.6; this integration is being adjusted to comply " - "with Architectural Decision Record 0019, more information can be found here: " - "https://github.com/home-assistant/architecture/blob/master/adr/0019-GPIO.md " - "Access via OWServer is still supported" - ) - - await self.check_mount_dir(mount_dir) - elif self.type == CONF_TYPE_OWSERVER: - host = config_entry.data[CONF_HOST] - port = config_entry.data[CONF_PORT] - _LOGGER.debug("Initializing using OWServer %s:%s", host, port) - await self.connect(host, port) + host = config_entry.data[CONF_HOST] + port = config_entry.data[CONF_PORT] + _LOGGER.debug("Initializing connection to %s:%s", host, port) + await self.connect(host, port) await self.discover_devices() if TYPE_CHECKING: assert self.devices @@ -126,63 +94,22 @@ class OneWireHub: async def discover_devices(self) -> None: """Discover all devices.""" if self.devices is None: - if self.type == CONF_TYPE_SYSBUS: - self.devices = await self.hass.async_add_executor_job( - self._discover_devices_sysbus - ) - if self.type == CONF_TYPE_OWSERVER: - self.devices = await self.hass.async_add_executor_job( - self._discover_devices_owserver - ) - - def _discover_devices_sysbus(self) -> list[OWDeviceDescription]: - """Discover all sysbus devices.""" - devices: list[OWDeviceDescription] = [] - assert self.pi1proxy - all_sensors = self.pi1proxy.find_all_sensors() - if not all_sensors: - _LOGGER.error( - "No onewire sensor found. Check if dtoverlay=w1-gpio " - "is in your /boot/config.txt. " - "Check the mount_dir parameter if it's defined" + self.devices = await self.hass.async_add_executor_job( + self._discover_devices ) - for interface in all_sensors: - device_family = interface.mac_address[:2] - device_id = f"{device_family}-{interface.mac_address[2:]}" - if device_family not in DEVICE_SUPPORT_SYSBUS: - _LOGGER.warning( - "Ignoring unknown device family (%s) found for device %s", - device_family, - device_id, - ) - continue - device_info: DeviceInfo = { - ATTR_IDENTIFIERS: {(DOMAIN, device_id)}, - ATTR_MANUFACTURER: DEVICE_MANUFACTURER.get( - device_family, MANUFACTURER_MAXIM - ), - ATTR_MODEL: device_family, - ATTR_NAME: device_id, - } - device = OWDirectDeviceDescription( - device_info=device_info, - interface=interface, - ) - devices.append(device) - return devices - def _discover_devices_owserver( + def _discover_devices( self, path: str = "/", parent_id: str | None = None ) -> list[OWDeviceDescription]: - """Discover all owserver devices.""" + """Discover all server devices.""" devices: list[OWDeviceDescription] = [] assert self.owproxy for device_path in self.owproxy.dir(path): device_id = os.path.split(os.path.split(device_path)[0])[1] device_family = self.owproxy.read(f"{device_path}family").decode() _LOGGER.debug("read `%sfamily`: %s", device_path, device_family) - device_type = self._get_device_type_owserver(device_path) - if not _is_known_owserver_device(device_family, device_type): + device_type = self._get_device_type(device_path) + if not _is_known_device(device_family, device_type): _LOGGER.warning( "Ignoring unknown device family/type (%s/%s) found for device %s", device_family, @@ -200,7 +127,7 @@ class OneWireHub: } if parent_id: device_info[ATTR_VIA_DEVICE] = (DOMAIN, parent_id) - device = OWServerDeviceDescription( + device = OWDeviceDescription( device_info=device_info, id=device_id, family=device_family, @@ -210,13 +137,13 @@ class OneWireHub: devices.append(device) if device_branches := DEVICE_COUPLERS.get(device_family): for branch in device_branches: - devices += self._discover_devices_owserver( + devices += self._discover_devices( f"{device_path}{branch}", device_id ) return devices - def _get_device_type_owserver(self, device_path: str) -> str: + def _get_device_type(self, device_path: str) -> str: """Get device model.""" if TYPE_CHECKING: assert self.owproxy diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 759b1f9eccf..9f376a6df7a 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -1,16 +1,13 @@ """Support for 1-Wire environment sensors.""" from __future__ import annotations -import asyncio from collections.abc import Callable, Mapping import copy from dataclasses import dataclass import logging import os from types import MappingProxyType -from typing import TYPE_CHECKING, Any - -from pi1wire import InvalidCRCException, OneWireInterface, UnsupportResponseException +from typing import Any from homeassistant.components.sensor import ( SensorDeviceClass, @@ -20,7 +17,6 @@ from homeassistant.components.sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - CONF_TYPE, ELECTRIC_POTENTIAL_VOLT, LIGHT_LUX, PERCENTAGE, @@ -29,13 +25,10 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from .const import ( - CONF_TYPE_OWSERVER, - CONF_TYPE_SYSBUS, DEVICE_KEYS_0_3, DEVICE_KEYS_A_B, DOMAIN, @@ -45,12 +38,7 @@ from .const import ( READ_MODE_FLOAT, READ_MODE_INT, ) -from .model import OWDirectDeviceDescription, OWServerDeviceDescription -from .onewire_entities import ( - OneWireBaseEntity, - OneWireEntityDescription, - OneWireProxyEntity, -) +from .onewire_entities import OneWireEntity, OneWireEntityDescription from .onewirehub import OneWireHub @@ -263,8 +251,6 @@ DEVICE_SENSORS: dict[str, tuple[OneWireSensorEntityDescription, ...]] = { } # EF sensors are usually hobbyboards specialized sensors. -# These can only be read by OWFS. Currently this driver only supports them -# via owserver (network protocol) HOBBYBOARD_EF: dict[str, tuple[OneWireSensorEntityDescription, ...]] = { "HobbyBoards_EF": ( @@ -383,109 +369,72 @@ async def async_setup_entry( """Set up 1-Wire platform.""" onewirehub = hass.data[DOMAIN][config_entry.entry_id] entities = await hass.async_add_executor_job( - get_entities, onewirehub, config_entry.data, config_entry.options + get_entities, onewirehub, config_entry.options ) async_add_entities(entities, True) def get_entities( - onewirehub: OneWireHub, - config: MappingProxyType[str, Any], - options: MappingProxyType[str, Any], -) -> list[SensorEntity]: + onewirehub: OneWireHub, options: MappingProxyType[str, Any] +) -> list[OneWireSensor]: """Get a list of entities.""" if not onewirehub.devices: return [] - entities: list[SensorEntity] = [] - conf_type = config[CONF_TYPE] - # We have an owserver on a remote(or local) host/port - if conf_type == CONF_TYPE_OWSERVER: - assert onewirehub.owproxy - for device in onewirehub.devices: - if TYPE_CHECKING: - assert isinstance(device, OWServerDeviceDescription) - family = device.family - device_type = device.type - device_id = device.id - device_info = device.device_info - device_sub_type = "std" - device_path = device.path - if "EF" in family: - device_sub_type = "HobbyBoard" - family = device_type - elif "7E" in family: - device_sub_type = "EDS" - family = device_type + entities: list[OneWireSensor] = [] + assert onewirehub.owproxy + for device in onewirehub.devices: + family = device.family + device_type = device.type + device_id = device.id + device_info = device.device_info + device_sub_type = "std" + device_path = device.path + if "EF" in family: + device_sub_type = "HobbyBoard" + family = device_type + elif "7E" in family: + device_sub_type = "EDS" + family = device_type - if family not in get_sensor_types(device_sub_type): - continue - for description in get_sensor_types(device_sub_type)[family]: - if description.key.startswith("moisture/"): - s_id = description.key.split(".")[1] - is_leaf = int( - onewirehub.owproxy.read( - f"{device_path}moisture/is_leaf.{s_id}" - ).decode() - ) - if is_leaf: - description = copy.deepcopy(description) - description.device_class = SensorDeviceClass.HUMIDITY - description.native_unit_of_measurement = PERCENTAGE - description.name = f"Wetness {s_id}" - override_key = None - if description.override_key: - override_key = description.override_key(device_id, options) - device_file = os.path.join( - os.path.split(device.path)[0], - override_key or description.key, + if family not in get_sensor_types(device_sub_type): + continue + for description in get_sensor_types(device_sub_type)[family]: + if description.key.startswith("moisture/"): + s_id = description.key.split(".")[1] + is_leaf = int( + onewirehub.owproxy.read( + f"{device_path}moisture/is_leaf.{s_id}" + ).decode() ) - name = f"{device_id} {description.name}" - entities.append( - OneWireProxySensor( - description=description, - device_id=device_id, - device_file=device_file, - device_info=device_info, - name=name, - owproxy=onewirehub.owproxy, - ) - ) - - # We have a raw GPIO ow sensor on a Pi - elif conf_type == CONF_TYPE_SYSBUS: - for device in onewirehub.devices: - if TYPE_CHECKING: - assert isinstance(device, OWDirectDeviceDescription) - p1sensor: OneWireInterface = device.interface - family = p1sensor.mac_address[:2] - device_id = f"{family}-{p1sensor.mac_address[2:]}" - device_info = device.device_info - description = SIMPLE_TEMPERATURE_SENSOR_DESCRIPTION - device_file = f"/sys/bus/w1/devices/{device_id}/w1_slave" + if is_leaf: + description = copy.deepcopy(description) + description.device_class = SensorDeviceClass.HUMIDITY + description.native_unit_of_measurement = PERCENTAGE + description.name = f"Wetness {s_id}" + override_key = None + if description.override_key: + override_key = description.override_key(device_id, options) + device_file = os.path.join( + os.path.split(device.path)[0], + override_key or description.key, + ) name = f"{device_id} {description.name}" entities.append( - OneWireDirectSensor( + OneWireSensor( description=description, device_id=device_id, device_file=device_file, device_info=device_info, name=name, - owsensor=p1sensor, + owproxy=onewirehub.owproxy, ) ) - return entities -class OneWireSensor(OneWireBaseEntity, SensorEntity): - """Mixin for sensor specific attributes.""" - - entity_description: OneWireSensorEntityDescription - - -class OneWireProxySensor(OneWireProxyEntity, OneWireSensor): - """Implementation of a 1-Wire sensor connected through owserver.""" +class OneWireSensor(OneWireEntity, SensorEntity): + """Implementation of a 1-Wire sensor.""" entity_description: OneWireSensorEntityDescription @@ -493,69 +442,3 @@ class OneWireProxySensor(OneWireProxyEntity, OneWireSensor): def native_value(self) -> StateType: """Return the state of the entity.""" return self._state - - -class OneWireDirectSensor(OneWireSensor): - """Implementation of a 1-Wire sensor directly connected to RPI GPIO.""" - - def __init__( - self, - description: OneWireSensorEntityDescription, - device_id: str, - device_info: DeviceInfo, - device_file: str, - name: str, - owsensor: OneWireInterface, - ) -> None: - """Initialize the sensor.""" - super().__init__( - description=description, - device_id=device_id, - device_info=device_info, - device_file=device_file, - name=name, - ) - self._attr_unique_id = device_file - self._owsensor = owsensor - - @property - def native_value(self) -> StateType: - """Return the state of the entity.""" - return self._state - - async def get_temperature(self) -> float: - """Get the latest data from the device.""" - attempts = 1 - while True: - try: - return await self.hass.async_add_executor_job( - self._owsensor.get_temperature - ) - except UnsupportResponseException as ex: - _LOGGER.debug( - "Cannot read from sensor %s (retry attempt %s): %s", - self._device_file, - attempts, - ex, - ) - await asyncio.sleep(0.2) - attempts += 1 - if attempts > 10: - raise - - async def async_update(self) -> None: - """Get the latest data from the device.""" - try: - self._value_raw = await self.get_temperature() - self._state = round(self._value_raw, 1) - except ( - FileNotFoundError, - InvalidCRCException, - UnsupportResponseException, - ) as ex: - _LOGGER.warning( - "Cannot read from sensor %s: %s", - self._device_file, - ex, - ) - self._state = None diff --git a/homeassistant/components/onewire/strings.json b/homeassistant/components/onewire/strings.json index b685479d359..734971cb2a1 100644 --- a/homeassistant/components/onewire/strings.json +++ b/homeassistant/components/onewire/strings.json @@ -4,22 +4,15 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" }, "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_path": "Directory not found." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "step": { - "owserver": { + "user": { "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" }, - "title": "Set owserver details" - }, - "user": { - "data": { - "type": "Connection type" - }, - "title": "Set up 1-Wire" + "title": "Set server details" } } }, @@ -28,11 +21,6 @@ "device_not_selected": "Select devices to configure" }, "step": { - "ack_no_options": { - "data": {}, - "description": "There are no options for the SysBus implementation", - "title": "OneWire SysBus Options" - }, "device_selection": { "data": { "clear_device_options": "Clear all device configurations", diff --git a/homeassistant/components/onewire/switch.py b/homeassistant/components/onewire/switch.py index a05757936f9..764cc403681 100644 --- a/homeassistant/components/onewire/switch.py +++ b/homeassistant/components/onewire/switch.py @@ -3,25 +3,22 @@ from __future__ import annotations from dataclasses import dataclass import os -from typing import TYPE_CHECKING, Any +from typing import Any from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_TYPE from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( - CONF_TYPE_OWSERVER, DEVICE_KEYS_0_3, DEVICE_KEYS_0_7, DEVICE_KEYS_A_B, DOMAIN, READ_MODE_BOOL, ) -from .model import OWServerDeviceDescription -from .onewire_entities import OneWireEntityDescription, OneWireProxyEntity +from .onewire_entities import OneWireEntity, OneWireEntityDescription from .onewirehub import OneWireHub @@ -153,24 +150,20 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up 1-Wire platform.""" - # Only OWServer implementation works with switches - if config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER: - onewirehub = hass.data[DOMAIN][config_entry.entry_id] + onewirehub = hass.data[DOMAIN][config_entry.entry_id] - entities = await hass.async_add_executor_job(get_entities, onewirehub) - async_add_entities(entities, True) + entities = await hass.async_add_executor_job(get_entities, onewirehub) + async_add_entities(entities, True) -def get_entities(onewirehub: OneWireHub) -> list[SwitchEntity]: +def get_entities(onewirehub: OneWireHub) -> list[OneWireSwitch]: """Get a list of entities.""" if not onewirehub.devices: return [] - entities: list[SwitchEntity] = [] + entities: list[OneWireSwitch] = [] for device in onewirehub.devices: - if TYPE_CHECKING: - assert isinstance(device, OWServerDeviceDescription) family = device.family device_type = device.type device_id = device.id @@ -186,7 +179,7 @@ def get_entities(onewirehub: OneWireHub) -> list[SwitchEntity]: device_file = os.path.join(os.path.split(device.path)[0], description.key) name = f"{device_id} {description.name}" entities.append( - OneWireProxySwitch( + OneWireSwitch( description=description, device_id=device_id, device_file=device_file, @@ -199,7 +192,7 @@ def get_entities(onewirehub: OneWireHub) -> list[SwitchEntity]: return entities -class OneWireProxySwitch(OneWireProxyEntity, SwitchEntity): +class OneWireSwitch(OneWireEntity, SwitchEntity): """Implementation of a 1-Wire switch.""" entity_description: OneWireSwitchEntityDescription @@ -211,8 +204,8 @@ class OneWireProxySwitch(OneWireProxyEntity, SwitchEntity): def turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" - self._write_value_ownet(b"1") + self._write_value(b"1") def turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" - self._write_value_ownet(b"0") + self._write_value(b"0") diff --git a/homeassistant/components/onewire/translations/bg.json b/homeassistant/components/onewire/translations/bg.json index e45a2db7197..4893ecc7a5d 100644 --- a/homeassistant/components/onewire/translations/bg.json +++ b/homeassistant/components/onewire/translations/bg.json @@ -7,16 +7,11 @@ "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "step": { - "owserver": { + "user": { "data": { "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" } - }, - "user": { - "data": { - "type": "\u0412\u0438\u0434 \u043d\u0430 \u0432\u0440\u044a\u0437\u043a\u0430\u0442\u0430" - } } } }, diff --git a/homeassistant/components/onewire/translations/ca.json b/homeassistant/components/onewire/translations/ca.json index b50e3abc1f6..ae434bc5be4 100644 --- a/homeassistant/components/onewire/translations/ca.json +++ b/homeassistant/components/onewire/translations/ca.json @@ -4,22 +4,15 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "Ha fallat la connexi\u00f3", - "invalid_path": "No s'ha trobat el directori." + "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { - "owserver": { + "user": { "data": { "host": "Amfitri\u00f3", "port": "Port" }, - "title": "Defineix els detalls d'owserver" - }, - "user": { - "data": { - "type": "Tipus de connexi\u00f3" - }, - "title": "Configuraci\u00f3 d'1-Wire" + "title": "Detalls del servidor" } } }, @@ -28,10 +21,6 @@ "device_not_selected": "Selecciona els dispositius a configurar" }, "step": { - "ack_no_options": { - "description": "No hi ha opcions per a la implementaci\u00f3 de SysBus", - "title": "Opcions SysBus de OneWire" - }, "configure_device": { "data": { "precision": "Precisi\u00f3 del sensor" diff --git a/homeassistant/components/onewire/translations/cs.json b/homeassistant/components/onewire/translations/cs.json index c5298d095d8..ae22bc1ce25 100644 --- a/homeassistant/components/onewire/translations/cs.json +++ b/homeassistant/components/onewire/translations/cs.json @@ -4,21 +4,10 @@ "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" }, "error": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "invalid_path": "Adres\u00e1\u0159 nebyl nalezen." + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, "step": { - "owserver": { - "data": { - "host": "Hostitel", - "port": "Port" - }, - "title": "Nastaven\u00ed owserver" - }, "user": { - "data": { - "type": "Typ p\u0159ipojen\u00ed" - }, "title": "Nastaven\u00ed 1-Wire" } } diff --git a/homeassistant/components/onewire/translations/de.json b/homeassistant/components/onewire/translations/de.json index a6347b12d75..feab77f3ec7 100644 --- a/homeassistant/components/onewire/translations/de.json +++ b/homeassistant/components/onewire/translations/de.json @@ -4,22 +4,15 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen", - "invalid_path": "Verzeichnis nicht gefunden." + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { - "owserver": { + "user": { "data": { "host": "Host", "port": "Port" }, - "title": "owserver-Details einstellen" - }, - "user": { - "data": { - "type": "Verbindungstyp" - }, - "title": "1-Wire einrichten" + "title": "Serverdetails festlegen" } } }, @@ -28,10 +21,6 @@ "device_not_selected": "Zu konfigurierende Ger\u00e4te ausw\u00e4hlen" }, "step": { - "ack_no_options": { - "description": "Es gibt keine Optionen f\u00fcr die SysBus-Implementierung", - "title": "OneWire SysBus-Optionen" - }, "configure_device": { "data": { "precision": "Sensorgenauigkeit" diff --git a/homeassistant/components/onewire/translations/el.json b/homeassistant/components/onewire/translations/el.json index 3d358decfe5..1f70ecff348 100644 --- a/homeassistant/components/onewire/translations/el.json +++ b/homeassistant/components/onewire/translations/el.json @@ -4,21 +4,14 @@ "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_path": "\u039f \u03ba\u03b1\u03c4\u03ac\u03bb\u03bf\u03b3\u03bf\u03c2 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5." + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { - "owserver": { + "user": { "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" }, - "title": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03b5\u03c1\u03b5\u03b9\u03ce\u03bd owserver" - }, - "user": { - "data": { - "type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" - }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 1-Wire" } } @@ -28,10 +21,6 @@ "device_not_selected": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7" }, "step": { - "ack_no_options": { - "description": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c5\u03bb\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 SysBus", - "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 SysBus OneWire" - }, "configure_device": { "data": { "precision": "\u0391\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1" diff --git a/homeassistant/components/onewire/translations/en.json b/homeassistant/components/onewire/translations/en.json index df61b436f65..62b4dddf659 100644 --- a/homeassistant/components/onewire/translations/en.json +++ b/homeassistant/components/onewire/translations/en.json @@ -4,22 +4,15 @@ "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect", - "invalid_path": "Directory not found." + "cannot_connect": "Failed to connect" }, "step": { - "owserver": { + "user": { "data": { "host": "Host", "port": "Port" }, - "title": "Set owserver details" - }, - "user": { - "data": { - "type": "Connection type" - }, - "title": "Set up 1-Wire" + "title": "Set server details" } } }, @@ -28,10 +21,6 @@ "device_not_selected": "Select devices to configure" }, "step": { - "ack_no_options": { - "description": "There are no options for the SysBus implementation", - "title": "OneWire SysBus Options" - }, "configure_device": { "data": { "precision": "Sensor Precision" diff --git a/homeassistant/components/onewire/translations/es.json b/homeassistant/components/onewire/translations/es.json index d789e46875f..9fa4912166f 100644 --- a/homeassistant/components/onewire/translations/es.json +++ b/homeassistant/components/onewire/translations/es.json @@ -4,21 +4,14 @@ "already_configured": "El dispositivo ya est\u00e1 configurado" }, "error": { - "cannot_connect": "No se pudo conectar", - "invalid_path": "Directorio no encontrado." + "cannot_connect": "No se pudo conectar" }, "step": { - "owserver": { + "user": { "data": { "host": "Host", "port": "Puerto" }, - "title": "Configurar los detalles del servidor" - }, - "user": { - "data": { - "type": "Tipo de conexi\u00f3n" - }, "title": "Configurar 1 cable" } } @@ -26,6 +19,21 @@ "options": { "error": { "device_not_selected": "Seleccionar los dispositivos a configurar" + }, + "step": { + "configure_device": { + "data": { + "precision": "Precisi\u00f3n del sensor" + }, + "description": "Selecciona la precisi\u00f3n del sensor {sensor_id}" + }, + "device_selection": { + "data": { + "clear_device_options": "Borra todas las configuraciones de dispositivo" + }, + "description": "Seleccione los pasos de configuraci\u00f3n a procesar", + "title": "Opciones de dispositivo OneWire" + } } } } \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/et.json b/homeassistant/components/onewire/translations/et.json index bb00ce8fbbc..cc3588be15a 100644 --- a/homeassistant/components/onewire/translations/et.json +++ b/homeassistant/components/onewire/translations/et.json @@ -4,22 +4,15 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud" }, "error": { - "cannot_connect": "\u00dchendus nurjus", - "invalid_path": "Kausta ei leitud." + "cannot_connect": "\u00dchendus nurjus" }, "step": { - "owserver": { - "data": { - "host": "", - "port": "" - }, - "title": "M\u00e4\u00e4ra owserver-i \u00fcksikasjad" - }, "user": { "data": { - "type": "\u00dchenduse t\u00fc\u00fcp" + "host": "Host", + "port": "Port" }, - "title": "Seadista 1-wire sidumine" + "title": "M\u00e4\u00e4ra serveri \u00fcksikasjad" } } }, @@ -28,10 +21,6 @@ "device_not_selected": "Vali seadistatav seade" }, "step": { - "ack_no_options": { - "description": "SysBusi rakendamiseks pole v\u00f5imalusi", - "title": "OneWire SysBusi valikud" - }, "configure_device": { "data": { "precision": "Anduri t\u00e4psus" diff --git a/homeassistant/components/onewire/translations/fr.json b/homeassistant/components/onewire/translations/fr.json index 08e3d3cf18b..937aa719f3d 100644 --- a/homeassistant/components/onewire/translations/fr.json +++ b/homeassistant/components/onewire/translations/fr.json @@ -4,22 +4,15 @@ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, "error": { - "cannot_connect": "\u00c9chec de connexion", - "invalid_path": "R\u00e9pertoire introuvable." + "cannot_connect": "\u00c9chec de connexion" }, "step": { - "owserver": { + "user": { "data": { "host": "H\u00f4te", "port": "Port" }, - "title": "D\u00e9finir les d\u00e9tails d'owserver" - }, - "user": { - "data": { - "type": "Type de connexion" - }, - "title": "Configurer 1-Wire" + "title": "Renseigner les informations du serveur" } } }, @@ -28,10 +21,6 @@ "device_not_selected": "S\u00e9lectionnez les appareils \u00e0 configurer" }, "step": { - "ack_no_options": { - "description": "Il n'y a pas d'option pour l'impl\u00e9mentation de SysBus", - "title": "Options de bus syst\u00e8me OneWire" - }, "configure_device": { "data": { "precision": "Pr\u00e9cision du capteur" diff --git a/homeassistant/components/onewire/translations/he.json b/homeassistant/components/onewire/translations/he.json index d83d1f76175..c3a67844fdd 100644 --- a/homeassistant/components/onewire/translations/he.json +++ b/homeassistant/components/onewire/translations/he.json @@ -7,7 +7,7 @@ "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" }, "step": { - "owserver": { + "user": { "data": { "host": "\u05de\u05d0\u05e8\u05d7", "port": "\u05e4\u05ea\u05d7\u05d4" diff --git a/homeassistant/components/onewire/translations/hu.json b/homeassistant/components/onewire/translations/hu.json index 7034f2eaa29..94581202b99 100644 --- a/homeassistant/components/onewire/translations/hu.json +++ b/homeassistant/components/onewire/translations/hu.json @@ -4,22 +4,15 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_path": "A k\u00f6nyvt\u00e1r nem tal\u00e1lhat\u00f3." + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { - "owserver": { + "user": { "data": { "host": "C\u00edm", "port": "Port" }, - "title": "Owserver adatok be\u00e1ll\u00edt\u00e1sa" - }, - "user": { - "data": { - "type": "Kapcsolat t\u00edpusa" - }, - "title": "A 1-Wire be\u00e1ll\u00edt\u00e1sa" + "title": "A szerver be\u00e1ll\u00edt\u00e1sa" } } }, @@ -32,9 +25,7 @@ "data": { "one": "\u00dcres", "other": "\u00dcres" - }, - "description": "A SysBus implement\u00e1ci\u00f3j\u00e1nak nincsenek opci\u00f3i.", - "title": "OneWire SysBus opci\u00f3k" + } }, "configure_device": { "data": { diff --git a/homeassistant/components/onewire/translations/id.json b/homeassistant/components/onewire/translations/id.json index a1cde1b35bd..46d04ad89f4 100644 --- a/homeassistant/components/onewire/translations/id.json +++ b/homeassistant/components/onewire/translations/id.json @@ -4,22 +4,15 @@ "already_configured": "Perangkat sudah dikonfigurasi" }, "error": { - "cannot_connect": "Gagal terhubung", - "invalid_path": "Direktori tidak ditemukan." + "cannot_connect": "Gagal terhubung" }, "step": { - "owserver": { + "user": { "data": { "host": "Host", "port": "Port" }, - "title": "Tetapkan detail owserver" - }, - "user": { - "data": { - "type": "Jenis koneksi" - }, - "title": "Siapkan 1-Wire" + "title": "Setel detail server" } } }, @@ -28,10 +21,6 @@ "device_not_selected": "Pilih perangkat untuk dikonfigurasi" }, "step": { - "ack_no_options": { - "description": "Tidak ada opsi untuk implementasi SysBus", - "title": "Opsi OneWire SysBus" - }, "configure_device": { "data": { "precision": "Presisi Sensor" diff --git a/homeassistant/components/onewire/translations/it.json b/homeassistant/components/onewire/translations/it.json index ec2edddc275..9679f7f3e69 100644 --- a/homeassistant/components/onewire/translations/it.json +++ b/homeassistant/components/onewire/translations/it.json @@ -4,22 +4,15 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi", - "invalid_path": "Directory non trovata." + "cannot_connect": "Impossibile connettersi" }, "step": { - "owserver": { + "user": { "data": { "host": "Host", "port": "Porta" }, - "title": "Imposta i dettagli dell'owserver" - }, - "user": { - "data": { - "type": "Tipo di connessione" - }, - "title": "Configurazione 1-Wire" + "title": "Imposta i dettagli del server" } } }, @@ -32,9 +25,7 @@ "data": { "one": "Vuoto", "other": "Vuoti" - }, - "description": "Non ci sono opzioni per l'implementazione SysBus", - "title": "Opzioni SysBus OneWire" + } }, "configure_device": { "data": { diff --git a/homeassistant/components/onewire/translations/ja.json b/homeassistant/components/onewire/translations/ja.json index c948a9567b0..75b01a0cbc9 100644 --- a/homeassistant/components/onewire/translations/ja.json +++ b/homeassistant/components/onewire/translations/ja.json @@ -4,21 +4,14 @@ "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_path": "\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002" + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" }, "step": { - "owserver": { + "user": { "data": { "host": "\u30db\u30b9\u30c8", "port": "\u30dd\u30fc\u30c8" }, - "title": "owserver\u306e\u8a73\u7d30\u8a2d\u5b9a" - }, - "user": { - "data": { - "type": "\u63a5\u7d9a\u30bf\u30a4\u30d7" - }, "title": "1-Wire\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" } } @@ -28,10 +21,6 @@ "device_not_selected": "\u8a2d\u5b9a\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e" }, "step": { - "ack_no_options": { - "description": "SysBus\u306e\u5b9f\u88c5\u306b\u95a2\u3059\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u306f\u3042\u308a\u307e\u305b\u3093", - "title": "OneWire SysBus\u306e\u30aa\u30d7\u30b7\u30e7\u30f3" - }, "configure_device": { "data": { "precision": "\u30bb\u30f3\u30b5\u30fc\u306e\u7cbe\u5ea6" diff --git a/homeassistant/components/onewire/translations/ka.json b/homeassistant/components/onewire/translations/ka.json index 1b3c7e8ef5c..be486ef2f62 100644 --- a/homeassistant/components/onewire/translations/ka.json +++ b/homeassistant/components/onewire/translations/ka.json @@ -4,21 +4,10 @@ "already_configured": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d0 \u10e3\u10d9\u10d5\u10d4 \u10d3\u10d0\u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d1\u10e3\u10da\u10d8\u10d0" }, "error": { - "cannot_connect": "\u10d3\u10d0\u10d9\u10d0\u10d5\u10e8\u10e0\u10d4\u10d1\u10d0 \u10d5\u10d4\u10e0 \u10db\u10dd\u10ee\u10d4\u10e0\u10ee\u10d3\u10d0", - "invalid_path": "\u10d3\u10d8\u10e0\u10d4\u10e5\u10e2\u10dd\u10e0\u10d8\u10d0 \u10d5\u10d4\u10e0 \u10db\u10dd\u10d8\u10eb\u10d4\u10d1\u10dc\u10d0." + "cannot_connect": "\u10d3\u10d0\u10d9\u10d0\u10d5\u10e8\u10e0\u10d4\u10d1\u10d0 \u10d5\u10d4\u10e0 \u10db\u10dd\u10ee\u10d4\u10e0\u10ee\u10d3\u10d0" }, "step": { - "owserver": { - "data": { - "host": "\u10f0\u10dd\u10e1\u10e2\u10d8", - "port": "\u10de\u10dd\u10e0\u10e2\u10d8" - }, - "title": "owserver \u10e1\u10d4\u10e0\u10d5\u10d4\u10e0\u10d8\u10e1 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d1\u10d0" - }, "user": { - "data": { - "type": "\u1c99\u10d0\u10d5\u10e8\u10d8\u10e0\u10d8\u10e1 \u10e2\u10d8\u10de\u10d8" - }, "title": "1-\u10db\u10d0\u10d5\u10d7\u10d8\u10da\u10d8\u10e1 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d1\u10d0" } } diff --git a/homeassistant/components/onewire/translations/ko.json b/homeassistant/components/onewire/translations/ko.json index 038be16108a..eeaa8f4c09d 100644 --- a/homeassistant/components/onewire/translations/ko.json +++ b/homeassistant/components/onewire/translations/ko.json @@ -4,21 +4,14 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_path": "\ub514\ub809\ud130\ub9ac\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { - "owserver": { + "user": { "data": { "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" }, - "title": "owserver \uc138\ubd80 \uc815\ubcf4 \uc124\uc815\ud558\uae30" - }, - "user": { - "data": { - "type": "\uc5f0\uacb0 \uc720\ud615" - }, "title": "1-Wire \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/onewire/translations/lb.json b/homeassistant/components/onewire/translations/lb.json index c7c302ab683..c403b09531a 100644 --- a/homeassistant/components/onewire/translations/lb.json +++ b/homeassistant/components/onewire/translations/lb.json @@ -4,21 +4,7 @@ "already_configured": "Apparat ass scho konfigur\u00e9iert" }, "error": { - "cannot_connect": "Feeler beim verbannen", - "invalid_path": "Dossier net fonnt" - }, - "step": { - "owserver": { - "data": { - "host": "Host", - "port": "Port" - } - }, - "user": { - "data": { - "type": "Typ vun der Verbindung" - } - } + "cannot_connect": "Feeler beim verbannen" } } } \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/nl.json b/homeassistant/components/onewire/translations/nl.json index 3ea1fd8e13c..652e5b17fd5 100644 --- a/homeassistant/components/onewire/translations/nl.json +++ b/homeassistant/components/onewire/translations/nl.json @@ -4,22 +4,15 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kan geen verbinding maken", - "invalid_path": "Directory niet gevonden." + "cannot_connect": "Kan geen verbinding maken" }, "step": { - "owserver": { + "user": { "data": { "host": "Host", "port": "Poort" }, - "title": "Owserver-details instellen" - }, - "user": { - "data": { - "type": "Verbindingstype" - }, - "title": "Stel 1-Wire in" + "title": "Serverdetails instellen" } } }, @@ -32,9 +25,7 @@ "data": { "one": "Leeg", "other": "Ander" - }, - "description": "Er zijn geen opties voor de SysBus implementatie", - "title": "OneWire SysBus opties" + } }, "configure_device": { "data": { diff --git a/homeassistant/components/onewire/translations/no.json b/homeassistant/components/onewire/translations/no.json index 62bd0ac4634..13b3df80fde 100644 --- a/homeassistant/components/onewire/translations/no.json +++ b/homeassistant/components/onewire/translations/no.json @@ -4,22 +4,15 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Tilkobling mislyktes", - "invalid_path": "Finner ikke mappen" + "cannot_connect": "Tilkobling mislyktes" }, "step": { - "owserver": { + "user": { "data": { "host": "Vert", "port": "Port" }, - "title": "Angi detaljer om owserver" - }, - "user": { - "data": { - "type": "Tilkoblingstype" - }, - "title": "Sett opp 1-Wire" + "title": "Angi serverdetaljer" } } }, @@ -28,10 +21,6 @@ "device_not_selected": "Velg enheter som skal konfigureres" }, "step": { - "ack_no_options": { - "description": "Det er ingen alternativer for SysBus-implementeringen", - "title": "OneWire SysBus-alternativer" - }, "configure_device": { "data": { "precision": "Sensorpresisjon" diff --git a/homeassistant/components/onewire/translations/pl.json b/homeassistant/components/onewire/translations/pl.json index fe0e1d54eb5..523afbd98d9 100644 --- a/homeassistant/components/onewire/translations/pl.json +++ b/homeassistant/components/onewire/translations/pl.json @@ -4,22 +4,15 @@ "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "invalid_path": "Nie znaleziono katalogu" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { - "owserver": { + "user": { "data": { "host": "Nazwa hosta lub adres IP", "port": "Port" }, - "title": "Ustawienia owserver" - }, - "user": { - "data": { - "type": "Rodzaj po\u0142\u0105czenia" - }, - "title": "Konfiguracja 1-Wire" + "title": "Konfiguracja serwera" } } }, @@ -28,10 +21,6 @@ "device_not_selected": "Wybierz urz\u0105dzenia do skonfigurowania" }, "step": { - "ack_no_options": { - "description": "Nie ma opcji dla implementacji magistrali SysBus", - "title": "Opcje magistrali OneWire SysBus" - }, "configure_device": { "data": { "precision": "Dok\u0142adno\u015b\u0107 sensora" diff --git a/homeassistant/components/onewire/translations/pt-BR.json b/homeassistant/components/onewire/translations/pt-BR.json index 61405f5089a..303d36f3cb2 100644 --- a/homeassistant/components/onewire/translations/pt-BR.json +++ b/homeassistant/components/onewire/translations/pt-BR.json @@ -4,22 +4,15 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar", - "invalid_path": "Diret\u00f3rio n\u00e3o encontrado." + "cannot_connect": "Falha ao conectar" }, "step": { - "owserver": { + "user": { "data": { - "host": "Nome do host", + "host": "Host", "port": "Porta" }, "title": "Definir detalhes do servidor" - }, - "user": { - "data": { - "type": "Tipo de conex\u00e3o" - }, - "title": "Configurar 1-Wire" } } }, @@ -28,10 +21,6 @@ "device_not_selected": "Selecione os dispositivos para configurar" }, "step": { - "ack_no_options": { - "description": "N\u00e3o h\u00e1 op\u00e7\u00f5es para a implementa\u00e7\u00e3o do SysBus", - "title": "Op\u00e7\u00f5es de OneWire SysBus" - }, "configure_device": { "data": { "precision": "Precis\u00e3o do Sensor" diff --git a/homeassistant/components/onewire/translations/pt.json b/homeassistant/components/onewire/translations/pt.json index bd1e14729e0..db0e0c2a137 100644 --- a/homeassistant/components/onewire/translations/pt.json +++ b/homeassistant/components/onewire/translations/pt.json @@ -5,14 +5,6 @@ }, "error": { "cannot_connect": "Falha na liga\u00e7\u00e3o" - }, - "step": { - "owserver": { - "data": { - "host": "Servidor", - "port": "Porta" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/ru.json b/homeassistant/components/onewire/translations/ru.json index 4bc24f85f19..300deadb447 100644 --- a/homeassistant/components/onewire/translations/ru.json +++ b/homeassistant/components/onewire/translations/ru.json @@ -4,21 +4,14 @@ "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_path": "\u041a\u0430\u0442\u0430\u043b\u043e\u0433 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { - "owserver": { + "user": { "data": { "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" }, - "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a owserver" - }, - "user": { - "data": { - "type": "\u0422\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" - }, "title": "1-Wire" } } @@ -28,10 +21,6 @@ "device_not_selected": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438" }, "step": { - "ack_no_options": { - "description": "\u0414\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 SysBus \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u043a\u0430\u043a\u0438\u0435-\u043b\u0438\u0431\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 OneWire SysBus" - }, "configure_device": { "data": { "precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c \u0434\u0430\u0442\u0447\u0438\u043a\u0430" diff --git a/homeassistant/components/onewire/translations/sk.json b/homeassistant/components/onewire/translations/sk.json index bd43098e555..8b2a2f7343b 100644 --- a/homeassistant/components/onewire/translations/sk.json +++ b/homeassistant/components/onewire/translations/sk.json @@ -4,20 +4,10 @@ "already_configured": "Zariadenie u\u017e je nakonfigurovan\u00e9" }, "error": { - "cannot_connect": "Nepodarilo sa pripoji\u0165", - "invalid_path": "Adres\u00e1r sa nena\u0161iel." + "cannot_connect": "Nepodarilo sa pripoji\u0165" }, "step": { - "owserver": { - "data": { - "port": "Port" - }, - "title": "Nastavenie owserver" - }, "user": { - "data": { - "type": "Typ pripojenia" - }, "title": "Nastavenie 1-Wire" } } diff --git a/homeassistant/components/onewire/translations/sl.json b/homeassistant/components/onewire/translations/sl.json index 7011c57c099..8797272c16c 100644 --- a/homeassistant/components/onewire/translations/sl.json +++ b/homeassistant/components/onewire/translations/sl.json @@ -2,9 +2,6 @@ "config": { "step": { "user": { - "data": { - "type": "Vrsta povezave" - }, "title": "Nastavite 1-Wire" } } diff --git a/homeassistant/components/onewire/translations/sv.json b/homeassistant/components/onewire/translations/sv.json index e5717348568..1b100c60d97 100644 --- a/homeassistant/components/onewire/translations/sv.json +++ b/homeassistant/components/onewire/translations/sv.json @@ -1,9 +1,9 @@ { - "config": { + "options": { "step": { - "owserver": { + "device_selection": { "data": { - "port": "Port" + "device_selection": "V\u00e4lj enheter att konfigurera" } } } diff --git a/homeassistant/components/onewire/translations/tr.json b/homeassistant/components/onewire/translations/tr.json index 6fea23cfc76..5c7257319c8 100644 --- a/homeassistant/components/onewire/translations/tr.json +++ b/homeassistant/components/onewire/translations/tr.json @@ -4,22 +4,15 @@ "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" }, "error": { - "cannot_connect": "Ba\u011flanma hatas\u0131", - "invalid_path": "Dizin bulunamad\u0131." + "cannot_connect": "Ba\u011flanma hatas\u0131" }, "step": { - "owserver": { + "user": { "data": { "host": "Sunucu", "port": "Port" }, "title": "Sunucu ayr\u0131nt\u0131lar\u0131n\u0131 ayarla" - }, - "user": { - "data": { - "type": "Ba\u011flant\u0131 t\u00fcr\u00fc" - }, - "title": "1-Wire'\u0131 kurun" } } }, @@ -32,9 +25,7 @@ "data": { "one": "Bo\u015f", "other": "Bo\u015f" - }, - "description": "SysBus uygulamas\u0131 i\u00e7in se\u00e7enek yok", - "title": "OneWire SysBus Se\u00e7enekleri" + } }, "configure_device": { "data": { diff --git a/homeassistant/components/onewire/translations/uk.json b/homeassistant/components/onewire/translations/uk.json index 9c9705d2993..879ba4c4414 100644 --- a/homeassistant/components/onewire/translations/uk.json +++ b/homeassistant/components/onewire/translations/uk.json @@ -4,21 +4,10 @@ "already_configured": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." }, "error": { - "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", - "invalid_path": "\u041a\u0430\u0442\u0430\u043b\u043e\u0433 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e." + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" }, "step": { - "owserver": { - "data": { - "host": "\u0425\u043e\u0441\u0442", - "port": "\u041f\u043e\u0440\u0442" - }, - "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e owserver" - }, "user": { - "data": { - "type": "\u0422\u0438\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" - }, "title": "1-Wire" } } diff --git a/homeassistant/components/onewire/translations/zh-Hant.json b/homeassistant/components/onewire/translations/zh-Hant.json index 8b11692a3f5..aa98353ca5a 100644 --- a/homeassistant/components/onewire/translations/zh-Hant.json +++ b/homeassistant/components/onewire/translations/zh-Hant.json @@ -4,22 +4,15 @@ "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_path": "\u672a\u627e\u5230\u88dd\u7f6e\u3002" + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { - "owserver": { + "user": { "data": { "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0" }, - "title": "\u8a2d\u5b9a owserver \u8a73\u7d30\u8cc7\u6599" - }, - "user": { - "data": { - "type": "\u9023\u7dda\u985e\u5225" - }, - "title": "\u8a2d\u5b9a 1-Wire" + "title": "\u8a2d\u5b9a\u4f3a\u670d\u5668\u8a73\u7d30\u8cc7\u8a0a" } } }, @@ -28,10 +21,6 @@ "device_not_selected": "\u9078\u64c7\u6240\u8981\u8a2d\u5b9a\u7684\u88dd\u7f6e" }, "step": { - "ack_no_options": { - "description": "SysBus implementation \u6c92\u6709\u8a2d\u5b9a\u9078\u9805", - "title": "OneWire SysBus \u9078\u9805" - }, "configure_device": { "data": { "precision": "\u611f\u6e2c\u5668\u7cbe\u6e96\u5ea6" diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index 8956f0ae2f9..7922d59ca53 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -2,6 +2,7 @@ from onvif.exceptions import ONVIFAuthError, ONVIFError, ONVIFTimeoutError from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS +from homeassistant.components.stream import CONF_RTSP_TRANSPORT, RTSP_TRANSPORTS from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, @@ -12,13 +13,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from .const import ( - CONF_RTSP_TRANSPORT, - CONF_SNAPSHOT_AUTH, - DEFAULT_ARGUMENTS, - DOMAIN, - RTSP_TRANS_PROTOCOLS, -) +from .const import CONF_SNAPSHOT_AUTH, DEFAULT_ARGUMENTS, DOMAIN from .device import ONVIFDevice @@ -99,7 +94,7 @@ async def async_populate_options(hass, entry): """Populate default options for device.""" options = { CONF_EXTRA_ARGUMENTS: DEFAULT_ARGUMENTS, - CONF_RTSP_TRANSPORT: RTSP_TRANS_PROTOCOLS[0], + CONF_RTSP_TRANSPORT: next(iter(RTSP_TRANSPORTS)), } hass.config_entries.async_update_entry(entry, options=options) diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index 3475df241b0..6aa7ae42767 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -9,6 +9,10 @@ from yarl import URL from homeassistant.components import ffmpeg from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS, get_ffmpeg_manager +from homeassistant.components.stream import ( + CONF_RTSP_TRANSPORT, + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import HTTP_BASIC_AUTHENTICATION from homeassistant.core import HomeAssistant @@ -27,7 +31,6 @@ from .const import ( ATTR_SPEED, ATTR_TILT, ATTR_ZOOM, - CONF_RTSP_TRANSPORT, CONF_SNAPSHOT_AUTH, CONTINUOUS_MOVE, DIR_DOWN, @@ -97,6 +100,9 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): self.stream_options[CONF_RTSP_TRANSPORT] = device.config_entry.options.get( CONF_RTSP_TRANSPORT ) + self.stream_options[ + CONF_USE_WALLCLOCK_AS_TIMESTAMPS + ] = device.config_entry.options.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS, False) self._basic_auth = ( device.config_entry.data.get(CONF_SNAPSHOT_AUTH) == HTTP_BASIC_AUTHENTICATION diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index c4579702675..1ee0be18467 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -13,6 +13,11 @@ from zeep.exceptions import Fault from homeassistant import config_entries from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS +from homeassistant.components.stream import ( + CONF_RTSP_TRANSPORT, + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, + RTSP_TRANSPORTS, +) from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -22,15 +27,7 @@ from homeassistant.const import ( ) from homeassistant.core import callback -from .const import ( - CONF_DEVICE_ID, - CONF_RTSP_TRANSPORT, - DEFAULT_ARGUMENTS, - DEFAULT_PORT, - DOMAIN, - LOGGER, - RTSP_TRANS_PROTOCOLS, -) +from .const import CONF_DEVICE_ID, DEFAULT_ARGUMENTS, DEFAULT_PORT, DOMAIN, LOGGER from .device import get_device CONF_MANUAL_INPUT = "Manually configure ONVIF device" @@ -279,8 +276,22 @@ class OnvifOptionsFlowHandler(config_entries.OptionsFlow): if user_input is not None: self.options[CONF_EXTRA_ARGUMENTS] = user_input[CONF_EXTRA_ARGUMENTS] self.options[CONF_RTSP_TRANSPORT] = user_input[CONF_RTSP_TRANSPORT] + self.options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] = user_input.get( + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, + self.config_entry.options.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS, False), + ) return self.async_create_entry(title="", data=self.options) + advanced_options = {} + if self.show_advanced_options: + advanced_options[ + vol.Optional( + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, + default=self.config_entry.options.get( + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, False + ), + ) + ] = bool return self.async_show_form( step_id="onvif_devices", data_schema=vol.Schema( @@ -294,9 +305,10 @@ class OnvifOptionsFlowHandler(config_entries.OptionsFlow): vol.Optional( CONF_RTSP_TRANSPORT, default=self.config_entry.options.get( - CONF_RTSP_TRANSPORT, RTSP_TRANS_PROTOCOLS[0] + CONF_RTSP_TRANSPORT, next(iter(RTSP_TRANSPORTS)) ), - ): vol.In(RTSP_TRANS_PROTOCOLS), + ): vol.In(RTSP_TRANSPORTS), + **advanced_options, } ), ) diff --git a/homeassistant/components/onvif/const.py b/homeassistant/components/onvif/const.py index 3a2e802a5a0..410088f28df 100644 --- a/homeassistant/components/onvif/const.py +++ b/homeassistant/components/onvif/const.py @@ -9,11 +9,8 @@ DEFAULT_PORT = 80 DEFAULT_ARGUMENTS = "-pred 1" CONF_DEVICE_ID = "deviceid" -CONF_RTSP_TRANSPORT = "rtsp_transport" CONF_SNAPSHOT_AUTH = "snapshot_auth" -RTSP_TRANS_PROTOCOLS = ["tcp", "udp", "udp_multicast", "http"] - ATTR_PAN = "pan" ATTR_TILT = "tilt" ATTR_ZOOM = "zoom" diff --git a/homeassistant/components/onvif/translations/bg.json b/homeassistant/components/onvif/translations/bg.json index e45e78b79ee..ba0e0dd6277 100644 --- a/homeassistant/components/onvif/translations/bg.json +++ b/homeassistant/components/onvif/translations/bg.json @@ -4,12 +4,6 @@ "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" }, "step": { - "auth": { - "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" - } - }, "configure": { "data": { "host": "\u0425\u043e\u0441\u0442", @@ -18,13 +12,6 @@ "port": "\u041f\u043e\u0440\u0442", "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" } - }, - "manual_input": { - "data": { - "host": "\u0425\u043e\u0441\u0442", - "name": "\u0418\u043c\u0435", - "port": "\u041f\u043e\u0440\u0442" - } } } } diff --git a/homeassistant/components/onvif/translations/ca.json b/homeassistant/components/onvif/translations/ca.json index 7ede84d4845..222921a0599 100644 --- a/homeassistant/components/onvif/translations/ca.json +++ b/homeassistant/components/onvif/translations/ca.json @@ -11,13 +11,6 @@ "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { - "auth": { - "data": { - "password": "Contrasenya", - "username": "Nom d'usuari" - }, - "title": "Configuraci\u00f3 d'autenticaci\u00f3" - }, "configure": { "data": { "host": "Amfitri\u00f3", @@ -41,14 +34,6 @@ }, "title": "Selecci\u00f3 de dispositiu ONVIF" }, - "manual_input": { - "data": { - "host": "Amfitri\u00f3", - "name": "Nom", - "port": "Port" - }, - "title": "Configura el dispositiu ONVIF" - }, "user": { "data": { "auto": "Cerca autom\u00e0ticament" diff --git a/homeassistant/components/onvif/translations/cs.json b/homeassistant/components/onvif/translations/cs.json index 4ddb2091cc3..100c4eb3788 100644 --- a/homeassistant/components/onvif/translations/cs.json +++ b/homeassistant/components/onvif/translations/cs.json @@ -11,13 +11,6 @@ "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, "step": { - "auth": { - "data": { - "password": "Heslo", - "username": "U\u017eivatelsk\u00e9 jm\u00e9no" - }, - "title": "Konfigurace ov\u011b\u0159ov\u00e1n\u00ed" - }, "configure": { "data": { "host": "Hostitel", @@ -40,14 +33,6 @@ }, "title": "Vyberte za\u0159\u00edzen\u00ed ONVIF" }, - "manual_input": { - "data": { - "host": "Hostitel", - "name": "Jm\u00e9no", - "port": "Port" - }, - "title": "Konfigurovat za\u0159\u00edzen\u00ed ONVIF" - }, "user": { "description": "Kliknut\u00edm na tla\u010d\u00edtko Odeslat vyhled\u00e1me ve va\u0161\u00ed s\u00edti za\u0159\u00edzen\u00ed ONVIF, kter\u00e1 podporuj\u00ed profil S. \n\nN\u011bkte\u0159\u00ed v\u00fdrobci vypli funkci ONVIF v z\u00e1kladn\u00edm nastaven\u00ed. Ujist\u011bte se, \u017ee je v konfiguraci kamery povolena funkce ONVIF.", "title": "Nastaven\u00ed za\u0159\u00edzen\u00ed ONVIF" diff --git a/homeassistant/components/onvif/translations/de.json b/homeassistant/components/onvif/translations/de.json index fd992c4db05..c4c745f1766 100644 --- a/homeassistant/components/onvif/translations/de.json +++ b/homeassistant/components/onvif/translations/de.json @@ -11,13 +11,6 @@ "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { - "auth": { - "data": { - "password": "Passwort", - "username": "Benutzername" - }, - "title": "Konfiguriere die Authentifizierung" - }, "configure": { "data": { "host": "Host", @@ -41,14 +34,6 @@ }, "title": "W\u00e4hle ein ONVIF-Ger\u00e4t" }, - "manual_input": { - "data": { - "host": "Host", - "name": "Name", - "port": "Port" - }, - "title": "Konfiguriere das ONVIF-Ger\u00e4t" - }, "user": { "data": { "auto": "Automatisch suchen" diff --git a/homeassistant/components/onvif/translations/el.json b/homeassistant/components/onvif/translations/el.json index 8bc93c4a3d3..911994c7344 100644 --- a/homeassistant/components/onvif/translations/el.json +++ b/homeassistant/components/onvif/translations/el.json @@ -11,13 +11,6 @@ "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { - "auth": { - "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" - }, - "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" - }, "configure": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", @@ -41,14 +34,6 @@ }, "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae ONVIF" }, - "manual_input": { - "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", - "port": "\u0398\u03cd\u03c1\u03b1" - }, - "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 ONVIF" - }, "user": { "data": { "auto": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7" diff --git a/homeassistant/components/onvif/translations/en.json b/homeassistant/components/onvif/translations/en.json index c922fc18482..c3b328646ee 100644 --- a/homeassistant/components/onvif/translations/en.json +++ b/homeassistant/components/onvif/translations/en.json @@ -11,13 +11,6 @@ "cannot_connect": "Failed to connect" }, "step": { - "auth": { - "data": { - "password": "Password", - "username": "Username" - }, - "title": "Configure authentication" - }, "configure": { "data": { "host": "Host", @@ -41,14 +34,6 @@ }, "title": "Select ONVIF device" }, - "manual_input": { - "data": { - "host": "Host", - "name": "Name", - "port": "Port" - }, - "title": "Configure ONVIF device" - }, "user": { "data": { "auto": "Search automatically" diff --git a/homeassistant/components/onvif/translations/es-419.json b/homeassistant/components/onvif/translations/es-419.json index d0db75beb96..55ff3166c66 100644 --- a/homeassistant/components/onvif/translations/es-419.json +++ b/homeassistant/components/onvif/translations/es-419.json @@ -8,13 +8,6 @@ "onvif_error": "Error al configurar el dispositivo ONVIF. Consulte los registros para obtener m\u00e1s informaci\u00f3n." }, "step": { - "auth": { - "data": { - "password": "Contrase\u00f1a", - "username": "Nombre de usuario" - }, - "title": "Configurar autenticaci\u00f3n" - }, "configure_profile": { "data": { "include": "Crear entidad de c\u00e1mara" @@ -28,13 +21,6 @@ }, "title": "Seleccionar dispositivo ONVIF" }, - "manual_input": { - "data": { - "host": "Host", - "port": "Puerto" - }, - "title": "Configurar dispositivo ONVIF" - }, "user": { "description": "Al hacer clic en enviar, buscaremos en su red dispositivos ONVIF que admitan Profile S. \n\nAlgunos fabricantes han comenzado a deshabilitar ONVIF por defecto. Aseg\u00farese de que ONVIF est\u00e9 habilitado en la configuraci\u00f3n de su c\u00e1mara.", "title": "Configuraci\u00f3n del dispositivo ONVIF" diff --git a/homeassistant/components/onvif/translations/es.json b/homeassistant/components/onvif/translations/es.json index d5c9688b875..6ba7dbe0ee8 100644 --- a/homeassistant/components/onvif/translations/es.json +++ b/homeassistant/components/onvif/translations/es.json @@ -11,13 +11,6 @@ "cannot_connect": "No se pudo conectar" }, "step": { - "auth": { - "data": { - "password": "Contrase\u00f1a", - "username": "Usuario" - }, - "title": "Configurar la autenticaci\u00f3n" - }, "configure": { "data": { "host": "Host", @@ -41,14 +34,6 @@ }, "title": "Seleccione el dispositivo ONVIF" }, - "manual_input": { - "data": { - "host": "Host", - "name": "Nombre", - "port": "Puerto" - }, - "title": "Configurar el dispositivo ONVIF" - }, "user": { "data": { "auto": "Buscar autom\u00e1ticamente" diff --git a/homeassistant/components/onvif/translations/et.json b/homeassistant/components/onvif/translations/et.json index 61eefa84d12..1d7bede6657 100644 --- a/homeassistant/components/onvif/translations/et.json +++ b/homeassistant/components/onvif/translations/et.json @@ -11,13 +11,6 @@ "cannot_connect": "\u00dchendamine nurjus" }, "step": { - "auth": { - "data": { - "password": "Salas\u00f5na", - "username": "Kasutajanimi" - }, - "title": "Autentimise seadistamine" - }, "configure": { "data": { "host": "Host", @@ -41,14 +34,6 @@ }, "title": "Vali ONVIF-seade" }, - "manual_input": { - "data": { - "host": "", - "name": "Nimi", - "port": "" - }, - "title": "H\u00e4\u00e4lesta ONVIF-seade" - }, "user": { "data": { "auto": "Otsi automaatselt" diff --git a/homeassistant/components/onvif/translations/fi.json b/homeassistant/components/onvif/translations/fi.json index 47673eeaaa5..d32021cd4b5 100644 --- a/homeassistant/components/onvif/translations/fi.json +++ b/homeassistant/components/onvif/translations/fi.json @@ -1,24 +1,11 @@ { "config": { "step": { - "auth": { - "data": { - "password": "Salasana", - "username": "K\u00e4ytt\u00e4j\u00e4tunnus" - }, - "title": "M\u00e4\u00e4rit\u00e4 todennus" - }, "configure_profile": { "data": { "include": "Luo kamerakohde" }, "title": "M\u00e4\u00e4rit\u00e4 profiilit" - }, - "manual_input": { - "data": { - "host": "Palvelin", - "port": "Portti" - } } } } diff --git a/homeassistant/components/onvif/translations/fr.json b/homeassistant/components/onvif/translations/fr.json index ab772fcec2d..6939205b4bb 100644 --- a/homeassistant/components/onvif/translations/fr.json +++ b/homeassistant/components/onvif/translations/fr.json @@ -11,13 +11,6 @@ "cannot_connect": "\u00c9chec de connexion" }, "step": { - "auth": { - "data": { - "password": "Mot de passe", - "username": "Nom d'utilisateur" - }, - "title": "Configurer l'authentification" - }, "configure": { "data": { "host": "H\u00f4te", @@ -41,14 +34,6 @@ }, "title": "S\u00e9lectionnez l'appareil ONVIF" }, - "manual_input": { - "data": { - "host": "H\u00f4te", - "name": "Nom", - "port": "Port" - }, - "title": "Configurer l\u2019appareil ONVIF" - }, "user": { "data": { "auto": "Rechercher automatiquement" diff --git a/homeassistant/components/onvif/translations/he.json b/homeassistant/components/onvif/translations/he.json index 5883d1afc53..727457fac34 100644 --- a/homeassistant/components/onvif/translations/he.json +++ b/homeassistant/components/onvif/translations/he.json @@ -11,13 +11,6 @@ "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" }, "step": { - "auth": { - "data": { - "password": "\u05e1\u05d9\u05e1\u05de\u05d4", - "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" - }, - "title": "\u05e7\u05d1\u05d9\u05e2\u05ea \u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc \u05d0\u05d9\u05de\u05d5\u05ea" - }, "configure": { "data": { "host": "\u05de\u05d0\u05e8\u05d7", @@ -41,14 +34,6 @@ }, "title": "\u05d1\u05d7\u05e8 \u05d4\u05ea\u05e7\u05df ONVIF" }, - "manual_input": { - "data": { - "host": "\u05de\u05d0\u05e8\u05d7", - "name": "\u05e9\u05dd", - "port": "\u05e4\u05d5\u05e8\u05d8" - }, - "title": "\u05e7\u05d1\u05d9\u05e2\u05ea \u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc \u05d4\u05ea\u05e7\u05df ONVIF" - }, "user": { "data": { "auto": "\u05d7\u05d9\u05e4\u05d5\u05e9 \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9" diff --git a/homeassistant/components/onvif/translations/hu.json b/homeassistant/components/onvif/translations/hu.json index 3754243a740..4fcb62c26fc 100644 --- a/homeassistant/components/onvif/translations/hu.json +++ b/homeassistant/components/onvif/translations/hu.json @@ -11,13 +11,6 @@ "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { - "auth": { - "data": { - "password": "Jelsz\u00f3", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - }, - "title": "Hiteles\u00edt\u00e9s konfigur\u00e1l\u00e1sa" - }, "configure": { "data": { "host": "C\u00edm", @@ -41,14 +34,6 @@ }, "title": "ONVIF eszk\u00f6z kiv\u00e1laszt\u00e1sa" }, - "manual_input": { - "data": { - "host": "C\u00edm", - "name": "Elnevez\u00e9s", - "port": "Port" - }, - "title": "ONVIF eszk\u00f6z konfigur\u00e1l\u00e1sa" - }, "user": { "data": { "auto": "Automatikus keres\u00e9s" diff --git a/homeassistant/components/onvif/translations/id.json b/homeassistant/components/onvif/translations/id.json index 77e1353270f..383287db875 100644 --- a/homeassistant/components/onvif/translations/id.json +++ b/homeassistant/components/onvif/translations/id.json @@ -11,13 +11,6 @@ "cannot_connect": "Gagal terhubung" }, "step": { - "auth": { - "data": { - "password": "Kata Sandi", - "username": "Nama Pengguna" - }, - "title": "Konfigurasikan autentikasi" - }, "configure": { "data": { "host": "Host", @@ -41,14 +34,6 @@ }, "title": "Pilih perangkat ONVIF" }, - "manual_input": { - "data": { - "host": "Host", - "name": "Nama", - "port": "Port" - }, - "title": "Konfigurasikan perangkat ONVIF" - }, "user": { "data": { "auto": "Cari secara otomatis" diff --git a/homeassistant/components/onvif/translations/it.json b/homeassistant/components/onvif/translations/it.json index 3590025a6ad..34d23e2f3fe 100644 --- a/homeassistant/components/onvif/translations/it.json +++ b/homeassistant/components/onvif/translations/it.json @@ -11,13 +11,6 @@ "cannot_connect": "Impossibile connettersi" }, "step": { - "auth": { - "data": { - "password": "Password", - "username": "Nome utente" - }, - "title": "Configura l'autenticazione" - }, "configure": { "data": { "host": "Host", @@ -41,14 +34,6 @@ }, "title": "Seleziona il dispositivo ONVIF" }, - "manual_input": { - "data": { - "host": "Host", - "name": "Nome", - "port": "Porta" - }, - "title": "Configura il dispositivo ONVIF" - }, "user": { "data": { "auto": "Cerca automaticamente" diff --git a/homeassistant/components/onvif/translations/ja.json b/homeassistant/components/onvif/translations/ja.json index a2d863b54fa..ed8e3aa6bd5 100644 --- a/homeassistant/components/onvif/translations/ja.json +++ b/homeassistant/components/onvif/translations/ja.json @@ -11,13 +11,6 @@ "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" }, "step": { - "auth": { - "data": { - "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", - "username": "\u30e6\u30fc\u30b6\u30fc\u540d" - }, - "title": "\u8a8d\u8a3c\u306e\u8a2d\u5b9a" - }, "configure": { "data": { "host": "\u30db\u30b9\u30c8", @@ -41,14 +34,6 @@ }, "title": "ONVIF\u30c7\u30d0\u30a4\u30b9\u306e\u9078\u629e" }, - "manual_input": { - "data": { - "host": "\u30db\u30b9\u30c8", - "name": "\u540d\u524d", - "port": "\u30dd\u30fc\u30c8" - }, - "title": "ONVIF\u30c7\u30d0\u30a4\u30b9\u306e\u8a2d\u5b9a" - }, "user": { "data": { "auto": "\u81ea\u52d5\u7684\u306b\u691c\u7d22" diff --git a/homeassistant/components/onvif/translations/ko.json b/homeassistant/components/onvif/translations/ko.json index 173cdb88512..5dcbd1df983 100644 --- a/homeassistant/components/onvif/translations/ko.json +++ b/homeassistant/components/onvif/translations/ko.json @@ -11,13 +11,6 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { - "auth": { - "data": { - "password": "\ube44\ubc00\ubc88\ud638", - "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - }, - "title": "\uc778\uc99d \uad6c\uc131\ud558\uae30" - }, "configure_profile": { "data": { "include": "\uce74\uba54\ub77c \uad6c\uc131\uc694\uc18c \ub9cc\ub4e4\uae30" @@ -31,14 +24,6 @@ }, "title": "ONVIF \uae30\uae30 \uc120\ud0dd\ud558\uae30" }, - "manual_input": { - "data": { - "host": "\ud638\uc2a4\ud2b8", - "name": "\uc774\ub984", - "port": "\ud3ec\ud2b8" - }, - "title": "ONVIF \uae30\uae30 \uad6c\uc131\ud558\uae30" - }, "user": { "description": "\ud655\uc778\uc744 \ud074\ub9ad\ud558\uba74 \ud504\ub85c\ud544 S \ub97c \uc9c0\uc6d0\ud558\ub294 ONVIF \uae30\uae30\ub97c \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uac80\uc0c9\ud569\ub2c8\ub2e4. \n\n\uc77c\ubd80 \uc81c\uc870\uc5c5\uccb4\ub294 \uae30\ubcf8\uac12\uc73c\ub85c ONVIF \ub97c \ube44\ud65c\uc131\ud654 \ud574 \ub193\uc740 \uacbd\uc6b0\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uce74\uba54\ub77c \uad6c\uc131\uc5d0\uc11c ONVIF \uac00 \ud65c\uc131\ud654\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "title": "ONVIF \uae30\uae30 \uc124\uc815\ud558\uae30" diff --git a/homeassistant/components/onvif/translations/lb.json b/homeassistant/components/onvif/translations/lb.json index f9b2843b5cb..008b52b5338 100644 --- a/homeassistant/components/onvif/translations/lb.json +++ b/homeassistant/components/onvif/translations/lb.json @@ -11,13 +11,6 @@ "cannot_connect": "Feeler beim verbannen" }, "step": { - "auth": { - "data": { - "password": "Passwuert", - "username": "Benotzernumm" - }, - "title": "Authentifikatioun konfigur\u00e9ieren" - }, "configure_profile": { "data": { "include": "Kamera Entit\u00e9it erstellen" @@ -31,14 +24,6 @@ }, "title": "ONVIF Apparat auswielen" }, - "manual_input": { - "data": { - "host": "Apparat", - "name": "Numm", - "port": "Port" - }, - "title": "ONVIF Apparat ariichten" - }, "user": { "description": "Andeems du op ofsch\u00e9cke klicks, g\u00ebtt dain Netzwierk fir ONVIF Apparater duerchsicht d\u00e9i den Profile S. \u00ebnnerst\u00ebtzen.\n\nVerschidde Hiersteller hunn ugefaang ONVIF standardm\u00e9isseg ze d\u00e9aktiv\u00e9ieren. Stell s\u00e9cher dass ONVIF an denger Kamera ugeschalt ass.", "title": "ONVIF Apparat ariichten" diff --git a/homeassistant/components/onvif/translations/nl.json b/homeassistant/components/onvif/translations/nl.json index c48d2764672..f76aef12557 100644 --- a/homeassistant/components/onvif/translations/nl.json +++ b/homeassistant/components/onvif/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al begonnen", + "already_in_progress": "De configuratie is momenteel al bezig", "no_h264": "Er waren geen H264-streams beschikbaar. Controleer de profielconfiguratie op uw apparaat.", "no_mac": "Kan geen unieke ID configureren voor ONVIF-apparaat.", "onvif_error": "Fout bij het instellen van ONVIF-apparaat. Controleer de logboeken voor meer informatie." @@ -11,13 +11,6 @@ "cannot_connect": "Kan geen verbinding maken" }, "step": { - "auth": { - "data": { - "password": "Wachtwoord", - "username": "Gebruikersnaam" - }, - "title": "Configureer authenticatie" - }, "configure": { "data": { "host": "Host", @@ -41,14 +34,6 @@ }, "title": "Selecteer ONVIF-apparaat" }, - "manual_input": { - "data": { - "host": "Host", - "name": "Naam", - "port": "Poort" - }, - "title": "Configureer ONVIF-apparaat" - }, "user": { "data": { "auto": "Automatisch zoeken" diff --git a/homeassistant/components/onvif/translations/no.json b/homeassistant/components/onvif/translations/no.json index 323f9aba5fe..a9087ba6be4 100644 --- a/homeassistant/components/onvif/translations/no.json +++ b/homeassistant/components/onvif/translations/no.json @@ -11,13 +11,6 @@ "cannot_connect": "Tilkobling mislyktes" }, "step": { - "auth": { - "data": { - "password": "Passord", - "username": "Brukernavn" - }, - "title": "Konfigurere godkjenning" - }, "configure": { "data": { "host": "Vert", @@ -41,14 +34,6 @@ }, "title": "Velg ONVIF-enhet" }, - "manual_input": { - "data": { - "host": "Vert", - "name": "Navn", - "port": "Port" - }, - "title": "Konfigurere ONVIF-enhet" - }, "user": { "data": { "auto": "S\u00f8k automatisk" diff --git a/homeassistant/components/onvif/translations/pl.json b/homeassistant/components/onvif/translations/pl.json index f45ad4064b2..2300f6d6041 100644 --- a/homeassistant/components/onvif/translations/pl.json +++ b/homeassistant/components/onvif/translations/pl.json @@ -11,13 +11,6 @@ "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { - "auth": { - "data": { - "password": "Has\u0142o", - "username": "Nazwa u\u017cytkownika" - }, - "title": "Konfiguracja uwierzytelniania" - }, "configure": { "data": { "host": "Nazwa hosta lub adres IP", @@ -41,14 +34,6 @@ }, "title": "Wybierz urz\u0105dzenie ONVIF" }, - "manual_input": { - "data": { - "host": "Nazwa hosta lub adres IP", - "name": "Nazwa", - "port": "Port" - }, - "title": "Konfiguracja urz\u0105dzenia ONVIF" - }, "user": { "data": { "auto": "Wyszukaj automatycznie" diff --git a/homeassistant/components/onvif/translations/pt-BR.json b/homeassistant/components/onvif/translations/pt-BR.json index d5586b2dd2b..f491f21d56b 100644 --- a/homeassistant/components/onvif/translations/pt-BR.json +++ b/homeassistant/components/onvif/translations/pt-BR.json @@ -11,13 +11,6 @@ "cannot_connect": "Falha ao conectar" }, "step": { - "auth": { - "data": { - "password": "Senha", - "username": "Usu\u00e1rio" - }, - "title": "Configurar autentica\u00e7\u00e3o" - }, "configure": { "data": { "host": "Nome do host", @@ -41,14 +34,6 @@ }, "title": "Selecionar dispositivo ONVIF" }, - "manual_input": { - "data": { - "host": "Nome do host", - "name": "Nome", - "port": "Porta" - }, - "title": "Configurar dispositivo ONVIF" - }, "user": { "data": { "auto": "Pesquisar automaticamente" diff --git a/homeassistant/components/onvif/translations/pt.json b/homeassistant/components/onvif/translations/pt.json index c3662a032a6..f79bbec3201 100644 --- a/homeassistant/components/onvif/translations/pt.json +++ b/homeassistant/components/onvif/translations/pt.json @@ -11,13 +11,6 @@ "cannot_connect": "Falha na liga\u00e7\u00e3o" }, "step": { - "auth": { - "data": { - "password": "Palavra-passe", - "username": "Utilizador" - }, - "title": "Configurar autentica\u00e7\u00e3o" - }, "configure_profile": { "data": { "include": "Criar entidade da c\u00e2mara" @@ -31,14 +24,6 @@ }, "title": "Selecione o dispositivo ONVIF" }, - "manual_input": { - "data": { - "host": "Servidor", - "name": "Nome", - "port": "Porta" - }, - "title": "Configurar dispositivo ONVIF" - }, "user": { "description": "Ao clickar submeter, iremos procurar na sua rede por dispositivos ONVIF que suportem o Perfil S.\n\nAlguns fabricantes come\u00e7aram a desabilitar o ONVIF por omiss\u00e3o. Por favor verifique que o ONVIF est\u00e1 activo na configura\u00e7\u00e3o da sua c\u00e2mara.", "title": "Configura\u00e7\u00e3o do dispositivo ONVIF" diff --git a/homeassistant/components/onvif/translations/ru.json b/homeassistant/components/onvif/translations/ru.json index c8643fa9ec3..d9b09be4d42 100644 --- a/homeassistant/components/onvif/translations/ru.json +++ b/homeassistant/components/onvif/translations/ru.json @@ -11,13 +11,6 @@ "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { - "auth": { - "data": { - "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" - }, - "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f" - }, "configure": { "data": { "host": "\u0425\u043e\u0441\u0442", @@ -41,14 +34,6 @@ }, "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e ONVIF" }, - "manual_input": { - "data": { - "host": "\u0425\u043e\u0441\u0442", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", - "port": "\u041f\u043e\u0440\u0442" - }, - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 ONVIF" - }, "user": { "data": { "auto": "\u0418\u0441\u043a\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438" diff --git a/homeassistant/components/onvif/translations/sk.json b/homeassistant/components/onvif/translations/sk.json index f9a7d7d07bf..f51c7e32bc8 100644 --- a/homeassistant/components/onvif/translations/sk.json +++ b/homeassistant/components/onvif/translations/sk.json @@ -5,23 +5,12 @@ "already_in_progress": "Konfigur\u00e1cia u\u017e prebieha" }, "step": { - "auth": { - "data": { - "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" - } - }, "configure": { "data": { "name": "N\u00e1zov", "port": "Port", "username": "Pou\u017e\u00edvate\u013esk\u00e9 meno" } - }, - "manual_input": { - "data": { - "name": "N\u00e1zov", - "port": "Port" - } } } } diff --git a/homeassistant/components/onvif/translations/sl.json b/homeassistant/components/onvif/translations/sl.json index 37994d88e14..cbc5104ac4f 100644 --- a/homeassistant/components/onvif/translations/sl.json +++ b/homeassistant/components/onvif/translations/sl.json @@ -8,13 +8,6 @@ "onvif_error": "Napaka pri nastavitvi naprave ONVIF. Za ve\u010d informacij preverite dnevnike." }, "step": { - "auth": { - "data": { - "password": "Geslo", - "username": "Uporabni\u0161ko ime" - }, - "title": "Nastavite preverjanje pristnosti" - }, "configure_profile": { "data": { "include": "Ustvari entiteto kamere" @@ -28,13 +21,6 @@ }, "title": "Izberite ONVIF napravo" }, - "manual_input": { - "data": { - "host": "Gostitelj", - "port": "Vrata" - }, - "title": "Konfigurirajte ONVIF napravo" - }, "user": { "description": "S klikom na oddajo bomo v omre\u017eju iskali naprave ONVIF, ki podpirajo profil S. \n\n Nekateri proizvajalci so ONVIF privzeto za\u010deli onemogo\u010dati. Prepri\u010dajte se, da je ONVIF omogo\u010den v konfiguraciji naprave.", "title": "Nastavitev naprave ONVIF" diff --git a/homeassistant/components/onvif/translations/sv.json b/homeassistant/components/onvif/translations/sv.json index 0eff403bf45..626daebc7b2 100644 --- a/homeassistant/components/onvif/translations/sv.json +++ b/homeassistant/components/onvif/translations/sv.json @@ -1,20 +1,8 @@ { "config": { "step": { - "auth": { - "data": { - "password": "L\u00f6senord", - "username": "Anv\u00e4ndarnamn" - } - }, "configure_profile": { "title": "Konfigurera Profiler" - }, - "manual_input": { - "data": { - "host": "V\u00e4rd", - "port": "Port" - } } } } diff --git a/homeassistant/components/onvif/translations/tr.json b/homeassistant/components/onvif/translations/tr.json index c471775e8fa..3659586fe44 100644 --- a/homeassistant/components/onvif/translations/tr.json +++ b/homeassistant/components/onvif/translations/tr.json @@ -11,13 +11,6 @@ "cannot_connect": "Ba\u011flanma hatas\u0131" }, "step": { - "auth": { - "data": { - "password": "Parola", - "username": "Kullan\u0131c\u0131 Ad\u0131" - }, - "title": "Kimlik do\u011frulamay\u0131 yap\u0131land\u0131r" - }, "configure": { "data": { "host": "Sunucu", @@ -41,14 +34,6 @@ }, "title": "ONVIF cihaz\u0131n\u0131 se\u00e7in" }, - "manual_input": { - "data": { - "host": "Sunucu", - "name": "Ad", - "port": "Port" - }, - "title": "ONVIF cihaz\u0131n\u0131 yap\u0131land\u0131r\u0131n" - }, "user": { "data": { "auto": "Otomatik olarak ara" diff --git a/homeassistant/components/onvif/translations/uk.json b/homeassistant/components/onvif/translations/uk.json index 82a816add04..30381d900e3 100644 --- a/homeassistant/components/onvif/translations/uk.json +++ b/homeassistant/components/onvif/translations/uk.json @@ -11,13 +11,6 @@ "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" }, "step": { - "auth": { - "data": { - "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" - }, - "title": "\u0423\u0432\u0456\u0439\u0442\u0438" - }, "configure_profile": { "data": { "include": "\u0421\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043e\u0431'\u0454\u043a\u0442 \u043a\u0430\u043c\u0435\u0440\u0438" @@ -31,14 +24,6 @@ }, "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 ONVIF" }, - "manual_input": { - "data": { - "host": "\u0425\u043e\u0441\u0442", - "name": "\u041d\u0430\u0437\u0432\u0430", - "port": "\u041f\u043e\u0440\u0442" - }, - "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e ONVIF" - }, "user": { "description": "\u041a\u043e\u043b\u0438 \u0412\u0438 \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u041d\u0430\u0434\u0456\u0441\u043b\u0430\u0442\u0438, \u043f\u043e\u0447\u043d\u0435\u0442\u044c\u0441\u044f \u043f\u043e\u0448\u0443\u043a \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 ONVIF, \u044f\u043a\u0456 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u044e\u0442\u044c Profile S. \n\n\u0414\u0435\u044f\u043a\u0456 \u0432\u0438\u0440\u043e\u0431\u043d\u0438\u043a\u0438 \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u0437\u0430 \u0443\u043c\u043e\u0432\u0447\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u044c ONVIF. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e ONVIF \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u0445 \u0412\u0430\u0448\u043e\u0457 \u043a\u0430\u043c\u0435\u0440\u0438.", "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e ONVIF" diff --git a/homeassistant/components/onvif/translations/zh-Hans.json b/homeassistant/components/onvif/translations/zh-Hans.json index 1f87cc0a8cb..7009e91b336 100644 --- a/homeassistant/components/onvif/translations/zh-Hans.json +++ b/homeassistant/components/onvif/translations/zh-Hans.json @@ -11,13 +11,6 @@ "cannot_connect": "\u8fde\u63a5\u5931\u8d25" }, "step": { - "auth": { - "data": { - "password": "\u5bc6\u7801", - "username": "\u7528\u6237\u540d" - }, - "title": "\u914d\u7f6e\u8ba4\u8bc1\u4fe1\u606f" - }, "configure": { "data": { "host": "\u4e3b\u673a\u5730\u5740", @@ -41,14 +34,6 @@ }, "title": "\u9009\u62e9 ONVIF \u8bbe\u5907" }, - "manual_input": { - "data": { - "host": "\u4e3b\u673a\u5730\u5740", - "name": "\u540d\u79f0", - "port": "\u7aef\u53e3" - }, - "title": "\u914d\u7f6e ONVIF \u8bbe\u5907" - }, "user": { "data": { "auto": "\u81ea\u52a8\u641c\u7d22" diff --git a/homeassistant/components/onvif/translations/zh-Hant.json b/homeassistant/components/onvif/translations/zh-Hant.json index 23d0f2d12f0..f4d2ddf036d 100644 --- a/homeassistant/components/onvif/translations/zh-Hant.json +++ b/homeassistant/components/onvif/translations/zh-Hant.json @@ -11,13 +11,6 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { - "auth": { - "data": { - "password": "\u5bc6\u78bc", - "username": "\u4f7f\u7528\u8005\u540d\u7a31" - }, - "title": "\u8a2d\u5b9a\u9a57\u8b49" - }, "configure": { "data": { "host": "\u4e3b\u6a5f\u7aef", @@ -41,14 +34,6 @@ }, "title": "\u9078\u64c7 ONVIF \u88dd\u7f6e" }, - "manual_input": { - "data": { - "host": "\u4e3b\u6a5f\u7aef", - "name": "\u540d\u7a31", - "port": "\u901a\u8a0a\u57e0" - }, - "title": "\u8a2d\u5b9a ONVIF \u88dd\u7f6e" - }, "user": { "data": { "auto": "\u81ea\u52d5\u641c\u5c0b" diff --git a/homeassistant/components/opengarage/translations/ko.json b/homeassistant/components/opengarage/translations/ko.json new file mode 100644 index 00000000000..b297ac33af6 --- /dev/null +++ b/homeassistant/components/opengarage/translations/ko.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "device_key": "\uc7a5\uce58 \ud0a4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openhome/media_player.py b/homeassistant/components/openhome/media_player.py index 95d9ca57992..b6a0b549c40 100644 --- a/homeassistant/components/openhome/media_player.py +++ b/homeassistant/components/openhome/media_player.py @@ -2,12 +2,15 @@ from __future__ import annotations import asyncio +from collections.abc import Awaitable, Callable, Coroutine import functools import logging +from typing import Any, TypeVar import aiohttp from async_upnp_client.client import UpnpError from openhomedevice.device import Device +from typing_extensions import Concatenate, ParamSpec import voluptuous as vol from homeassistant.components import media_source @@ -27,6 +30,10 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import ATTR_PIN_INDEX, DATA_OPENHOME, SERVICE_INVOKE_PIN +_OpenhomeDeviceT = TypeVar("_OpenhomeDeviceT", bound="OpenhomeDevice") +_R = TypeVar("_R") +_P = ParamSpec("_P") + SUPPORT_OPENHOME = ( MediaPlayerEntityFeature.SELECT_SOURCE | MediaPlayerEntityFeature.TURN_OFF @@ -74,19 +81,27 @@ async def async_setup_platform( ) -def catch_request_errors(): +def catch_request_errors() -> Callable[ + [Callable[Concatenate[_OpenhomeDeviceT, _P], Awaitable[_R]]], + Callable[Concatenate[_OpenhomeDeviceT, _P], Coroutine[Any, Any, _R | None]], +]: """Catch asyncio.TimeoutError, aiohttp.ClientError, UpnpError errors.""" - def call_wrapper(func): + def call_wrapper( + func: Callable[Concatenate[_OpenhomeDeviceT, _P], Awaitable[_R]] + ) -> Callable[Concatenate[_OpenhomeDeviceT, _P], Coroutine[Any, Any, _R | None]]: """Call wrapper for decorator.""" @functools.wraps(func) - async def wrapper(self, *args, **kwargs): + async def wrapper( + self: _OpenhomeDeviceT, *args: _P.args, **kwargs: _P.kwargs + ) -> _R | None: """Catch asyncio.TimeoutError, aiohttp.ClientError, UpnpError errors.""" try: return await func(self, *args, **kwargs) except (asyncio.TimeoutError, aiohttp.ClientError, UpnpError): _LOGGER.error("Error during call %s", func.__name__) + return None return wrapper @@ -194,7 +209,9 @@ class OpenhomeDevice(MediaPlayerEntity): """Send the play_media command to the media player.""" if media_source.is_media_source_id(media_id): media_type = MEDIA_TYPE_MUSIC - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = play_item.url if media_type != MEDIA_TYPE_MUSIC: diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index 1ea24ae9709..5ec8c6420d5 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -23,10 +23,7 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant, ServiceCall -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.device_registry import ( - async_get_registry as async_get_dev_reg, -) +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import ConfigType @@ -436,7 +433,7 @@ class OpenThermGatewayDevice: _LOGGER.debug( "Connected to OpenTherm Gateway %s at %s", self.gw_version, self.device_path ) - dev_reg = await async_get_dev_reg(self.hass) + dev_reg = dr.async_get(self.hass) gw_dev = dev_reg.async_get_or_create( config_entry_id=self.config_entry_id, identifiers={(DOMAIN, self.gw_id)}, diff --git a/homeassistant/components/opentherm_gw/binary_sensor.py b/homeassistant/components/opentherm_gw/binary_sensor.py index f630cdf273b..e4880ed26e9 100644 --- a/homeassistant/components/opentherm_gw/binary_sensor.py +++ b/homeassistant/components/opentherm_gw/binary_sensor.py @@ -6,10 +6,10 @@ from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySenso from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID 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, async_generate_entity_id from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.entity_registry import async_get_registry from . import DOMAIN from .const import ( @@ -32,7 +32,7 @@ async def async_setup_entry( sensors = [] deprecated_sensors = [] gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] - ent_reg = await async_get_registry(hass) + ent_reg = er.async_get(hass) for var, info in BINARY_SENSOR_INFO.items(): device_class = info[0] friendly_name_format = info[1] diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py index 9b2e6c45899..7fb518b0e6d 100644 --- a/homeassistant/components/opentherm_gw/sensor.py +++ b/homeassistant/components/opentherm_gw/sensor.py @@ -6,10 +6,10 @@ from homeassistant.components.sensor import ENTITY_ID_FORMAT, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID 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, async_generate_entity_id from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.entity_registry import async_get_registry from . import DOMAIN from .const import ( @@ -32,7 +32,7 @@ async def async_setup_entry( sensors = [] deprecated_sensors = [] gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] - ent_reg = await async_get_registry(hass) + ent_reg = er.async_get(hass) for var, info in SENSOR_INFO.items(): device_class = info[0] unit = info[1] diff --git a/homeassistant/components/opentherm_gw/translations/bg.json b/homeassistant/components/opentherm_gw/translations/bg.json index a7c30b4a45a..b55da1b4098 100644 --- a/homeassistant/components/opentherm_gw/translations/bg.json +++ b/homeassistant/components/opentherm_gw/translations/bg.json @@ -11,8 +11,7 @@ "device": "\u041f\u044a\u0442 \u0438\u043b\u0438 URL \u0430\u0434\u0440\u0435\u0441", "id": "ID", "name": "\u0418\u043c\u0435" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -21,8 +20,7 @@ "init": { "data": { "floor_temperature": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u043d\u0430 \u043f\u043e\u0434\u0430" - }, - "description": "\u041e\u043f\u0446\u0438\u0438 \u0437\u0430 OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/ca.json b/homeassistant/components/opentherm_gw/translations/ca.json index c4caf76d233..6427c3c7633 100644 --- a/homeassistant/components/opentherm_gw/translations/ca.json +++ b/homeassistant/components/opentherm_gw/translations/ca.json @@ -11,8 +11,7 @@ "device": "Ruta o URL", "id": "ID", "name": "Nom" - }, - "title": "Passarel\u00b7la d'OpenTherm" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Llegeix precisi\u00f3", "set_precision": "Defineix precisi\u00f3", "temporary_override_mode": "Mode de sobreescriptura temporal" - }, - "description": "Opcions del la passarel\u00b7la d'enlla\u00e7 d'OpenTherm" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/cs.json b/homeassistant/components/opentherm_gw/translations/cs.json index 39d70d05cfc..5bf8d4fc385 100644 --- a/homeassistant/components/opentherm_gw/translations/cs.json +++ b/homeassistant/components/opentherm_gw/translations/cs.json @@ -11,8 +11,7 @@ "device": "Cesta nebo URL", "id": "ID", "name": "Jm\u00e9no" - }, - "title": "Br\u00e1na OpenTherm" + } } } }, @@ -21,8 +20,7 @@ "init": { "data": { "floor_temperature": "Teplota podlahy" - }, - "description": "Mo\u017enosti br\u00e1ny OpenTherm" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/da.json b/homeassistant/components/opentherm_gw/translations/da.json index cca7cb347db..a9d675c2c6c 100644 --- a/homeassistant/components/opentherm_gw/translations/da.json +++ b/homeassistant/components/opentherm_gw/translations/da.json @@ -10,8 +10,7 @@ "device": "Sti eller webadresse", "id": "Id", "name": "Navn" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -20,8 +19,7 @@ "init": { "data": { "floor_temperature": "Gulvtemperatur" - }, - "description": "Indstillinger for OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/de.json b/homeassistant/components/opentherm_gw/translations/de.json index e7be7815366..1217839eaed 100644 --- a/homeassistant/components/opentherm_gw/translations/de.json +++ b/homeassistant/components/opentherm_gw/translations/de.json @@ -11,8 +11,7 @@ "device": "Pfad oder URL", "id": "ID", "name": "Name" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Pr\u00e4zision abfragen", "set_precision": "Pr\u00e4zision einstellen", "temporary_override_mode": "Tempor\u00e4rer Sollwert\u00fcbersteuerungsmodus" - }, - "description": "Optionen f\u00fcr das OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/el.json b/homeassistant/components/opentherm_gw/translations/el.json index 92dac2e9b3d..82be1ff6ce9 100644 --- a/homeassistant/components/opentherm_gw/translations/el.json +++ b/homeassistant/components/opentherm_gw/translations/el.json @@ -11,8 +11,7 @@ "device": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" - }, - "title": "\u03a0\u03cd\u03bb\u03b7 OpenTherm" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "\u0394\u03b9\u03ac\u03b2\u03b1\u03c3\u03b5 \u03c4\u03b7\u03bd \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", "set_precision": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1\u03c2", "temporary_override_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c1\u03b9\u03bd\u03ae\u03c2 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7\u03c2 \u03c3\u03b7\u03bc\u03b5\u03af\u03bf\u03c5 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2" - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 OpenTherm" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/en.json b/homeassistant/components/opentherm_gw/translations/en.json index c1e897c631b..c84e9dcf0e2 100644 --- a/homeassistant/components/opentherm_gw/translations/en.json +++ b/homeassistant/components/opentherm_gw/translations/en.json @@ -11,8 +11,7 @@ "device": "Path or URL", "id": "ID", "name": "Name" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Read Precision", "set_precision": "Set Precision", "temporary_override_mode": "Temporary Setpoint Override Mode" - }, - "description": "Options for the OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/es-419.json b/homeassistant/components/opentherm_gw/translations/es-419.json index f9a6f60b463..6e28121d852 100644 --- a/homeassistant/components/opentherm_gw/translations/es-419.json +++ b/homeassistant/components/opentherm_gw/translations/es-419.json @@ -10,8 +10,7 @@ "device": "Ruta o URL", "id": "Identificaci\u00f3n", "name": "Nombre" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -22,8 +21,7 @@ "floor_temperature": "Temperatura del piso", "read_precision": "Leer precisi\u00f3n", "set_precision": "Establecer precisi\u00f3n" - }, - "description": "Opciones para OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/es.json b/homeassistant/components/opentherm_gw/translations/es.json index d780548a8fa..2b6cc0afd7f 100644 --- a/homeassistant/components/opentherm_gw/translations/es.json +++ b/homeassistant/components/opentherm_gw/translations/es.json @@ -1,7 +1,7 @@ { "config": { "error": { - "already_configured": "Gateway ya configurado", + "already_configured": "El dispositivo ya est\u00e1 configurado", "cannot_connect": "No se pudo conectar", "id_exists": "El ID del Gateway ya existe" }, @@ -11,8 +11,7 @@ "device": "Ruta o URL", "id": "ID", "name": "Nombre" - }, - "title": "Gateway OpenTherm" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Leer precisi\u00f3n", "set_precision": "Establecer precisi\u00f3n", "temporary_override_mode": "Modo de anulaci\u00f3n temporal del punto de ajuste" - }, - "description": "Opciones para OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/et.json b/homeassistant/components/opentherm_gw/translations/et.json index 67528b034c5..2458c8f1d52 100644 --- a/homeassistant/components/opentherm_gw/translations/et.json +++ b/homeassistant/components/opentherm_gw/translations/et.json @@ -11,8 +11,7 @@ "device": "Rada v\u00f5i URL", "id": "", "name": "Nimi" - }, - "title": "" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Lugemi t\u00e4psus", "set_precision": "M\u00e4\u00e4ra lugemi t\u00e4psus", "temporary_override_mode": "Ajutine seadepunkti alistamine" - }, - "description": "OpenTherm Gateway suvandid" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/fr.json b/homeassistant/components/opentherm_gw/translations/fr.json index 32b642fa639..d0a058eacfa 100644 --- a/homeassistant/components/opentherm_gw/translations/fr.json +++ b/homeassistant/components/opentherm_gw/translations/fr.json @@ -11,8 +11,7 @@ "device": "Chemin ou URL", "id": "ID", "name": "Nom" - }, - "title": "Passerelle OpenTherm" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Pr\u00e9cision de lecture", "set_precision": "D\u00e9finir la pr\u00e9cision", "temporary_override_mode": "Mode de neutralisation du point de consigne temporaire" - }, - "description": "Options pour la passerelle OpenTherm" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/hu.json b/homeassistant/components/opentherm_gw/translations/hu.json index e2a284d7dd1..9e2b6478bec 100644 --- a/homeassistant/components/opentherm_gw/translations/hu.json +++ b/homeassistant/components/opentherm_gw/translations/hu.json @@ -11,8 +11,7 @@ "device": "El\u00e9r\u00e9si \u00fat vagy URL", "id": "ID", "name": "Elnevez\u00e9s" - }, - "title": "OpenTherm \u00e1tj\u00e1r\u00f3" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Pontoss\u00e1g olvas\u00e1sa", "set_precision": "Pontoss\u00e1g be\u00e1ll\u00edt\u00e1sa", "temporary_override_mode": "Ideiglenes be\u00e1ll\u00edt\u00e1s fel\u00fclb\u00edr\u00e1l\u00e1si m\u00f3dja" - }, - "description": "Opci\u00f3k az OpenTherm \u00e1tj\u00e1r\u00f3hoz" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/id.json b/homeassistant/components/opentherm_gw/translations/id.json index f3677b9de3b..5f87fd0ed4b 100644 --- a/homeassistant/components/opentherm_gw/translations/id.json +++ b/homeassistant/components/opentherm_gw/translations/id.json @@ -11,8 +11,7 @@ "device": "Jalur atau URL", "id": "ID", "name": "Nama" - }, - "title": "Gateway OpenTherm" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Tingkat Presisi Baca", "set_precision": "Atur Presisi", "temporary_override_mode": "Mode Penimpaan Setpoint Sementara" - }, - "description": "Pilihan untuk Gateway OpenTherm" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/it.json b/homeassistant/components/opentherm_gw/translations/it.json index 4b3407cd95f..4d1feac60dd 100644 --- a/homeassistant/components/opentherm_gw/translations/it.json +++ b/homeassistant/components/opentherm_gw/translations/it.json @@ -11,8 +11,7 @@ "device": "Percorso o URL", "id": "ID", "name": "Nome" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Leggi la precisione", "set_precision": "Imposta la precisione", "temporary_override_mode": "Modalit\u00e0 di esclusione temporanea del setpoint" - }, - "description": "Opzioni per OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/ja.json b/homeassistant/components/opentherm_gw/translations/ja.json index fa31eced5ab..16f4d55eac0 100644 --- a/homeassistant/components/opentherm_gw/translations/ja.json +++ b/homeassistant/components/opentherm_gw/translations/ja.json @@ -11,8 +11,7 @@ "device": "\u30d1\u30b9\u307e\u305f\u306fURL", "id": "ID", "name": "\u540d\u524d" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "\u7cbe\u5ea6\u3092\u8aad\u307f\u8fbc\u3080", "set_precision": "\u7cbe\u5ea6\u3092\u8a2d\u5b9a\u3059\u308b", "temporary_override_mode": "\u4e00\u6642\u7684\u306a\u30bb\u30c3\u30c8\u30dd\u30a4\u30f3\u30c8\u306e\u30aa\u30fc\u30d0\u30fc\u30e9\u30a4\u30c9\u30e2\u30fc\u30c9" - }, - "description": "OpenTherm Gateway\u306e\u30aa\u30d7\u30b7\u30e7\u30f3" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/ko.json b/homeassistant/components/opentherm_gw/translations/ko.json index 178bf915a6b..01d1971ca6f 100644 --- a/homeassistant/components/opentherm_gw/translations/ko.json +++ b/homeassistant/components/opentherm_gw/translations/ko.json @@ -11,8 +11,7 @@ "device": "\uacbd\ub85c \ub610\ub294 URL", "id": "ID", "name": "\uc774\ub984" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "\uc77d\uae30 \uc815\ubc00\ub3c4", "set_precision": "\uc815\ubc00\ub3c4 \uc124\uc815\ud558\uae30", "temporary_override_mode": "\uc784\uc2dc \uc124\uc815\uac12 \uc7ac\uc815\uc758 \ubaa8\ub4dc" - }, - "description": "OpenTherm Gateway \uc635\uc158" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/lb.json b/homeassistant/components/opentherm_gw/translations/lb.json index e90a6c2a310..b9e28bbab0b 100644 --- a/homeassistant/components/opentherm_gw/translations/lb.json +++ b/homeassistant/components/opentherm_gw/translations/lb.json @@ -11,8 +11,7 @@ "device": "Pfad oder URL", "id": "ID", "name": "Numm" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -21,8 +20,7 @@ "init": { "data": { "floor_temperature": "Buedem Temperatur" - }, - "description": "Optioune fir OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/nl.json b/homeassistant/components/opentherm_gw/translations/nl.json index 025cdea128d..97f392075d8 100644 --- a/homeassistant/components/opentherm_gw/translations/nl.json +++ b/homeassistant/components/opentherm_gw/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "error": { - "already_configured": "Gateway al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", "id_exists": "Gateway id bestaat al" }, @@ -11,8 +11,7 @@ "device": "Pad of URL", "id": "ID", "name": "Naam" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Lees Precisie", "set_precision": "Precisie instellen", "temporary_override_mode": "Tijdelijke setpoint-overschrijvingsmodus" - }, - "description": "Opties voor de OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/no.json b/homeassistant/components/opentherm_gw/translations/no.json index 9ef1aa0a5fb..5cf2e9732e9 100644 --- a/homeassistant/components/opentherm_gw/translations/no.json +++ b/homeassistant/components/opentherm_gw/translations/no.json @@ -11,8 +11,7 @@ "device": "Bane eller URL-adresse", "id": "", "name": "Navn" - }, - "title": "" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Les presisjon", "set_precision": "Angi presisjon", "temporary_override_mode": "Midlertidig overstyringsmodus for settpunkt" - }, - "description": "Alternativer for OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/pl.json b/homeassistant/components/opentherm_gw/translations/pl.json index 69dd6040093..fafc4fdc0d7 100644 --- a/homeassistant/components/opentherm_gw/translations/pl.json +++ b/homeassistant/components/opentherm_gw/translations/pl.json @@ -11,8 +11,7 @@ "device": "\u015acie\u017cka lub adres URL", "id": "Identyfikator", "name": "Nazwa" - }, - "title": "Bramka OpenTherm" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Odczytaj precyzj\u0119", "set_precision": "Ustaw precyzj\u0119", "temporary_override_mode": "Tryb tymczasowej zmiany nastawy" - }, - "description": "Opcje dla bramki OpenTherm" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/pt-BR.json b/homeassistant/components/opentherm_gw/translations/pt-BR.json index cf4ed0846d2..018aed87dc3 100644 --- a/homeassistant/components/opentherm_gw/translations/pt-BR.json +++ b/homeassistant/components/opentherm_gw/translations/pt-BR.json @@ -11,8 +11,7 @@ "device": "Caminho ou URL", "id": "ID", "name": "Nome" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Precis\u00e3o de leitura", "set_precision": "Definir precis\u00e3o", "temporary_override_mode": "Modo de substitui\u00e7\u00e3o tempor\u00e1ria do ponto de ajuste" - }, - "description": "Op\u00e7\u00f5es para o OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/ru.json b/homeassistant/components/opentherm_gw/translations/ru.json index 9f2bfff07cf..e743830624a 100644 --- a/homeassistant/components/opentherm_gw/translations/ru.json +++ b/homeassistant/components/opentherm_gw/translations/ru.json @@ -11,8 +11,7 @@ "device": "\u041f\u0443\u0442\u044c \u0438\u043b\u0438 URL-\u0430\u0434\u0440\u0435\u0441", "id": "ID", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" - }, - "title": "OpenTherm" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c \u0447\u0442\u0435\u043d\u0438\u044f", "set_precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438", "temporary_override_mode": "\u0420\u0435\u0436\u0438\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0430\u0432\u043a\u0438" - }, - "description": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u0448\u043b\u044e\u0437\u0430 Opentherm" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/sl.json b/homeassistant/components/opentherm_gw/translations/sl.json index a54ceb5031e..9e58b21c4e0 100644 --- a/homeassistant/components/opentherm_gw/translations/sl.json +++ b/homeassistant/components/opentherm_gw/translations/sl.json @@ -10,8 +10,7 @@ "device": "Pot ali URL", "id": "ID", "name": "Ime" - }, - "title": "OpenTherm Prehod" + } } } }, @@ -20,8 +19,7 @@ "init": { "data": { "floor_temperature": "Temperatura nadstropja" - }, - "description": "Mo\u017enosti za prehod OpenTherm" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/sv.json b/homeassistant/components/opentherm_gw/translations/sv.json index 01aa96564ac..8ba8716897f 100644 --- a/homeassistant/components/opentherm_gw/translations/sv.json +++ b/homeassistant/components/opentherm_gw/translations/sv.json @@ -10,8 +10,7 @@ "device": "S\u00f6kv\u00e4g eller URL", "id": "ID", "name": "Namn" - }, - "title": "OpenTherm Gateway" + } } } }, @@ -20,8 +19,7 @@ "init": { "data": { "floor_temperature": "Golvetemperatur" - }, - "description": "Alternativ f\u00f6r OpenTherm Gateway" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/tr.json b/homeassistant/components/opentherm_gw/translations/tr.json index a969927ebe2..72a603cc827 100644 --- a/homeassistant/components/opentherm_gw/translations/tr.json +++ b/homeassistant/components/opentherm_gw/translations/tr.json @@ -11,8 +11,7 @@ "device": "Yol veya URL", "id": "ID", "name": "Ad" - }, - "title": "OpenTherm A\u011f Ge\u00e7idi" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "Hassas Okuma", "set_precision": "Hassasiyeti Ayarla", "temporary_override_mode": "Ge\u00e7ici Ayar Noktas\u0131 Ge\u00e7ersiz K\u0131lma Modu" - }, - "description": "OpenTherm A\u011f Ge\u00e7idi i\u00e7in Se\u00e7enekler" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/uk.json b/homeassistant/components/opentherm_gw/translations/uk.json index fdffbfec00e..89fa29a5bf2 100644 --- a/homeassistant/components/opentherm_gw/translations/uk.json +++ b/homeassistant/components/opentherm_gw/translations/uk.json @@ -11,8 +11,7 @@ "device": "\u0428\u043b\u044f\u0445 \u0430\u0431\u043e URL-\u0430\u0434\u0440\u0435\u0441\u0430", "id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440", "name": "\u041d\u0430\u0437\u0432\u0430" - }, - "title": "OpenTherm" + } } } }, @@ -21,8 +20,7 @@ "init": { "data": { "floor_temperature": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u043f\u0456\u0434\u043b\u043e\u0433\u0438" - }, - "description": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0434\u043b\u044f \u0448\u043b\u044e\u0437\u0443 Opentherm" + } } } } diff --git a/homeassistant/components/opentherm_gw/translations/zh-Hant.json b/homeassistant/components/opentherm_gw/translations/zh-Hant.json index 7b8466aa424..d5b9d02de42 100644 --- a/homeassistant/components/opentherm_gw/translations/zh-Hant.json +++ b/homeassistant/components/opentherm_gw/translations/zh-Hant.json @@ -11,8 +11,7 @@ "device": "\u8def\u5f91\u6216 URL", "id": "ID", "name": "\u540d\u7a31" - }, - "title": "OpenTherm \u9598\u9053\u5668" + } } } }, @@ -24,8 +23,7 @@ "read_precision": "\u8b80\u53d6\u7cbe\u6e96\u5ea6", "set_precision": "\u8a2d\u5b9a\u7cbe\u6e96\u5ea6", "temporary_override_mode": "\u81e8\u6642 Setpoint \u8986\u84cb\u6a21\u5f0f" - }, - "description": "OpenTherm \u9598\u9053\u5668\u9078\u9805" + } } } } diff --git a/homeassistant/components/openuv/translations/es.json b/homeassistant/components/openuv/translations/es.json index 014eba04f52..bb5f36499ba 100644 --- a/homeassistant/components/openuv/translations/es.json +++ b/homeassistant/components/openuv/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Estas coordenadas ya est\u00e1n registradas." + "already_configured": "La ubicaci\u00f3n ya est\u00e1 configurada" }, "error": { "invalid_api_key": "Clave API no v\u00e1lida" diff --git a/homeassistant/components/openuv/translations/nl.json b/homeassistant/components/openuv/translations/nl.json index bd56a1fa4e0..a85799658e3 100644 --- a/homeassistant/components/openuv/translations/nl.json +++ b/homeassistant/components/openuv/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { "invalid_api_key": "Ongeldige API-sleutel" diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index b623ed86c3a..8d673507929 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -31,6 +31,7 @@ from homeassistant.components.weather import ( ) from homeassistant.const import ( DEGREE, + LENGTH_KILOMETERS, LENGTH_MILLIMETERS, PERCENTAGE, PRESSURE_HPA, @@ -65,6 +66,7 @@ ATTR_API_CLOUDS = "clouds" ATTR_API_RAIN = "rain" ATTR_API_SNOW = "snow" ATTR_API_UV_INDEX = "uv_index" +ATTR_API_VISIBILITY_DISTANCE = "visibility_distance" ATTR_API_WEATHER_CODE = "weather_code" ATTR_API_FORECAST = "forecast" UPDATE_LISTENER = "update_listener" @@ -243,6 +245,12 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( native_unit_of_measurement=UV_INDEX, state_class=SensorStateClass.MEASUREMENT, ), + SensorEntityDescription( + key=ATTR_API_VISIBILITY_DISTANCE, + name="Visibility", + native_unit_of_measurement=LENGTH_KILOMETERS, + state_class=SensorStateClass.MEASUREMENT, + ), SensorEntityDescription( key=ATTR_API_CONDITION, name="Condition", diff --git a/homeassistant/components/openweathermap/translations/ar.json b/homeassistant/components/openweathermap/translations/ar.json index 1f69a1cf739..5ff07ca51e8 100644 --- a/homeassistant/components/openweathermap/translations/ar.json +++ b/homeassistant/components/openweathermap/translations/ar.json @@ -6,8 +6,7 @@ "language": "\u0627\u0644\u0644\u063a\u0629", "name": "\u0627\u0633\u0645 \u0627\u0644\u062a\u0643\u0627\u0645\u0644" }, - "description": "\u0642\u0645 \u0628\u0625\u0639\u062f\u0627\u062f \u062a\u0643\u0627\u0645\u0644 OpenWeatherMap. \u0644\u0625\u0646\u0634\u0627\u0621 \u0645\u0641\u062a\u0627\u062d API \u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649 https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "\u0642\u0645 \u0628\u0625\u0639\u062f\u0627\u062f \u062a\u0643\u0627\u0645\u0644 OpenWeatherMap. \u0644\u0625\u0646\u0634\u0627\u0621 \u0645\u0641\u062a\u0627\u062d API \u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649 https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/bg.json b/homeassistant/components/openweathermap/translations/bg.json index c1816ddae03..a46653b9fed 100644 --- a/homeassistant/components/openweathermap/translations/bg.json +++ b/homeassistant/components/openweathermap/translations/bg.json @@ -16,8 +16,7 @@ "longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430", "mode": "\u0420\u0435\u0436\u0438\u043c", "name": "\u0418\u043c\u0435 \u043d\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u0442\u0430" - }, - "title": "OpenWeatherMap" + } } } }, diff --git a/homeassistant/components/openweathermap/translations/ca.json b/homeassistant/components/openweathermap/translations/ca.json index f304a8d4f9f..251bb11a386 100644 --- a/homeassistant/components/openweathermap/translations/ca.json +++ b/homeassistant/components/openweathermap/translations/ca.json @@ -17,8 +17,7 @@ "mode": "Mode", "name": "Nom" }, - "description": "Per generar la clau API, v\u00e9s a https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "Per generar la clau API, v\u00e9s a https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/cs.json b/homeassistant/components/openweathermap/translations/cs.json index 42383643be1..c793e56916f 100644 --- a/homeassistant/components/openweathermap/translations/cs.json +++ b/homeassistant/components/openweathermap/translations/cs.json @@ -17,8 +17,7 @@ "mode": "Re\u017eim", "name": "N\u00e1zev integrace" }, - "description": "Nastaven\u00ed integrace OpenWeatherMap. Chcete-li vygenerovat API kl\u00ed\u010d, p\u0159ejd\u011bte na https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "Nastaven\u00ed integrace OpenWeatherMap. Chcete-li vygenerovat API kl\u00ed\u010d, p\u0159ejd\u011bte na https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/de.json b/homeassistant/components/openweathermap/translations/de.json index f74065ca1a1..644956b1d49 100644 --- a/homeassistant/components/openweathermap/translations/de.json +++ b/homeassistant/components/openweathermap/translations/de.json @@ -17,8 +17,7 @@ "mode": "Modus", "name": "Name" }, - "description": "Um den API-Schl\u00fcssel zu generieren, besuche https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "Um den API-Schl\u00fcssel zu generieren, besuche https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/el.json b/homeassistant/components/openweathermap/translations/el.json index 90b876033f6..dada058b430 100644 --- a/homeassistant/components/openweathermap/translations/el.json +++ b/homeassistant/components/openweathermap/translations/el.json @@ -17,8 +17,7 @@ "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, - "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 OpenWeatherMap. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 OpenWeatherMap. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/en.json b/homeassistant/components/openweathermap/translations/en.json index 55f8d0a2338..27bfb9393d1 100644 --- a/homeassistant/components/openweathermap/translations/en.json +++ b/homeassistant/components/openweathermap/translations/en.json @@ -17,8 +17,7 @@ "mode": "Mode", "name": "Name" }, - "description": "To generate API key go to https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "To generate API key go to https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/es.json b/homeassistant/components/openweathermap/translations/es.json index 8a44668e8ac..295de30fe47 100644 --- a/homeassistant/components/openweathermap/translations/es.json +++ b/homeassistant/components/openweathermap/translations/es.json @@ -15,10 +15,9 @@ "latitude": "Latitud", "longitude": "Longitud", "mode": "Modo", - "name": "Nombre de la integraci\u00f3n" + "name": "Nombre" }, - "description": "Configurar la integraci\u00f3n de OpenWeatherMap. Para generar la clave API, ve a https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "Para generar la clave API, ve a https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/et.json b/homeassistant/components/openweathermap/translations/et.json index 0d991c99e3f..02a8731c73b 100644 --- a/homeassistant/components/openweathermap/translations/et.json +++ b/homeassistant/components/openweathermap/translations/et.json @@ -17,8 +17,7 @@ "mode": "Re\u017eiim", "name": "Nimi" }, - "description": "API-v\u00f5tme loomiseks mine aadressile https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "API-v\u00f5tme loomiseks mine aadressile https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/fr.json b/homeassistant/components/openweathermap/translations/fr.json index 387c3eefb28..6beac076ba0 100644 --- a/homeassistant/components/openweathermap/translations/fr.json +++ b/homeassistant/components/openweathermap/translations/fr.json @@ -17,8 +17,7 @@ "mode": "Mode", "name": "Nom" }, - "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/he.json b/homeassistant/components/openweathermap/translations/he.json index cadadeb1865..4de4cdf9e40 100644 --- a/homeassistant/components/openweathermap/translations/he.json +++ b/homeassistant/components/openweathermap/translations/he.json @@ -16,8 +16,7 @@ "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", "mode": "\u05de\u05e6\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 f5a7dade833..8b28a50e771 100644 --- a/homeassistant/components/openweathermap/translations/hu.json +++ b/homeassistant/components/openweathermap/translations/hu.json @@ -17,8 +17,7 @@ "mode": "M\u00f3d", "name": "Elnevez\u00e9s" }, - "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://openweathermap.org/appid webhelyet", - "title": "OpenWeatherMap" + "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://openweathermap.org/appid webhelyet" } } }, diff --git a/homeassistant/components/openweathermap/translations/id.json b/homeassistant/components/openweathermap/translations/id.json index d8ea0344cea..efa891428c9 100644 --- a/homeassistant/components/openweathermap/translations/id.json +++ b/homeassistant/components/openweathermap/translations/id.json @@ -17,8 +17,7 @@ "mode": "Mode", "name": "Nama" }, - "description": "Untuk membuat kunci API, buka https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "Untuk membuat kunci API, buka https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/it.json b/homeassistant/components/openweathermap/translations/it.json index 133fa704109..475ae84503c 100644 --- a/homeassistant/components/openweathermap/translations/it.json +++ b/homeassistant/components/openweathermap/translations/it.json @@ -17,8 +17,7 @@ "mode": "Modalit\u00e0", "name": "Nome" }, - "description": "Per generare la chiave API, vai su https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "Per generare la chiave API, vai su https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/ja.json b/homeassistant/components/openweathermap/translations/ja.json index 1253ba5af21..511d59b2138 100644 --- a/homeassistant/components/openweathermap/translations/ja.json +++ b/homeassistant/components/openweathermap/translations/ja.json @@ -17,8 +17,7 @@ "mode": "\u30e2\u30fc\u30c9", "name": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u540d\u524d" }, - "description": "OpenWeatherMap\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://openweathermap.org/appid \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "OpenWeatherMap" + "description": "OpenWeatherMap\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://openweathermap.org/appid \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044" } } }, diff --git a/homeassistant/components/openweathermap/translations/ko.json b/homeassistant/components/openweathermap/translations/ko.json index d2f5d9aa123..131caf44eff 100644 --- a/homeassistant/components/openweathermap/translations/ko.json +++ b/homeassistant/components/openweathermap/translations/ko.json @@ -17,8 +17,7 @@ "mode": "\ubaa8\ub4dc", "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc774\ub984" }, - "description": "OpenWeatherMap \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://openweathermap.org/appid \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", - "title": "OpenWeatherMap" + "description": "OpenWeatherMap \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://openweathermap.org/appid \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694" } } }, diff --git a/homeassistant/components/openweathermap/translations/lb.json b/homeassistant/components/openweathermap/translations/lb.json index bed28c80575..0cb8299fdbf 100644 --- a/homeassistant/components/openweathermap/translations/lb.json +++ b/homeassistant/components/openweathermap/translations/lb.json @@ -17,8 +17,7 @@ "mode": "Modus", "name": "Numm vun der Integratioun" }, - "description": "OpenWeatherMap Integratioun ariichten. Fir een API Schl\u00ebssel z'erstelle g\u00e9i op https://openweathermap.org/appid", - "title": "OpenWeatherMap API Schl\u00ebssel" + "description": "OpenWeatherMap Integratioun ariichten. Fir een API Schl\u00ebssel z'erstelle g\u00e9i op https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/nl.json b/homeassistant/components/openweathermap/translations/nl.json index 1c72f10c6d9..85a685590e2 100644 --- a/homeassistant/components/openweathermap/translations/nl.json +++ b/homeassistant/components/openweathermap/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "OpenWeatherMap-integratie voor deze co\u00f6rdinaten is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -10,15 +10,14 @@ "step": { "user": { "data": { - "api_key": "OpenWeatherMap API-sleutel", + "api_key": "API-sleutel", "language": "Taal", "latitude": "Breedtegraad", "longitude": "Lengtegraad", - "mode": "Mode", + "mode": "Modus", "name": "Naam" }, - "description": "Om een API sleutel te genereren ga naar https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "Om een API sleutel te genereren ga naar https://openweathermap.org/appid" } } }, @@ -27,7 +26,7 @@ "init": { "data": { "language": "Taal", - "mode": "Mode" + "mode": "Modus" } } } diff --git a/homeassistant/components/openweathermap/translations/no.json b/homeassistant/components/openweathermap/translations/no.json index d751553465a..de839e3987e 100644 --- a/homeassistant/components/openweathermap/translations/no.json +++ b/homeassistant/components/openweathermap/translations/no.json @@ -17,8 +17,7 @@ "mode": "Modus", "name": "Navn" }, - "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/pl.json b/homeassistant/components/openweathermap/translations/pl.json index de8d857f168..be8e052bb3e 100644 --- a/homeassistant/components/openweathermap/translations/pl.json +++ b/homeassistant/components/openweathermap/translations/pl.json @@ -17,8 +17,7 @@ "mode": "Tryb", "name": "Nazwa" }, - "description": "Aby wygenerowa\u0107 klucz API, przejd\u017a do https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "Aby wygenerowa\u0107 klucz API, przejd\u017a do https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/pt-BR.json b/homeassistant/components/openweathermap/translations/pt-BR.json index 795db96f36f..9178fb8ac0b 100644 --- a/homeassistant/components/openweathermap/translations/pt-BR.json +++ b/homeassistant/components/openweathermap/translations/pt-BR.json @@ -17,8 +17,7 @@ "mode": "Modo", "name": "Nome" }, - "description": "Para gerar a chave de API, acesse https://openweathermap.org/appid", - "title": "OpenWeatherMap" + "description": "Para gerar a chave de API, acesse https://openweathermap.org/appid" } } }, diff --git a/homeassistant/components/openweathermap/translations/ru.json b/homeassistant/components/openweathermap/translations/ru.json index a0724e90f01..ccbe560a3df 100644 --- a/homeassistant/components/openweathermap/translations/ru.json +++ b/homeassistant/components/openweathermap/translations/ru.json @@ -17,8 +17,7 @@ "mode": "\u0420\u0435\u0436\u0438\u043c", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "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" + "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." } } }, diff --git a/homeassistant/components/openweathermap/translations/sv.json b/homeassistant/components/openweathermap/translations/sv.json index cafa9c0fdd0..c0fdf3bbfdb 100644 --- a/homeassistant/components/openweathermap/translations/sv.json +++ b/homeassistant/components/openweathermap/translations/sv.json @@ -10,8 +10,7 @@ "language": "Spr\u00e5k", "mode": "L\u00e4ge", "name": "Integrationens namn" - }, - "title": "OpenWeatherMap" + } } } }, diff --git a/homeassistant/components/openweathermap/translations/tr.json b/homeassistant/components/openweathermap/translations/tr.json index 6458852712e..ad2da8b8102 100644 --- a/homeassistant/components/openweathermap/translations/tr.json +++ b/homeassistant/components/openweathermap/translations/tr.json @@ -17,8 +17,7 @@ "mode": "Mod", "name": "Ad" }, - "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://openweathermap.org/appid adresine gidin.", - "title": "OpenWeatherMap" + "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://openweathermap.org/appid adresine gidin." } } }, diff --git a/homeassistant/components/openweathermap/translations/uk.json b/homeassistant/components/openweathermap/translations/uk.json index 7a39cfa078e..8b3db718680 100644 --- a/homeassistant/components/openweathermap/translations/uk.json +++ b/homeassistant/components/openweathermap/translations/uk.json @@ -17,8 +17,7 @@ "mode": "\u0420\u0435\u0436\u0438\u043c", "name": "\u041d\u0430\u0437\u0432\u0430" }, - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 OpenWeatherMap. \u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u043a\u043b\u044e\u0447\u0430 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 https://openweathermap.org/appid.", - "title": "OpenWeatherMap" + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 OpenWeatherMap. \u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u043a\u043b\u044e\u0447\u0430 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 https://openweathermap.org/appid." } } }, diff --git a/homeassistant/components/openweathermap/translations/zh-Hant.json b/homeassistant/components/openweathermap/translations/zh-Hant.json index fcebc12fe31..407bd026291 100644 --- a/homeassistant/components/openweathermap/translations/zh-Hant.json +++ b/homeassistant/components/openweathermap/translations/zh-Hant.json @@ -17,8 +17,7 @@ "mode": "\u6a21\u5f0f", "name": "\u540d\u7a31" }, - "description": "\u8acb\u81f3 https://openweathermap.org/appid \u4ee5\u7522\u751f API \u91d1\u9470", - "title": "OpenWeatherMap" + "description": "\u8acb\u81f3 https://openweathermap.org/appid \u4ee5\u7522\u751f API \u91d1\u9470" } } }, diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index f4814e64d9a..26341621051 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -36,6 +36,7 @@ from .const import ( ATTR_API_SNOW, ATTR_API_TEMPERATURE, ATTR_API_UV_INDEX, + ATTR_API_VISIBILITY_DISTANCE, ATTR_API_WEATHER, ATTR_API_WEATHER_CODE, ATTR_API_WIND_BEARING, @@ -72,6 +73,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ) async def _async_update_data(self): + """Update the data.""" data = {} async with async_timeout.timeout(20): try: @@ -137,11 +139,13 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ATTR_API_WEATHER: current_weather.detailed_status, ATTR_API_CONDITION: self._get_condition(current_weather.weather_code), ATTR_API_UV_INDEX: current_weather.uvi, + ATTR_API_VISIBILITY_DISTANCE: current_weather.visibility_distance, ATTR_API_WEATHER_CODE: current_weather.weather_code, ATTR_API_FORECAST: forecast_weather, } def _get_forecast_from_weather_response(self, weather_response): + """Extract the forecast data from the weather response.""" forecast_arg = "forecast" if self._forecast_mode == FORECAST_MODE_ONECALL_HOURLY: forecast_arg = "forecast_hourly" @@ -152,6 +156,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ] def _convert_forecast(self, entry): + """Convert the forecast data.""" forecast = { ATTR_FORECAST_TIME: dt.utc_from_timestamp( entry.reference_time("unix") @@ -182,6 +187,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): @staticmethod def _fmt_dewpoint(dewpoint): + """Format the dewpoint data.""" if dewpoint is not None: return round(kelvin_to_celsius(dewpoint), 1) return None diff --git a/homeassistant/components/opnsense/device_tracker.py b/homeassistant/components/opnsense/device_tracker.py index 9a024b2b260..e726a0484f9 100644 --- a/homeassistant/components/opnsense/device_tracker.py +++ b/homeassistant/components/opnsense/device_tracker.py @@ -1,10 +1,12 @@ """Device tracker support for OPNSense routers.""" from homeassistant.components.device_tracker import DeviceScanner +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType from . import CONF_TRACKER_INTERFACE, OPNSENSE_DATA -async def async_get_scanner(hass, config, discovery_info=None): +async def async_get_scanner(hass: HomeAssistant, config: ConfigType) -> DeviceScanner: """Configure the OPNSense device_tracker.""" interface_client = hass.data[OPNSENSE_DATA]["interfaces"] scanner = OPNSenseDeviceScanner( diff --git a/homeassistant/components/osramlightify/light.py b/homeassistant/components/osramlightify/light.py index 20e50b8b7fd..a247497550f 100644 --- a/homeassistant/components/osramlightify/light.py +++ b/homeassistant/components/osramlightify/light.py @@ -18,9 +18,8 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, LightEntity, + LightEntityFeature, ) from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant @@ -210,13 +209,18 @@ class Luminary(LightEntity): """Get list of supported features.""" features = 0 if "lum" in self._luminary.supported_features(): - features = features | SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + features = features | SUPPORT_BRIGHTNESS | LightEntityFeature.TRANSITION if "temp" in self._luminary.supported_features(): - features = features | SUPPORT_COLOR_TEMP | SUPPORT_TRANSITION + features = features | SUPPORT_COLOR_TEMP | LightEntityFeature.TRANSITION if "rgb" in self._luminary.supported_features(): - features = features | SUPPORT_COLOR | SUPPORT_TRANSITION | SUPPORT_EFFECT + features = ( + features + | SUPPORT_COLOR + | LightEntityFeature.TRANSITION + | LightEntityFeature.EFFECT + ) return features @@ -416,7 +420,7 @@ class OsramLightifyGroup(Luminary): """Get list of supported features.""" features = super()._get_supported_features() if self._luminary.scenes(): - features = features | SUPPORT_EFFECT + features = features | LightEntityFeature.EFFECT return features diff --git a/homeassistant/components/overkiz/executor.py b/homeassistant/components/overkiz/executor.py index 127851db36a..9bf7ef43b02 100644 --- a/homeassistant/components/overkiz/executor.py +++ b/homeassistant/components/overkiz/executor.py @@ -68,17 +68,18 @@ class OverkizExecutor: async def async_execute_command(self, command_name: str, *args: Any) -> None: """Execute device command in async context.""" + parameters = [arg for arg in args if arg is not None] # Set the execution duration to 0 seconds for RTS devices on supported commands # Default execution duration is 30 seconds and will block consecutive commands if ( self.device.protocol == Protocol.RTS and command_name not in COMMANDS_WITHOUT_DELAY ): - args = args + (0,) + parameters.append(0) exec_id = await self.coordinator.client.execute_command( self.device.device_url, - Command(command_name, list(args)), + Command(command_name, parameters), "Home Assistant", ) diff --git a/homeassistant/components/overkiz/light.py b/homeassistant/components/overkiz/light.py index 0ad6cb0d9c5..bb06b645d24 100644 --- a/homeassistant/components/overkiz/light.py +++ b/homeassistant/components/overkiz/light.py @@ -48,11 +48,12 @@ class OverkizLight(OverkizEntity, LightEntity): self._attr_supported_color_modes: set[ColorMode] = set() if self.executor.has_command(OverkizCommand.SET_RGB): - self._attr_supported_color_modes.add(ColorMode.RGB) - if self.executor.has_command(OverkizCommand.SET_INTENSITY): - self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS) - if not self.supported_color_modes: - self._attr_supported_color_modes = {ColorMode.ONOFF} + self._attr_color_mode = ColorMode.RGB + elif self.executor.has_command(OverkizCommand.SET_INTENSITY): + self._attr_color_mode = ColorMode.BRIGHTNESS + else: + self._attr_color_mode = ColorMode.ONOFF + self._attr_supported_color_modes = {self._attr_color_mode} @property def is_on(self) -> bool: diff --git a/homeassistant/components/overkiz/number.py b/homeassistant/components/overkiz/number.py index e6e64162dd8..741c666a42a 100644 --- a/homeassistant/components/overkiz/number.py +++ b/homeassistant/components/overkiz/number.py @@ -38,6 +38,8 @@ NUMBER_DESCRIPTIONS: list[OverkizNumberDescription] = [ name="My Position", icon="mdi:content-save-cog", command=OverkizCommand.SET_MEMORIZED_1_POSITION, + min_value=0, + max_value=100, entity_category=EntityCategory.CONFIG, ), # WaterHeater: Expected Number Of Shower (2 - 4) @@ -84,6 +86,8 @@ NUMBER_DESCRIPTIONS: list[OverkizNumberDescription] = [ key=OverkizState.CORE_LEVEL, icon="mdi:patio-heater", command=OverkizCommand.SET_LEVEL, + min_value=0, + max_value=100, inverted=True, ), ] diff --git a/homeassistant/components/overkiz/translations/es.json b/homeassistant/components/overkiz/translations/es.json index f1702a6d703..817864deba8 100644 --- a/homeassistant/components/overkiz/translations/es.json +++ b/homeassistant/components/overkiz/translations/es.json @@ -1,12 +1,15 @@ { "config": { "abort": { - "already_configured": "La cuenta ya est\u00e1 configurada" + "already_configured": "La cuenta ya est\u00e1 configurada", + "reauth_successful": "Re-autenticaci\u00f3n realizada correctamente", + "reauth_wrong_account": "S\u00f3lo puedes volver a autenticar esta entrada con la misma cuenta y hub de Overkiz" }, "error": { "cannot_connect": "Error al conectar", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "server_in_maintenance": "El servidor est\u00e1 inactivo por mantenimiento", + "too_many_attempts": "Demasiados intentos con un 'token' inv\u00e1lido, bloqueado temporalmente", "too_many_requests": "Demasiadas solicitudes, int\u00e9ntalo de nuevo m\u00e1s tarde.", "unknown": "Error inesperado" }, diff --git a/homeassistant/components/overkiz/translations/nl.json b/homeassistant/components/overkiz/translations/nl.json index 5057f12afa6..957ca0862fc 100644 --- a/homeassistant/components/overkiz/translations/nl.json +++ b/homeassistant/components/overkiz/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol", + "reauth_successful": "Herauthenticatie geslaagd", "reauth_wrong_account": "U kunt deze invoer alleen opnieuw verifi\u00ebren met hetzelfde Overkiz account en hub" }, "error": { diff --git a/homeassistant/components/overkiz/translations/select.ko.json b/homeassistant/components/overkiz/translations/select.ko.json new file mode 100644 index 00000000000..c2af52fab9c --- /dev/null +++ b/homeassistant/components/overkiz/translations/select.ko.json @@ -0,0 +1,7 @@ +{ + "state": { + "overkiz__open_closed_pedestrian": { + "closed": "\ub2eb\ud798" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/select.sk.json b/homeassistant/components/overkiz/translations/select.sk.json new file mode 100644 index 00000000000..460b1d67415 --- /dev/null +++ b/homeassistant/components/overkiz/translations/select.sk.json @@ -0,0 +1,7 @@ +{ + "state": { + "overkiz__open_closed_pedestrian": { + "pedestrian": "Chodec" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/sensor.sk.json b/homeassistant/components/overkiz/translations/sensor.sk.json new file mode 100644 index 00000000000..cfa849319eb --- /dev/null +++ b/homeassistant/components/overkiz/translations/sensor.sk.json @@ -0,0 +1,10 @@ +{ + "state": { + "overkiz__discrete_rssi_level": { + "good": "Dobr\u00e1" + }, + "overkiz__priority_lock_originator": { + "wind": "Vietor" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/sk.json b/homeassistant/components/overkiz/translations/sk.json index 71a7aea5018..93eb8dcc1b5 100644 --- a/homeassistant/components/overkiz/translations/sk.json +++ b/homeassistant/components/overkiz/translations/sk.json @@ -5,6 +5,13 @@ }, "error": { "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "password": "Heslo" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/es.json b/homeassistant/components/ovo_energy/translations/es.json index aa9708c60d0..549f3af16ca 100644 --- a/homeassistant/components/ovo_energy/translations/es.json +++ b/homeassistant/components/ovo_energy/translations/es.json @@ -5,7 +5,7 @@ "cannot_connect": "No se pudo conectar", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" }, - "flow_title": "OVO Energy: {username}", + "flow_title": "{username}", "step": { "reauth": { "data": { @@ -20,7 +20,7 @@ "username": "Usuario" }, "description": "Configurar una instancia de OVO Energy para acceder a su consumo de energ\u00eda.", - "title": "A\u00f1adir OVO Energy" + "title": "A\u00f1adir cuenta de OVO Energy" } } } diff --git a/homeassistant/components/owntracks/__init__.py b/homeassistant/components/owntracks/__init__.py index a9f89d26238..5a21862c767 100644 --- a/homeassistant/components/owntracks/__init__.py +++ b/homeassistant/components/owntracks/__init__.py @@ -18,7 +18,10 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.typing import ConfigType from homeassistant.setup import async_when_setup @@ -140,7 +143,7 @@ async def async_connect_mqtt(hass, component): return message["topic"] = msg.topic - hass.helpers.dispatcher.async_dispatcher_send(DOMAIN, hass, context, message) + async_dispatcher_send(hass, DOMAIN, hass, context, message) await mqtt.async_subscribe(hass, context.mqtt_topic, async_handle_mqtt_message, 1) @@ -179,7 +182,7 @@ async def handle_webhook(hass, webhook_id, request): # Keep it as a 200 response so the incorrect packet is discarded return json_response([]) - hass.helpers.dispatcher.async_dispatcher_send(DOMAIN, hass, context, message) + async_dispatcher_send(hass, DOMAIN, hass, context, message) response = [] diff --git a/homeassistant/components/owntracks/device_tracker.py b/homeassistant/components/owntracks/device_tracker.py index ae4d88df718..92f34617462 100644 --- a/homeassistant/components/owntracks/device_tracker.py +++ b/homeassistant/components/owntracks/device_tracker.py @@ -26,7 +26,7 @@ async def async_setup_entry( ) -> None: """Set up OwnTracks based off an entry.""" # Restore previously loaded devices - dev_reg = await device_registry.async_get_registry(hass) + dev_reg = device_registry.async_get(hass) dev_ids = { identifier[1] for device in dev_reg.devices.values() diff --git a/homeassistant/components/owntracks/messages.py b/homeassistant/components/owntracks/messages.py index b85a37dadf9..4f17c8a5375 100644 --- a/homeassistant/components/owntracks/messages.py +++ b/homeassistant/components/owntracks/messages.py @@ -159,7 +159,7 @@ def encrypt_message(secret, topic, message): if key is None: _LOGGER.warning( - "Unable to encrypt payload because no decryption key known " "for topic %s", + "Unable to encrypt payload because no decryption key known for topic %s", topic, ) return None diff --git a/homeassistant/components/owntracks/translations/es.json b/homeassistant/components/owntracks/translations/es.json index 1832e701559..dc420a806f4 100644 --- a/homeassistant/components/owntracks/translations/es.json +++ b/homeassistant/components/owntracks/translations/es.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No conectado a Home Assistant Cloud.", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { diff --git a/homeassistant/components/owntracks/translations/fr.json b/homeassistant/components/owntracks/translations/fr.json index 9d651c66e94..e2e440cafa5 100644 --- a/homeassistant/components/owntracks/translations/fr.json +++ b/homeassistant/components/owntracks/translations/fr.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "\u00cates-vous s\u00fbr de vouloir configurer OwnTracks?", + "description": "Voulez-vous vraiment configurer OwnTracks\u00a0?", "title": "Configurer OwnTracks" } } diff --git a/homeassistant/components/p1_monitor/translations/bg.json b/homeassistant/components/p1_monitor/translations/bg.json index 5c4f6a7bdc3..acbebfb4d36 100644 --- a/homeassistant/components/p1_monitor/translations/bg.json +++ b/homeassistant/components/p1_monitor/translations/bg.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \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" + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/ca.json b/homeassistant/components/p1_monitor/translations/ca.json index 8d83cfd5026..d82a93389fe 100644 --- a/homeassistant/components/p1_monitor/translations/ca.json +++ b/homeassistant/components/p1_monitor/translations/ca.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Ha fallat la connexi\u00f3", - "unknown": "Error inesperat" + "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/cs.json b/homeassistant/components/p1_monitor/translations/cs.json index 7981cfc800e..7a27355056b 100644 --- a/homeassistant/components/p1_monitor/translations/cs.json +++ b/homeassistant/components/p1_monitor/translations/cs.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/de.json b/homeassistant/components/p1_monitor/translations/de.json index 31a19a7a195..8ac00192251 100644 --- a/homeassistant/components/p1_monitor/translations/de.json +++ b/homeassistant/components/p1_monitor/translations/de.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Verbindung fehlgeschlagen", - "unknown": "Unerwarteter Fehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/el.json b/homeassistant/components/p1_monitor/translations/el.json index 82c79d53acc..fd520c381f8 100644 --- a/homeassistant/components/p1_monitor/translations/el.json +++ b/homeassistant/components/p1_monitor/translations/el.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", - "unknown": "\u0391\u03bd\u03b5\u03c0\u03ac\u03bd\u03c4\u03b5\u03c7\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/en.json b/homeassistant/components/p1_monitor/translations/en.json index 34b64082b43..4bd61c19bdc 100644 --- a/homeassistant/components/p1_monitor/translations/en.json +++ b/homeassistant/components/p1_monitor/translations/en.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Failed to connect", - "unknown": "Unexpected error" + "cannot_connect": "Failed to connect" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/es.json b/homeassistant/components/p1_monitor/translations/es.json index 46bfbb59e4f..31976986ec4 100644 --- a/homeassistant/components/p1_monitor/translations/es.json +++ b/homeassistant/components/p1_monitor/translations/es.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "No se pudo conectar", - "unknown": "Error inesperado" + "cannot_connect": "No se pudo conectar" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/et.json b/homeassistant/components/p1_monitor/translations/et.json index 96ab4e46491..69090de7640 100644 --- a/homeassistant/components/p1_monitor/translations/et.json +++ b/homeassistant/components/p1_monitor/translations/et.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "\u00dchendamine nurjus", - "unknown": "Ootamatu t\u00f5rge" + "cannot_connect": "\u00dchendamine nurjus" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/fr.json b/homeassistant/components/p1_monitor/translations/fr.json index 34c0a37fb2e..34699234e0e 100644 --- a/homeassistant/components/p1_monitor/translations/fr.json +++ b/homeassistant/components/p1_monitor/translations/fr.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "\u00c9chec de connexion", - "unknown": "Erreur inattendue" + "cannot_connect": "\u00c9chec de connexion" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/he.json b/homeassistant/components/p1_monitor/translations/he.json index fbd52ea83ec..33660936e12 100644 --- a/homeassistant/components/p1_monitor/translations/he.json +++ b/homeassistant/components/p1_monitor/translations/he.json @@ -1,8 +1,7 @@ { "config": { "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" + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/hu.json b/homeassistant/components/p1_monitor/translations/hu.json index b0c30613234..af1f1e62fdb 100644 --- a/homeassistant/components/p1_monitor/translations/hu.json +++ b/homeassistant/components/p1_monitor/translations/hu.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni", - "unknown": "V\u00e1ratlan hiba" + "cannot_connect": "Nem siker\u00fclt csatlakozni" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/id.json b/homeassistant/components/p1_monitor/translations/id.json index a1241576b2e..2deaf4c09e0 100644 --- a/homeassistant/components/p1_monitor/translations/id.json +++ b/homeassistant/components/p1_monitor/translations/id.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Gagal terhubung", - "unknown": "Kesalahan yang tidak diharapkan" + "cannot_connect": "Gagal terhubung" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/it.json b/homeassistant/components/p1_monitor/translations/it.json index 25cac38aff6..61b6d9d75f0 100644 --- a/homeassistant/components/p1_monitor/translations/it.json +++ b/homeassistant/components/p1_monitor/translations/it.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Impossibile connettersi", - "unknown": "Errore imprevisto" + "cannot_connect": "Impossibile connettersi" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/ja.json b/homeassistant/components/p1_monitor/translations/ja.json index b47610f27e3..29bfd50332f 100644 --- a/homeassistant/components/p1_monitor/translations/ja.json +++ b/homeassistant/components/p1_monitor/translations/ja.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", - "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/ko.json b/homeassistant/components/p1_monitor/translations/ko.json new file mode 100644 index 00000000000..e2d6ae5c199 --- /dev/null +++ b/homeassistant/components/p1_monitor/translations/ko.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/p1_monitor/translations/nl.json b/homeassistant/components/p1_monitor/translations/nl.json index fbb81d70c5d..1f5e9e5a422 100644 --- a/homeassistant/components/p1_monitor/translations/nl.json +++ b/homeassistant/components/p1_monitor/translations/nl.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Kan geen verbinding maken", - "unknown": "Onverwachte fout" + "cannot_connect": "Kan geen verbinding maken" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/no.json b/homeassistant/components/p1_monitor/translations/no.json index a6b967e4a46..d0827d0e357 100644 --- a/homeassistant/components/p1_monitor/translations/no.json +++ b/homeassistant/components/p1_monitor/translations/no.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Tilkobling mislyktes", - "unknown": "Uventet feil" + "cannot_connect": "Tilkobling mislyktes" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/pl.json b/homeassistant/components/p1_monitor/translations/pl.json index 5aacfb63336..9ec0f1bcef0 100644 --- a/homeassistant/components/p1_monitor/translations/pl.json +++ b/homeassistant/components/p1_monitor/translations/pl.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "unknown": "Nieoczekiwany b\u0142\u0105d" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/pt-BR.json b/homeassistant/components/p1_monitor/translations/pt-BR.json index 777a1fc5633..dda88d419e6 100644 --- a/homeassistant/components/p1_monitor/translations/pt-BR.json +++ b/homeassistant/components/p1_monitor/translations/pt-BR.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Falha ao conectar", - "unknown": "Erro inesperado" + "cannot_connect": "Falha ao conectar" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/ru.json b/homeassistant/components/p1_monitor/translations/ru.json index 78277de42c1..4f118fe952c 100644 --- a/homeassistant/components/p1_monitor/translations/ru.json +++ b/homeassistant/components/p1_monitor/translations/ru.json @@ -1,8 +1,7 @@ { "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.", - "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/tr.json b/homeassistant/components/p1_monitor/translations/tr.json index 88684e7bcb0..f00060462fd 100644 --- a/homeassistant/components/p1_monitor/translations/tr.json +++ b/homeassistant/components/p1_monitor/translations/tr.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "Ba\u011flanma hatas\u0131", - "unknown": "Beklenmeyen hata" + "cannot_connect": "Ba\u011flanma hatas\u0131" }, "step": { "user": { diff --git a/homeassistant/components/p1_monitor/translations/zh-Hant.json b/homeassistant/components/p1_monitor/translations/zh-Hant.json index fafb7f9b7c2..a62ff38bbda 100644 --- a/homeassistant/components/p1_monitor/translations/zh-Hant.json +++ b/homeassistant/components/p1_monitor/translations/zh-Hant.json @@ -1,8 +1,7 @@ { "config": { "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557", - "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { "user": { diff --git a/homeassistant/components/panasonic_viera/media_player.py b/homeassistant/components/panasonic_viera/media_player.py index fd44c2853f1..7b75809f827 100644 --- a/homeassistant/components/panasonic_viera/media_player.py +++ b/homeassistant/components/panasonic_viera/media_player.py @@ -188,7 +188,9 @@ class PanasonicVieraTVEntity(MediaPlayerEntity): """Play media.""" if media_source.is_media_source_id(media_id): media_type = MEDIA_TYPE_URL - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = play_item.url if media_type != MEDIA_TYPE_URL: diff --git a/homeassistant/components/panasonic_viera/translations/nl.json b/homeassistant/components/panasonic_viera/translations/nl.json index fa63892730e..7e778c1e932 100644 --- a/homeassistant/components/panasonic_viera/translations/nl.json +++ b/homeassistant/components/panasonic_viera/translations/nl.json @@ -7,14 +7,14 @@ }, "error": { "cannot_connect": "Kan geen verbinding maken", - "invalid_pin_code": "De PIN-code die u hebt ingevoerd is ongeldig" + "invalid_pin_code": "De Pincode die u hebt ingevoerd is ongeldig" }, "step": { "pairing": { "data": { - "pin": "PIN-code" + "pin": "Pincode" }, - "description": "Voer de PIN-code in die op uw TV wordt weergegeven", + "description": "Voer de Pincode in die op uw TV wordt weergegeven", "title": "Koppelen" }, "user": { diff --git a/homeassistant/components/panel_custom/__init__.py b/homeassistant/components/panel_custom/__init__.py index d0ff0ad44a0..493a738c1ea 100644 --- a/homeassistant/components/panel_custom/__init__.py +++ b/homeassistant/components/panel_custom/__init__.py @@ -1,4 +1,6 @@ """Register a custom front end panel.""" +from __future__ import annotations + import logging import voluptuous as vol @@ -70,27 +72,27 @@ CONFIG_SCHEMA = vol.Schema( @bind_hass async def async_register_panel( - hass, + hass: HomeAssistant, # The url to serve the panel - frontend_url_path, + frontend_url_path: str, # The webcomponent name that loads your panel - webcomponent_name, + webcomponent_name: str, # Title/icon for sidebar - sidebar_title=None, - sidebar_icon=None, + sidebar_title: str | None = None, + sidebar_icon: str | None = None, # JS source of your panel - js_url=None, + js_url: str | None = None, # JS module of your panel - module_url=None, + module_url: str | None = None, # If your panel should be run inside an iframe - embed_iframe=DEFAULT_EMBED_IFRAME, + embed_iframe: bool = DEFAULT_EMBED_IFRAME, # Should user be asked for confirmation when loading external source - trust_external=DEFAULT_TRUST_EXTERNAL, + trust_external: bool = DEFAULT_TRUST_EXTERNAL, # Configuration to be passed to the panel - config=None, + config: ConfigType | None = None, # If your panel should only be shown to admin users - require_admin=False, -): + require_admin: bool = False, +) -> None: """Register a new custom panel.""" if js_url is None and module_url is None: raise ValueError("Either js_url, module_url or html_url is required.") diff --git a/homeassistant/components/peco/translations/ca.json b/homeassistant/components/peco/translations/ca.json index 1cca8a08a7f..d66b3e67e3c 100644 --- a/homeassistant/components/peco/translations/ca.json +++ b/homeassistant/components/peco/translations/ca.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "Comtat" - }, - "description": "Trieu el teu comtat a continuaci\u00f3.", - "title": "Comptador d'interrupcions PECO" + } } } } diff --git a/homeassistant/components/peco/translations/de.json b/homeassistant/components/peco/translations/de.json index 4eb10b3e5e9..2d2c9bca38c 100644 --- a/homeassistant/components/peco/translations/de.json +++ b/homeassistant/components/peco/translations/de.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "Bundesland" - }, - "description": "Bitte w\u00e4hle unten dein Bundesland aus.", - "title": "PECO-St\u00f6rungsz\u00e4hler" + } } } } diff --git a/homeassistant/components/peco/translations/el.json b/homeassistant/components/peco/translations/el.json index 6b47cc3ccc1..9e912b952cc 100644 --- a/homeassistant/components/peco/translations/el.json +++ b/homeassistant/components/peco/translations/el.json @@ -7,9 +7,7 @@ "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" + } } } } diff --git a/homeassistant/components/peco/translations/en.json b/homeassistant/components/peco/translations/en.json index 60483a1d65c..6f7ff2b0b12 100644 --- a/homeassistant/components/peco/translations/en.json +++ b/homeassistant/components/peco/translations/en.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "County" - }, - "description": "Please choose your county below.", - "title": "PECO Outage Counter" + } } } } diff --git a/homeassistant/components/onewire/translations/af.json b/homeassistant/components/peco/translations/es.json similarity index 52% rename from homeassistant/components/onewire/translations/af.json rename to homeassistant/components/peco/translations/es.json index 779febe67cd..ecfb586561c 100644 --- a/homeassistant/components/onewire/translations/af.json +++ b/homeassistant/components/peco/translations/es.json @@ -1,12 +1,12 @@ { "config": { - "error": { - "invalid_path": "Verzeichnis nicht gefunden." + "abort": { + "already_configured": "El servicio ya est\u00e1 configurado" }, "step": { "user": { "data": { - "type": "Verbindungstyp" + "county": "Condado" } } } diff --git a/homeassistant/components/peco/translations/et.json b/homeassistant/components/peco/translations/et.json index 117d0502ca3..0b9303808d8 100644 --- a/homeassistant/components/peco/translations/et.json +++ b/homeassistant/components/peco/translations/et.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "Maakond" - }, - "description": "Vali allpool oma maakond.", - "title": "PECO katkestuste loendur" + } } } } diff --git a/homeassistant/components/peco/translations/fr.json b/homeassistant/components/peco/translations/fr.json index e78f828f1af..2063ae4e661 100644 --- a/homeassistant/components/peco/translations/fr.json +++ b/homeassistant/components/peco/translations/fr.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "Comt\u00e9" - }, - "description": "Veuillez choisir votre comt\u00e9 ci-dessous.", - "title": "Compteur de pannes PECO" + } } } } diff --git a/homeassistant/components/peco/translations/hu.json b/homeassistant/components/peco/translations/hu.json index 2e1e3482eb5..4de9d9a122a 100644 --- a/homeassistant/components/peco/translations/hu.json +++ b/homeassistant/components/peco/translations/hu.json @@ -7,9 +7,7 @@ "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" + } } } } diff --git a/homeassistant/components/peco/translations/id.json b/homeassistant/components/peco/translations/id.json index 0879348f593..f222d4db975 100644 --- a/homeassistant/components/peco/translations/id.json +++ b/homeassistant/components/peco/translations/id.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "Kota/kabupaten" - }, - "description": "Pilih kota/kabupaten Anda di bawah ini.", - "title": "Penghitung Pemadaman PECO" + } } } } diff --git a/homeassistant/components/peco/translations/it.json b/homeassistant/components/peco/translations/it.json index a7b76c27f43..fc18d732b56 100644 --- a/homeassistant/components/peco/translations/it.json +++ b/homeassistant/components/peco/translations/it.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "Provincia" - }, - "description": "Scegli la tua provincia qui sotto.", - "title": "Contatore interruzioni PECO" + } } } } diff --git a/homeassistant/components/peco/translations/ja.json b/homeassistant/components/peco/translations/ja.json index 139e1f77bd9..b0da74e5c56 100644 --- a/homeassistant/components/peco/translations/ja.json +++ b/homeassistant/components/peco/translations/ja.json @@ -7,9 +7,7 @@ "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" + } } } } diff --git a/homeassistant/components/peco/translations/nl.json b/homeassistant/components/peco/translations/nl.json index 8e98f5077e4..5b8333a9c6d 100644 --- a/homeassistant/components/peco/translations/nl.json +++ b/homeassistant/components/peco/translations/nl.json @@ -1,15 +1,13 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "step": { "user": { "data": { "county": "County" - }, - "description": "Kies hieronder uw county", - "title": "PECO Uitval Teller" + } } } } diff --git a/homeassistant/components/peco/translations/no.json b/homeassistant/components/peco/translations/no.json index 00f9c025114..7b3906f4b67 100644 --- a/homeassistant/components/peco/translations/no.json +++ b/homeassistant/components/peco/translations/no.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "fylke" - }, - "description": "Velg ditt fylke nedenfor.", - "title": "PECO avbruddsteller" + } } } } diff --git a/homeassistant/components/peco/translations/pl.json b/homeassistant/components/peco/translations/pl.json index bdd8a7ffb7c..e2a56bf1916 100644 --- a/homeassistant/components/peco/translations/pl.json +++ b/homeassistant/components/peco/translations/pl.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "Hrabstwo" - }, - "description": "Wybierz swoje hrabstwo poni\u017cej.", - "title": "Licznik awarii PECO" + } } } } diff --git a/homeassistant/components/peco/translations/pt-BR.json b/homeassistant/components/peco/translations/pt-BR.json index 845baf5b479..5d4e35de90b 100644 --- a/homeassistant/components/peco/translations/pt-BR.json +++ b/homeassistant/components/peco/translations/pt-BR.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "Munic\u00edpio" - }, - "description": "Por favor, escolha seu munic\u00edpio abaixo.", - "title": "Contador de Interrup\u00e7\u00e3o PECO" + } } } } diff --git a/homeassistant/components/peco/translations/ru.json b/homeassistant/components/peco/translations/ru.json index 6c2c8d85cc6..dc5b481fc7c 100644 --- a/homeassistant/components/peco/translations/ru.json +++ b/homeassistant/components/peco/translations/ru.json @@ -7,9 +7,7 @@ "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" + } } } } diff --git a/homeassistant/components/peco/translations/tr.json b/homeassistant/components/peco/translations/tr.json index 6a76e600789..97375e1f61f 100644 --- a/homeassistant/components/peco/translations/tr.json +++ b/homeassistant/components/peco/translations/tr.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "\u0130l\u00e7e" - }, - "description": "L\u00fctfen a\u015fa\u011f\u0131dan il\u00e7enizi se\u00e7iniz.", - "title": "PECO Kesinti Sayac\u0131" + } } } } diff --git a/homeassistant/components/peco/translations/zh-Hant.json b/homeassistant/components/peco/translations/zh-Hant.json index 94318397f6f..69d9400d2f6 100644 --- a/homeassistant/components/peco/translations/zh-Hant.json +++ b/homeassistant/components/peco/translations/zh-Hant.json @@ -7,9 +7,7 @@ "user": { "data": { "county": "\u7e23\u5e02" - }, - "description": "\u8acb\u9078\u64c7\u7e23\u5e02\u3002", - "title": "PECO Outage \u8a08\u6578\u5668" + } } } } diff --git a/homeassistant/components/person/translations/es.json b/homeassistant/components/person/translations/es.json index 98fca470569..6576d02bb5e 100644 --- a/homeassistant/components/person/translations/es.json +++ b/homeassistant/components/person/translations/es.json @@ -2,7 +2,7 @@ "state": { "_": { "home": "En casa", - "not_home": "Fuera de casa" + "not_home": "Fuera" } }, "title": "Persona" diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 29abbe5dd71..9785aaf54a3 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -15,12 +15,13 @@ from homeassistant.const import ( CONF_PIN, CONF_USERNAME, ) +from homeassistant.data_entry_flow import FlowResult from . import LOGGER from .const import CONF_ALLOW_NOTIFY, CONF_SYSTEM, CONST_APP_ID, CONST_APP_NAME, DOMAIN -async def validate_input( +async def _validate_input( hass: core.HomeAssistant, host: str, api_version: int ) -> tuple[dict, PhilipsTV]: """Validate the user input allows us to connect.""" @@ -47,7 +48,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._hub: PhilipsTV | None = None self._pair_state: Any = None - async def _async_create_current(self): + async def _async_create_current(self) -> FlowResult: system = self._current[CONF_SYSTEM] return self.async_create_entry( @@ -55,7 +56,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): data=self._current, ) - async def async_step_pair(self, user_input: dict | None = None) -> dict: + async def async_step_pair( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Attempt to pair with device.""" assert self._hub @@ -106,13 +109,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._current[CONF_PASSWORD] = password return await self._async_create_current() - async def async_step_user(self, user_input: dict | None = None) -> dict: + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the initial step.""" errors = {} if user_input: self._current = user_input try: - hub = await validate_input( + hub = await _validate_input( self.hass, user_input[CONF_HOST], user_input[CONF_API_VERSION] ) except ConnectionFailure as exc: @@ -146,7 +151,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @core.callback - def async_get_options_flow(config_entry): + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) @@ -158,7 +165,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): """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/philips_js/device_trigger.py b/homeassistant/components/philips_js/device_trigger.py index 09784dae63f..69a5932576f 100644 --- a/homeassistant/components/philips_js/device_trigger.py +++ b/homeassistant/components/philips_js/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for control of device.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -12,7 +10,8 @@ from homeassistant.components.automation import ( from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE from homeassistant.core import CALLBACK_TYPE, HomeAssistant -from homeassistant.helpers.device_registry import DeviceRegistry, async_get_registry +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.typing import ConfigType from . import PhilipsTVDataUpdateCoordinator @@ -30,7 +29,7 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for device.""" triggers = [] triggers.append( @@ -50,18 +49,18 @@ async def async_attach_trigger( config: ConfigType, action: AutomationActionType, automation_info: AutomationTriggerInfo, -) -> CALLBACK_TYPE | None: +) -> CALLBACK_TYPE: """Attach a trigger.""" trigger_data = automation_info["trigger_data"] - registry: DeviceRegistry = await async_get_registry(hass) - if config[CONF_TYPE] == TRIGGER_TYPE_TURN_ON: + registry: dr.DeviceRegistry = dr.async_get(hass) + if (trigger_type := config[CONF_TYPE]) == TRIGGER_TYPE_TURN_ON: variables = { "trigger": { **trigger_data, "platform": "device", "domain": DOMAIN, "device_id": config[CONF_DEVICE_ID], - "description": f"philips_js '{config[CONF_TYPE]}' event", + "description": f"philips_js '{trigger_type}' event", } } @@ -73,4 +72,4 @@ async def async_attach_trigger( if coordinator: return coordinator.turn_on.async_attach(action, variables) - return None + raise HomeAssistantError(f"Unhandled trigger type {trigger_type}") diff --git a/homeassistant/components/philips_js/translations/nl.json b/homeassistant/components/philips_js/translations/nl.json index 0b172e24f56..09f6b3b8f3d 100644 --- a/homeassistant/components/philips_js/translations/nl.json +++ b/homeassistant/components/philips_js/translations/nl.json @@ -12,7 +12,7 @@ "step": { "pair": { "data": { - "pin": "PIN-code" + "pin": "Pincode" }, "description": "Voer de pincode in die op uw tv wordt weergegeven", "title": "Koppel" diff --git a/homeassistant/components/pi_hole/translations/nl.json b/homeassistant/components/pi_hole/translations/nl.json index 156a248a80b..8199969aae8 100644 --- a/homeassistant/components/pi_hole/translations/nl.json +++ b/homeassistant/components/pi_hole/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Service al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "step": { "api_key": { diff --git a/homeassistant/components/picnic/translations/ca.json b/homeassistant/components/picnic/translations/ca.json index 83c0b75f9d3..aefba6058c1 100644 --- a/homeassistant/components/picnic/translations/ca.json +++ b/homeassistant/components/picnic/translations/ca.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/de.json b/homeassistant/components/picnic/translations/de.json index 65b10f61df3..6079f023d91 100644 --- a/homeassistant/components/picnic/translations/de.json +++ b/homeassistant/components/picnic/translations/de.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/el.json b/homeassistant/components/picnic/translations/el.json index ba974da6225..42717397a6b 100644 --- a/homeassistant/components/picnic/translations/el.json +++ b/homeassistant/components/picnic/translations/el.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/en.json b/homeassistant/components/picnic/translations/en.json index 06b3018f88e..13b62c78757 100644 --- a/homeassistant/components/picnic/translations/en.json +++ b/homeassistant/components/picnic/translations/en.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/es.json b/homeassistant/components/picnic/translations/es.json index f7a170871ef..5054ae22f5b 100644 --- a/homeassistant/components/picnic/translations/es.json +++ b/homeassistant/components/picnic/translations/es.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/et.json b/homeassistant/components/picnic/translations/et.json index 41f5018079c..e8d45971d37 100644 --- a/homeassistant/components/picnic/translations/et.json +++ b/homeassistant/components/picnic/translations/et.json @@ -19,6 +19,5 @@ } } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/fr.json b/homeassistant/components/picnic/translations/fr.json index 77d9756b5b8..fa9699417b5 100644 --- a/homeassistant/components/picnic/translations/fr.json +++ b/homeassistant/components/picnic/translations/fr.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Pique-nique" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/he.json b/homeassistant/components/picnic/translations/he.json index 856ac220d64..f8c5c7a1b01 100644 --- a/homeassistant/components/picnic/translations/he.json +++ b/homeassistant/components/picnic/translations/he.json @@ -19,6 +19,5 @@ } } } - }, - "title": "\u05e4\u05d9\u05e7\u05e0\u05d9\u05e7" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/hu.json b/homeassistant/components/picnic/translations/hu.json index 3841b7ddbaf..99908bb47d2 100644 --- a/homeassistant/components/picnic/translations/hu.json +++ b/homeassistant/components/picnic/translations/hu.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Piknik" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/id.json b/homeassistant/components/picnic/translations/id.json index db97b991f6f..576f3b8a0e8 100644 --- a/homeassistant/components/picnic/translations/id.json +++ b/homeassistant/components/picnic/translations/id.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/it.json b/homeassistant/components/picnic/translations/it.json index 209b6d6fdb9..f0495ee8d17 100644 --- a/homeassistant/components/picnic/translations/it.json +++ b/homeassistant/components/picnic/translations/it.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/ja.json b/homeassistant/components/picnic/translations/ja.json index 194cffd7e6a..fd9fe67db73 100644 --- a/homeassistant/components/picnic/translations/ja.json +++ b/homeassistant/components/picnic/translations/ja.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/nl.json b/homeassistant/components/picnic/translations/nl.json index dc040fc03d0..7f7a2199d27 100644 --- a/homeassistant/components/picnic/translations/nl.json +++ b/homeassistant/components/picnic/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/no.json b/homeassistant/components/picnic/translations/no.json index ffd38bce705..1ebb4b8dece 100644 --- a/homeassistant/components/picnic/translations/no.json +++ b/homeassistant/components/picnic/translations/no.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/pl.json b/homeassistant/components/picnic/translations/pl.json index abf8a4f9469..f45a8520a14 100644 --- a/homeassistant/components/picnic/translations/pl.json +++ b/homeassistant/components/picnic/translations/pl.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/pt-BR.json b/homeassistant/components/picnic/translations/pt-BR.json index b864d13923d..296ca588e28 100644 --- a/homeassistant/components/picnic/translations/pt-BR.json +++ b/homeassistant/components/picnic/translations/pt-BR.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/ru.json b/homeassistant/components/picnic/translations/ru.json index 9d7a7fbdb23..2f912b07667 100644 --- a/homeassistant/components/picnic/translations/ru.json +++ b/homeassistant/components/picnic/translations/ru.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/tr.json b/homeassistant/components/picnic/translations/tr.json index b689f65ff96..a48dc699dd0 100644 --- a/homeassistant/components/picnic/translations/tr.json +++ b/homeassistant/components/picnic/translations/tr.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/picnic/translations/zh-Hant.json b/homeassistant/components/picnic/translations/zh-Hant.json index a82f4ace04d..99184422cef 100644 --- a/homeassistant/components/picnic/translations/zh-Hant.json +++ b/homeassistant/components/picnic/translations/zh-Hant.json @@ -19,6 +19,5 @@ } } } - }, - "title": "Picnic" + } } \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/es.json b/homeassistant/components/plaato/translations/es.json index d38bf2a8265..693c6bf99c7 100644 --- a/homeassistant/components/plaato/translations/es.json +++ b/homeassistant/components/plaato/translations/es.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", + "cloud_not_connected": "No conectado a Home Assistant Cloud.", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n.", "webhook_not_internet_accessible": "Tu instancia de Home Assistant debe estar accesible desde Internet para recibir mensajes webhook." }, "create_entry": { - "default": "\u00a1Tu Plaato {device_type} con nombre **{device_name}** se configur\u00f3 correctamente!" + "default": "\u00a1El dispositivo Plaato {device_type} con nombre **{device_name}** se ha configurado correctamente!" }, "error": { "invalid_webhook_device": "Has seleccionado un dispositivo que no admite el env\u00edo de datos a un webhook. Solo est\u00e1 disponible para Airlock", @@ -27,8 +28,8 @@ "device_name": "Nombre de su dispositivo", "device_type": "Tipo de dispositivo Plaato" }, - "description": "\u00bfEst\u00e1s seguro de que quieres configurar el Airlock de Plaato?", - "title": "Configurar el webhook de Plaato" + "description": "\u00bfQuieres empezar la configuraci\u00f3n?", + "title": "Configura dispositivos Plaato" }, "webhook": { "description": "Para enviar eventos a Home Assistant, deber\u00e1 configurar la funci\u00f3n de webhook en Plaato Airlock. \n\n Complete la siguiente informaci\u00f3n: \n\n - URL: `{webhook_url}`\n - M\u00e9todo: POST \n\n Consulte [la documentaci\u00f3n]({docs_url}) para obtener m\u00e1s detalles.", diff --git a/homeassistant/components/plaato/translations/id.json b/homeassistant/components/plaato/translations/id.json index 99783fd4a63..b159dec5a13 100644 --- a/homeassistant/components/plaato/translations/id.json +++ b/homeassistant/components/plaato/translations/id.json @@ -20,7 +20,7 @@ "token": "Tempel Token Auth di sini", "use_webhook": "Gunakan webhook" }, - "description": "Untuk dapat melakukan kueri API diperlukan 'auth_token'. Nilai token dapat diperoleh dengan mengikuti [petunjuk ini](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token)\n\nPerangkat yang dipilih: **{device_type}** \n\nJika Anda lebih memilih untuk menggunakan metode webhook bawaan (hanya Airlock), centang centang kotak di bawah ini dan kosongkan nilai Auth Token'", + "description": "Untuk dapat melakukan kueri API diperlukan `auth_token`. Nilai token dapat diperoleh dengan mengikuti [petunjuk ini](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token)\n\nPerangkat yang dipilih: **{device_type}** \n\nJika Anda lebih memilih untuk menggunakan metode webhook bawaan (hanya Airlock), centang centang kotak di bawah ini dan kosongkan nilai Auth Token'", "title": "Pilih metode API" }, "user": { diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index 83e2874ed73..938402d8ea9 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Account is al geconfigureerd", "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." + "webhook_not_internet_accessible": "Je Home Assistant-instantie moet toegankelijk zijn vanaf internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Uw Plaato {device_type} met naam **{device_name}** is succesvol ingesteld!" @@ -28,7 +28,7 @@ "device_name": "Geef uw apparaat een naam", "device_type": "Type Plaato-apparaat" }, - "description": "Wilt u beginnen met instellen?", + "description": "Wil je beginnen met instellen?", "title": "Stel de Plaato-apparaten in" }, "webhook": { diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 4ca83d98242..dbfe55077d7 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -299,14 +299,14 @@ def async_cleanup_plex_devices(hass, entry): device_registry = dev_reg.async_get(hass) entity_registry = ent_reg.async_get(hass) - device_entries = hass.helpers.device_registry.async_entries_for_config_entry( + device_entries = dev_reg.async_entries_for_config_entry( device_registry, entry.entry_id ) for device_entry in device_entries: if ( len( - hass.helpers.entity_registry.async_entries_for_device( + ent_reg.async_entries_for_device( entity_registry, device_entry.id, include_disabled_entities=True ) ) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 084356abf7b..912732efe98 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": [ - "plexapi==4.10.1", + "plexapi==4.11.2", "plexauth==0.0.6", "plexwebsocket==0.0.13" ], diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index e5d420f46b0..ce76c4be3ff 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -1,11 +1,14 @@ """Support to interface with the Plex API.""" from __future__ import annotations +from collections.abc import Callable from functools import wraps import logging +from typing import TypeVar import plexapi.exceptions import requests.exceptions +from typing_extensions import Concatenate, ParamSpec from homeassistant.components.media_player import ( DOMAIN as MP_DOMAIN, @@ -17,6 +20,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, @@ -24,7 +28,6 @@ from homeassistant.helpers.dispatcher import ( ) from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.network import is_internal_request from .const import ( @@ -43,17 +46,25 @@ from .const import ( from .media_browser import browse_media from .services import process_plex_payload +_PlexMediaPlayerT = TypeVar("_PlexMediaPlayerT", bound="PlexMediaPlayer") +_R = TypeVar("_R") +_P = ParamSpec("_P") + _LOGGER = logging.getLogger(__name__) -def needs_session(func): +def needs_session( + func: Callable[Concatenate[_PlexMediaPlayerT, _P], _R] +) -> Callable[Concatenate[_PlexMediaPlayerT, _P], _R | None]: """Ensure session is available for certain attributes.""" @wraps(func) - def get_session_attribute(self, *args): + def get_session_attribute( + self: _PlexMediaPlayerT, *args: _P.args, **kwargs: _P.kwargs + ) -> _R | None: if self.session is None: return None - return func(self, *args) + return func(self, *args, **kwargs) return get_session_attribute @@ -65,7 +76,7 @@ async def async_setup_entry( ) -> None: """Set up Plex media_player from a config entry.""" server_id = config_entry.data[CONF_SERVER_IDENTIFIER] - registry = await async_get_registry(hass) + registry = er.async_get(hass) @callback def async_new_media_players(new_entities): diff --git a/homeassistant/components/plex/services.py b/homeassistant/components/plex/services.py index b7eff8043f8..0847583635d 100644 --- a/homeassistant/components/plex/services.py +++ b/homeassistant/components/plex/services.py @@ -123,8 +123,10 @@ def process_plex_payload( plex_url = URL(content_id) if plex_url.name: if len(plex_url.parts) == 2: - # The path contains a single item, will always be a ratingKey - content = int(plex_url.name) + if plex_url.name == "search": + content = {} + else: + content = int(plex_url.name) else: # For "special" items like radio stations content = plex_url.path @@ -132,7 +134,10 @@ def process_plex_payload( plex_server = get_plex_server(hass, plex_server_id=server_id) else: # Handle legacy payloads without server_id in URL host position - content = int(plex_url.host) # type: ignore[arg-type] + if plex_url.host == "search": + content = {} + else: + content = int(plex_url.host) # type: ignore[arg-type] extra_params = dict(plex_url.query) else: content = json.loads(content_id) diff --git a/homeassistant/components/plex/translations/bg.json b/homeassistant/components/plex/translations/bg.json index d1b1867c246..0e39e4b8b04 100644 --- a/homeassistant/components/plex/translations/bg.json +++ b/homeassistant/components/plex/translations/bg.json @@ -27,12 +27,6 @@ }, "description": "\u041d\u0430\u043b\u0438\u0447\u043d\u0438 \u0441\u0430 \u043d\u044f\u043a\u043e\u043b\u043a\u043e \u0441\u044a\u0440\u0432\u044a\u0440\u0430, \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0435\u0434\u0438\u043d:", "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 Plex \u0441\u044a\u0440\u0432\u044a\u0440" - }, - "user": { - "title": "Plex Media Server" - }, - "user_advanced": { - "title": "Plex Media Server" } } }, diff --git a/homeassistant/components/plex/translations/ca.json b/homeassistant/components/plex/translations/ca.json index 6cad3cb9d6e..b917a81a772 100644 --- a/homeassistant/components/plex/translations/ca.json +++ b/homeassistant/components/plex/translations/ca.json @@ -35,14 +35,12 @@ "title": "Selecciona servidor Plex" }, "user": { - "description": "V\u00e9s a [plex.tv](https://plex.tv) per enlla\u00e7ar un servidor Plex.", - "title": "Servidor Multim\u00e8dia Plex" + "description": "V\u00e9s a [plex.tv](https://plex.tv) per enlla\u00e7ar un servidor Plex." }, "user_advanced": { "data": { "setup_method": "M\u00e8tode de configuraci\u00f3" - }, - "title": "Servidor Multim\u00e8dia Plex" + } } } }, diff --git a/homeassistant/components/plex/translations/cs.json b/homeassistant/components/plex/translations/cs.json index f5e7e538f84..851a8782023 100644 --- a/homeassistant/components/plex/translations/cs.json +++ b/homeassistant/components/plex/translations/cs.json @@ -35,14 +35,12 @@ "title": "Vyberte server Plex" }, "user": { - "description": "Pro propojen\u00ed Plex serveru, pokra\u010dujte na [plex.tv](https://plex.tv).", - "title": "Plex Media Server" + "description": "Pro propojen\u00ed Plex serveru, pokra\u010dujte na [plex.tv](https://plex.tv)." }, "user_advanced": { "data": { "setup_method": "Metoda nastaven\u00ed" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/de.json b/homeassistant/components/plex/translations/de.json index 130d34505d2..3eb1c1b7707 100644 --- a/homeassistant/components/plex/translations/de.json +++ b/homeassistant/components/plex/translations/de.json @@ -35,14 +35,12 @@ "title": "Plex-Server ausw\u00e4hlen" }, "user": { - "description": "Gehe zu [plex.tv] (https://plex.tv), um einen Plex-Server zu verbinden", - "title": "Plex Media Server" + "description": "Gehe zu [plex.tv] (https://plex.tv), um einen Plex-Server zu verbinden" }, "user_advanced": { "data": { "setup_method": "Einrichtungsmethode" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/el.json b/homeassistant/components/plex/translations/el.json index 679ef3d3937..cd2147b51f4 100644 --- a/homeassistant/components/plex/translations/el.json +++ b/homeassistant/components/plex/translations/el.json @@ -35,14 +35,12 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Plex" }, "user": { - "description": "\u03a3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c3\u03c4\u03bf [plex.tv](https://plex.tv) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Plex.", - "title": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 Plex Media" + "description": "\u03a3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c3\u03c4\u03bf [plex.tv](https://plex.tv) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Plex." }, "user_advanced": { "data": { "setup_method": "\u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2" - }, - "title": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 Plex Media" + } } } }, diff --git a/homeassistant/components/plex/translations/en.json b/homeassistant/components/plex/translations/en.json index 834594e1d27..1bd738ea10e 100644 --- a/homeassistant/components/plex/translations/en.json +++ b/homeassistant/components/plex/translations/en.json @@ -35,14 +35,12 @@ "title": "Select Plex server" }, "user": { - "description": "Continue to [plex.tv](https://plex.tv) to link a Plex server.", - "title": "Plex Media Server" + "description": "Continue to [plex.tv](https://plex.tv) to link a Plex server." }, "user_advanced": { "data": { "setup_method": "Setup method" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/es.json b/homeassistant/components/plex/translations/es.json index c31dcf26e07..7099f63a9e8 100644 --- a/homeassistant/components/plex/translations/es.json +++ b/homeassistant/components/plex/translations/es.json @@ -3,10 +3,10 @@ "abort": { "all_configured": "Todos los servidores vinculados ya configurados", "already_configured": "Este servidor Plex ya est\u00e1 configurado", - "already_in_progress": "Plex se est\u00e1 configurando", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente", "token_request_timeout": "Tiempo de espera agotado para la obtenci\u00f3n del token", - "unknown": "Fall\u00f3 por razones desconocidas" + "unknown": "Error inesperado" }, "error": { "faulty_credentials": "La autorizaci\u00f3n ha fallado, verifica el token", @@ -35,14 +35,12 @@ "title": "Seleccione el servidor Plex" }, "user": { - "description": "Continuar hacia [plex.tv](https://plex.tv) para vincular un servidor Plex.", - "title": "Plex Media Server" + "description": "Continuar hacia [plex.tv](https://plex.tv) para vincular un servidor Plex." }, "user_advanced": { "data": { "setup_method": "M\u00e9todo de configuraci\u00f3n" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/et.json b/homeassistant/components/plex/translations/et.json index d815a092bca..31e6084146d 100644 --- a/homeassistant/components/plex/translations/et.json +++ b/homeassistant/components/plex/translations/et.json @@ -35,14 +35,12 @@ "title": "Vali Plex'i server" }, "user": { - "description": "Plexi serveri linkimiseks mine lehele [plex.tv] (https://plex.tv).", - "title": "" + "description": "Plexi serveri linkimiseks mine lehele [plex.tv] (https://plex.tv)." }, "user_advanced": { "data": { "setup_method": "Seadistusmeetod" - }, - "title": "" + } } } }, diff --git a/homeassistant/components/plex/translations/fi.json b/homeassistant/components/plex/translations/fi.json index 2c5872fe85a..4900dbbbec9 100644 --- a/homeassistant/components/plex/translations/fi.json +++ b/homeassistant/components/plex/translations/fi.json @@ -10,14 +10,10 @@ }, "title": "Manuaalinen Plex-konfigurointi" }, - "user": { - "title": "Plex Media Server" - }, "user_advanced": { "data": { "setup_method": "Asennusmenetelm\u00e4" - }, - "title": "Plex-mediapalvelin" + } } } }, diff --git a/homeassistant/components/plex/translations/fr.json b/homeassistant/components/plex/translations/fr.json index 73704d31c7a..e4c711afd8a 100644 --- a/homeassistant/components/plex/translations/fr.json +++ b/homeassistant/components/plex/translations/fr.json @@ -35,14 +35,12 @@ "title": "S\u00e9lectionnez le serveur Plex" }, "user": { - "description": "Continuez sur [plex.tv] (https://plex.tv) pour lier un serveur Plex.", - "title": "Plex Media Server" + "description": "Continuez sur [plex.tv] (https://plex.tv) pour lier un serveur Plex." }, "user_advanced": { "data": { "setup_method": "M\u00e9thode de configuration" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/hu.json b/homeassistant/components/plex/translations/hu.json index 3be797cc04f..3015494af0a 100644 --- a/homeassistant/components/plex/translations/hu.json +++ b/homeassistant/components/plex/translations/hu.json @@ -35,14 +35,12 @@ "title": "Plex-kiszolg\u00e1l\u00f3 kiv\u00e1laszt\u00e1sa" }, "user": { - "description": "Folytassa a [plex.tv] (https://plex.tv) oldalt a Plex szerver \u00f6sszekapcsol\u00e1s\u00e1hoz.", - "title": "Plex Media Server" + "description": "Folytassa a [plex.tv] (https://plex.tv) oldalt a Plex szerver \u00f6sszekapcsol\u00e1s\u00e1hoz." }, "user_advanced": { "data": { "setup_method": "Be\u00e1ll\u00edt\u00e1si m\u00f3dszer" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/id.json b/homeassistant/components/plex/translations/id.json index 7c596835e03..ccc2786ae54 100644 --- a/homeassistant/components/plex/translations/id.json +++ b/homeassistant/components/plex/translations/id.json @@ -35,14 +35,12 @@ "title": "Pilih server Plex" }, "user": { - "description": "Lanjutkan [plex.tv](https://plex.tv) untuk menautkan server Plex.", - "title": "Server Media Plex" + "description": "Lanjutkan [plex.tv](https://plex.tv) untuk menautkan server Plex." }, "user_advanced": { "data": { "setup_method": "Metode penyiapan" - }, - "title": "Server Media Plex" + } } } }, diff --git a/homeassistant/components/plex/translations/it.json b/homeassistant/components/plex/translations/it.json index 8fb0603a8ee..876ab634523 100644 --- a/homeassistant/components/plex/translations/it.json +++ b/homeassistant/components/plex/translations/it.json @@ -35,14 +35,12 @@ "title": "Seleziona il server Plex" }, "user": { - "description": "Continua su [plex.tv](https://plex.tv) per collegare un server Plex.", - "title": "Plex Media Server" + "description": "Continua su [plex.tv](https://plex.tv) per collegare un server Plex." }, "user_advanced": { "data": { "setup_method": "Metodo di impostazione" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/ja.json b/homeassistant/components/plex/translations/ja.json index 4b48bdfe695..be42bab1d85 100644 --- a/homeassistant/components/plex/translations/ja.json +++ b/homeassistant/components/plex/translations/ja.json @@ -35,14 +35,12 @@ "title": "Plex\u30b5\u30fc\u30d0\u30fc\u3092\u9078\u629e" }, "user": { - "description": "[plex.tv](https://plex.tv) \u306b\u9032\u307f\u3001Plex server\u3092\u30ea\u30f3\u30af\u3057\u307e\u3059\u3002", - "title": "Plex Media Server" + "description": "[plex.tv](https://plex.tv) \u306b\u9032\u307f\u3001Plex server\u3092\u30ea\u30f3\u30af\u3057\u307e\u3059\u3002" }, "user_advanced": { "data": { "setup_method": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u65b9\u6cd5" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/ko.json b/homeassistant/components/plex/translations/ko.json index d6b0c6a2341..453f32ee15b 100644 --- a/homeassistant/components/plex/translations/ko.json +++ b/homeassistant/components/plex/translations/ko.json @@ -35,14 +35,12 @@ "title": "Plex \uc11c\ubc84 \uc120\ud0dd\ud558\uae30" }, "user": { - "description": "Plex \uc11c\ubc84\ub97c \uc5f0\uacb0\ud558\ub824\uba74 [plex.tv](https://plex.tv)\ub85c \uacc4\uc18d \uc9c4\ud589\ud574\uc8fc\uc138\uc694.", - "title": "Plex \ubbf8\ub514\uc5b4 \uc11c\ubc84" + "description": "Plex \uc11c\ubc84\ub97c \uc5f0\uacb0\ud558\ub824\uba74 [plex.tv](https://plex.tv)\ub85c \uacc4\uc18d \uc9c4\ud589\ud574\uc8fc\uc138\uc694." }, "user_advanced": { "data": { "setup_method": "\uc124\uc815 \ubc29\ubc95" - }, - "title": "Plex \ubbf8\ub514\uc5b4 \uc11c\ubc84" + } } } }, diff --git a/homeassistant/components/plex/translations/lb.json b/homeassistant/components/plex/translations/lb.json index 916bcbc9042..d623b7cc011 100644 --- a/homeassistant/components/plex/translations/lb.json +++ b/homeassistant/components/plex/translations/lb.json @@ -35,14 +35,12 @@ "title": "Plex Server auswielen" }, "user": { - "description": "Verbann dech mat [plex.tv](https://pley.tv) fir ee Plex Server ze verlinken.", - "title": "Plex Media Server" + "description": "Verbann dech mat [plex.tv](https://pley.tv) fir ee Plex Server ze verlinken." }, "user_advanced": { "data": { "setup_method": "Setup Method" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/nl.json b/homeassistant/components/plex/translations/nl.json index a196555e7ea..4f299e8b5c1 100644 --- a/homeassistant/components/plex/translations/nl.json +++ b/homeassistant/components/plex/translations/nl.json @@ -3,8 +3,8 @@ "abort": { "all_configured": "Alle gekoppelde servers zijn al geconfigureerd", "already_configured": "Deze Plex-server is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", - "reauth_successful": "Herauthenticatie was succesvol", + "already_in_progress": "De configuratie is momenteel al bezig", + "reauth_successful": "Herauthenticatie geslaagd", "token_request_timeout": "Time-out verkrijgen van token", "unknown": "Onverwachte fout" }, @@ -23,7 +23,7 @@ "port": "Poort", "ssl": "Maakt gebruik van een SSL-certificaat", "token": "Token (optioneel)", - "verify_ssl": "Controleer het SSL-certificaat" + "verify_ssl": "SSL-certificaat verifi\u00ebren" }, "title": "Handmatige Plex-configuratie" }, @@ -35,14 +35,12 @@ "title": "Selecteer Plex server" }, "user": { - "description": "Ga verder naar [plex.tv] (https://plex.tv) om een Plex-server te koppelen.", - "title": "Plex Media Server" + "description": "Ga verder naar [plex.tv] (https://plex.tv) om een Plex-server te koppelen." }, "user_advanced": { "data": { "setup_method": "Installatiemethode" - }, - "title": "Plex Media Server" + } } } }, @@ -52,7 +50,7 @@ "data": { "ignore_new_shared_users": "Negeer nieuwe beheerde/gedeelde gebruikers", "ignore_plex_web_clients": "Negeer Plex-webclients", - "monitored_users": "Gecontroleerde gebruikers", + "monitored_users": "Gemonitorde gebruikers", "use_episode_art": "Gebruik aflevering kunst" }, "description": "Opties voor Plex-mediaspelers" diff --git a/homeassistant/components/plex/translations/no.json b/homeassistant/components/plex/translations/no.json index ddd6ef4cdb2..c34b4b1c257 100644 --- a/homeassistant/components/plex/translations/no.json +++ b/homeassistant/components/plex/translations/no.json @@ -35,14 +35,12 @@ "title": "Velg Plex-server" }, "user": { - "description": "Fortsett til [plex.tv] (https://plex.tv) for \u00e5 koble en Plex-server.", - "title": "" + "description": "Fortsett til [plex.tv] (https://plex.tv) for \u00e5 koble en Plex-server." }, "user_advanced": { "data": { "setup_method": "Oppsettmetode" - }, - "title": "" + } } } }, diff --git a/homeassistant/components/plex/translations/pl.json b/homeassistant/components/plex/translations/pl.json index bd3e45da688..d4648973a5f 100644 --- a/homeassistant/components/plex/translations/pl.json +++ b/homeassistant/components/plex/translations/pl.json @@ -35,14 +35,12 @@ "title": "Wybierz serwer Plex" }, "user": { - "description": "Przejd\u017a do [plex.tv](https://plex.tv), aby po\u0142\u0105czy\u0107 serwer Plex.", - "title": "Serwer medi\u00f3w Plex" + "description": "Przejd\u017a do [plex.tv](https://plex.tv), aby po\u0142\u0105czy\u0107 serwer Plex." }, "user_advanced": { "data": { "setup_method": "Metoda konfiguracji" - }, - "title": "Serwer medi\u00f3w Plex" + } } } }, diff --git a/homeassistant/components/plex/translations/pt-BR.json b/homeassistant/components/plex/translations/pt-BR.json index ea74d2b173b..f8e9aa7dea4 100644 --- a/homeassistant/components/plex/translations/pt-BR.json +++ b/homeassistant/components/plex/translations/pt-BR.json @@ -35,14 +35,12 @@ "title": "Selecione servidor Plex" }, "user": { - "description": "Continue para [plex.tv](https://plex.tv) para vincular um servidor Plex.", - "title": "Plex Media Server" + "description": "Continue para [plex.tv](https://plex.tv) para vincular um servidor Plex." }, "user_advanced": { "data": { "setup_method": "M\u00e9todo de configura\u00e7\u00e3o" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/ru.json b/homeassistant/components/plex/translations/ru.json index b1530f0808b..fa05661f29c 100644 --- a/homeassistant/components/plex/translations/ru.json +++ b/homeassistant/components/plex/translations/ru.json @@ -35,14 +35,12 @@ "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 Plex" }, "user": { - "description": "\u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 [plex.tv](https://plex.tv), \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 Plex \u043a Home Assistant.", - "title": "Plex Media Server" + "description": "\u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 [plex.tv](https://plex.tv), \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 Plex \u043a Home Assistant." }, "user_advanced": { "data": { "setup_method": "\u0421\u043f\u043e\u0441\u043e\u0431 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/sl.json b/homeassistant/components/plex/translations/sl.json index b1622219402..7d47584aad6 100644 --- a/homeassistant/components/plex/translations/sl.json +++ b/homeassistant/components/plex/translations/sl.json @@ -33,14 +33,12 @@ "title": "Izberite stre\u017enik Plex" }, "user": { - "description": "Nadaljujte do [plex.tv] (https://plex.tv), da pove\u017eete stre\u017enik Plex.", - "title": "Plex medijski stre\u017enik" + "description": "Nadaljujte do [plex.tv] (https://plex.tv), da pove\u017eete stre\u017enik Plex." }, "user_advanced": { "data": { "setup_method": "Na\u010din nastavitve" - }, - "title": "Plex medijski stre\u017enik" + } } } }, diff --git a/homeassistant/components/plex/translations/sv.json b/homeassistant/components/plex/translations/sv.json index 6ca36d302af..63b12e70e40 100644 --- a/homeassistant/components/plex/translations/sv.json +++ b/homeassistant/components/plex/translations/sv.json @@ -25,14 +25,10 @@ "description": "V\u00e4lj flera servrar tillg\u00e4ngliga, v\u00e4lj en:", "title": "V\u00e4lj Plex-server" }, - "user": { - "title": "Plex Media Server" - }, "user_advanced": { "data": { "setup_method": "Inst\u00e4llningsmetod" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/tr.json b/homeassistant/components/plex/translations/tr.json index c052006687f..423fc1ddbcb 100644 --- a/homeassistant/components/plex/translations/tr.json +++ b/homeassistant/components/plex/translations/tr.json @@ -35,14 +35,12 @@ "title": "Plex sunucusunu se\u00e7in" }, "user": { - "description": "Bir Plex sunucusunu ba\u011flamak i\u00e7in [plex.tv](https://plex.tv) ile devam edin.", - "title": "Plex Medya Sunucusu" + "description": "Bir Plex sunucusunu ba\u011flamak i\u00e7in [plex.tv](https://plex.tv) ile devam edin." }, "user_advanced": { "data": { "setup_method": "Kurulum y\u00f6ntemi" - }, - "title": "Plex Medya Sunucusu" + } } } }, diff --git a/homeassistant/components/plex/translations/uk.json b/homeassistant/components/plex/translations/uk.json index 20351cf735a..16ac314c1ad 100644 --- a/homeassistant/components/plex/translations/uk.json +++ b/homeassistant/components/plex/translations/uk.json @@ -35,14 +35,12 @@ "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 Plex" }, "user": { - "description": "\u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 [plex.tv](https://plex.tv), \u0449\u043e\u0431 \u043f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 Plex \u0434\u043e Home Assistant.", - "title": "Plex Media Server" + "description": "\u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 [plex.tv](https://plex.tv), \u0449\u043e\u0431 \u043f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 Plex \u0434\u043e Home Assistant." }, "user_advanced": { "data": { "setup_method": "\u0421\u043f\u043e\u0441\u0456\u0431 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" - }, - "title": "Plex Media Server" + } } } }, diff --git a/homeassistant/components/plex/translations/zh-Hans.json b/homeassistant/components/plex/translations/zh-Hans.json index 02f548f2286..877543e90b0 100644 --- a/homeassistant/components/plex/translations/zh-Hans.json +++ b/homeassistant/components/plex/translations/zh-Hans.json @@ -32,14 +32,10 @@ "description": "\u6709\u591a\u4e2a\u53ef\u7528\u670d\u52a1\u5668\uff0c\u8bf7\u9009\u62e9\uff1a", "title": "\u9009\u62e9 Plex \u670d\u52a1\u5668" }, - "user": { - "title": "Plex \u5a92\u4f53\u670d\u52a1\u5668" - }, "user_advanced": { "data": { "setup_method": "\u8bbe\u7f6e\u65b9\u6cd5" - }, - "title": "Plex \u5a92\u4f53\u670d\u52a1\u5668" + } } } }, diff --git a/homeassistant/components/plex/translations/zh-Hant.json b/homeassistant/components/plex/translations/zh-Hant.json index 7f19fa0d035..7f05dc4b3be 100644 --- a/homeassistant/components/plex/translations/zh-Hant.json +++ b/homeassistant/components/plex/translations/zh-Hant.json @@ -35,14 +35,12 @@ "title": "\u9078\u64c7 Plex \u4f3a\u670d\u5668" }, "user": { - "description": "\u7e7c\u7e8c\u81f3 [plex.tv](https://plex.tv) \u4ee5\u9023\u7d50\u4e00\u7d44 Plex \u4f3a\u670d\u5668\u3002", - "title": "Plex \u5a92\u9ad4\u4f3a\u670d\u5668" + "description": "\u7e7c\u7e8c\u81f3 [plex.tv](https://plex.tv) \u4ee5\u9023\u7d50\u4e00\u7d44 Plex \u4f3a\u670d\u5668\u3002" }, "user_advanced": { "data": { "setup_method": "\u8a2d\u5b9a\u6a21\u5f0f" - }, - "title": "Plex \u5a92\u9ad4\u4f3a\u670d\u5668" + } } } }, diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 91a56d09ee5..c74a1baa9be 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -31,7 +31,7 @@ async def async_setup_entry( async_add_entities( PlugwiseClimateEntity(coordinator, device_id) for device_id, device in coordinator.data.devices.items() - if device["class"] in THERMOSTAT_CLASSES + if device["dev_class"] in THERMOSTAT_CLASSES ) @@ -53,9 +53,9 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): # Determine preset modes self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE - if presets := self.device.get("presets"): + if presets := self.device.get("preset_modes"): self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE - self._attr_preset_modes = list(presets) + self._attr_preset_modes = presets # Determine hvac modes and current hvac mode self._attr_hvac_modes = [HVACMode.HEAT] @@ -105,6 +105,7 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): return HVACAction.HEATING if heater_central_data["binary_sensors"].get("cooling_state"): return HVACAction.COOLING + return HVACAction.IDLE @property @@ -132,9 +133,6 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): @plugwise_command async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set the hvac mode.""" - 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"), diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 3bcad0b88aa..63bd2a6d8f1 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -1,51 +1,53 @@ """Constants for Plugwise component.""" +from __future__ import annotations + from datetime import timedelta import logging +from typing import Final from homeassistant.const import Platform -DOMAIN = "plugwise" +DOMAIN: Final = "plugwise" LOGGER = logging.getLogger(__package__) -API = "api" -FLOW_SMILE = "smile (Adam/Anna/P1)" -FLOW_STRETCH = "stretch (Stretch)" -FLOW_TYPE = "flow_type" -GATEWAY = "gateway" -PW_TYPE = "plugwise_type" -SMILE = "smile" -STRETCH = "stretch" -STRETCH_USERNAME = "stretch" -UNIT_LUMEN = "lm" +API: Final = "api" +FLOW_SMILE: Final = "smile (Adam/Anna/P1)" +FLOW_STRETCH: Final = "stretch (Stretch)" +FLOW_TYPE: Final = "flow_type" +GATEWAY: Final = "gateway" +PW_TYPE: Final = "plugwise_type" +SMILE: Final = "smile" +STRETCH: Final = "stretch" +STRETCH_USERNAME: Final = "stretch" +UNIT_LUMEN: Final = "lm" -PLATFORMS_GATEWAY = [ +PLATFORMS_GATEWAY: Final[list[str]] = [ Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SENSOR, - Platform.SWITCH, Platform.SELECT, + Platform.SWITCH, ] -ZEROCONF_MAP = { +ZEROCONF_MAP: Final[dict[str, str]] = { "smile": "P1", "smile_thermo": "Anna", "smile_open_therm": "Adam", "stretch": "Stretch", } - # Default directives -DEFAULT_MAX_TEMP = 30 -DEFAULT_MIN_TEMP = 4 -DEFAULT_PORT = 80 -DEFAULT_SCAN_INTERVAL = { +DEFAULT_MAX_TEMP: Final = 30 +DEFAULT_MIN_TEMP: Final = 4 +DEFAULT_PORT: Final = 80 +DEFAULT_SCAN_INTERVAL: Final[dict[str, timedelta]] = { "power": timedelta(seconds=10), "stretch": timedelta(seconds=60), "thermostat": timedelta(seconds=60), } -DEFAULT_USERNAME = "smile" +DEFAULT_USERNAME: Final = "smile" -THERMOSTAT_CLASSES = [ +THERMOSTAT_CLASSES: Final[list[str]] = [ "thermostat", "thermostatic_radiator_valve", "zone_thermometer", diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index b0896c3cd6d..491eb7c7db8 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -45,8 +45,8 @@ class PlugwiseEntity(CoordinatorEntity[PlugwiseDataUpdateCoordinator]): manufacturer=data.get("vendor"), model=data.get("model"), name=f"Smile {coordinator.data.gateway['smile_name']}", - sw_version=data.get("fw"), - hw_version=data.get("hw"), + sw_version=data.get("firmware"), + hw_version=data.get("hardware"), ) if device_id != coordinator.data.gateway["gateway_id"]: diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index f540c6d0b9c..7eb2b1371d5 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio from typing import Any +from aiohttp import ClientConnectionError from plugwise.exceptions import InvalidAuthentication, PlugwiseException from plugwise.smile import Smile @@ -44,7 +45,7 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: except InvalidAuthentication: LOGGER.error("Invalid username or Smile ID") return False - except PlugwiseException as err: + except (ClientConnectionError, PlugwiseException) as err: raise ConfigEntryNotReady( f"Error while communicating to device {api.smile_name}" ) from err @@ -112,7 +113,7 @@ def migrate_sensor_entities( # Migrating opentherm_outdoor_temperature to opentherm_outdoor_air_temperature sensor for device_id, device in coordinator.data.devices.items(): - if device["class"] != "heater_central": + if device["dev_class"] != "heater_central": continue old_unique_id = f"{device_id}-outdoor_temperature" diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index 68ed6d65647..a311151f645 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -2,7 +2,7 @@ "domain": "plugwise", "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", - "requirements": ["plugwise==0.17.3"], + "requirements": ["plugwise==0.18.5"], "codeowners": ["@CoMPaTech", "@bouwew", "@brefra", "@frenck"], "zeroconf": ["_plugwise._tcp.local."], "config_flow": true, diff --git a/homeassistant/components/plugwise/select.py b/homeassistant/components/plugwise/select.py index b3cfb366889..cac9c3e5637 100644 --- a/homeassistant/components/plugwise/select.py +++ b/homeassistant/components/plugwise/select.py @@ -1,64 +1,113 @@ """Plugwise Select component for Home Assistant.""" from __future__ import annotations -from homeassistant.components.select import SelectEntity +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +from plugwise import Smile + +from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_ON from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, THERMOSTAT_CLASSES +from .const import DOMAIN from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity +@dataclass +class PlugwiseSelectDescriptionMixin: + """Mixin values for Plugwise Select entities.""" + + command: Callable[[Smile, str, str], Awaitable[Any]] + current_option: str + options: str + + +@dataclass +class PlugwiseSelectEntityDescription( + SelectEntityDescription, PlugwiseSelectDescriptionMixin +): + """Class describing Plugwise Number entities.""" + + +SELECT_TYPES = ( + PlugwiseSelectEntityDescription( + key="select_schedule", + name="Thermostat Schedule", + icon="mdi:calendar-clock", + command=lambda api, loc, opt: api.set_schedule_state(loc, opt, STATE_ON), + current_option="selected_schedule", + options="available_schedules", + ), + PlugwiseSelectEntityDescription( + key="select_regulation_mode", + name="Regulation Mode", + icon="mdi:hvac", + entity_category=EntityCategory.CONFIG, + command=lambda api, loc, opt: api.set_regulation_mode(opt), + current_option="regulation_mode", + options="regulation_modes", + ), +) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Smile selector from a config entry.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id] - async_add_entities( - PlugwiseSelectEntity(coordinator, device_id) - for device_id, device in coordinator.data.devices.items() - if device["class"] in THERMOSTAT_CLASSES - and len(device.get("available_schedules")) > 1 - ) + coordinator: PlugwiseDataUpdateCoordinator = hass.data[DOMAIN][ + config_entry.entry_id + ] + + entities: list[PlugwiseSelectEntity] = [] + for device_id, device in coordinator.data.devices.items(): + for description in SELECT_TYPES: + if description.options in device and len(device[description.options]) > 1: + entities.append( + PlugwiseSelectEntity(coordinator, device_id, description) + ) + + async_add_entities(entities) class PlugwiseSelectEntity(PlugwiseEntity, SelectEntity): """Represent Smile selector.""" + entity_description: PlugwiseSelectEntityDescription + def __init__( self, coordinator: PlugwiseDataUpdateCoordinator, device_id: str, + entity_description: PlugwiseSelectEntityDescription, ) -> None: """Initialise the selector.""" super().__init__(coordinator, device_id) - self._attr_unique_id = f"{device_id}-select_schedule" - self._attr_name = (f"{self.device.get('name', '')} Select Schedule").lstrip() + self.entity_description = entity_description + self._attr_unique_id = f"{device_id}-{entity_description.key}" + self._attr_name = (f"{self.device['name']} {entity_description.name}").lstrip() @property - def current_option(self) -> str | None: + def current_option(self) -> str: """Return the selected entity option to represent the entity state.""" - return self.device.get("selected_schedule") + return self.device[self.entity_description.current_option] @property def options(self) -> list[str]: - """Return a set of selectable options.""" - return self.device.get("available_schedules", []) + """Return the selectable entity options.""" + return self.device[self.entity_description.options] async def async_select_option(self, option: str) -> None: - """Change the selected option.""" - if not ( - await self.coordinator.api.set_schedule_state( - self.device.get("location"), - option, - STATE_ON, - ) - ): - raise HomeAssistantError(f"Failed to change to schedule {option}") + """Change to the selected entity option.""" + await self.entity_description.command( + self.coordinator.api, self.device["location"], option + ) + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/plugwise/translations/es.json b/homeassistant/components/plugwise/translations/es.json index 8f3ce70cb88..e1d1bf3359b 100644 --- a/homeassistant/components/plugwise/translations/es.json +++ b/homeassistant/components/plugwise/translations/es.json @@ -4,17 +4,17 @@ "already_configured": "El servicio ya est\u00e1 configurado" }, "error": { - "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntelo de nuevo.", + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida, comprueba los 8 caracteres de tu Smile ID", "unknown": "Error inesperado" }, - "flow_title": "Smile: {name}", + "flow_title": "{name}", "step": { "user": { "data": { "flow_type": "Tipo de conexi\u00f3n" }, - "description": "Detalles", + "description": "Producto:", "title": "Conectarse a Smile" }, "user_gateway": { diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index d295695016f..8109386013c 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "De service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -23,7 +23,7 @@ "host": "IP-adres", "password": "Smile-ID", "port": "Poort", - "username": "Smile Gebruikersnaam" + "username": "Smile gebruikersnaam" }, "description": "Voer in", "title": "Maak verbinding met de Smile" diff --git a/homeassistant/components/plugwise/util.py b/homeassistant/components/plugwise/util.py index 58c7715815e..55d9c204dd3 100644 --- a/homeassistant/components/plugwise/util.py +++ b/homeassistant/components/plugwise/util.py @@ -9,21 +9,23 @@ from homeassistant.exceptions import HomeAssistantError from .entity import PlugwiseEntity -_P = ParamSpec("_P") +_PlugwiseEntityT = TypeVar("_PlugwiseEntityT", bound=PlugwiseEntity) _R = TypeVar("_R") -_T = TypeVar("_T", bound=PlugwiseEntity) +_P = ParamSpec("_P") def plugwise_command( - func: Callable[Concatenate[_T, _P], Awaitable[_R]] # type: ignore[misc] -) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, _R]]: # type: ignore[misc] + func: Callable[Concatenate[_PlugwiseEntityT, _P], Awaitable[_R]] +) -> Callable[Concatenate[_PlugwiseEntityT, _P], Coroutine[Any, Any, _R]]: """Decorate Plugwise calls that send commands/make changes to the device. A decorator that wraps the passed in function, catches Plugwise errors, and requests an coordinator update to update status of the devices asap. """ - async def handler(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R: + async def handler( + self: _PlugwiseEntityT, *args: _P.args, **kwargs: _P.kwargs + ) -> _R: try: return await func(self, *args, **kwargs) except PlugwiseException as error: diff --git a/homeassistant/components/plum_lightpad/translations/es.json b/homeassistant/components/plum_lightpad/translations/es.json index b5f3c8b1439..c22321bc0b7 100644 --- a/homeassistant/components/plum_lightpad/translations/es.json +++ b/homeassistant/components/plum_lightpad/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada" + "already_configured": "La cuenta ya est\u00e1 configurada" }, "error": { "cannot_connect": "No se pudo conectar" diff --git a/homeassistant/components/plum_lightpad/translations/nl.json b/homeassistant/components/plum_lightpad/translations/nl.json index 8410cabbbb9..6009e1fb384 100644 --- a/homeassistant/components/plum_lightpad/translations/nl.json +++ b/homeassistant/components/plum_lightpad/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Account is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "step": { "user": { diff --git a/homeassistant/components/point/translations/es.json b/homeassistant/components/point/translations/es.json index c495a4fe3bd..3d1f47c2ceb 100644 --- a/homeassistant/components/point/translations/es.json +++ b/homeassistant/components/point/translations/es.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n.", - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "external_setup": "Point se ha configurado correctamente a partir de otro flujo.", "no_flows": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n.", "unknown_authorize_url_generation": "Error desconocido al generar una URL de autorizaci\u00f3n." diff --git a/homeassistant/components/point/translations/nl.json b/homeassistant/components/point/translations/nl.json index f0ab4d8696e..9a0f0c940a8 100644 --- a/homeassistant/components/point/translations/nl.json +++ b/homeassistant/components/point/translations/nl.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "already_setup": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", - "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "already_setup": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", "external_setup": "Punt succesvol geconfigureerd vanuit een andere stroom.", - "no_flows": "De component is niet geconfigureerd. Gelieve de documentatie volgen.", + "no_flows": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "error": { "follow_link": "Volg de link en verifieer voordat je op Verzenden klikt", @@ -23,7 +23,7 @@ "data": { "flow_impl": "Leverancier" }, - "description": "Wilt u beginnen met instellen?", + "description": "Wil je beginnen met instellen?", "title": "Kies een authenticatie methode" } } diff --git a/homeassistant/components/poolsense/translations/bg.json b/homeassistant/components/poolsense/translations/bg.json index a89cca15270..eb033e74f0f 100644 --- a/homeassistant/components/poolsense/translations/bg.json +++ b/homeassistant/components/poolsense/translations/bg.json @@ -11,9 +11,7 @@ "data": { "email": "Email", "password": "\u041f\u0430\u0440\u043e\u043b\u0430" - }, - "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?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/ca.json b/homeassistant/components/poolsense/translations/ca.json index 923aedcee66..d5d996f444a 100644 --- a/homeassistant/components/poolsense/translations/ca.json +++ b/homeassistant/components/poolsense/translations/ca.json @@ -11,9 +11,7 @@ "data": { "email": "Correu electr\u00f2nic", "password": "Contrasenya" - }, - "description": "Vols comen\u00e7ar la configuraci\u00f3?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/cs.json b/homeassistant/components/poolsense/translations/cs.json index d4d94bd7334..520720ed5a1 100644 --- a/homeassistant/components/poolsense/translations/cs.json +++ b/homeassistant/components/poolsense/translations/cs.json @@ -11,9 +11,7 @@ "data": { "email": "E-mail", "password": "Heslo" - }, - "description": "Chcete za\u010d\u00edt nastavovat?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/de.json b/homeassistant/components/poolsense/translations/de.json index 5ce9313b442..d19b110ff96 100644 --- a/homeassistant/components/poolsense/translations/de.json +++ b/homeassistant/components/poolsense/translations/de.json @@ -11,9 +11,7 @@ "data": { "email": "E-Mail", "password": "Passwort" - }, - "description": "M\u00f6chtest Du mit der Einrichtung beginnen?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/el.json b/homeassistant/components/poolsense/translations/el.json index bb70337158e..f3fe349d939 100644 --- a/homeassistant/components/poolsense/translations/el.json +++ b/homeassistant/components/poolsense/translations/el.json @@ -11,9 +11,7 @@ "data": { "email": "Email", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" - }, - "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;", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/en.json b/homeassistant/components/poolsense/translations/en.json index 5203d6228c4..9e78fd62910 100644 --- a/homeassistant/components/poolsense/translations/en.json +++ b/homeassistant/components/poolsense/translations/en.json @@ -11,9 +11,7 @@ "data": { "email": "Email", "password": "Password" - }, - "description": "Do you want to start set up?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/es.json b/homeassistant/components/poolsense/translations/es.json index e2e5272f7ac..d4b68388db2 100644 --- a/homeassistant/components/poolsense/translations/es.json +++ b/homeassistant/components/poolsense/translations/es.json @@ -11,9 +11,7 @@ "data": { "email": "Correo electr\u00f3nico", "password": "Contrase\u00f1a" - }, - "description": "[%key:common::config_flow::description%]", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/et.json b/homeassistant/components/poolsense/translations/et.json index 4e1dc8db1ff..57c3b8aef6e 100644 --- a/homeassistant/components/poolsense/translations/et.json +++ b/homeassistant/components/poolsense/translations/et.json @@ -11,9 +11,7 @@ "data": { "email": "E-post", "password": "Salas\u00f5na" - }, - "description": "Kas alustan seadistamist?", - "title": "" + } } } } diff --git a/homeassistant/components/poolsense/translations/fr.json b/homeassistant/components/poolsense/translations/fr.json index 4a32776bccb..dda4b0da753 100644 --- a/homeassistant/components/poolsense/translations/fr.json +++ b/homeassistant/components/poolsense/translations/fr.json @@ -11,9 +11,7 @@ "data": { "email": "Courriel", "password": "Mot de passe" - }, - "description": "Voulez-vous commencer la configuration\u00a0?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/he.json b/homeassistant/components/poolsense/translations/he.json index f285e1ec479..8c832faf350 100644 --- a/homeassistant/components/poolsense/translations/he.json +++ b/homeassistant/components/poolsense/translations/he.json @@ -11,8 +11,7 @@ "data": { "email": "\u05d3\u05d5\u05d0\"\u05dc", "password": "\u05e1\u05d9\u05e1\u05de\u05d4" - }, - "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05d7\u05d9\u05dc \u05d1\u05d4\u05d2\u05d3\u05e8\u05d4?" + } } } } diff --git a/homeassistant/components/poolsense/translations/hu.json b/homeassistant/components/poolsense/translations/hu.json index 39274e14c21..3a80610269b 100644 --- a/homeassistant/components/poolsense/translations/hu.json +++ b/homeassistant/components/poolsense/translations/hu.json @@ -11,9 +11,7 @@ "data": { "email": "E-mail", "password": "Jelsz\u00f3" - }, - "description": "El szeretn\u00e9 kezdeni a be\u00e1ll\u00edt\u00e1st?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/id.json b/homeassistant/components/poolsense/translations/id.json index 6e40f5f0925..3cf0dcd65bb 100644 --- a/homeassistant/components/poolsense/translations/id.json +++ b/homeassistant/components/poolsense/translations/id.json @@ -11,9 +11,7 @@ "data": { "email": "Email", "password": "Kata Sandi" - }, - "description": "Ingin memulai penyiapan?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/it.json b/homeassistant/components/poolsense/translations/it.json index e6ea298e31d..d4a55b4a768 100644 --- a/homeassistant/components/poolsense/translations/it.json +++ b/homeassistant/components/poolsense/translations/it.json @@ -11,9 +11,7 @@ "data": { "email": "Email", "password": "Password" - }, - "description": "Vuoi iniziare la configurazione?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/ja.json b/homeassistant/components/poolsense/translations/ja.json index 28d7e1d3d18..63dee3f0739 100644 --- a/homeassistant/components/poolsense/translations/ja.json +++ b/homeassistant/components/poolsense/translations/ja.json @@ -11,9 +11,7 @@ "data": { "email": "E\u30e1\u30fc\u30eb", "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" - }, - "description": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u958b\u59cb\u3057\u307e\u3059\u304b\uff1f", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/ko.json b/homeassistant/components/poolsense/translations/ko.json index ec8c7dfc90f..d649b93e3ae 100644 --- a/homeassistant/components/poolsense/translations/ko.json +++ b/homeassistant/components/poolsense/translations/ko.json @@ -11,9 +11,7 @@ "data": { "email": "\uc774\uba54\uc77c", "password": "\ube44\ubc00\ubc88\ud638" - }, - "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/lb.json b/homeassistant/components/poolsense/translations/lb.json index 4d7065e6380..2a671d8e1de 100644 --- a/homeassistant/components/poolsense/translations/lb.json +++ b/homeassistant/components/poolsense/translations/lb.json @@ -11,9 +11,7 @@ "data": { "email": "E-Mail", "password": "Passwuert" - }, - "description": "Soll den Ariichtungs Prozess gestart ginn?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/nl.json b/homeassistant/components/poolsense/translations/nl.json index 1fd59ebf2ea..46fc915d7bd 100644 --- a/homeassistant/components/poolsense/translations/nl.json +++ b/homeassistant/components/poolsense/translations/nl.json @@ -11,9 +11,7 @@ "data": { "email": "E-mail", "password": "Wachtwoord" - }, - "description": "Wilt u beginnen met instellen?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/no.json b/homeassistant/components/poolsense/translations/no.json index e2873a36080..3b1ab46d773 100644 --- a/homeassistant/components/poolsense/translations/no.json +++ b/homeassistant/components/poolsense/translations/no.json @@ -11,9 +11,7 @@ "data": { "email": "E-post", "password": "Passord" - }, - "description": "Vil du starte oppsettet?", - "title": "" + } } } } diff --git a/homeassistant/components/poolsense/translations/pl.json b/homeassistant/components/poolsense/translations/pl.json index 6f87cacce4f..e29ac245760 100644 --- a/homeassistant/components/poolsense/translations/pl.json +++ b/homeassistant/components/poolsense/translations/pl.json @@ -11,9 +11,7 @@ "data": { "email": "Adres e-mail", "password": "Has\u0142o" - }, - "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/pt-BR.json b/homeassistant/components/poolsense/translations/pt-BR.json index 8b2e2bddda0..3fa0e51875e 100644 --- a/homeassistant/components/poolsense/translations/pt-BR.json +++ b/homeassistant/components/poolsense/translations/pt-BR.json @@ -11,9 +11,7 @@ "data": { "email": "Email", "password": "Senha" - }, - "description": "Deseja iniciar a configura\u00e7\u00e3o?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/pt.json b/homeassistant/components/poolsense/translations/pt.json index 0dc8303b485..cc2666db4c5 100644 --- a/homeassistant/components/poolsense/translations/pt.json +++ b/homeassistant/components/poolsense/translations/pt.json @@ -11,9 +11,7 @@ "data": { "email": "Email", "password": "Palavra-passe" - }, - "description": "[%key:common::config_flow::description%]", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/ru.json b/homeassistant/components/poolsense/translations/ru.json index 09c94368cda..90911c39d8d 100644 --- a/homeassistant/components/poolsense/translations/ru.json +++ b/homeassistant/components/poolsense/translations/ru.json @@ -11,9 +11,7 @@ "data": { "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", "password": "\u041f\u0430\u0440\u043e\u043b\u044c" - }, - "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/sv.json b/homeassistant/components/poolsense/translations/sv.json deleted file mode 100644 index ad96ce6310d..00000000000 --- a/homeassistant/components/poolsense/translations/sv.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "config": { - "step": { - "user": { - "description": "Vill du starta konfigurationen?" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/poolsense/translations/tr.json b/homeassistant/components/poolsense/translations/tr.json index e1b4f150e92..1e2e9d0c5b8 100644 --- a/homeassistant/components/poolsense/translations/tr.json +++ b/homeassistant/components/poolsense/translations/tr.json @@ -11,9 +11,7 @@ "data": { "email": "E-posta", "password": "Parola" - }, - "description": "Kuruluma ba\u015flamak ister misiniz?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/uk.json b/homeassistant/components/poolsense/translations/uk.json index 6ac3b97f741..9723921f479 100644 --- a/homeassistant/components/poolsense/translations/uk.json +++ b/homeassistant/components/poolsense/translations/uk.json @@ -11,9 +11,7 @@ "data": { "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", "password": "\u041f\u0430\u0440\u043e\u043b\u044c" - }, - "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/poolsense/translations/zh-Hant.json b/homeassistant/components/poolsense/translations/zh-Hant.json index 62ffca35e9f..957a11cfd61 100644 --- a/homeassistant/components/poolsense/translations/zh-Hant.json +++ b/homeassistant/components/poolsense/translations/zh-Hant.json @@ -11,9 +11,7 @@ "data": { "email": "\u96fb\u5b50\u90f5\u4ef6", "password": "\u5bc6\u78bc" - }, - "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f", - "title": "PoolSense" + } } } } diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index af75ace8bd4..31e249ec806 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -29,7 +29,6 @@ from .const import ( POWERWALL_API_CHANGED, POWERWALL_COORDINATOR, POWERWALL_HTTP_SESSION, - POWERWALL_LOGIN_FAILED_COUNT, UPDATE_INTERVAL, ) from .models import PowerwallBaseInfo, PowerwallData, PowerwallRuntimeData @@ -40,8 +39,6 @@ PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] _LOGGER = logging.getLogger(__name__) -MAX_LOGIN_FAILURES = 5 - API_CHANGED_ERROR_BODY = ( "It seems like your powerwall uses an unsupported version. " "Please update the software of your powerwall or if it is " @@ -68,29 +65,15 @@ class PowerwallDataManager: self.runtime_data = runtime_data self.power_wall = power_wall - @property - def login_failed_count(self) -> int: - """Return the current number of failed logins.""" - return self.runtime_data[POWERWALL_LOGIN_FAILED_COUNT] - @property def api_changed(self) -> int: """Return true if the api has changed out from under us.""" return self.runtime_data[POWERWALL_API_CHANGED] - def _increment_failed_logins(self) -> None: - self.runtime_data[POWERWALL_LOGIN_FAILED_COUNT] += 1 - - def _clear_failed_logins(self) -> None: - self.runtime_data[POWERWALL_LOGIN_FAILED_COUNT] = 0 - def _recreate_powerwall_login(self) -> None: """Recreate the login on auth failure.""" - http_session = self.runtime_data[POWERWALL_HTTP_SESSION] - http_session.close() - http_session = requests.Session() - self.runtime_data[POWERWALL_HTTP_SESSION] = http_session - self.power_wall = Powerwall(self.ip_address, http_session=http_session) + if self.power_wall.is_authenticated(): + self.power_wall.logout() self.power_wall.login(self.password or "") async def async_update_data(self) -> PowerwallData: @@ -121,17 +104,15 @@ class PowerwallDataManager: raise UpdateFailed("The powerwall api has changed") from err except AccessDeniedError as err: if attempt == 1: - self._increment_failed_logins() + # failed to authenticate => the credentials must be wrong raise ConfigEntryAuthFailed from err if self.password is None: raise ConfigEntryAuthFailed from err - raise UpdateFailed( - f"Login attempt {self.login_failed_count}/{MAX_LOGIN_FAILURES} failed, will retry: {err}" - ) from err + _LOGGER.debug("Access denied, trying to reauthenticate") + # there is still an attempt left to authenticate, so we continue in the loop except APIError as err: raise UpdateFailed(f"Updated failed due to {err}, will retry") from err else: - self._clear_failed_logins() return data raise RuntimeError("unreachable") @@ -174,7 +155,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: api_changed=False, base_info=base_info, http_session=http_session, - login_failed_count=0, coordinator=None, ) diff --git a/homeassistant/components/powerwall/const.py b/homeassistant/components/powerwall/const.py index b1791c4300e..9df710e2df4 100644 --- a/homeassistant/components/powerwall/const.py +++ b/homeassistant/components/powerwall/const.py @@ -7,7 +7,6 @@ POWERWALL_BASE_INFO: Final = "base_info" POWERWALL_COORDINATOR: Final = "coordinator" POWERWALL_API_CHANGED: Final = "api_changed" POWERWALL_HTTP_SESSION: Final = "http_session" -POWERWALL_LOGIN_FAILED_COUNT: Final = "login_failed_count" UPDATE_INTERVAL = 30 diff --git a/homeassistant/components/powerwall/models.py b/homeassistant/components/powerwall/models.py index 6f8ccb98459..522dc2806bf 100644 --- a/homeassistant/components/powerwall/models.py +++ b/homeassistant/components/powerwall/models.py @@ -45,7 +45,6 @@ class PowerwallRuntimeData(TypedDict): """Run time data for the powerwall.""" coordinator: DataUpdateCoordinator | None - login_failed_count: int base_info: PowerwallBaseInfo api_changed: bool http_session: Session diff --git a/homeassistant/components/powerwall/translations/es.json b/homeassistant/components/powerwall/translations/es.json index 5d82ecda8e5..c0f73c2ac99 100644 --- a/homeassistant/components/powerwall/translations/es.json +++ b/homeassistant/components/powerwall/translations/es.json @@ -12,6 +12,9 @@ }, "flow_title": "Powerwall de Tesla ({ip_address})", "step": { + "confirm_discovery": { + "description": "\u00bfQuieres configurar {name} ({ip_address})?" + }, "reauth_confim": { "data": { "password": "Contrase\u00f1a" diff --git a/homeassistant/components/powerwall/translations/nl.json b/homeassistant/components/powerwall/translations/nl.json index 007d4e9e86b..45172d177b9 100644 --- a/homeassistant/components/powerwall/translations/nl.json +++ b/homeassistant/components/powerwall/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/profiler/translations/nl.json b/homeassistant/components/profiler/translations/nl.json index 8b99a128bd3..8690611b1c9 100644 --- a/homeassistant/components/profiler/translations/nl.json +++ b/homeassistant/components/profiler/translations/nl.json @@ -5,7 +5,7 @@ }, "step": { "user": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/prosegur/translations/nl.json b/homeassistant/components/prosegur/translations/nl.json index d87556b0742..db84f7df172 100644 --- a/homeassistant/components/prosegur/translations/nl.json +++ b/homeassistant/components/prosegur/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/proxmoxve/__init__.py b/homeassistant/components/proxmoxve/__init__.py index d10e64772ae..1334a66cfa0 100644 --- a/homeassistant/components/proxmoxve/__init__.py +++ b/homeassistant/components/proxmoxve/__init__.py @@ -21,6 +21,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, @@ -178,9 +179,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: for component in PLATFORMS: await hass.async_create_task( - hass.helpers.discovery.async_load_platform( - component, DOMAIN, {"config": config}, config - ) + async_load_platform(hass, component, DOMAIN, {"config": config}, config) ) return True diff --git a/homeassistant/components/proxy/manifest.json b/homeassistant/components/proxy/manifest.json index ff3e610323f..d12f5ed92fd 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.1.0"], + "requirements": ["pillow==9.1.1"], "codeowners": [] } diff --git a/homeassistant/components/ps4/__init__.py b/homeassistant/components/ps4/__init__.py index e6c22552fc2..7e215060d73 100644 --- a/homeassistant/components/ps4/__init__.py +++ b/homeassistant/components/ps4/__init__.py @@ -116,7 +116,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Migrate Version 2 -> Version 3: Update identifier format. if version == 2: # Prevent changing entity_id. Updates entity registry. - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) for entity_id, e_entry in registry.entities.items(): if e_entry.config_entry_id == entry.entry_id: diff --git a/homeassistant/components/ps4/media_player.py b/homeassistant/components/ps4/media_player.py index ef6cc3c364b..1a1c93e210a 100644 --- a/homeassistant/components/ps4/media_player.py +++ b/homeassistant/components/ps4/media_player.py @@ -333,8 +333,8 @@ class PS4Device(MediaPlayerEntity): # If cannot get status on startup, assume info from registry. if status is None: _LOGGER.info("Assuming status from registry") - e_registry = await entity_registry.async_get_registry(self.hass) - d_registry = await device_registry.async_get_registry(self.hass) + e_registry = entity_registry.async_get(self.hass) + d_registry = device_registry.async_get(self.hass) for entity_id, entry in e_registry.entities.items(): if entry.config_entry_id == self._entry_id: self._unique_id = entry.unique_id diff --git a/homeassistant/components/ps4/translations/bg.json b/homeassistant/components/ps4/translations/bg.json index 8ed9242fc3e..ac2b20066f4 100644 --- a/homeassistant/components/ps4/translations/bg.json +++ b/homeassistant/components/ps4/translations/bg.json @@ -14,8 +14,7 @@ }, "step": { "creds": { - "description": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0438 \u0441\u0430 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u0434\u0430\u043d\u043d\u0438. \u041d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \"\u0417\u0430\u043f\u0430\u0437\u0432\u0430\u043d\u0435\" \u0438 \u0441\u043b\u0435\u0434 \u0442\u043e\u0432\u0430 \u0432 PS4 2nd Screen App, \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \"Refresh devices\" \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \"Home-Assistant\" \u0437\u0430 \u0434\u0430 \u043f\u0440\u043e\u0434\u044a\u043b\u0436\u0438\u0442\u0435.", - "title": "PlayStation 4" + "description": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0438 \u0441\u0430 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u0434\u0430\u043d\u043d\u0438. \u041d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \"\u0417\u0430\u043f\u0430\u0437\u0432\u0430\u043d\u0435\" \u0438 \u0441\u043b\u0435\u0434 \u0442\u043e\u0432\u0430 \u0432 PS4 2nd Screen App, \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \"Refresh devices\" \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \"Home-Assistant\" \u0437\u0430 \u0434\u0430 \u043f\u0440\u043e\u0434\u044a\u043b\u0436\u0438\u0442\u0435." }, "link": { "data": { @@ -23,17 +22,13 @@ "ip_address": "IP \u0430\u0434\u0440\u0435\u0441", "name": "\u0418\u043c\u0435", "region": "\u0420\u0435\u0433\u0438\u043e\u043d" - }, - "description": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0430\u0448\u0430\u0442\u0430 PlayStation 4 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f. \u0417\u0430 \u201ePIN\u201c \u043e\u0442\u0438\u0434\u0435\u0442\u0435 \u0432 \u201eSettings\u201c \u043d\u0430 \u0412\u0430\u0448\u0430\u0442\u0430 PlayStation 4 \u043a\u043e\u043d\u0437\u043e\u043b\u0430. \u0421\u043b\u0435\u0434 \u0442\u043e\u0432\u0430 \u043f\u0440\u0435\u043c\u0438\u043d\u0435\u0442\u0435 \u043a\u044a\u043c \u201eMobile App Connection Settings\u201c \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u201eAdd Device\u201c. \u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f PIN \u043a\u043e\u0434. \u041c\u043e\u043b\u044f, \u043f\u0440\u043e\u0447\u0435\u0442\u0435\u0442\u0435 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0442\u0430](https://www.home-assistant.io/components/ps4/) \u0437\u0430 \u0434\u043e\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b\u043d\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f.", - "title": "PlayStation 4" + } }, "mode": { "data": { "ip_address": "IP \u0430\u0434\u0440\u0435\u0441 (\u041e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, \u0430\u043a\u043e \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0442\u043e \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435).", "mode": "\u0420\u0435\u0436\u0438\u043c \u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0435" - }, - "description": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0440\u0435\u0436\u0438\u043c \u0437\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0435. \u041f\u043e\u043b\u0435\u0442\u043e IP \u0430\u0434\u0440\u0435\u0441 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u043e\u0441\u0442\u0430\u0432\u0438 \u043f\u0440\u0430\u0437\u043d\u043e, \u0430\u043a\u043e \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435, \u0442\u044a\u0439 \u043a\u0430\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0442\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0449\u0435 \u0431\u044a\u0434\u0430\u0442 \u043e\u0442\u043a\u0440\u0438\u0442\u0438.", - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/ca.json b/homeassistant/components/ps4/translations/ca.json index 6486c154360..37cfabecf05 100644 --- a/homeassistant/components/ps4/translations/ca.json +++ b/homeassistant/components/ps4/translations/ca.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Credencials necess\u00e0ries. Prem 'Envia' i, a continuaci\u00f3, a la segona pantalla de l'aplicaci\u00f3 de la PS4, actualitza els dispositius i selecciona 'Home-Assistant' per continuar.", - "title": "PlayStation 4" + "description": "Credencials necess\u00e0ries. Prem 'Envia' i, a continuaci\u00f3, a la segona pantalla de l'aplicaci\u00f3 de la PS4, actualitza els dispositius i selecciona 'Home-Assistant' per continuar." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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/cs.json b/homeassistant/components/ps4/translations/cs.json index 0ef5eb5afee..e4a256e7522 100644 --- a/homeassistant/components/ps4/translations/cs.json +++ b/homeassistant/components/ps4/translations/cs.json @@ -13,26 +13,19 @@ "no_ipaddress": "Zadejte IP adresu PlayStation 4, kter\u00fd chcete konfigurovat." }, "step": { - "creds": { - "title": "PlayStation 4" - }, "link": { "data": { "code": "PIN k\u00f3d", "ip_address": "IP adresa", "name": "Jm\u00e9no", "region": "Region" - }, - "description": "Zadejte sv\u00e9 informace o PlayStation 4. \"PIN\" naleznete v \"Nastaven\u00ed\" sv\u00e9 konzole PlayStation 4. Pot\u00e9 p\u0159ejd\u011bte do \"Nastaven\u00ed p\u0159ipojen\u00ed mobiln\u00ed aplikace\" a vyberte \"P\u0159idat za\u0159\u00edzen\u00ed\". Zadejte PIN. Dal\u0161\u00ed informace naleznete v [dokumentaci](https://www.home-assistant.io/components/ps4/).", - "title": "PlayStation 4" + } }, "mode": { "data": { "ip_address": "IP adresa (Pokud pou\u017e\u00edv\u00e1te automatick\u00e9 zji\u0161\u0165ov\u00e1n\u00ed, ponechte pr\u00e1zdn\u00e9.)", "mode": "Re\u017eim nastaven\u00ed" - }, - "description": "Vyberte re\u017eim pro konfiguraci. Pole IP adresa m\u016f\u017ee b\u00fdt ponech\u00e1no pr\u00e1zdn\u00e9, pokud vyberete \"automatick\u00e9 zji\u0161\u0165ov\u00e1n\u00ed\", proto\u017ee za\u0159\u00edzen\u00ed budou automaticky objevena.", - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/da.json b/homeassistant/components/ps4/translations/da.json index 747e2d07146..2f95f6fa485 100644 --- a/homeassistant/components/ps4/translations/da.json +++ b/homeassistant/components/ps4/translations/da.json @@ -13,8 +13,7 @@ }, "step": { "creds": { - "description": "Der kr\u00e6ves legitimationsoplysninger. Tryk p\u00e5 'Indsend' og derefter i PS4 2. sk\u00e6rm-app, opdater enheder, og v\u00e6lg 'Home Assistant'-enhed for at forts\u00e6tte.", - "title": "PlayStation 4" + "description": "Der kr\u00e6ves legitimationsoplysninger. Tryk p\u00e5 'Indsend' og derefter i PS4 2. sk\u00e6rm-app, opdater enheder, og v\u00e6lg 'Home Assistant'-enhed for at forts\u00e6tte." }, "link": { "data": { @@ -22,17 +21,13 @@ "ip_address": "IP-adresse", "name": "Navn", "region": "Omr\u00e5de" - }, - "description": "Indtast dine PlayStation 4-oplysninger. For 'PIN' skal du navigere til 'Indstillinger' p\u00e5 din PlayStation 4-konsol. Naviger derefter til 'Mobile App Connection Settings' og v\u00e6lg 'Add Device'. Indtast den pinkode, der vises. Se [dokumentation](https://www.home-assistant.io/components/ps4/) for yderligere oplysninger.", - "title": "PlayStation 4" + } }, "mode": { "data": { "ip_address": "IP-adresse (lad det v\u00e6re tomt, hvis du bruger automatisk registrering).", "mode": "Konfigurationstilstand" - }, - "description": "V\u00e6lg tilstand for konfiguration. IP-adressefeltet kan v\u00e6re tomt, hvis du v\u00e6lger automatisk registrering, da enheder automatisk bliver fundet.", - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/de.json b/homeassistant/components/ps4/translations/de.json index 22a50cdf3e3..8a5d700f2fc 100644 --- a/homeassistant/components/ps4/translations/de.json +++ b/homeassistant/components/ps4/translations/de.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Anmeldeinformationen ben\u00f6tigt. Klicke auf \"Senden\" und dann in der PS4 Second Screen App, aktualisiere die Ger\u00e4te und w\u00e4hle das \"Home-Assistant\"-Ger\u00e4t aus, um fortzufahren.", - "title": "PlayStation 4" + "description": "Anmeldeinformationen ben\u00f6tigt. Klicke auf \"Senden\" und dann in der PS4 2nd Screen App, aktualisiere die Ger\u00e4te und w\u00e4hle das \"Home-Assistant\"-Ger\u00e4t aus, um fortzufahren." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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 f3899b682f3..7b5350a0418 100644 --- a/homeassistant/components/ps4/translations/el.json +++ b/homeassistant/components/ps4/translations/el.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "\u0391\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1. \u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \"\u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae\" \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae PS4 2nd Screen App, \u03b1\u03bd\u03b1\u03bd\u03b5\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \"Home-Assistant\" \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5.", - "title": "PlayStation 4" + "description": "\u0391\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1. \u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \"\u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae\" \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae PS4 2nd Screen App, \u03b1\u03bd\u03b1\u03bd\u03b5\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \"Home-Assistant\" \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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 1c8640efe2e..e8a0c58a566 100644 --- a/homeassistant/components/ps4/translations/en.json +++ b/homeassistant/components/ps4/translations/en.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Credentials needed. Press 'Submit' and then in the PS4 2nd Screen App, refresh devices and select the 'Home-Assistant' device to continue.", - "title": "PlayStation 4" + "description": "Credentials needed. Press 'Submit' and then in the PS4 2nd Screen App, refresh devices and select the 'Home-Assistant' device to continue." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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/es-419.json b/homeassistant/components/ps4/translations/es-419.json index 220183775f4..c5d8fb80a54 100644 --- a/homeassistant/components/ps4/translations/es-419.json +++ b/homeassistant/components/ps4/translations/es-419.json @@ -13,8 +13,7 @@ }, "step": { "creds": { - "description": "Credenciales necesarias. Presione 'Enviar' y luego en la aplicaci\u00f3n de la segunda pantalla de PS4, actualice los dispositivos y seleccione el dispositivo 'Home-Assistant' para continuar.", - "title": "Playstation 4" + "description": "Credenciales necesarias. Presione 'Enviar' y luego en la aplicaci\u00f3n de la segunda pantalla de PS4, actualice los dispositivos y seleccione el dispositivo 'Home-Assistant' para continuar." }, "link": { "data": { @@ -22,17 +21,13 @@ "ip_address": "Direcci\u00f3n IP", "name": "Nombre", "region": "Regi\u00f3n" - }, - "description": "Ingresa tu informaci\u00f3n de PlayStation 4. Para 'PIN', navegue hasta 'Configuraci\u00f3n' en su consola PlayStation 4. Luego navegue a 'Configuraci\u00f3n de conexi\u00f3n de la aplicaci\u00f3n m\u00f3vil' y seleccione 'Agregar dispositivo'. Ingrese el PIN que se muestra.", - "title": "Playstation 4" + } }, "mode": { "data": { "ip_address": "Direcci\u00f3n IP (dejar en blanco si se utiliza el descubrimiento autom\u00e1tico).", "mode": "Modo de configuraci\u00f3n" - }, - "description": "Seleccione el modo para la configuraci\u00f3n. El campo Direcci\u00f3n IP puede dejarse en blanco si selecciona Descubrimiento autom\u00e1tico, ya que los dispositivos se descubrir\u00e1n autom\u00e1ticamente.", - "title": "Playstation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/es.json b/homeassistant/components/ps4/translations/es.json index f65a27c61b6..a2c6e6fd1f4 100644 --- a/homeassistant/components/ps4/translations/es.json +++ b/homeassistant/components/ps4/translations/es.json @@ -15,26 +15,21 @@ }, "step": { "creds": { - "description": "Credenciales necesarias. Pulsa 'Enviar' y, a continuaci\u00f3n, en la app de segunda pantalla de PS4, actualiza la lista de dispositivos y selecciona el dispositivo 'Home-Assistant' para continuar.", - "title": "PlayStation 4" + "description": "Credenciales necesarias. Pulsa 'Enviar' y, a continuaci\u00f3n, en la app de segunda pantalla de PS4, actualiza la lista de dispositivos y selecciona el dispositivo 'Home-Assistant' para continuar." }, "link": { "data": { - "code": "PIN", + "code": "C\u00f3digo PIN", "ip_address": "Direcci\u00f3n IP", "name": "Nombre", "region": "Regi\u00f3n" - }, - "description": "Introduce la informaci\u00f3n de tu PlayStation 4. Para el 'PIN', ve a los 'Ajustes' en tu PlayStation 4. Despu\u00e9s dir\u00edgete hasta 'Ajustes de conexi\u00f3n de la aplicaci\u00f3n para m\u00f3viles' y selecciona 'A\u00f1adir dispositivo'. Introduce el PIN mostrado. Consulta la [documentaci\u00f3n](https://www.home-assistant.io/components/ps4/) para m\u00e1s informaci\u00f3n.", - "title": "PlayStation 4" + } }, "mode": { "data": { "ip_address": "Direcci\u00f3n IP (d\u00e9jalo en blanco si usas la detecci\u00f3n autom\u00e1tica).", "mode": "Modo configuraci\u00f3n" - }, - "description": "Selecciona el modo de configuraci\u00f3n. El campo de direcci\u00f3n IP puede dejarse en blanco si se selecciona la detecci\u00f3n autom\u00e1tica, ya que los dispositivos se detectar\u00e1n autom\u00e1ticamente.", - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/et.json b/homeassistant/components/ps4/translations/et.json index 83ef7a6f14c..071d7813976 100644 --- a/homeassistant/components/ps4/translations/et.json +++ b/homeassistant/components/ps4/translations/et.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Vaja on volitusi. Vajuta nuppu Esita ja seej\u00e4rel v\u00e4rskenda PS4 2nd Screen rakenduses seadmeid ning vali j\u00e4tkamiseks seade \u201eHome-Assistant\u201d.", - "title": "" + "description": "Vaja on volitusi. Vajuta nuppu Esita ja seej\u00e4rel v\u00e4rskenda PS4 2nd Screen rakenduses seadmeid ning vali j\u00e4tkamiseks seade \u201eHome-Assistant\u201d." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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": "" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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/fi.json b/homeassistant/components/ps4/translations/fi.json index f61b9f0c162..8b179c48e4b 100644 --- a/homeassistant/components/ps4/translations/fi.json +++ b/homeassistant/components/ps4/translations/fi.json @@ -1,24 +1,19 @@ { "config": { "step": { - "creds": { - "title": "Playstation 4" - }, "link": { "data": { "code": "PIN", "ip_address": "IP-osoite", "name": "Nimi", "region": "Alue" - }, - "title": "Playstation 4" + } }, "mode": { "data": { "ip_address": "IP-osoite (j\u00e4t\u00e4 tyhj\u00e4ksi, jos k\u00e4yt\u00e4t automaattista etsint\u00e4\u00e4).", "mode": "Konfigurointitila" - }, - "title": "Playstation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/fr.json b/homeassistant/components/ps4/translations/fr.json index 8bc2575d246..73e2e18e22a 100644 --- a/homeassistant/components/ps4/translations/fr.json +++ b/homeassistant/components/ps4/translations/fr.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Informations d'identification requises. Appuyez sur \u00ab\u00a0Envoyer\u00a0\u00bb, puis dans l'application PS4 Second Screen, actualisez les appareils et s\u00e9lectionnez l'appareil \u00ab\u00a0Home Assistant\u00a0\u00bb pour continuer.", - "title": "PlayStation 4" + "description": "Informations d'identification requises. Appuyez sur \u00ab\u00a0Envoyer\u00a0\u00bb, puis dans l'application PS4 Second Screen, actualisez les appareils et s\u00e9lectionnez l'appareil \u00ab\u00a0Home Assistant\u00a0\u00bb pour continuer." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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/he.json b/homeassistant/components/ps4/translations/he.json index 421f869d8c5..0aaba028b7e 100644 --- a/homeassistant/components/ps4/translations/he.json +++ b/homeassistant/components/ps4/translations/he.json @@ -8,20 +8,13 @@ "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" }, "step": { - "creds": { - "title": "\u05e4\u05dc\u05d9\u05d9\u05e1\u05d8\u05d9\u05d9\u05e9\u05df 4" - }, "link": { "data": { "code": "\u05e7\u05d5\u05d3 PIN", "ip_address": "\u05db\u05ea\u05d5\u05d1\u05ea IP", "name": "\u05e9\u05dd", "region": "\u05d0\u05d9\u05d6\u05d5\u05e8" - }, - "title": "\u05e4\u05dc\u05d9\u05d9\u05e1\u05d8\u05d9\u05d9\u05e9\u05df 4" - }, - "mode": { - "title": "\u05e4\u05dc\u05d9\u05d9\u05e1\u05d8\u05d9\u05d9\u05e9\u05df 4" + } } } } diff --git a/homeassistant/components/ps4/translations/hu.json b/homeassistant/components/ps4/translations/hu.json index 24da7fc15f5..ab48f3177e4 100644 --- a/homeassistant/components/ps4/translations/hu.json +++ b/homeassistant/components/ps4/translations/hu.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "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" + "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." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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 beb15fb5c4c..67c25538464 100644 --- a/homeassistant/components/ps4/translations/id.json +++ b/homeassistant/components/ps4/translations/id.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Kredensial diperlukan. Tekan 'Kirim' dan kemudian di Aplikasi Layar ke-2 PS4, segarkan perangkat dan pilih perangkat 'Home-Assistant' untuk melanjutkan.", - "title": "PlayStation 4" + "description": "Kredensial diperlukan. Tekan 'Kirim' dan kemudian di Aplikasi Layar ke-2 PS4, segarkan perangkat dan pilih perangkat 'Home-Assistant' untuk melanjutkan." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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 3faca963317..aec6d943d4c 100644 --- a/homeassistant/components/ps4/translations/it.json +++ b/homeassistant/components/ps4/translations/it.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Credenziali necessarie. Premi 'Invia' e poi, nella seconda schermata della App PS4, aggiorna i dispositivi e seleziona il dispositivo 'Home-Assistant' per continuare.", - "title": "PlayStation 4" + "description": "Credenziali necessarie. Premi 'Invia' e poi, nell'applicazione PS4 Second Screen, aggiorna i dispositivi e seleziona il dispositivo 'Home-Assistant' per continuare." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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 92be62f7229..47d32431b54 100644 --- a/homeassistant/components/ps4/translations/ja.json +++ b/homeassistant/components/ps4/translations/ja.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "\u8a8d\u8a3c\u60c5\u5831\u304c\u5fc5\u8981\u3067\u3059\u3002'\u9001\u4fe1(submit)' \u3092\u62bc\u3057\u3066\u3001PS4\u306e2nd Screen App\u3067\u30c7\u30d0\u30a4\u30b9\u3092\u66f4\u65b0\u3057\u3001'Home-Assistant' \u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3057\u3066\u7d9a\u884c\u3057\u307e\u3059\u3002", - "title": "Play Station 4" + "description": "\u8a8d\u8a3c\u60c5\u5831\u304c\u5fc5\u8981\u3067\u3059\u3002'\u9001\u4fe1(submit)' \u3092\u62bc\u3057\u3066\u3001PS4\u306e2nd Screen App\u3067\u30c7\u30d0\u30a4\u30b9\u3092\u66f4\u65b0\u3057\u3001'Home-Assistant' \u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3057\u3066\u7d9a\u884c\u3057\u307e\u3059\u3002" }, "link": { "data": { @@ -27,20 +26,16 @@ }, "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" + } }, "mode": { "data": { - "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)", + "ip_address": "IP\u30a2\u30c9\u30ec\u30b9(\u81ea\u52d5\u691c\u51fa\u3092\u4f7f\u7528\u3059\u308b\u5834\u5408\u306f\u7a7a\u767d\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/ko.json b/homeassistant/components/ps4/translations/ko.json index 129927a9bab..b720db8241b 100644 --- a/homeassistant/components/ps4/translations/ko.json +++ b/homeassistant/components/ps4/translations/ko.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "\uc790\uaca9 \uc99d\uba85\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. '\ud655\uc778'\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c PS4 \uc138\ucee8\ub4dc \uc2a4\ud06c\ub9b0 \uc571\uc5d0\uc11c \uae30\uae30\ub97c \uc0c8\ub85c\uace0\uce68 \ud558\uace0 'Home-Assistant' \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", - "title": "PlayStation 4" + "description": "\uc790\uaca9 \uc99d\uba85\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. '\ud655\uc778'\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c PS4 \uc138\ucee8\ub4dc \uc2a4\ud06c\ub9b0 \uc571\uc5d0\uc11c \uae30\uae30\ub97c \uc0c8\ub85c\uace0\uce68 \ud558\uace0 'Home-Assistant' \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694." }, "link": { "data": { @@ -24,17 +23,13 @@ "ip_address": "IP \uc8fc\uc18c", "name": "\uc774\ub984", "region": "\uc9c0\uc5ed" - }, - "description": "PlayStation 4 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. PIN \ucf54\ub4dc\ub97c \ud655\uc778\ud558\ub824\uba74, PlayStation 4 \ucf58\uc194\uc5d0\uc11c '\uc124\uc815'\uc73c\ub85c \uc774\ub3d9\ud55c \ub4a4 '\ubaa8\ubc14\uc77c \uc571 \uc811\uc18d \uc124\uc815'\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec '\uae30\uae30 \ub4f1\ub85d\ud558\uae30'\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc(\uc744)\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \ucd94\uac00 \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c](https://www.home-assistant.io/components/ps4/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "title": "PlayStation 4" + } }, "mode": { "data": { "ip_address": "IP \uc8fc\uc18c (\uc790\ub3d9 \uac80\uc0c9\uc744 \uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0 \ube44\uc6cc\ub450\uc138\uc694)", "mode": "\uad6c\uc131 \ubaa8\ub4dc" - }, - "description": "\uad6c\uc131\ud560 \ubaa8\ub4dc\ub97c \uc120\ud0dd\ud569\ub2c8\ub2e4. \uc790\ub3d9 \uac80\uc0c9\uc744 \uc120\ud0dd\ud558\uba74 \uae30\uae30\uac00 \uc790\ub3d9\uc73c\ub85c \uac80\uc0c9\ub418\ubbc0\ub85c IP \uc8fc\uc18c \ud544\ub4dc\ub294 \ube44\uc6cc\ub450\uc154\ub3c4 \ub429\ub2c8\ub2e4.", - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/lb.json b/homeassistant/components/ps4/translations/lb.json index 444de75469c..b093b2bdd8e 100644 --- a/homeassistant/components/ps4/translations/lb.json +++ b/homeassistant/components/ps4/translations/lb.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Umeldungsinformatioun sinn n\u00e9ideg. Dr\u00e9ckt op 'Ofsch\u00e9cken' , dann an der PS4 App, 2ten Ecran, erneiert Apparater an wielt den Home-Assistant Apparat aus fir weider ze fueren.", - "title": "PlayStation 4" + "description": "Umeldungsinformatioun sinn n\u00e9ideg. Dr\u00e9ckt op 'Ofsch\u00e9cken' , dann an der PS4 App, 2ten Ecran, erneiert Apparater an wielt den Home-Assistant Apparat aus fir weider ze fueren." }, "link": { "data": { @@ -24,17 +23,13 @@ "ip_address": "IP Adresse", "name": "Numm", "region": "Regioun" - }, - "description": "G\u00ebff deng Playstation 4 Informatiounen an. Fir 'PIN Code', g\u00e9i an d'Astellunge vun der Playstation 4 Konsole. Dann op 'Mobile App Verbindungs Astellungen' a wiel \"Apparat dob\u00e4isetzen' aus. G\u00ebff de PIN Code an deen ugewise g\u00ebtt. Lies [Dokumentatioun](https://www.home-assistant.io/components/ps4/) fir w\u00e9ider Informatiounen.", - "title": "PlayStation 4" + } }, "mode": { "data": { "ip_address": "IP Address (Eidel loossen falls Auto Discovery benotzt g\u00ebtt)", "mode": "Konfiguratioun's Modus" - }, - "description": "Konfiguratioun's Modus auswielen. D'Feld IP Adress kann eidel bl\u00e9iwen wann Auto Discovery benotzt g\u00ebtt, well d'Apparaten automatesch entdeckt ginn.", - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/nl.json b/homeassistant/components/ps4/translations/nl.json index 1a6ddd26f7f..c6e16281c19 100644 --- a/homeassistant/components/ps4/translations/nl.json +++ b/homeassistant/components/ps4/translations/nl.json @@ -10,26 +10,23 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "credential_timeout": "Time-out van inlog service. Druk op Submit om opnieuw te starten.", - "login_failed": "Kan Playstation 4 niet koppelen. Controleer of de PIN-code correct is.", + "login_failed": "Kan Playstation 4 niet koppelen. Controleer of de Pincode correct is.", "no_ipaddress": "Voer het IP-adres in van de PlayStation 4 die u wilt configureren." }, "step": { "creds": { - "description": "Aanmeldingsgegevens zijn nodig. Druk op 'Verzenden' en vervolgens in de PS4-app voor het 2e scherm, vernieuw apparaten en selecteer Home Assistant om door te gaan.", - "title": "PlayStation 4" + "description": "Aanmeldingsgegevens zijn nodig. Druk op 'Verzenden' en vervolgens in de PS4-app voor het 2e scherm, vernieuw apparaten en selecteer Home Assistant om door te gaan." }, "link": { "data": { - "code": "PIN-code", + "code": "Pincode", "ip_address": "IP-adres", "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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/nn.json b/homeassistant/components/ps4/translations/nn.json index a9b44db3731..31a3808910a 100644 --- a/homeassistant/components/ps4/translations/nn.json +++ b/homeassistant/components/ps4/translations/nn.json @@ -5,18 +5,11 @@ "port_997_bind_error": "Kunne ikkje binde til port 997. Sj\u00e5 [dokumentasjonen] (https://www.home-assistant.io/components/ps4/) for ytterlegare informasjon." }, "step": { - "creds": { - "title": "Playstation 4" - }, "link": { "data": { "code": "PIN", "name": "Namn" - }, - "title": "Playstation 4" - }, - "mode": { - "title": "Playstation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/no.json b/homeassistant/components/ps4/translations/no.json index 69332862a33..f2d61d0955f 100644 --- a/homeassistant/components/ps4/translations/no.json +++ b/homeassistant/components/ps4/translations/no.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Legitimasjon n\u00f8dvendig. Trykk 'Send' og deretter i PS4-ens andre skjerm app, kan du oppdatere enheter, og velg 'Home-Assistant' enheten for \u00e5 fortsette.", - "title": "" + "description": "Legitimasjon n\u00f8dvendig. Trykk 'Send' og deretter i PS4-ens andre skjerm app, kan du oppdatere enheter, og velg 'Home-Assistant' enheten for \u00e5 fortsette." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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": "" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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 af4d36b9bf2..0ebb2fa9267 100644 --- a/homeassistant/components/ps4/translations/pl.json +++ b/homeassistant/components/ps4/translations/pl.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Wymagane s\u0105 po\u015bwiadczenia. Naci\u015bnij przycisk \"Zatwierd\u017a\", a nast\u0119pnie w aplikacji PS4 Second Screen, od\u015bwie\u017c urz\u0105dzenia i wybierz urz\u0105dzenie 'Home-Assistant', aby kontynuowa\u0107.", - "title": "PlayStation 4" + "description": "Wymagane s\u0105 po\u015bwiadczenia. Naci\u015bnij przycisk \"Zatwierd\u017a\", a nast\u0119pnie w aplikacji PS4 Second Screen, od\u015bwie\u017c urz\u0105dzenia i wybierz urz\u0105dzenie 'Home-Assistant', aby kontynuowa\u0107." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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 12d54264f78..f7731cc7ee8 100644 --- a/homeassistant/components/ps4/translations/pt-BR.json +++ b/homeassistant/components/ps4/translations/pt-BR.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Credenciais necess\u00e1rias. Pressione 'Enviar' e, em seguida, no PS4, na Tela do segundo aplicativo, atualize os dispositivos e selecione o dispositivo 'Home-Assistant' para continuar.", - "title": "Playstation 4" + "description": "Credenciais necess\u00e1rias. Pressione 'Enviar' e, em seguida, no PS4, na Tela do segundo aplicativo, atualize os dispositivos e selecione o dispositivo 'Home-Assistant' para continuar." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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/pt.json b/homeassistant/components/ps4/translations/pt.json index 5956937ac2f..cf428838b0b 100644 --- a/homeassistant/components/ps4/translations/pt.json +++ b/homeassistant/components/ps4/translations/pt.json @@ -13,23 +13,18 @@ "login_failed": "Falha ao emparelhar com a PlayStation 4. Verifique se o PIN est\u00e1 correto." }, "step": { - "creds": { - "title": "PlayStation 4" - }, "link": { "data": { "code": "PIN", "ip_address": "Endere\u00e7o de IP", "name": "Nome", "region": "Regi\u00e3o" - }, - "title": "PlayStation 4" + } }, "mode": { "data": { "mode": "Modo de configura\u00e7\u00e3o" - }, - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/ru.json b/homeassistant/components/ps4/translations/ru.json index 646346095b0..2b50ad57490 100644 --- a/homeassistant/components/ps4/translations/ru.json +++ b/homeassistant/components/ps4/translations/ru.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 'PS4 Second Screen' \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0441\u043f\u0438\u0441\u043e\u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0438 \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e 'Home-Assistant'.", - "title": "PlayStation 4" + "description": "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 'PS4 Second Screen' \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0441\u043f\u0438\u0441\u043e\u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0438 \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e 'Home-Assistant'." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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/sl.json b/homeassistant/components/ps4/translations/sl.json index 1d5752eb326..5e056d76687 100644 --- a/homeassistant/components/ps4/translations/sl.json +++ b/homeassistant/components/ps4/translations/sl.json @@ -13,8 +13,7 @@ }, "step": { "creds": { - "description": "Potrebne so poverilnice. Pritisnite 'Po\u0161lji' in nato v aplikaciji PS4 2nd Screen App, osve\u017eite naprave in izberite napravo 'Home-Assistant' za nadaljevanje.", - "title": "PlayStation 4" + "description": "Potrebne so poverilnice. Pritisnite 'Po\u0161lji' in nato v aplikaciji PS4 2nd Screen App, osve\u017eite naprave in izberite napravo 'Home-Assistant' za nadaljevanje." }, "link": { "data": { @@ -22,17 +21,13 @@ "ip_address": "IP naslov", "name": "Ime", "region": "Regija" - }, - "description": "Vnesite va\u0161e PlayStation 4 podatke. Za 'PIN' pojdite na 'Nastavitve' na konzoli PlayStation 4. Nato se pomaknite do mo\u017enosti \u00bbNastavitve povezave z mobilno aplikacijo\u00ab in izberite \u00bbDodaj napravo\u00ab. Vnesite prikazano kodo PIN. Dodatne informacije najdete v [dokumentaciji] (https://www.home-assistant.io/components/ps4/).", - "title": "PlayStation 4" + } }, "mode": { "data": { "ip_address": "Naslov IP (Pustite prazno, \u010de uporabljate samodejno odkrivanje).", "mode": "Na\u010din konfiguracije" - }, - "description": "Izberite na\u010din za konfiguracijo. IP-Naslov, polje lahko pustite prazno, \u010de izberete samodejno odkrivanje, saj bodo naprave samodejno odkrite.", - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/sv.json b/homeassistant/components/ps4/translations/sv.json index ec117fb9b98..7435865c5d0 100644 --- a/homeassistant/components/ps4/translations/sv.json +++ b/homeassistant/components/ps4/translations/sv.json @@ -13,8 +13,7 @@ }, "step": { "creds": { - "description": "Autentiseringsuppgifter beh\u00f6vs. Tryck p\u00e5 'Skicka' och sedan uppdatera enheter i appen \"PS4 Second Screen\" p\u00e5 din mobiltelefon eller surfplatta och v\u00e4lj 'Home Assistent' enheten att forts\u00e4tta.", - "title": "PlayStation 4" + "description": "Autentiseringsuppgifter beh\u00f6vs. Tryck p\u00e5 'Skicka' och sedan uppdatera enheter i appen \"PS4 Second Screen\" p\u00e5 din mobiltelefon eller surfplatta och v\u00e4lj 'Home Assistent' enheten att forts\u00e4tta." }, "link": { "data": { @@ -22,17 +21,13 @@ "ip_address": "IP-adress", "name": "Namn", "region": "Region" - }, - "description": "Ange din PlayStation 4 information. F\u00f6r 'PIN', navigera till 'Inst\u00e4llningar' p\u00e5 din PlayStation 4 konsol. Navigera sedan till \"Inst\u00e4llningar f\u00f6r mobilappanslutning\" och v\u00e4lj \"L\u00e4gg till enhet\". Ange PIN-koden som visas.", - "title": "PlayStation 4" + } }, "mode": { "data": { "ip_address": "IP-adress (l\u00e4mna tom om du anv\u00e4nder automatisk uppt\u00e4ckt).", "mode": "Konfigureringsl\u00e4ge" - }, - "description": "V\u00e4lj l\u00e4ge f\u00f6r konfigurering. F\u00e4ltet IP-adress kan l\u00e4mnas tomt om du v\u00e4ljer Automatisk uppt\u00e4ckt, eftersom enheter d\u00e5 kommer att identifieras automatiskt.", - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/th.json b/homeassistant/components/ps4/translations/th.json index 2ffe2fb805a..bf910763725 100644 --- a/homeassistant/components/ps4/translations/th.json +++ b/homeassistant/components/ps4/translations/th.json @@ -1,20 +1,13 @@ { "config": { "step": { - "creds": { - "title": "PlayStation 4" - }, "link": { "data": { "code": "PIN", "ip_address": "\u0e17\u0e35\u0e48\u0e2d\u0e22\u0e39\u0e48 IP", "name": "\u0e0a\u0e37\u0e48\u0e2d", "region": "\u0e20\u0e39\u0e21\u0e34\u0e20\u0e32\u0e04" - }, - "title": "PlayStation 4" - }, - "mode": { - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/tr.json b/homeassistant/components/ps4/translations/tr.json index a2e8149db7d..09cd66d6826 100644 --- a/homeassistant/components/ps4/translations/tr.json +++ b/homeassistant/components/ps4/translations/tr.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "Kimlik bilgileri gerekli. \"G\u00f6nder\"e bas\u0131n ve ard\u0131ndan PS4 2. Ekran Uygulamas\u0131nda cihazlar\u0131 yenileyin ve devam etmek i\u00e7in \"Home-Asistan\" cihaz\u0131n\u0131 se\u00e7in.", - "title": "PlayStation 4" + "description": "Kimlik bilgileri gerekli. \"G\u00f6nder\"e bas\u0131n ve ard\u0131ndan PS4 2. Ekran Uygulamas\u0131nda cihazlar\u0131 yenileyin ve devam etmek i\u00e7in \"Home-Asistan\" cihaz\u0131n\u0131 se\u00e7in." }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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/uk.json b/homeassistant/components/ps4/translations/uk.json index 696a46bf8d7..bf6716649c0 100644 --- a/homeassistant/components/ps4/translations/uk.json +++ b/homeassistant/components/ps4/translations/uk.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **, \u0430 \u043f\u043e\u0442\u0456\u043c \u0432 \u0434\u043e\u0434\u0430\u0442\u043a\u0443 'PS4 Second Screen' \u043e\u043d\u043e\u0432\u0456\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u0456 \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 'Home-Assistant'.", - "title": "PlayStation 4" + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **, \u0430 \u043f\u043e\u0442\u0456\u043c \u0432 \u0434\u043e\u0434\u0430\u0442\u043a\u0443 'PS4 Second Screen' \u043e\u043d\u043e\u0432\u0456\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u0456 \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 'Home-Assistant'." }, "link": { "data": { @@ -24,17 +23,13 @@ "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", "name": "\u041d\u0430\u0437\u0432\u0430", "region": "\u0420\u0435\u0433\u0456\u043e\u043d" - }, - "description": "\u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f PIN-\u043a\u043e\u0434\u0443 \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0434\u043e \u043f\u0443\u043d\u043a\u0442\u0443 ** \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f ** \u043d\u0430 \u043a\u043e\u043d\u0441\u043e\u043b\u0456 PlayStation 4. \u041f\u043e\u0442\u0456\u043c \u0432\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 ** \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u043e\u0433\u043e \u0434\u043e\u0434\u0430\u0442\u043a\u0430 ** \u0456 \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044c ** \u0414\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 ** . \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438](https://www.home-assistant.io/components/ps4/) \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0431\u0456\u043b\u044c\u0448 \u0434\u043e\u043a\u043b\u0430\u0434\u043d\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457.", - "title": "PlayStation 4" + } }, "mode": { "data": { "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430 (\u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c \u043f\u0440\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u0456 \u0440\u0435\u0436\u0438\u043c\u0443 \u0430\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f)", "mode": "\u0420\u0435\u0436\u0438\u043c" - }, - "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f. \u041f\u043e\u043b\u0435 'IP-\u0430\u0434\u0440\u0435\u0441\u0430' \u043c\u043e\u0436\u043d\u0430 \u0437\u0430\u043b\u0438\u0448\u0438\u0442\u0438 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c, \u044f\u043a\u0449\u043e \u0432\u0438\u0431\u0440\u0430\u043d\u043e 'Auto Discovery', \u043e\u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0431\u0443\u0434\u0443\u0442\u044c \u0434\u043e\u0434\u0430\u043d\u0456 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e.", - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/zh-Hans.json b/homeassistant/components/ps4/translations/zh-Hans.json index 3c240d96131..69f0694f853 100644 --- a/homeassistant/components/ps4/translations/zh-Hans.json +++ b/homeassistant/components/ps4/translations/zh-Hans.json @@ -12,8 +12,7 @@ }, "step": { "creds": { - "description": "\u9700\u8981\u51ed\u636e\u3002\u8bf7\u70b9\u51fb\u201c\u63d0\u4ea4\u201d\u7136\u540e\u5728 PS4 \u7b2c\u4e8c\u5c4f\u5e55\u5e94\u7528\u7a0b\u5e8f\u4e2d\u5237\u65b0\u8bbe\u5907\u5e76\u9009\u62e9\u201cHome-Assistant\u201d\u8bbe\u5907\u4ee5\u7ee7\u7eed\u3002", - "title": "PlayStation 4" + "description": "\u9700\u8981\u51ed\u636e\u3002\u8bf7\u70b9\u51fb\u201c\u63d0\u4ea4\u201d\u7136\u540e\u5728 PS4 \u7b2c\u4e8c\u5c4f\u5e55\u5e94\u7528\u7a0b\u5e8f\u4e2d\u5237\u65b0\u8bbe\u5907\u5e76\u9009\u62e9\u201cHome-Assistant\u201d\u8bbe\u5907\u4ee5\u7ee7\u7eed\u3002" }, "link": { "data": { @@ -21,15 +20,12 @@ "ip_address": "IP \u5730\u5740", "name": "\u540d\u79f0", "region": "\u5730\u533a" - }, - "description": "\u8f93\u5165\u60a8\u7684 PlayStation 4 \u4fe1\u606f\u3002\u5bf9\u4e8e \"PIN\", \u8bf7\u5bfc\u822a\u5230 PlayStation 4 \u63a7\u5236\u53f0\u4e0a\u7684 \"\u8bbe\u7f6e\"\u3002\u7136\u540e\u5bfc\u822a\u5230 \"\u79fb\u52a8\u5e94\u7528\u8fde\u63a5\u8bbe\u7f6e\", \u7136\u540e\u9009\u62e9 \"\u6dfb\u52a0\u8bbe\u5907\"\u3002\u8f93\u5165\u663e\u793a\u7684 PIN\u3002", - "title": "PlayStation 4" + } }, "mode": { "data": { "mode": "\u8bbe\u7f6e\u6a21\u5f0f" - }, - "title": "PlayStation 4" + } } } } diff --git a/homeassistant/components/ps4/translations/zh-Hant.json b/homeassistant/components/ps4/translations/zh-Hant.json index ae7e86377d8..231e3ecc5e0 100644 --- a/homeassistant/components/ps4/translations/zh-Hant.json +++ b/homeassistant/components/ps4/translations/zh-Hant.json @@ -15,8 +15,7 @@ }, "step": { "creds": { - "description": "\u9700\u8981\u6191\u8b49\u3002\u6309\u4e0b\u300c\u50b3\u9001\u300d\u5f8c\u3001\u65bc PS4 \u7b2c\u4e8c\u756b\u9762 App\uff0c\u66f4\u65b0\u88dd\u7f6e\u4e26\u9078\u64c7\u300cHome-Assistant\u300d\u4ee5\u7e7c\u7e8c\u3002", - "title": "PlayStation 4" + "description": "\u9700\u8981\u6191\u8b49\u3002\u6309\u4e0b\u300c\u50b3\u9001\u300d\u5f8c\u3001\u65bc PS4 \u7b2c\u4e8c\u756b\u9762 App\uff0c\u66f4\u65b0\u88dd\u7f6e\u4e26\u9078\u64c7\u300cHome-Assistant\u300d\u4ee5\u7e7c\u7e8c\u3002" }, "link": { "data": { @@ -27,9 +26,7 @@ }, "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" + } }, "mode": { "data": { @@ -38,9 +35,7 @@ }, "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/es.json b/homeassistant/components/pure_energie/translations/es.json index eb5d98c7ebf..9725448be66 100644 --- a/homeassistant/components/pure_energie/translations/es.json +++ b/homeassistant/components/pure_energie/translations/es.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Anfitri\u00f3n" + "host": "Host" } }, "zeroconf_confirm": { diff --git a/homeassistant/components/pure_energie/translations/ko.json b/homeassistant/components/pure_energie/translations/ko.json new file mode 100644 index 00000000000..b4373b03a25 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "zeroconf_confirm": { + "title": "Pure Energie Meter \uae30\uae30 \ubc1c\uacac" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvoutput/translations/nl.json b/homeassistant/components/pvoutput/translations/nl.json index 1619f899b73..e991fe84e32 100644 --- a/homeassistant/components/pvoutput/translations/nl.json +++ b/homeassistant/components/pvoutput/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -10,7 +10,7 @@ "step": { "reauth_confirm": { "data": { - "api_key": "API sleutel" + "api_key": "API-sleutel" }, "description": "Om opnieuw te verifi\u00ebren met PVOutput, moet u de API-sleutel op {account_url} ophalen." }, diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/ca.json b/homeassistant/components/pvpc_hourly_pricing/translations/ca.json index ef5e00e25c3..b435e12a90a 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ca.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ca.json @@ -10,9 +10,7 @@ "power": "Pot\u00e8ncia contractada (kW)", "power_p3": "Pot\u00e8ncia contractada del per\u00edode vall P3 (kW)", "tariff": "Tarifa aplicable per zona geogr\u00e0fica" - }, - "description": "Aquest sensor utilitza l'API oficial per obtenir els [preus per hora de l'electricitat (PVPC)](https://www.esios.ree.es/es/pvpc) a Espanya.\nPer a m\u00e9s informaci\u00f3, consulta la [documentaci\u00f3 de la integraci\u00f3](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Configuraci\u00f3 del sensor" + } } } }, @@ -23,9 +21,7 @@ "power": "Pot\u00e8ncia contractada (kW)", "power_p3": "Pot\u00e8ncia contractada del per\u00edode vall P3 (kW)", "tariff": "Tarifa aplicable per zona geogr\u00e0fica" - }, - "description": "Aquest sensor utilitza l'API oficial per obtenir els [preus per hora de l'electricitat (PVPC)](https://www.esios.ree.es/es/pvpc) a Espanya.\nPer a m\u00e9s informaci\u00f3, consulta la [documentaci\u00f3 de la integraci\u00f3](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Configuraci\u00f3 del sensor" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/cs.json b/homeassistant/components/pvpc_hourly_pricing/translations/cs.json index 2dc8bc03afa..69d5d867dfa 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/cs.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/cs.json @@ -7,8 +7,7 @@ "user": { "data": { "name": "Jm\u00e9no senzoru" - }, - "title": "V\u00fdb\u011br tarifu" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/de.json b/homeassistant/components/pvpc_hourly_pricing/translations/de.json index 545a3d2cd9f..49f54ee41d8 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/de.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/de.json @@ -10,9 +10,7 @@ "power": "Vertraglich vereinbarte Leistung (kW)", "power_p3": "Vertraglich vereinbarte Leistung f\u00fcr Talperiode P3 (kW)", "tariff": "Geltender Tarif nach geografischer Zone" - }, - "description": "Dieser Sensor verwendet die offizielle API, um [st\u00fcndliche Strompreise (PVPC)] (https://www.esios.ree.es/es/pvpc) in Spanien zu erhalten. Weitere Informationen findest du in den [Integrations-Dokumentation] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Sensoreinrichtung" + } } } }, @@ -23,9 +21,7 @@ "power": "Vertraglich vereinbarte Leistung (kW)", "power_p3": "Vertraglich vereinbarte Leistung f\u00fcr Talperiode P3 (kW)", "tariff": "Geltender Tarif nach geografischer Zone" - }, - "description": "Dieser Sensor verwendet die offizielle API, um [st\u00fcndliche Strompreise (PVPC)](https://www.esios.ree.es/es/pvpc) in Spanien zu erhalten.\nEine genauere Erkl\u00e4rung findest du in den [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Sensoreinrichtung" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/el.json b/homeassistant/components/pvpc_hourly_pricing/translations/el.json index af128ba3bb3..4c9d056daf7 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/el.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/el.json @@ -10,9 +10,7 @@ "power": "\u03a3\u03c5\u03bc\u03b2\u03b1\u03c4\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 (kW)", "power_p3": "\u03a3\u03c5\u03bc\u03b2\u03b1\u03c4\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03af\u03bf\u03b4\u03bf \u03ba\u03bf\u03b9\u03bb\u03ac\u03b4\u03b1\u03c2 P3 (kW)", "tariff": "\u0399\u03c3\u03c7\u03cd\u03bf\u03bd \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03bf \u03b1\u03bd\u03ac \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03ae \u03b6\u03ce\u03bd\u03b7" - }, - "description": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf \u03b5\u03c0\u03af\u03c3\u03b7\u03bc\u03bf API \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03b9 [\u03c9\u03c1\u03b9\u03b1\u03af\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03b9\u03ba\u03ae\u03c2 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\u03c2 (PVPC)](https://www.esios.ree.es/es/pvpc) \u03c3\u03c4\u03b7\u03bd \u0399\u03c3\u03c0\u03b1\u03bd\u03af\u03b1.\n\u0393\u03b9\u03b1 \u03c0\u03b9\u03bf \u03b1\u03ba\u03c1\u03b9\u03b2\u03b5\u03af\u03c2 \u03b5\u03be\u03b7\u03b3\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03b1 [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1" + } } } }, @@ -23,9 +21,7 @@ "power": "\u03a3\u03c5\u03bc\u03b2\u03b1\u03c4\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 (kW)", "power_p3": "\u03a3\u03c5\u03bc\u03b2\u03b1\u03c4\u03b9\u03ba\u03ae \u03b9\u03c3\u03c7\u03cd\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03af\u03bf\u03b4\u03bf \u03ba\u03bf\u03b9\u03bb\u03ac\u03b4\u03b1\u03c2 P3 (kW)", "tariff": "\u0399\u03c3\u03c7\u03cd\u03bf\u03bd \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03bf \u03b1\u03bd\u03ac \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03ae \u03b6\u03ce\u03bd\u03b7" - }, - "description": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf \u03b5\u03c0\u03af\u03c3\u03b7\u03bc\u03bf API \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03b9 [\u03c9\u03c1\u03b9\u03b1\u03af\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03b9\u03ba\u03ae\u03c2 \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\u03c2 (PVPC)](https://www.esios.ree.es/es/pvpc) \u03c3\u03c4\u03b7\u03bd \u0399\u03c3\u03c0\u03b1\u03bd\u03af\u03b1.\n\u0393\u03b9\u03b1 \u03c0\u03b9\u03bf \u03b1\u03ba\u03c1\u03b9\u03b2\u03b5\u03af\u03c2 \u03b5\u03be\u03b7\u03b3\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03b1 [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/en.json b/homeassistant/components/pvpc_hourly_pricing/translations/en.json index 38f45d36ab6..9667d14fd05 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/en.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/en.json @@ -10,9 +10,7 @@ "power": "Contracted power (kW)", "power_p3": "Contracted power for valley period P3 (kW)", "tariff": "Applicable tariff by geographic zone" - }, - "description": "This sensor uses official API to get [hourly pricing of electricity (PVPC)](https://www.esios.ree.es/es/pvpc) in Spain.\nFor more precise explanation visit the [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Sensor setup" + } } } }, @@ -23,9 +21,7 @@ "power": "Contracted power (kW)", "power_p3": "Contracted power for valley period P3 (kW)", "tariff": "Applicable tariff by geographic zone" - }, - "description": "This sensor uses official API to get [hourly pricing of electricity (PVPC)](https://www.esios.ree.es/es/pvpc) in Spain.\nFor more precise explanation visit the [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Sensor setup" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/es-419.json b/homeassistant/components/pvpc_hourly_pricing/translations/es-419.json index ed6ab16c8b5..a07120f5147 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/es-419.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/es-419.json @@ -8,9 +8,7 @@ "data": { "name": "Nombre del sensor", "tariff": "Tarifa contratada (1, 2 o 3 per\u00edodos)" - }, - "description": "Este sensor utiliza la API oficial para obtener [precios por hora de la electricidad (PVPC)] (https://www.esios.ree.es/es/pvpc) en Espa\u00f1a. \n Para obtener una explicaci\u00f3n m\u00e1s precisa, visite los [documentos de integraci\u00f3n] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\nSeleccione la tarifa contratada en funci\u00f3n de la cantidad de per\u00edodos de facturaci\u00f3n por d\u00eda: \n - 1 per\u00edodo: normal \n - 2 per\u00edodos: discriminaci\u00f3n (tarifa nocturna) \n - 3 per\u00edodos: coche el\u00e9ctrico (tarifa nocturna de 3 per\u00edodos)", - "title": "Selecci\u00f3n de tarifa" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/es.json b/homeassistant/components/pvpc_hourly_pricing/translations/es.json index 4af0de8f594..eb96606831c 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/es.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/es.json @@ -9,10 +9,8 @@ "name": "Nombre del sensor", "power": "Potencia contratada (kW)", "power_p3": "Potencia contratada para el per\u00edodo valle P3 (kW)", - "tariff": "Tarifa contratada (1, 2 o 3 per\u00edodos)" - }, - "description": "Este sensor utiliza la API oficial para obtener [el precio horario de la electricidad (PVPC)](https://www.esios.ree.es/es/pvpc) en Espa\u00f1a.\nPara obtener una explicaci\u00f3n m\u00e1s precisa, visita los [documentos de la integraci\u00f3n](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).\n\nSelecciona la tarifa contratada en funci\u00f3n del n\u00famero de per\u00edodos de facturaci\u00f3n por d\u00eda:\n- 1 per\u00edodo: normal\n- 2 per\u00edodos: discriminaci\u00f3n (tarifa nocturna)\n- 3 per\u00edodos: coche el\u00e9ctrico (tarifa nocturna de 3 per\u00edodos)", - "title": "Selecci\u00f3n de tarifa" + "tariff": "Tarifa aplicable por zona geogr\u00e1fica" + } } } }, @@ -23,9 +21,7 @@ "power": "Potencia contratada (kW)", "power_p3": "Potencia contratada para el per\u00edodo valle P3 (kW)", "tariff": "Tarifa aplicable por zona geogr\u00e1fica" - }, - "description": "Este sensor utiliza la API oficial para obtener el [precio horario de la electricidad (PVPC)](https://www.esios.ree.es/es/pvpc) en Espa\u00f1a.\nPara una explicaci\u00f3n m\u00e1s precisa visita los [documentos de integraci\u00f3n](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Configuraci\u00f3n del sensor" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/et.json b/homeassistant/components/pvpc_hourly_pricing/translations/et.json index 5db4f9ec04b..8554ca18196 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/et.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/et.json @@ -10,9 +10,7 @@ "power": "Lepinguj\u00e4rgne v\u00f5imsus (kW)", "power_p3": "Lepinguj\u00e4rgne v\u00f5imsus soodusperioodil P3 (kW)", "tariff": "Kohaldatav tariif geograafilise tsooni j\u00e4rgi" - }, - "description": "See andur kasutab ametlikku API-d, et saada [elektri tunnihinda (PVPC)](https://www.esios.ree.es/es/pvpc) Hispaanias.\nT\u00e4psema selgituse saamiseks k\u00fclasta [integratsioonidokumente](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Anduri seadistamine" + } } } }, @@ -23,9 +21,7 @@ "power": "Lepinguj\u00e4rgne v\u00f5imsus (kW)", "power_p3": "Lepinguj\u00e4rgne v\u00f5imsus soodusperioodil P3 (kW)", "tariff": "Kohaldatav tariif geograafilise tsooni j\u00e4rgi" - }, - "description": "See andur kasutab ametlikku API-d, et saada [elektri tunnihinda (PVPC)](https://www.esios.ree.es/es/pvpc) Hispaanias.\nT\u00e4psema selgituse saamiseks k\u00fclasta [integratsioonidokumente](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Anduri seadistamine" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/fr.json b/homeassistant/components/pvpc_hourly_pricing/translations/fr.json index afbea0852c4..1f0c447ff0f 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/fr.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/fr.json @@ -10,9 +10,7 @@ "power": "Puissance souscrite (kW)", "power_p3": "Puissance souscrite pour la p\u00e9riode de vall\u00e9e P3 (kW)", "tariff": "Tarif applicable par zone g\u00e9ographique" - }, - "description": "Ce capteur utilise l'API officielle pour obtenir [tarification horaire de l'\u00e9lectricit\u00e9 (PVPC)](https://www.esios.ree.es/es/pvpc) en Espagne.\n Pour des explications plus pr\u00e9cises, visitez les [docs d'int\u00e9gration](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Configuration du capteur" + } } } }, @@ -23,9 +21,7 @@ "power": "Puissance souscrite (kW)", "power_p3": "Puissance souscrite pour la p\u00e9riode de vall\u00e9e P3 (kW)", "tariff": "Tarif applicable par zone g\u00e9ographique" - }, - "description": "Ce capteur utilise l'API officielle pour obtenir [tarification horaire de l'\u00e9lectricit\u00e9 (PVPC)](https://www.esios.ree.es/es/pvpc) en Espagne.\n Pour des explications plus pr\u00e9cises, visitez les [docs d'int\u00e9gration](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Configuration du capteur" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/he.json b/homeassistant/components/pvpc_hourly_pricing/translations/he.json index 951e9b21b2f..48a6eeeea33 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/he.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/he.json @@ -3,12 +3,5 @@ "abort": { "already_configured": "\u05e9\u05d9\u05e8\u05d5\u05ea \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" } - }, - "options": { - "step": { - "init": { - "title": "\u05d4\u05d2\u05d3\u05e8\u05ea \u05d7\u05d9\u05d9\u05e9\u05df" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/hu.json b/homeassistant/components/pvpc_hourly_pricing/translations/hu.json index c654c92969a..121a87af124 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/hu.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/hu.json @@ -10,9 +10,7 @@ "power": "Szerz\u0151d\u00e9s szerinti teljes\u00edtm\u00e9ny (kW)", "power_p3": "Szerz\u0151d\u00f6tt teljes\u00edtm\u00e9ny P3 v\u00f6lgyid\u0151szakra (kW)", "tariff": "Alkalmazand\u00f3 tarifa f\u00f6ldrajzi z\u00f3n\u00e1nk\u00e9nt" - }, - "description": "Ez az \u00e9rz\u00e9kel\u0151 a hivatalos API-t haszn\u00e1lja a [villamos energia \u00f3r\u00e1nk\u00e9nti \u00e1raz\u00e1s\u00e1nak (PVPC)] (https://www.esios.ree.es/es/pvpc) megszerz\u00e9s\u00e9hez Spanyolorsz\u00e1gban.\n Pontosabb magyar\u00e1zat\u00e9rt keresse fel az [integr\u00e1ci\u00f3s dokumentumok] oldalt (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "\u00c9rz\u00e9kel\u0151 be\u00e1ll\u00edt\u00e1sa" + } } } }, @@ -23,9 +21,7 @@ "power": "Szerz\u0151d\u00e9s szerinti teljes\u00edtm\u00e9ny (kW)", "power_p3": "Szerz\u0151d\u00f6tt teljes\u00edtm\u00e9ny P3 v\u00f6lgyid\u0151szakra (kW)", "tariff": "Alkalmazand\u00f3 tarifa f\u00f6ldrajzi z\u00f3n\u00e1k szerint" - }, - "description": "Ez az \u00e9rz\u00e9kel\u0151 a hivatalos API-t haszn\u00e1lja a [villamos energia \u00f3r\u00e1nk\u00e9nti \u00e1raz\u00e1s\u00e1nak (PVPC)] (https://www.esios.ree.es/es/pvpc) megszerz\u00e9s\u00e9hez Spanyolorsz\u00e1gban.\nPontosabb magyar\u00e1zat\u00e9rt keresse fel az [integr\u00e1ci\u00f3s dokumentumok] oldalt (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "\u00c9rz\u00e9kel\u0151 be\u00e1ll\u00edt\u00e1sa" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/id.json b/homeassistant/components/pvpc_hourly_pricing/translations/id.json index 5705a2a4fcb..2bc5c67e533 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/id.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/id.json @@ -10,9 +10,7 @@ "power": "Daya terkontrak (kW)", "power_p3": "Daya terkontrak untuk periode lembah P3 (kW)", "tariff": "Tarif yang berlaku menurut zona geografis" - }, - "description": "Sensor ini menggunakan API resmi untuk mendapatkan [harga listrik per jam (PVPC)](https://www.esios.ree.es/es/pvpc) di Spanyol.\nUntuk penjelasan yang lebih tepat, kunjungi [dokumen integrasi](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Penyiapan sensor" + } } } }, @@ -23,9 +21,7 @@ "power": "Daya terkontrak (kW)", "power_p3": "Daya terkontrak untuk periode lembah P3 (kW)", "tariff": "Tarif yang berlaku menurut zona geografis" - }, - "description": "Sensor ini menggunakan API resmi untuk mendapatkan [harga listrik per jam (PVPC)](https://www.esios.ree.es/es/pvpc) di Spanyol.\nUntuk penjelasan yang lebih tepat, kunjungi [dokumen integrasi](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Penyiapan sensor" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/it.json b/homeassistant/components/pvpc_hourly_pricing/translations/it.json index 79e6e627a7e..bd6f8494d49 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/it.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/it.json @@ -10,9 +10,7 @@ "power": "Potenza contrattuale (kW)", "power_p3": "Potenza contrattuale per il periodo di valle P3 (kW)", "tariff": "Tariffa applicabile per zona geografica" - }, - "description": "Questo sensore utilizza l'API ufficiale per ottenere il [prezzo orario dell'elettricit\u00e0 (PVPC)](https://www.esios.ree.es/es/pvpc) in Spagna.\nPer una spiegazione pi\u00f9 precisa, visita i [documenti di integrazione](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Configurazione del sensore" + } } } }, @@ -23,9 +21,7 @@ "power": "Potenza contrattuale (kW)", "power_p3": "Potenza contrattuale per il periodo di valle P3 (kW)", "tariff": "Tariffa applicabile per zona geografica" - }, - "description": "Questo sensore utilizza l'API ufficiale per ottenere il [prezzo orario dell'elettricit\u00e0 (PVPC)](https://www.esios.ree.es/es/pvpc) in Spagna.\nPer una spiegazione pi\u00f9 precisa, visita i [documenti di integrazione](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Configurazione del sensore" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/ja.json b/homeassistant/components/pvpc_hourly_pricing/translations/ja.json index d88ee379678..6b85de978d3 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ja.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ja.json @@ -10,9 +10,7 @@ "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(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" + } } } }, @@ -23,9 +21,7 @@ "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(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/pvpc_hourly_pricing/translations/ko.json b/homeassistant/components/pvpc_hourly_pricing/translations/ko.json index c44d1217961..fe834a94f86 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ko.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ko.json @@ -8,9 +8,7 @@ "data": { "name": "\uc13c\uc11c \uc774\ub984", "tariff": "\uacc4\uc57d \uc694\uae08\uc81c (1, 2 \ub610\ub294 3 \uad6c\uac04)" - }, - "description": "\uc774 \uc13c\uc11c\ub294 \uacf5\uc2dd API\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2a4\ud398\uc778\uc758 [\uc2dc\uac04\ub2f9 \uc804\uae30 \uc694\uae08 (PVPC)](https://www.esios.ree.es/es/pvpc) \uc744 \uac00\uc838\uc635\ub2c8\ub2e4.\n\ubcf4\ub2e4 \uc790\uc138\ud55c \uc124\uba85\uc740 [\uad00\ub828 \ubb38\uc11c](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.\n\n1\uc77c\ub2f9 \uccad\uad6c \uad6c\uac04\uc5d0 \ub530\ub77c \uacc4\uc57d \uc694\uae08\uc81c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.\n - 1 \uad6c\uac04: \uc77c\ubc18 \uc694\uae08\uc81c\n - 2 \uad6c\uac04: \ucc28\ub4f1 \uc694\uae08\uc81c (\uc57c\uac04 \uc694\uae08) \n - 3 \uad6c\uac04: \uc804\uae30\uc790\ub3d9\ucc28 (3 \uad6c\uac04 \uc57c\uac04 \uc694\uae08)", - "title": "\uc694\uae08\uc81c \uc120\ud0dd" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/lb.json b/homeassistant/components/pvpc_hourly_pricing/translations/lb.json index 78894094b72..d9e6d87437b 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/lb.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/lb.json @@ -8,9 +8,7 @@ "data": { "name": "Numm vum Sensor", "tariff": "Kontraktuellen Tarif (1, 2 oder 3 Perioden)" - }, - "description": "D\u00ebse Sensor benotzt d\u00e9i offiziell API fir de [Stonne Pr\u00e4is fir Elektrizit\u00e9it a Spuenien (PVPC)](https://www.esios.ree.es/es/pvpc) ze kr\u00e9ien. Fir m\u00e9i pr\u00e4zise Erkl\u00e4runge kuck [Dokumentatioun vun der Integratioun](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).\n\nWiel den Taux bas\u00e9ierend op der Unzuel vun de Rechnungsz\u00e4ite pro Dag aus:\n- 1 Period: Normal\n- 2 perioden: Nuets Tarif\n- 3 Perioden: Elektreschen Auto (Nuets Tarif fir 3 Perioden)", - "title": "Auswiel vum Tarif" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/nl.json b/homeassistant/components/pvpc_hourly_pricing/translations/nl.json index b4bc784dedf..a7d1ac0e743 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/nl.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "step": { "user": { @@ -10,9 +10,7 @@ "power": "Gecontracteerd vermogen (kW)", "power_p3": "Gecontracteerd vermogen voor dalperiode P3 (kW)", "tariff": "Toepasselijk tarief per geografische zone" - }, - "description": "Deze sensor gebruikt de offici\u00eble API om [uurprijs van elektriciteit (PVPC)](https://www.esios.ree.es/es/pvpc) in Spanje te verkrijgen.\n Ga voor een nauwkeurigere uitleg naar de [integratiedocumenten](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Sensor instellen" + } } } }, @@ -23,9 +21,7 @@ "power": "Gecontracteerd vermogen (kW)", "power_p3": "Gecontracteerd vermogen voor dalperiode P3 (kW)", "tariff": "Toepasselijk tarief per geografische zone" - }, - "description": "Deze sensor maakt gebruik van offici\u00eble API om [uurprijzen van elektriciteit (PVPC)](https://www.esios.ree.es/es/pvpc) in Spanje te krijgen.\nGa voor een preciezere uitleg naar de [integratiedocumenten](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Sensor setup" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/no.json b/homeassistant/components/pvpc_hourly_pricing/translations/no.json index 7429626674e..77dadcbcffd 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/no.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/no.json @@ -10,9 +10,7 @@ "power": "Kontrahert effekt (kW)", "power_p3": "Kontraktstr\u00f8m for dalperiode P3 (kW)", "tariff": "Gjeldende tariff etter geografisk sone" - }, - "description": "Denne sensoren bruker offisiell API for \u00e5 f\u00e5 [timeprisering av elektrisitet (PVPC)] (https://www.esios.ree.es/es/pvpc) i Spania.\n For mer presis forklaring bes\u00f8k [integrasjonsdokumentene] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Oppsett av sensor" + } } } }, @@ -23,9 +21,7 @@ "power": "Kontrahert effekt (kW)", "power_p3": "Kontrahert kraft for dalperioden P3 (kW)", "tariff": "Gjeldende tariff etter geografisk sone" - }, - "description": "Denne sensoren bruker offisiell API for \u00e5 f\u00e5 [timeprisering av elektrisitet (PVPC)] (https://www.esios.ree.es/es/pvpc) i Spania.\n For mer presis forklaring bes\u00f8k [integrasjonsdokumentene] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Oppsett av sensor" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/pl.json b/homeassistant/components/pvpc_hourly_pricing/translations/pl.json index 052cf66c1a6..52fb03fa985 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/pl.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/pl.json @@ -10,9 +10,7 @@ "power": "Moc zakontraktowana (kW)", "power_p3": "Moc zakontraktowana dla okresu zni\u017ckowego P3 (kW)", "tariff": "Obowi\u0105zuj\u0105ca taryfa wed\u0142ug strefy geograficznej" - }, - "description": "Ten sensor u\u017cywa oficjalnego interfejsu API w celu uzyskania [godzinowej ceny energii elektrycznej (PVPC)] (https://www.esios.ree.es/es/pvpc) w Hiszpanii. \n Aby uzyska\u0107 bardziej szczeg\u00f3\u0142owe wyja\u015bnienia, odwied\u017a [dokumentacj\u0119 dotycz\u0105c\u0105 integracji] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\n Wybierz stawk\u0119 umown\u0105 na podstawie liczby okres\u00f3w rozliczeniowych dziennie: \n - 1 okres: normalny \n - 2 okresy: dyskryminacja (nocna stawka) \n - 3 okresy: samoch\u00f3d elektryczny (stawka nocna za 3 okresy)", - "title": "Konfiguracja sensora" + } } } }, @@ -23,9 +21,7 @@ "power": "Moc zakontraktowana (kW)", "power_p3": "Moc zakontraktowana dla okresu zni\u017ckowego P3 (kW)", "tariff": "Obowi\u0105zuj\u0105ca taryfa wed\u0142ug strefy geograficznej" - }, - "description": "Ten sensor u\u017cywa oficjalnego interfejsu API w celu uzyskania [godzinowej ceny energii elektrycznej (PVPC)] (https://www.esios.ree.es/es/pvpc) w Hiszpanii. \n Aby uzyska\u0107 bardziej szczeg\u00f3\u0142owe wyja\u015bnienia, odwied\u017a [dokumentacj\u0119 dotycz\u0105c\u0105 integracji] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\n Wybierz stawk\u0119 umown\u0105 na podstawie liczby okres\u00f3w rozliczeniowych dziennie: \n - 1 okres: normalny \n - 2 okresy: dyskryminacja (nocna stawka) \n - 3 okresy: samoch\u00f3d elektryczny (stawka nocna za 3 okresy)", - "title": "Konfiguracja sensora" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json b/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json index e5754180a7c..7d66f5af13a 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json @@ -10,9 +10,7 @@ "power": "Pot\u00eancia contratada (kW)", "power_p3": "Pot\u00eancia contratada para o per\u00edodo de vale P3 (kW)", "tariff": "Tarifa aplic\u00e1vel por zona geogr\u00e1fica" - }, - "description": "Esse sensor usa a API oficial para obter [pre\u00e7os por hora de eletricidade (PVPC)](https://www.esios.ree.es/es/pvpc) na Espanha. \nPara uma explica\u00e7\u00e3o mais precisa, visite os [documentos de integra\u00e7\u00e3o](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\nSelecione a taxa contratada com base no n\u00famero de per\u00edodos de cobran\u00e7a por dia: \n- 1 per\u00edodo: normal \n- 2 per\u00edodos: discrimina\u00e7\u00e3o (taxa noturna) \n- 3 per\u00edodos: carro el\u00e9trico (taxa noturna de 3 per\u00edodos)", - "title": "Configura\u00e7\u00e3o do sensor" + } } } }, @@ -23,9 +21,7 @@ "power": "Pot\u00eancia contratada (kW)", "power_p3": "Pot\u00eancia contratada para o per\u00edodo de vale P3 (kW)", "tariff": "Tarifa aplic\u00e1vel por zona geogr\u00e1fica" - }, - "description": "Este sensor usa a API oficial para obter [pre\u00e7os por hora de eletricidade (PVPC)](https://www.esios.ree.es/es/pvpc) na Espanha.\n Para uma explica\u00e7\u00e3o mais precisa, visite os [documentos de integra\u00e7\u00e3o](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "Configura\u00e7\u00e3o do sensor" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/ru.json b/homeassistant/components/pvpc_hourly_pricing/translations/ru.json index e68bc7e6289..7994c217216 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ru.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ru.json @@ -10,9 +10,7 @@ "power": "\u0414\u043e\u0433\u043e\u0432\u043e\u0440\u043d\u0430\u044f \u043c\u043e\u0449\u043d\u043e\u0441\u0442\u044c (\u043a\u0412\u0442)", "power_p3": "\u0414\u043e\u0433\u043e\u0432\u043e\u0440\u043d\u0430\u044f \u043c\u043e\u0449\u043d\u043e\u0441\u0442\u044c \u043d\u0430 \u043f\u0435\u0440\u0438\u043e\u0434 P3 (\u043a\u0412\u0442)", "tariff": "\u041f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u043c\u044b\u0439 \u0442\u0430\u0440\u0438\u0444 \u043f\u043e \u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0437\u043e\u043d\u0435" - }, - "description": "\u042d\u0442\u043e\u0442 \u0441\u0435\u043d\u0441\u043e\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 API \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f [\u043f\u043e\u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0446\u0435\u043d\u044b \u0437\u0430 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u044d\u043d\u0435\u0440\u0433\u0438\u044e (PVPC)](https://www.esios.ree.es/es/pvpc) \u0432 \u0418\u0441\u043f\u0430\u043d\u0438\u0438.\n\u0414\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, \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](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0430" + } } } }, @@ -23,9 +21,7 @@ "power": "\u0414\u043e\u0433\u043e\u0432\u043e\u0440\u043d\u0430\u044f \u043c\u043e\u0449\u043d\u043e\u0441\u0442\u044c (\u043a\u0412\u0442)", "power_p3": "\u0414\u043e\u0433\u043e\u0432\u043e\u0440\u043d\u0430\u044f \u043c\u043e\u0449\u043d\u043e\u0441\u0442\u044c \u043d\u0430 \u043f\u0435\u0440\u0438\u043e\u0434 P3 (\u043a\u0412\u0442)", "tariff": "\u041f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u043c\u044b\u0439 \u0442\u0430\u0440\u0438\u0444 \u043f\u043e \u0433\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0437\u043e\u043d\u0435" - }, - "description": "\u042d\u0442\u043e\u0442 \u0441\u0435\u043d\u0441\u043e\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 API \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f [\u043f\u043e\u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u0446\u0435\u043d\u044b \u0437\u0430 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u044d\u043d\u0435\u0440\u0433\u0438\u044e (PVPC)](https://www.esios.ree.es/es/pvpc) \u0432 \u0418\u0441\u043f\u0430\u043d\u0438\u0438.\n\u0414\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, \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](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0430" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/sl.json b/homeassistant/components/pvpc_hourly_pricing/translations/sl.json index c6765575c07..cc41e12b4b2 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/sl.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/sl.json @@ -8,9 +8,7 @@ "data": { "name": "Ime tipala", "tariff": "Pogodbena tarifa (1, 2 ali 3 obdobja)" - }, - "description": "Ta senzor uporablja uradni API za [urno dolo\u010danje cen elektri\u010dne energije (PVPC)] (https://www.esios.ree.es/es/pvpc) v \u0160paniji. \n Za natan\u010dnej\u0161o razlago obi\u0161\u010dite [integracijski dokumenti] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\n Izberite pogodbeno tarifo glede na \u0161tevilo obra\u010dunskih obdobij na dan: \n - 1 obdobje: normalno \n - 2 obdobji: diskriminacija (no\u010dna cena) \n - 3 obdobja: elektri\u010dni avtomobil (no\u010dna cena 3 obdobja)", - "title": "Izbira tarife" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/tr.json b/homeassistant/components/pvpc_hourly_pricing/translations/tr.json index 5d27e2bb033..908f04f6622 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/tr.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/tr.json @@ -10,9 +10,7 @@ "power": "S\u00f6zle\u015fmeli g\u00fc\u00e7 (kW)", "power_p3": "Vadi d\u00f6nemi i\u00e7in taahh\u00fct edilen g\u00fc\u00e7 P3 (kW)", "tariff": "Co\u011frafi b\u00f6lgeye g\u00f6re ge\u00e7erli tarife" - }, - "description": "Bu sens\u00f6r, \u0130spanya'da [saatlik elektrik fiyatland\u0131rmas\u0131 (PVPC)](https://www.esios.ree.es/es/pvpc) almak i\u00e7in resmi API'yi kullan\u0131r.\n Daha kesin a\u00e7\u0131klama i\u00e7in [entegrasyon belgelerini](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/) ziyaret edin.", - "title": "Sens\u00f6r kurulumu" + } } } }, @@ -23,9 +21,7 @@ "power": "S\u00f6zle\u015fmeli g\u00fc\u00e7 (kW)", "power_p3": "Vadi d\u00f6nemi i\u00e7in taahh\u00fct edilen g\u00fc\u00e7 P3 (kW)", "tariff": "Co\u011frafi b\u00f6lgeye g\u00f6re ge\u00e7erli tarife" - }, - "description": "Bu sens\u00f6r, \u0130spanya'da [saatlik elektrik fiyatland\u0131rmas\u0131 (PVPC)](https://www.esios.ree.es/es/pvpc) almak i\u00e7in resmi API'yi kullan\u0131r.\n Daha kesin a\u00e7\u0131klama i\u00e7in [entegrasyon belgelerini](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/) ziyaret edin.", - "title": "Sens\u00f6r kurulumu" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/uk.json b/homeassistant/components/pvpc_hourly_pricing/translations/uk.json index da2136d7765..bc6d06b1b04 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/uk.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/uk.json @@ -8,9 +8,7 @@ "data": { "name": "\u041d\u0430\u0437\u0432\u0430", "tariff": "\u041a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u043d\u0438\u0439 \u0442\u0430\u0440\u0438\u0444 (1, 2 \u0430\u0431\u043e 3 \u043f\u0435\u0440\u0456\u043e\u0434\u0438)" - }, - "description": "\u0426\u0435\u0439 \u0441\u0435\u043d\u0441\u043e\u0440 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454 \u043e\u0444\u0456\u0446\u0456\u0439\u043d\u0438\u0439 API \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f [\u043f\u043e\u0433\u043e\u0434\u0438\u043d\u043d\u043e\u0457 \u0446\u0456\u043d\u0438 \u0437\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u0435\u043d\u0435\u0440\u0433\u0456\u044e (PVPC)] (https://www.esios.ree.es/es/pvpc) \u0432 \u0406\u0441\u043f\u0430\u043d\u0456\u0457.\n\u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0431\u0456\u043b\u044c\u0448 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044c \u0437 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0454\u044e] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\n\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0430\u0440\u0438\u0444, \u0437\u0430\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u043d\u0430 \u043a\u0456\u043b\u044c\u043a\u043e\u0441\u0442\u0456 \u0440\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043a\u043e\u0432\u0438\u0445 \u043f\u0435\u0440\u0456\u043e\u0434\u0456\u0432 \u0432 \u0434\u0435\u043d\u044c:\n- 1 \u043f\u0435\u0440\u0456\u043e\u0434: normal\n- 2 \u043f\u0435\u0440\u0456\u043e\u0434\u0438: discrimination (nightly rate)\n- 3 \u043f\u0435\u0440\u0456\u043e\u0434\u0438: electric car (nightly rate of 3 periods)", - "title": "\u0412\u0438\u0431\u0456\u0440 \u0442\u0430\u0440\u0438\u0444\u0443" + } } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/zh-Hant.json b/homeassistant/components/pvpc_hourly_pricing/translations/zh-Hant.json index ace4cce089a..35ace573ead 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/zh-Hant.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/zh-Hant.json @@ -10,9 +10,7 @@ "power": "\u5408\u7d04\u529f\u7387\uff08kW\uff09", "power_p3": "\u4f4e\u5cf0\u671f P3 \u5408\u7d04\u529f\u7387\uff08kW\uff09", "tariff": "\u5206\u5340\u9069\u7528\u8cbb\u7387" - }, - "description": "\u6b64\u611f\u6e2c\u5668\u4f7f\u7528\u4e86\u975e\u5b98\u65b9 API \u4ee5\u53d6\u5f97\u897f\u73ed\u7259 [\u8a08\u6642\u96fb\u50f9\uff08PVPC\uff09](https://www.esios.ree.es/es/pvpc)\u3002\n\u95dc\u65bc\u66f4\u8a73\u7d30\u7684\u8aaa\u660e\uff0c\u8acb\u53c3\u95b1 [\u6574\u5408\u6587\u4ef6](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/)\u3002", - "title": "\u611f\u61c9\u5668\u8a2d\u5b9a" + } } } }, @@ -23,9 +21,7 @@ "power": "\u5408\u7d04\u529f\u7387\uff08kW\uff09", "power_p3": "\u4f4e\u5cf0\u671f P3 \u5408\u7d04\u529f\u7387\uff08kW\uff09", "tariff": "\u5206\u5340\u9069\u7528\u8cbb\u7387" - }, - "description": "\u6b64\u611f\u6e2c\u5668\u4f7f\u7528\u4e86\u975e\u5b98\u65b9 API \u4ee5\u53d6\u5f97\u897f\u73ed\u7259 [\u8a08\u6642\u96fb\u50f9\uff08PVPC\uff09](https://www.esios.ree.es/es/pvpc)\u3002\n\u95dc\u65bc\u66f4\u8a73\u7d30\u7684\u8aaa\u660e\uff0c\u8acb\u53c3\u95b1 [\u6574\u5408\u6587\u4ef6](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/)\u3002", - "title": "\u611f\u61c9\u5668\u8a2d\u5b9a" + } } } } diff --git a/homeassistant/components/qnap_qsw/__init__.py b/homeassistant/components/qnap_qsw/__init__.py index c1b96a3298e..26ed8066686 100644 --- a/homeassistant/components/qnap_qsw/__init__.py +++ b/homeassistant/components/qnap_qsw/__init__.py @@ -1,64 +1,17 @@ """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 .const import DOMAIN 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 +PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/qnap_qsw/binary_sensor.py b/homeassistant/components/qnap_qsw/binary_sensor.py new file mode 100644 index 00000000000..467a3314070 --- /dev/null +++ b/homeassistant/components/qnap_qsw/binary_sensor.py @@ -0,0 +1,88 @@ +"""Support for the QNAP QSW binary sensors.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Final + +from aioqsw.const import QSD_ANOMALY, QSD_FIRMWARE_CONDITION, QSD_MESSAGE + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ATTR_MESSAGE, DOMAIN +from .coordinator import QswUpdateCoordinator +from .entity import QswEntityDescription, QswSensorEntity + + +@dataclass +class QswBinarySensorEntityDescription( + BinarySensorEntityDescription, QswEntityDescription +): + """A class that describes QNAP QSW binary sensor entities.""" + + attributes: dict[str, list[str]] | None = None + + +BINARY_SENSOR_TYPES: Final[tuple[QswBinarySensorEntityDescription, ...]] = ( + QswBinarySensorEntityDescription( + attributes={ + ATTR_MESSAGE: [QSD_FIRMWARE_CONDITION, QSD_MESSAGE], + }, + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + key=QSD_FIRMWARE_CONDITION, + name="Anomaly", + subkey=QSD_ANOMALY, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Add QNAP QSW binary sensors from a config_entry.""" + coordinator: QswUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + QswBinarySensor(coordinator, description, entry) + for description in BINARY_SENSOR_TYPES + if ( + description.key in coordinator.data + and description.subkey in coordinator.data[description.key] + ) + ) + + +class QswBinarySensor(QswSensorEntity, BinarySensorEntity): + """Define a QNAP QSW binary sensor.""" + + entity_description: QswBinarySensorEntityDescription + + def __init__( + self, + coordinator: QswUpdateCoordinator, + description: QswBinarySensorEntityDescription, + entry: ConfigEntry, + ) -> None: + """Initialize.""" + super().__init__(coordinator, entry) + self._attr_name = f"{self.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 _async_update_attrs(self) -> None: + """Update binary sensor attributes.""" + self._attr_is_on = self.get_device_value( + self.entity_description.key, self.entity_description.subkey + ) + super()._async_update_attrs() diff --git a/homeassistant/components/qnap_qsw/button.py b/homeassistant/components/qnap_qsw/button.py new file mode 100644 index 00000000000..1c13310fe05 --- /dev/null +++ b/homeassistant/components/qnap_qsw/button.py @@ -0,0 +1,77 @@ +"""Support for the QNAP QSW buttons.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Final + +from aioqsw.localapi import QnapQswApi + +from homeassistant.components.button import ( + ButtonDeviceClass, + ButtonEntity, + ButtonEntityDescription, +) +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, QSW_REBOOT +from .coordinator import QswUpdateCoordinator +from .entity import QswEntity + + +@dataclass +class QswButtonDescriptionMixin: + """Mixin to describe a Button entity.""" + + press_action: Callable[[QnapQswApi], Awaitable[bool]] + + +@dataclass +class QswButtonDescription(ButtonEntityDescription, QswButtonDescriptionMixin): + """Class to describe a Button entity.""" + + +BUTTON_TYPES: Final[tuple[QswButtonDescription, ...]] = ( + QswButtonDescription( + device_class=ButtonDeviceClass.RESTART, + entity_category=EntityCategory.CONFIG, + key=QSW_REBOOT, + name="Reboot", + press_action=lambda qsw: qsw.reboot(), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Add QNAP QSW buttons from a config_entry.""" + coordinator: QswUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + QswButton(coordinator, description, entry) for description in BUTTON_TYPES + ) + + +class QswButton(QswEntity, ButtonEntity): + """Define a QNAP QSW button.""" + + entity_description: QswButtonDescription + + def __init__( + self, + coordinator: QswUpdateCoordinator, + description: QswButtonDescription, + entry: ConfigEntry, + ) -> None: + """Initialize.""" + super().__init__(coordinator, entry) + self._attr_name = f"{self.product} {description.name}" + self._attr_unique_id = f"{entry.unique_id}_{description.key}" + self.entity_description = description + + async def async_press(self) -> None: + """Triggers the QNAP QSW button action.""" + await self.entity_description.press_action(self.coordinator.qsw) diff --git a/homeassistant/components/qnap_qsw/const.py b/homeassistant/components/qnap_qsw/const.py index b55a817927f..e583c0250f4 100644 --- a/homeassistant/components/qnap_qsw/const.py +++ b/homeassistant/components/qnap_qsw/const.py @@ -3,10 +3,12 @@ from typing import Final ATTR_MAX: Final = "max" +ATTR_MESSAGE: Final = "message" DOMAIN: Final = "qnap_qsw" MANUFACTURER: Final = "QNAP" RPM: Final = "rpm" +QSW_REBOOT = "reboot" QSW_TIMEOUT_SEC: Final = 25 diff --git a/homeassistant/components/qnap_qsw/coordinator.py b/homeassistant/components/qnap_qsw/coordinator.py index 064953b1446..c018c1f3848 100644 --- a/homeassistant/components/qnap_qsw/coordinator.py +++ b/homeassistant/components/qnap_qsw/coordinator.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import timedelta import logging +from typing import Any from aioqsw.exceptions import QswError from aioqsw.localapi import QnapQswApi @@ -18,7 +19,7 @@ SCAN_INTERVAL = timedelta(seconds=60) _LOGGER = logging.getLogger(__name__) -class QswUpdateCoordinator(DataUpdateCoordinator): +class QswUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): """Class to manage fetching data from the QNAP QSW device.""" def __init__(self, hass: HomeAssistant, qsw: QnapQswApi) -> None: @@ -32,7 +33,7 @@ class QswUpdateCoordinator(DataUpdateCoordinator): update_interval=SCAN_INTERVAL, ) - async def _async_update_data(self): + async def _async_update_data(self) -> dict[str, Any]: """Update data via library.""" async with async_timeout.timeout(QSW_TIMEOUT_SEC): try: diff --git a/homeassistant/components/qnap_qsw/diagnostics.py b/homeassistant/components/qnap_qsw/diagnostics.py new file mode 100644 index 00000000000..3730bab24a8 --- /dev/null +++ b/homeassistant/components/qnap_qsw/diagnostics.py @@ -0,0 +1,37 @@ +"""Support for the QNAP QSW diagnostics.""" +from __future__ import annotations + +from typing import Any + +from aioqsw.const import QSD_MAC, QSD_SERIAL + +from homeassistant.components.diagnostics.util import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import QswUpdateCoordinator + +TO_REDACT_CONFIG = [ + CONF_USERNAME, + CONF_PASSWORD, + CONF_UNIQUE_ID, +] + +TO_REDACT_DATA = [ + QSD_MAC, + QSD_SERIAL, +] + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: QswUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + return { + "config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT_CONFIG), + "coord_data": async_redact_data(coordinator.data, TO_REDACT_DATA), + } diff --git a/homeassistant/components/qnap_qsw/entity.py b/homeassistant/components/qnap_qsw/entity.py new file mode 100644 index 00000000000..c3550610d83 --- /dev/null +++ b/homeassistant/components/qnap_qsw/entity.py @@ -0,0 +1,93 @@ +"""Entity classes for the QNAP QSW integration.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from aioqsw.const import ( + QSD_FIRMWARE, + QSD_FIRMWARE_INFO, + QSD_MAC, + QSD_PRODUCT, + QSD_SYSTEM_BOARD, +) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_URL +from homeassistant.core import callback +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.entity import DeviceInfo, EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import MANUFACTURER +from .coordinator import QswUpdateCoordinator + + +class QswEntity(CoordinatorEntity[QswUpdateCoordinator]): + """Define an QNAP QSW entity.""" + + def __init__( + self, + coordinator: QswUpdateCoordinator, + entry: ConfigEntry, + ) -> None: + """Initialize.""" + super().__init__(coordinator) + + self.product = self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT) + 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.product, + name=self.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 + + +@dataclass +class QswEntityDescriptionMixin: + """Mixin to describe a QSW entity.""" + + subkey: str + + +class QswEntityDescription(EntityDescription, QswEntityDescriptionMixin): + """Class to describe a QSW entity.""" + + attributes: dict[str, list[str]] | None = None + + +class QswSensorEntity(QswEntity): + """Base class for QSW sensor entities.""" + + entity_description: QswEntityDescription + + @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 attributes.""" + 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/manifest.json b/homeassistant/components/qnap_qsw/manifest.json index 17709b275ca..9331a7df468 100644 --- a/homeassistant/components/qnap_qsw/manifest.json +++ b/homeassistant/components/qnap_qsw/manifest.json @@ -3,7 +3,7 @@ "name": "QNAP QSW", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/qnap_qsw", - "requirements": ["aioqsw==0.0.5"], + "requirements": ["aioqsw==0.0.8"], "codeowners": ["@Noltari"], "iot_class": "local_polling", "loggers": ["aioqsw"] diff --git a/homeassistant/components/qnap_qsw/sensor.py b/homeassistant/components/qnap_qsw/sensor.py index 8453232ce6f..0de8ec4a39e 100644 --- a/homeassistant/components/qnap_qsw/sensor.py +++ b/homeassistant/components/qnap_qsw/sensor.py @@ -7,8 +7,6 @@ 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, @@ -28,17 +26,16 @@ 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 +from .entity import QswEntityDescription, QswSensorEntity @dataclass -class QswSensorEntityDescription(SensorEntityDescription): +class QswSensorEntityDescription(SensorEntityDescription, QswEntityDescription): """A class that describes QNAP QSW sensor entities.""" attributes: dict[str, list[str]] | None = None - subkey: str = "" SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( @@ -96,7 +93,7 @@ async def async_setup_entry( ) -class QswSensor(QswEntity, SensorEntity): +class QswSensor(QswSensorEntity, SensorEntity): """Define a QNAP QSW sensor.""" entity_description: QswSensorEntityDescription @@ -109,30 +106,17 @@ class QswSensor(QswEntity, SensorEntity): ) -> None: """Initialize.""" super().__init__(coordinator, entry) - self._attr_name = ( - f"{self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT)} {description.name}" - ) + self._attr_name = f"{self.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() - } + super()._async_update_attrs() diff --git a/homeassistant/components/qnap_qsw/translations/bg.json b/homeassistant/components/qnap_qsw/translations/bg.json new file mode 100644 index 00000000000..33ce2a4028f --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/bg.json @@ -0,0 +1,20 @@ +{ + "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" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "url": "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/qnap_qsw/translations/ca.json b/homeassistant/components/qnap_qsw/translations/ca.json new file mode 100644 index 00000000000..575ed369b7b --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/ca.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "invalid_id": "El dispositiu ha retornat un ID \u00fanic inv\u00e0lid" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "password": "Contrasenya", + "url": "URL", + "username": "Nom d'usuari" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/es.json b/homeassistant/components/qnap_qsw/translations/es.json new file mode 100644 index 00000000000..b58fcb71fc7 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/es.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "invalid_id": "El dispositivo ha devuelto un ID \u00fanico inv\u00e1lido" + }, + "error": { + "cannot_connect": "Ha fallado la conexi\u00f3n", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "url": "URL", + "username": "Nombre de usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/he.json b/homeassistant/components/qnap_qsw/translations/he.json new file mode 100644 index 00000000000..fbe984e0b32 --- /dev/null +++ b/homeassistant/components/qnap_qsw/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" + }, + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/ja.json b/homeassistant/components/qnap_qsw/translations/ja.json new file mode 100644 index 00000000000..0658405fd3f --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/ja.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "invalid_id": "\u30c7\u30d0\u30a4\u30b9\u304c\u7121\u52b9\u306a\u30e6\u30cb\u30fc\u30afID\u3092\u8fd4\u3057\u307e\u3057\u305f" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c" + }, + "step": { + "user": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "url": "URL", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/sv.json b/homeassistant/components/qnap_qsw/translations/sv.json new file mode 100644 index 00000000000..416ef964cf3 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/sv.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad", + "invalid_id": "Enheten returnerade ett ogiltigt unikt ID" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/tr.json b/homeassistant/components/qnap_qsw/translations/tr.json new file mode 100644 index 00000000000..309f2da3a90 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "invalid_id": "Cihaz ge\u00e7ersiz bir benzersiz kimlik d\u00f6nd\u00fcrd\u00fc" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "url": "URL", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/zh-Hans.json b/homeassistant/components/qnap_qsw/translations/zh-Hans.json new file mode 100644 index 00000000000..84c6b9f4026 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/zh-Hans.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e", + "invalid_id": "\u8bbe\u5907\u8fd4\u56de\u4e86\u65e0\u6548\u7684\u552f\u4e00 ID" + }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "invalid_auth": "\u51ed\u8bc1\u65e0\u6548" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u7801", + "url": "\u4e3b\u673a\u5730\u5740", + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index 66a50bf98d9..6715d1ba6db 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.1.0", "pyzbar==0.1.7"], + "requirements": ["pillow==9.1.1", "pyzbar==0.1.7"], "codeowners": [], "iot_class": "calculated", "loggers": ["pyzbar"] diff --git a/homeassistant/components/qwikswitch/__init__.py b/homeassistant/components/qwikswitch/__init__.py index e529483d0cc..2df4a2ab73e 100644 --- a/homeassistant/components/qwikswitch/__init__.py +++ b/homeassistant/components/qwikswitch/__init__.py @@ -21,7 +21,10 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType @@ -150,7 +153,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: def callback_value_changed(_qsd, qsid, _val): """Update entity values based on device change.""" _LOGGER.debug("Dispatch %s (update from devices)", qsid) - hass.helpers.dispatcher.async_dispatcher_send(qsid, None) + async_dispatcher_send(hass, qsid, None) session = async_get_clientsession(hass) qsusb = QSUsb( @@ -221,7 +224,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if qspacket[QS_ID] in sensor_ids: _LOGGER.debug("Dispatch %s ((%s))", qspacket[QS_ID], qspacket) - hass.helpers.dispatcher.async_dispatcher_send(qspacket[QS_ID], qspacket) + async_dispatcher_send(hass, qspacket[QS_ID], qspacket) # Update all ha_objects hass.async_add_job(qsusb.update_from_devices) diff --git a/homeassistant/components/rachio/translations/es.json b/homeassistant/components/rachio/translations/es.json index 7e4a03c138a..f3821b7aa5c 100644 --- a/homeassistant/components/rachio/translations/es.json +++ b/homeassistant/components/rachio/translations/es.json @@ -4,8 +4,8 @@ "already_configured": "El dispositivo ya est\u00e1 configurado" }, "error": { - "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.", - "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", "unknown": "Error inesperado" }, "step": { @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "manual_run_mins": "Durante cu\u00e1nto tiempo, en minutos, permanece encendida una estaci\u00f3n cuando el interruptor est\u00e1 activado." + "manual_run_mins": "Duraci\u00f3n en minutos a ejecutar cuando se active un interruptor de zona" } } } diff --git a/homeassistant/components/radio_browser/translations/es.json b/homeassistant/components/radio_browser/translations/es.json index 6bb377d2d28..aa8e6336550 100644 --- a/homeassistant/components/radio_browser/translations/es.json +++ b/homeassistant/components/radio_browser/translations/es.json @@ -2,6 +2,11 @@ "config": { "abort": { "single_instance_allowed": "Ya est\u00e1 configurado. Solamente una configuraci\u00f3n es posible." + }, + "step": { + "user": { + "description": "\u00bfQuieres a\u00f1adir el navegador radio a Home Assistant?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/nl.json b/homeassistant/components/radio_browser/translations/nl.json index d8f46a3130b..a4e4d7a6a33 100644 --- a/homeassistant/components/radio_browser/translations/nl.json +++ b/homeassistant/components/radio_browser/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "user": { diff --git a/homeassistant/components/rainforest_eagle/data.py b/homeassistant/components/rainforest_eagle/data.py index 52f40e81d40..c7ef596bb61 100644 --- a/homeassistant/components/rainforest_eagle/data.py +++ b/homeassistant/components/rainforest_eagle/data.py @@ -7,8 +7,8 @@ import logging import aioeagle import aiohttp import async_timeout +from eagle100 import Eagle as Eagle100Reader from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout -from uEagle import Eagle as Eagle100Reader from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_TYPE diff --git a/homeassistant/components/rainforest_eagle/manifest.json b/homeassistant/components/rainforest_eagle/manifest.json index b4fbc78f241..b875a7f1ff4 100644 --- a/homeassistant/components/rainforest_eagle/manifest.json +++ b/homeassistant/components/rainforest_eagle/manifest.json @@ -2,8 +2,8 @@ "domain": "rainforest_eagle", "name": "Rainforest Eagle", "documentation": "https://www.home-assistant.io/integrations/rainforest_eagle", - "requirements": ["aioeagle==1.1.0", "uEagle==0.0.2"], - "codeowners": ["@gtdiehl", "@jcalbert"], + "requirements": ["aioeagle==1.1.0", "eagle100==0.1.1"], + "codeowners": ["@gtdiehl", "@jcalbert", "@hastarin"], "iot_class": "local_polling", "config_flow": true, "dhcp": [ @@ -11,5 +11,5 @@ "macaddress": "D8D5B9*" } ], - "loggers": ["aioeagle", "uEagle"] + "loggers": ["aioeagle", "eagle100"] } diff --git a/homeassistant/components/rainforest_eagle/translations/ko.json b/homeassistant/components/rainforest_eagle/translations/ko.json new file mode 100644 index 00000000000..2da0f2c8ee1 --- /dev/null +++ b/homeassistant/components/rainforest_eagle/translations/ko.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "\ub514\ubc14\uc774\uc2a4\uac00 \uc774\ubbf8 \uc124\uc815\ub418\uc5b4 \uc788\uc74c" + }, + "error": { + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index d212f1638b4..6d51be9d921 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -50,10 +50,7 @@ from .const import ( LOGGER, ) -DEFAULT_ATTRIBUTION = "Data provided by Green Electronics LLC" -DEFAULT_ICON = "mdi:water" DEFAULT_SSL = True -DEFAULT_UPDATE_INTERVAL = timedelta(seconds=15) CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) diff --git a/homeassistant/components/rainmachine/binary_sensor.py b/homeassistant/components/rainmachine/binary_sensor.py index fb404adb199..1818222a8f4 100644 --- a/homeassistant/components/rainmachine/binary_sensor.py +++ b/homeassistant/components/rainmachine/binary_sensor.py @@ -1,6 +1,5 @@ """This platform provides binary sensors for key RainMachine data.""" from dataclasses import dataclass -from functools import partial from homeassistant.components.binary_sensor import ( BinarySensorEntity, @@ -21,6 +20,7 @@ from .const import ( DOMAIN, ) from .model import RainMachineDescriptionMixinApiCategory +from .util import key_exists TYPE_FLOW_SENSOR = "flow_sensor" TYPE_FREEZE = "freeze" @@ -46,6 +46,7 @@ BINARY_SENSOR_DESCRIPTIONS = ( name="Flow Sensor", icon="mdi:water-pump", api_category=DATA_PROVISION_SETTINGS, + data_key="useFlowSensor", ), RainMachineBinarySensorDescription( key=TYPE_FREEZE, @@ -53,6 +54,7 @@ BINARY_SENSOR_DESCRIPTIONS = ( icon="mdi:cancel", entity_category=EntityCategory.DIAGNOSTIC, api_category=DATA_RESTRICTIONS_CURRENT, + data_key="freeze", ), RainMachineBinarySensorDescription( key=TYPE_FREEZE_PROTECTION, @@ -60,6 +62,7 @@ BINARY_SENSOR_DESCRIPTIONS = ( icon="mdi:weather-snowy", entity_category=EntityCategory.DIAGNOSTIC, api_category=DATA_RESTRICTIONS_UNIVERSAL, + data_key="freezeProtectEnabled", ), RainMachineBinarySensorDescription( key=TYPE_HOT_DAYS, @@ -67,6 +70,7 @@ BINARY_SENSOR_DESCRIPTIONS = ( icon="mdi:thermometer-lines", entity_category=EntityCategory.DIAGNOSTIC, api_category=DATA_RESTRICTIONS_UNIVERSAL, + data_key="hotDaysExtraWatering", ), RainMachineBinarySensorDescription( key=TYPE_HOURLY, @@ -75,6 +79,7 @@ BINARY_SENSOR_DESCRIPTIONS = ( entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, api_category=DATA_RESTRICTIONS_CURRENT, + data_key="hourly", ), RainMachineBinarySensorDescription( key=TYPE_MONTH, @@ -83,6 +88,7 @@ BINARY_SENSOR_DESCRIPTIONS = ( entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, api_category=DATA_RESTRICTIONS_CURRENT, + data_key="month", ), RainMachineBinarySensorDescription( key=TYPE_RAINDELAY, @@ -91,6 +97,7 @@ BINARY_SENSOR_DESCRIPTIONS = ( entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, api_category=DATA_RESTRICTIONS_CURRENT, + data_key="rainDelay", ), RainMachineBinarySensorDescription( key=TYPE_RAINSENSOR, @@ -99,6 +106,7 @@ BINARY_SENSOR_DESCRIPTIONS = ( entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, api_category=DATA_RESTRICTIONS_CURRENT, + data_key="rainSensor", ), RainMachineBinarySensorDescription( key=TYPE_WEEKDAY, @@ -107,6 +115,7 @@ BINARY_SENSOR_DESCRIPTIONS = ( entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, api_category=DATA_RESTRICTIONS_CURRENT, + data_key="weekDay", ), ) @@ -118,35 +127,20 @@ async def async_setup_entry( controller = hass.data[DOMAIN][entry.entry_id][DATA_CONTROLLER] coordinators = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] - @callback - 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( - ProvisionSettingsBinarySensor, - entry, - coordinators[DATA_PROVISION_SETTINGS], - ) - - if api_category == DATA_RESTRICTIONS_CURRENT: - return partial( - CurrentRestrictionsBinarySensor, - entry, - coordinators[DATA_RESTRICTIONS_CURRENT], - ) - - return partial( - UniversalRestrictionsBinarySensor, - entry, - coordinators[DATA_RESTRICTIONS_UNIVERSAL], - ) + api_category_sensor_map = { + DATA_PROVISION_SETTINGS: ProvisionSettingsBinarySensor, + DATA_RESTRICTIONS_CURRENT: CurrentRestrictionsBinarySensor, + DATA_RESTRICTIONS_UNIVERSAL: UniversalRestrictionsBinarySensor, + } async_add_entities( [ - async_get_sensor_by_api_category(description.api_category)( - controller, description + api_category_sensor_map[description.api_category]( + entry, coordinator, controller, description ) for description in BINARY_SENSOR_DESCRIPTIONS + if (coordinator := coordinators[description.api_category]) is not None + and key_exists(coordinator.data, description.data_key) ] ) @@ -158,17 +152,17 @@ class CurrentRestrictionsBinarySensor(RainMachineEntity, BinarySensorEntity): def update_from_latest_data(self) -> None: """Update the state.""" if self.entity_description.key == TYPE_FREEZE: - self._attr_is_on = self.coordinator.data["freeze"] + self._attr_is_on = self.coordinator.data.get("freeze") elif self.entity_description.key == TYPE_HOURLY: - self._attr_is_on = self.coordinator.data["hourly"] + self._attr_is_on = self.coordinator.data.get("hourly") elif self.entity_description.key == TYPE_MONTH: - self._attr_is_on = self.coordinator.data["month"] + self._attr_is_on = self.coordinator.data.get("month") elif self.entity_description.key == TYPE_RAINDELAY: - self._attr_is_on = self.coordinator.data["rainDelay"] + self._attr_is_on = self.coordinator.data.get("rainDelay") elif self.entity_description.key == TYPE_RAINSENSOR: - self._attr_is_on = self.coordinator.data["rainSensor"] + self._attr_is_on = self.coordinator.data.get("rainSensor") elif self.entity_description.key == TYPE_WEEKDAY: - self._attr_is_on = self.coordinator.data["weekDay"] + self._attr_is_on = self.coordinator.data.get("weekDay") class ProvisionSettingsBinarySensor(RainMachineEntity, BinarySensorEntity): @@ -188,6 +182,6 @@ class UniversalRestrictionsBinarySensor(RainMachineEntity, BinarySensorEntity): def update_from_latest_data(self) -> None: """Update the state.""" if self.entity_description.key == TYPE_FREEZE_PROTECTION: - self._attr_is_on = self.coordinator.data["freezeProtectEnabled"] + self._attr_is_on = self.coordinator.data.get("freezeProtectEnabled") elif self.entity_description.key == TYPE_HOT_DAYS: - self._attr_is_on = self.coordinator.data["hotDaysExtraWatering"] + self._attr_is_on = self.coordinator.data.get("hotDaysExtraWatering") diff --git a/homeassistant/components/rainmachine/manifest.json b/homeassistant/components/rainmachine/manifest.json index 331f191d029..98dc9a6c877 100644 --- a/homeassistant/components/rainmachine/manifest.json +++ b/homeassistant/components/rainmachine/manifest.json @@ -3,7 +3,7 @@ "name": "RainMachine", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/rainmachine", - "requirements": ["regenmaschine==2022.01.0"], + "requirements": ["regenmaschine==2022.05.1"], "codeowners": ["@bachya"], "iot_class": "local_polling", "homekit": { diff --git a/homeassistant/components/rainmachine/model.py b/homeassistant/components/rainmachine/model.py index 9f638d486aa..680a47c5d42 100644 --- a/homeassistant/components/rainmachine/model.py +++ b/homeassistant/components/rainmachine/model.py @@ -7,6 +7,7 @@ class RainMachineDescriptionMixinApiCategory: """Define an entity description mixin for binary and regular sensors.""" api_category: str + data_key: str @dataclass diff --git a/homeassistant/components/rainmachine/sensor.py b/homeassistant/components/rainmachine/sensor.py index b825faca7e1..522c57cf7a2 100644 --- a/homeassistant/components/rainmachine/sensor.py +++ b/homeassistant/components/rainmachine/sensor.py @@ -3,7 +3,6 @@ from __future__ import annotations from dataclasses import dataclass from datetime import datetime, timedelta -from functools import partial from homeassistant.components.sensor import ( SensorDeviceClass, @@ -33,6 +32,7 @@ from .model import ( RainMachineDescriptionMixinApiCategory, RainMachineDescriptionMixinUid, ) +from .util import key_exists DEFAULT_ZONE_COMPLETION_TIME_WOBBLE_TOLERANCE = timedelta(seconds=5) @@ -68,6 +68,7 @@ SENSOR_DESCRIPTIONS = ( entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, api_category=DATA_PROVISION_SETTINGS, + data_key="flowSensorClicksPerCubicMeter", ), RainMachineSensorDescriptionApiCategory( key=TYPE_FLOW_SENSOR_CONSUMED_LITERS, @@ -78,6 +79,7 @@ SENSOR_DESCRIPTIONS = ( entity_registry_enabled_default=False, state_class=SensorStateClass.TOTAL_INCREASING, api_category=DATA_PROVISION_SETTINGS, + data_key="flowSensorWateringClicks", ), RainMachineSensorDescriptionApiCategory( key=TYPE_FLOW_SENSOR_START_INDEX, @@ -87,6 +89,7 @@ SENSOR_DESCRIPTIONS = ( native_unit_of_measurement="index", entity_registry_enabled_default=False, api_category=DATA_PROVISION_SETTINGS, + data_key="flowSensorStartIndex", ), RainMachineSensorDescriptionApiCategory( key=TYPE_FLOW_SENSOR_WATERING_CLICKS, @@ -97,6 +100,7 @@ SENSOR_DESCRIPTIONS = ( entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, api_category=DATA_PROVISION_SETTINGS, + data_key="flowSensorWateringClicks", ), RainMachineSensorDescriptionApiCategory( key=TYPE_FREEZE_TEMP, @@ -107,6 +111,7 @@ SENSOR_DESCRIPTIONS = ( device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, api_category=DATA_RESTRICTIONS_UNIVERSAL, + data_key="freezeProtectTemp", ), ) @@ -118,27 +123,18 @@ async def async_setup_entry( controller = hass.data[DOMAIN][entry.entry_id][DATA_CONTROLLER] coordinators = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] - @callback - 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( - ProvisionSettingsSensor, - entry, - coordinators[DATA_PROVISION_SETTINGS], - ) - - return partial( - UniversalRestrictionsSensor, - entry, - coordinators[DATA_RESTRICTIONS_UNIVERSAL], - ) + api_category_sensor_map = { + DATA_PROVISION_SETTINGS: ProvisionSettingsSensor, + DATA_RESTRICTIONS_UNIVERSAL: UniversalRestrictionsSensor, + } sensors = [ - async_get_sensor_by_api_category(description.api_category)( - controller, description + api_category_sensor_map[description.api_category]( + entry, coordinator, controller, description ) for description in SENSOR_DESCRIPTIONS + if (coordinator := coordinators[description.api_category]) is not None + and key_exists(coordinator.data, description.data_key) ] zone_coordinator = coordinators[DATA_ZONES] @@ -198,7 +194,7 @@ class UniversalRestrictionsSensor(RainMachineEntity, SensorEntity): def update_from_latest_data(self) -> None: """Update the state.""" if self.entity_description.key == TYPE_FREEZE_TEMP: - self._attr_native_value = self.coordinator.data["freezeProtectTemp"] + self._attr_native_value = self.coordinator.data.get("freezeProtectTemp") class ZoneTimeRemainingSensor(RainMachineEntity, SensorEntity): diff --git a/homeassistant/components/rainmachine/switch.py b/homeassistant/components/rainmachine/switch.py index 007aec97a3e..8d339682305 100644 --- a/homeassistant/components/rainmachine/switch.py +++ b/homeassistant/components/rainmachine/switch.py @@ -389,23 +389,32 @@ class RainMachineZone(RainMachineActivitySwitch): self._attr_is_on = bool(data["state"]) - self._attr_extra_state_attributes.update( - { - 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["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_VEGETATION_TYPE: VEGETATION_MAP.get(data["type"], 99), - } - ) + attrs = { + ATTR_CURRENT_CYCLE: data["cycle"], + ATTR_ID: data["uid"], + ATTR_NO_CYCLES: data["noOfCycles"], + 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_VEGETATION_TYPE: VEGETATION_MAP.get(data["type"], 99), + } + + if "waterSense" in data: + if "area" in data["waterSense"]: + attrs[ATTR_AREA] = round(data["waterSense"]["area"], 2) + if "fieldCapacity" in data["waterSense"]: + attrs[ATTR_FIELD_CAPACITY] = round( + data["waterSense"]["fieldCapacity"], 2 + ) + if "precipitationRate" in data["waterSense"]: + attrs[ATTR_PRECIP_RATE] = round( + data["waterSense"]["precipitationRate"], 2 + ) + + self._attr_extra_state_attributes.update(attrs) class RainMachineZoneEnabled(RainMachineEnabledSwitch): diff --git a/homeassistant/components/rainmachine/translations/bg.json b/homeassistant/components/rainmachine/translations/bg.json index b54660f8e9f..1239915231b 100644 --- a/homeassistant/components/rainmachine/translations/bg.json +++ b/homeassistant/components/rainmachine/translations/bg.json @@ -4,7 +4,7 @@ "step": { "user": { "data": { - "ip_address": "\u0410\u0434\u0440\u0435\u0441", + "ip_address": "\u0418\u043c\u0435 \u043d\u0430 \u0445\u043e\u0441\u0442 \u0438\u043b\u0438 IP \u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u0430", "port": "\u041f\u043e\u0440\u0442" }, diff --git a/homeassistant/components/rainmachine/translations/es.json b/homeassistant/components/rainmachine/translations/es.json index 317339ed39f..3e13d925b34 100644 --- a/homeassistant/components/rainmachine/translations/es.json +++ b/homeassistant/components/rainmachine/translations/es.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "ip_address": "Nombre de host o direcci\u00f3n IP", + "ip_address": "Nombre del host o direcci\u00f3n IP", "password": "Contrase\u00f1a", "port": "Puerto" }, diff --git a/homeassistant/components/rainmachine/translations/ko.json b/homeassistant/components/rainmachine/translations/ko.json index 0e38f4c4dfa..220cb38d8c5 100644 --- a/homeassistant/components/rainmachine/translations/ko.json +++ b/homeassistant/components/rainmachine/translations/ko.json @@ -6,6 +6,7 @@ "error": { "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, + "flow_title": "{ip}", "step": { "user": { "data": { diff --git a/homeassistant/components/rainmachine/util.py b/homeassistant/components/rainmachine/util.py new file mode 100644 index 00000000000..27a0636688e --- /dev/null +++ b/homeassistant/components/rainmachine/util.py @@ -0,0 +1,14 @@ +"""Define RainMachine utilities.""" +from __future__ import annotations + +from typing import Any + + +def key_exists(data: dict[str, Any], search_key: str) -> bool: + """Return whether a key exists in a nested dict.""" + for key, value in data.items(): + if key == search_key: + return True + if isinstance(value, dict): + return key_exists(value, search_key) + return False diff --git a/homeassistant/components/raspberry_pi/__init__.py b/homeassistant/components/raspberry_pi/__init__.py new file mode 100644 index 00000000000..ab1114722c6 --- /dev/null +++ b/homeassistant/components/raspberry_pi/__init__.py @@ -0,0 +1,26 @@ +"""The Raspberry Pi integration.""" +from __future__ import annotations + +from homeassistant.components.hassio import get_os_info +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up a Raspberry Pi config entry.""" + if (os_info := get_os_info(hass)) is None: + # The hassio integration has not yet fetched data from the supervisor + raise ConfigEntryNotReady + + board: str + if (board := os_info.get("board")) is None or not board.startswith("rpi"): + # Not running on a Raspberry Pi, Home Assistant may have been migrated + hass.async_create_task(hass.config_entries.async_remove(entry.entry_id)) + return False + + await hass.config_entries.flow.async_init( + "rpi_power", context={"source": "onboarding"} + ) + + return True diff --git a/homeassistant/components/raspberry_pi/config_flow.py b/homeassistant/components/raspberry_pi/config_flow.py new file mode 100644 index 00000000000..db0f8643e5c --- /dev/null +++ b/homeassistant/components/raspberry_pi/config_flow.py @@ -0,0 +1,22 @@ +"""Config flow for the Raspberry Pi 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 DOMAIN + + +class RaspberryPiConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Raspberry Pi.""" + + VERSION = 1 + + async def async_step_system(self, data: dict[str, Any] | None = None) -> FlowResult: + """Handle the initial step.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + return self.async_create_entry(title="Raspberry Pi", data={}) diff --git a/homeassistant/components/raspberry_pi/const.py b/homeassistant/components/raspberry_pi/const.py new file mode 100644 index 00000000000..48c004a6447 --- /dev/null +++ b/homeassistant/components/raspberry_pi/const.py @@ -0,0 +1,3 @@ +"""Constants for the Raspberry Pi integration.""" + +DOMAIN = "raspberry_pi" diff --git a/homeassistant/components/raspberry_pi/hardware.py b/homeassistant/components/raspberry_pi/hardware.py new file mode 100644 index 00000000000..343ba69d76b --- /dev/null +++ b/homeassistant/components/raspberry_pi/hardware.py @@ -0,0 +1,54 @@ +"""The Raspberry Pi hardware platform.""" +from __future__ import annotations + +from homeassistant.components.hardware.models import BoardInfo, HardwareInfo +from homeassistant.components.hassio import get_os_info +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError + +from .const import DOMAIN + +BOARD_NAMES = { + "rpi": "Raspberry Pi", + "rpi0": "Raspberry Pi Zero", + "rpi0-w": "Raspberry Pi Zero W", + "rpi2": "Raspberry Pi 2", + "rpi3": "Raspberry Pi 3 (32-bit)", + "rpi3-64": "Raspberry Pi 3", + "rpi4": "Raspberry Pi 4 (32-bit)", + "rpi4-64": "Raspberry Pi 4", +} + +MODELS = { + "rpi": "1", + "rpi0": "zero", + "rpi0-w": "zero_w", + "rpi2": "2", + "rpi3": "3", + "rpi3-64": "3", + "rpi4": "4", + "rpi4-64": "4", +} + + +@callback +def async_info(hass: HomeAssistant) -> HardwareInfo: + """Return board info.""" + if (os_info := get_os_info(hass)) is None: + raise HomeAssistantError + board: str + if (board := os_info.get("board")) is None: + raise HomeAssistantError + if not board.startswith("rpi"): + raise HomeAssistantError + + return HardwareInfo( + board=BoardInfo( + hassio_board_id=board, + manufacturer=DOMAIN, + model=MODELS.get(board), + revision=None, + ), + name=BOARD_NAMES.get(board, f"Unknown Raspberry Pi model '{board}'"), + url=None, + ) diff --git a/homeassistant/components/raspberry_pi/manifest.json b/homeassistant/components/raspberry_pi/manifest.json new file mode 100644 index 00000000000..5ba4f87e783 --- /dev/null +++ b/homeassistant/components/raspberry_pi/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "raspberry_pi", + "name": "Raspberry Pi", + "config_flow": false, + "documentation": "https://www.home-assistant.io/integrations/raspberry_pi", + "dependencies": ["hardware", "hassio"], + "codeowners": ["@home-assistant/core"], + "integration_type": "hardware" +} diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 1ff7c886c35..4063e443e8b 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -1,142 +1,39 @@ """Support for recording details.""" from __future__ import annotations -import abc -import asyncio -from collections.abc import Callable, Iterable -from dataclasses import dataclass -from datetime import datetime, timedelta import logging -import queue -import sqlite3 -import threading -import time -from typing import Any, TypeVar, cast +from typing import Any -from lru import LRU # pylint: disable=no-name-in-module -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 import voluptuous as vol -from homeassistant.components import persistent_notification -from homeassistant.const import ( - ATTR_ENTITY_ID, - CONF_EXCLUDE, - EVENT_HOMEASSISTANT_FINAL_WRITE, - EVENT_HOMEASSISTANT_STARTED, - EVENT_HOMEASSISTANT_STOP, - EVENT_STATE_CHANGED, - MATCH_ALL, -) -from homeassistant.core import ( - CALLBACK_TYPE, - CoreState, - Event, - HomeAssistant, - ServiceCall, - callback, -) +from homeassistant.const import CONF_EXCLUDE, EVENT_STATE_CHANGED +from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import ( INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA, INCLUDE_EXCLUDE_FILTER_SCHEMA_INNER, convert_include_exclude_filter, - generate_filter, -) -from homeassistant.helpers.event import ( - async_track_time_change, - async_track_time_interval, - async_track_utc_time_change, ) from homeassistant.helpers.integration_platform import ( async_process_integration_platforms, ) -from homeassistant.helpers.service import async_extract_entity_ids from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -import homeassistant.util.dt as dt_util -from . import history, migration, purge, statistics, websocket_api +from . import statistics, websocket_api from .const import ( CONF_DB_INTEGRITY_CHECK, DATA_INSTANCE, - DB_WORKER_PREFIX, DOMAIN, - MAX_QUEUE_BACKLOG, + EXCLUDE_ATTRIBUTES, SQLITE_URL_PREFIX, ) -from .executor import DBInterruptibleThreadPoolExecutor -from .models import ( - SCHEMA_VERSION, - Base, - Events, - StateAttributes, - States, - StatisticsRuns, - process_timestamp, -) -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, - periodic_db_cleanups, - session_scope, - setup_connection_for_dialect, - validate_or_move_away_sqlite_database, - write_lock_db_sqlite, -) +from .core import Recorder +from .services import async_register_services +from .tasks import AddRecorderPlatformTask _LOGGER = logging.getLogger(__name__) -T = TypeVar("T") - -EXCLUDE_ATTRIBUTES = f"{DOMAIN}_exclude_attributes_by_domain" - -SERVICE_PURGE = "purge" -SERVICE_PURGE_ENTITIES = "purge_entities" -SERVICE_ENABLE = "enable" -SERVICE_DISABLE = "disable" - -ATTR_KEEP_DAYS = "keep_days" -ATTR_REPACK = "repack" -ATTR_APPLY_FILTER = "apply_filter" - -SERVICE_PURGE_SCHEMA = vol.Schema( - { - vol.Optional(ATTR_KEEP_DAYS): cv.positive_int, - vol.Optional(ATTR_REPACK, default=False): cv.boolean, - vol.Optional(ATTR_APPLY_FILTER, default=False): cv.boolean, - } -) - -ATTR_DOMAINS = "domains" -ATTR_ENTITY_GLOBS = "entity_globs" - -SERVICE_PURGE_ENTITIES_SCHEMA = vol.Schema( - { - vol.Optional(ATTR_DOMAINS, default=[]): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(ATTR_ENTITY_GLOBS, default=[]): vol.All( - cv.ensure_list, [cv.string] - ), - } -).extend(cv.ENTITY_SERVICE_FIELDS) -SERVICE_ENABLE_SCHEMA = vol.Schema({}) -SERVICE_DISABLE_SCHEMA = vol.Schema({}) DEFAULT_URL = "sqlite:///{hass_config_path}" DEFAULT_DB_FILE = "home-assistant_v2.db" @@ -144,25 +41,6 @@ DEFAULT_DB_INTEGRITY_CHECK = True DEFAULT_DB_MAX_RETRIES = 10 DEFAULT_DB_RETRY_WAIT = 3 DEFAULT_COMMIT_INTERVAL = 1 -KEEPALIVE_TIME = 30 - -# Controls how often we clean up -# States and Events objects -EXPIRE_AFTER_COMMITS = 120 - -# The number of attribute ids to cache in memory -# -# Based on: -# - The number of overlapping attributes -# - How frequently states with overlapping attributes will change -# - How much memory our low end hardware has -STATE_ATTRIBUTES_ID_CACHE_SIZE = 2048 - -SHUTDOWN_TASK = object() - - -DB_LOCK_TIMEOUT = 30 -DB_LOCK_QUEUE_CHECK_TIMEOUT = 1 CONF_AUTO_PURGE = "auto_purge" CONF_AUTO_REPACK = "auto_repack" @@ -174,8 +52,6 @@ CONF_PURGE_INTERVAL = "purge_interval" CONF_EVENT_TYPES = "event_types" CONF_COMMIT_INTERVAL = "commit_interval" -INVALIDATED_ERR = "Database connection invalidated" -CONNECTIVITY_ERR = "Error in database connectivity during commit" EXCLUDE_SCHEMA = INCLUDE_EXCLUDE_FILTER_SCHEMA_INNER.extend( {vol.Optional(CONF_EVENT_TYPES): vol.All(cv.ensure_list, [cv.string])} @@ -232,10 +108,6 @@ CONFIG_SCHEMA = vol.Schema( ) -# Pool size must accommodate Recorder thread + All db executors -MAX_DB_EXECUTOR_WORKERS = POOL_SIZE - 1 - - def get_instance(hass: HomeAssistant) -> Recorder: """Get the recorder instance.""" instance: Recorder = hass.data[DATA_INSTANCE] @@ -289,13 +161,11 @@ 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() instance.start() - _async_register_services(hass, instance) - history.async_setup(hass) + async_register_services(hass, instance) statistics.async_setup(hass) websocket_api.async_setup(hass) await async_process_integration_platforms(hass, DOMAIN, _process_recorder_platform) @@ -308,1208 +178,4 @@ async def _process_recorder_platform( ) -> None: """Process a recorder platform.""" instance: Recorder = hass.data[DATA_INSTANCE] - instance.queue.put(AddRecorderPlatformTask(domain, platform)) - - -@callback -def _async_register_services(hass: HomeAssistant, instance: Recorder) -> None: - """Register recorder services.""" - - async def async_handle_purge_service(service: ServiceCall) -> None: - """Handle calls to the purge service.""" - instance.do_adhoc_purge(**service.data) - - hass.services.async_register( - DOMAIN, SERVICE_PURGE, async_handle_purge_service, schema=SERVICE_PURGE_SCHEMA - ) - - async def async_handle_purge_entities_service(service: ServiceCall) -> None: - """Handle calls to the purge entities service.""" - entity_ids = await async_extract_entity_ids(hass, service) - domains = service.data.get(ATTR_DOMAINS, []) - entity_globs = service.data.get(ATTR_ENTITY_GLOBS, []) - - instance.do_adhoc_purge_entities(entity_ids, domains, entity_globs) - - hass.services.async_register( - DOMAIN, - SERVICE_PURGE_ENTITIES, - async_handle_purge_entities_service, - schema=SERVICE_PURGE_ENTITIES_SCHEMA, - ) - - async def async_handle_enable_service(service: ServiceCall) -> None: - instance.set_enable(True) - - hass.services.async_register( - DOMAIN, - SERVICE_ENABLE, - async_handle_enable_service, - schema=SERVICE_ENABLE_SCHEMA, - ) - - async def async_handle_disable_service(service: ServiceCall) -> None: - instance.set_enable(False) - - hass.services.async_register( - DOMAIN, - SERVICE_DISABLE, - async_handle_disable_service, - schema=SERVICE_DISABLE_SCHEMA, - ) - - -class RecorderTask(abc.ABC): - """ABC for recorder tasks.""" - - commit_before = True - - @abc.abstractmethod - def run(self, instance: Recorder) -> None: - """Handle the task.""" - - -@dataclass -class ClearStatisticsTask(RecorderTask): - """Object to store statistics_ids which for which to remove statistics.""" - - statistic_ids: list[str] - - def run(self, instance: Recorder) -> None: - """Handle the task.""" - statistics.clear_statistics(instance, self.statistic_ids) - - -@dataclass -class UpdateStatisticsMetadataTask(RecorderTask): - """Object to store statistics_id and unit for update of statistics metadata.""" - - statistic_id: str - unit_of_measurement: str | None - - def run(self, instance: Recorder) -> None: - """Handle the task.""" - statistics.update_statistics_metadata( - instance, self.statistic_id, self.unit_of_measurement - ) - - -@dataclass -class PurgeTask(RecorderTask): - """Object to store information about purge task.""" - - purge_before: datetime - repack: bool - apply_filter: bool - - 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. - 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)) - - -@dataclass -class PurgeEntitiesTask(RecorderTask): - """Object to store entity information about purge task.""" - - entity_filter: Callable[[str], bool] - - def run(self, instance: Recorder) -> None: - """Purge entities from the database.""" - if purge.purge_entity_data(instance, self.entity_filter): - return - # Schedule a new purge task if this one didn't finish - instance.queue.put(PurgeEntitiesTask(self.entity_filter)) - - -@dataclass -class PerodicCleanupTask(RecorderTask): - """An object to insert into the recorder to trigger cleanup tasks when auto purge is disabled.""" - - def run(self, instance: Recorder) -> None: - """Handle the task.""" - periodic_db_cleanups(instance) - - -@dataclass -class StatisticsTask(RecorderTask): - """An object to insert into the recorder queue to run a statistics task.""" - - start: datetime - - def run(self, instance: Recorder) -> None: - """Run statistics task.""" - if statistics.compile_statistics(instance, self.start): - return - # Schedule a new statistics task if this one didn't finish - instance.queue.put(StatisticsTask(self.start)) - - -@dataclass -class ExternalStatisticsTask(RecorderTask): - """An object to insert into the recorder queue to run an external statistics task.""" - - metadata: dict - statistics: Iterable[dict] - - def run(self, instance: Recorder) -> None: - """Run statistics task.""" - if statistics.add_external_statistics(instance, self.metadata, self.statistics): - return - # Schedule a new statistics task if this one didn't finish - instance.queue.put(ExternalStatisticsTask(self.metadata, self.statistics)) - - -@dataclass -class AdjustStatisticsTask(RecorderTask): - """An object to insert into the recorder queue to run an adjust statistics task.""" - - statistic_id: str - start_time: datetime - sum_adjustment: float - - def run(self, instance: Recorder) -> None: - """Run statistics task.""" - if statistics.adjust_statistics( - instance, - self.statistic_id, - self.start_time, - self.sum_adjustment, - ): - return - # Schedule a new adjust statistics task if this one didn't finish - instance.queue.put( - AdjustStatisticsTask( - self.statistic_id, self.start_time, self.sum_adjustment - ) - ) - - -@dataclass -class WaitTask(RecorderTask): - """An object to insert into the recorder queue to tell it set the _queue_watch event.""" - - commit_before = False - - def run(self, instance: Recorder) -> None: - """Handle the task.""" - instance._queue_watch.set() # pylint: disable=[protected-access] - - -@dataclass -class DatabaseLockTask(RecorderTask): - """An object to insert into the recorder queue to prevent writes to the database.""" - - database_locked: asyncio.Event - database_unlock: threading.Event - queue_overflow: bool - - def run(self, instance: Recorder) -> None: - """Handle the task.""" - instance._lock_database(self) # pylint: disable=[protected-access] - - -@dataclass -class StopTask(RecorderTask): - """An object to insert into the recorder queue to stop the event handler.""" - - commit_before = False - - def run(self, instance: Recorder) -> None: - """Handle the task.""" - instance.stop_requested = True - - -@dataclass -class EventTask(RecorderTask): - """An event to be processed.""" - - event: Event - commit_before = False - - def run(self, instance: Recorder) -> None: - """Handle the task.""" - # pylint: disable-next=[protected-access] - 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.""" - - stop_requested: bool - - def __init__( - self, - hass: HomeAssistant, - auto_purge: bool, - auto_repack: bool, - keep_days: int, - commit_interval: int, - uri: str, - db_max_retries: int, - db_retry_wait: int, - 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.db_url = uri - self.db_max_retries = db_max_retries - self.db_retry_wait = db_retry_wait - self.async_db_ready: asyncio.Future[bool] = asyncio.Future() - self.async_recorder_ready = asyncio.Event() - self._queue_watch = threading.Event() - self.engine: Engine | None = None - self.run_history = RunHistory() - - self.entity_filter = entity_filter - self.exclude_t = exclude_t - - self.schema_version = 0 - self._commits_without_expire = 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 - self._event_listener: CALLBACK_TYPE | None = None - self.async_migration_event = asyncio.Event() - self.migration_in_progress = False - self._queue_watcher: CALLBACK_TYPE | None = None - self._db_supports_row_number = True - self._database_lock_task: DatabaseLockTask | None = None - 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: - """Enable or disable recording events and states.""" - self.enabled = enable - - @callback - def async_start_executor(self) -> None: - """Start the executor.""" - self._db_executor = DBInterruptibleThreadPoolExecutor( - thread_name_prefix=DB_WORKER_PREFIX, - max_workers=MAX_DB_EXECUTOR_WORKERS, - shutdown_hook=self._shutdown_pool, - ) - - def _shutdown_pool(self) -> None: - """Close the dbpool connections in the current thread.""" - if self.engine and hasattr(self.engine.pool, "shutdown"): - self.engine.pool.shutdown() - - @callback - def async_initialize(self) -> None: - """Initialize the recorder.""" - self._event_listener = self.hass.bus.async_listen( - MATCH_ALL, self.event_listener, event_filter=self._async_event_filter - ) - self._queue_watcher = async_track_time_interval( - 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 - ) -> asyncio.Future[T]: - """Add an executor job from within the event loop.""" - return self.hass.loop.run_in_executor(self._db_executor, target, *args) - - def _stop_executor(self) -> None: - """Stop the executor.""" - assert self._db_executor is not None - self._db_executor.shutdown() - self._db_executor = None - - @callback - def _async_check_queue(self, *_: Any) -> None: - """Periodic check of the queue size to ensure we do not exaust memory. - - The queue grows during migraton or if something really goes wrong. - """ - size = self.queue.qsize() - _LOGGER.debug("Recorder queue size is: %s", size) - if size <= MAX_QUEUE_BACKLOG: - return - _LOGGER.error( - "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() - - @callback - def _async_stop_queue_watcher_and_event_listener(self) -> None: - """Stop watching the queue and listening for events.""" - if self._queue_watcher: - self._queue_watcher() - self._queue_watcher = None - if self._event_listener: - 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.""" - if event.event_type in self.exclude_t: - return False - - if (entity_id := event.data.get(ATTR_ENTITY_ID)) is None: - return True - - if isinstance(entity_id, str): - return self.entity_filter(entity_id) - - if isinstance(entity_id, list): - for eid in entity_id: - if self.entity_filter(eid): - return True - return False - - # Unknown what it is. - return True - - def do_adhoc_purge(self, **kwargs: Any) -> None: - """Trigger an adhoc purge retaining keep_days worth of data.""" - keep_days = kwargs.get(ATTR_KEEP_DAYS, self.keep_days) - repack = cast(bool, kwargs[ATTR_REPACK]) - apply_filter = cast(bool, kwargs[ATTR_APPLY_FILTER]) - - purge_before = dt_util.utcnow() - timedelta(days=keep_days) - self.queue.put(PurgeTask(purge_before, repack, apply_filter)) - - def do_adhoc_purge_entities( - self, entity_ids: set[str], domains: list[str], entity_globs: list[str] - ) -> None: - """Trigger an adhoc purge of requested entities.""" - entity_filter = generate_filter(domains, list(entity_ids), [], [], entity_globs) - self.queue.put(PurgeEntitiesTask(entity_filter)) - - def do_adhoc_statistics(self, **kwargs: Any) -> None: - """Trigger an adhoc statistics run.""" - if not (start := kwargs.get("start")): - start = statistics.get_start_time() - self.queue.put(StatisticsTask(start)) - - @callback - def async_register(self) -> None: - """Post connection initialize.""" - - def _empty_queue(event: Event) -> None: - """Empty the queue if its still present at final write.""" - - # If the queue is full of events to be processed because - # the database is so broken that every event results in a retry - # we will never be able to get though the events to shutdown in time. - # - # We drain all the events in the queue and then insert - # an empty one to ensure the next thing the recorder sees - # is a request to shutdown. - while True: - try: - self.queue.get_nowait() - except queue.Empty: - break - self.queue.put(StopTask()) - - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_FINAL_WRITE, _empty_queue) - - async def _async_shutdown(event: Event) -> None: - """Shut down the Recorder.""" - if not self._hass_started.done(): - self._hass_started.set_result(SHUTDOWN_TASK) - self.queue.put(StopTask()) - self._async_stop_listeners() - await self.hass.async_add_executor_job(self.join) - - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_shutdown) - - if self.hass.state == CoreState.running: - self._hass_started.set_result(None) - return - - @callback - def _async_hass_started(event: Event) -> None: - """Notify that hass has started.""" - self._hass_started.set_result(None) - - self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STARTED, _async_hass_started - ) - - @callback - def async_connection_failed(self) -> None: - """Connect failed tasks.""" - self.async_db_ready.set_result(False) - persistent_notification.async_create( - self.hass, - "The recorder could not start, check [the logs](/config/logs)", - "Recorder", - ) - self._async_stop_listeners() - - @callback - def async_connection_success(self) -> None: - """Connect success tasks.""" - self.async_db_ready.set_result(True) - self.async_start_executor() - - @callback - def _async_recorder_ready(self) -> None: - """Finish start and mark recorder ready.""" - self._async_setup_periodic_tasks() - self.async_recorder_ready.set() - - @callback - def async_nightly_tasks(self, now: datetime) -> None: - """Trigger the purge.""" - if self.auto_purge: - # 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=repack, apply_filter=False)) - else: - self.queue.put(PerodicCleanupTask()) - - @callback - def async_periodic_statistics(self, now: datetime) -> None: - """Trigger the statistics run. - - Short term statistics run every 5 minutes - """ - start = statistics.get_start_time() - self.queue.put(StatisticsTask(start)) - - @callback - def async_adjust_statistics( - self, statistic_id: str, start_time: datetime, sum_adjustment: float - ) -> None: - """Adjust statistics.""" - self.queue.put(AdjustStatisticsTask(statistic_id, start_time, sum_adjustment)) - - @callback - def async_clear_statistics(self, statistic_ids: list[str]) -> None: - """Clear statistics for a list of statistic_ids.""" - self.queue.put(ClearStatisticsTask(statistic_ids)) - - @callback - def async_update_statistics_metadata( - self, statistic_id: str, unit_of_measurement: str | None - ) -> None: - """Update statistics metadata for a statistic_id.""" - self.queue.put(UpdateStatisticsMetadataTask(statistic_id, unit_of_measurement)) - - @callback - def async_external_statistics(self, metadata: dict, stats: Iterable[dict]) -> None: - """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.""" - if self.hass.is_stopping or not self.get_session: - # 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 - 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 - self._periodic_listener = async_track_utc_time_change( - self.hass, self.async_periodic_statistics, minute=range(0, 60, 5), second=10 - ) - - async def _async_wait_for_started(self) -> object | None: - """Wait for the hass started future.""" - return await self._hass_started - - def _wait_startup_or_shutdown(self) -> object | None: - """Wait for startup or shutdown before starting.""" - return asyncio.run_coroutine_threadsafe( - self._async_wait_for_started(), self.hass.loop - ).result() - - def run(self) -> None: - """Start processing events to save.""" - current_version = self._setup_recorder() - - if current_version is None: - 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() - else: - self.migration_in_progress = True - - self.hass.add_job(self.async_connection_success) - - # If shutdown happened before Home Assistant finished starting - if self._wait_startup_or_shutdown() is SHUTDOWN_TASK: - self.migration_in_progress = False - # Make sure we cleanly close the run if - # we restart before startup finishes - self._shutdown() - return - - # We wait to start the migration until startup has finished - # since it can be cpu intensive and we do not want it to compete - # 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 - # is reached, we need to reinitialize the listener. - self.hass.add_job(self.async_initialize) - else: - persistent_notification.create( - self.hass, - "The database migration failed, check [the logs](/config/logs)." - "Database Migration Failed", - "recorder_database_migration", - ) - self._shutdown() - return - - _LOGGER.debug("Recorder processing the queue") - self.hass.add_job(self._async_recorder_ready) - self._run_event_loop() - - def _run_event_loop(self) -> None: - """Run the event loop for the recorder.""" - # Use a session for the event read loop - # with a commit every time the event time - # has changed. This reduces the disk io. - 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 - _LOGGER.exception("Error while processing event %s: %s", task, err) - - self._shutdown() - - def _process_one_task_or_recover(self, task: RecorderTask) -> None: - """Process an event, reconnect, or recover a malformed database.""" - try: - # If its not an event, commit everything - # that is pending before running the task - if task.commit_before: - self._commit_event_session_or_retry() - return task.run(self) - except exc.DatabaseError as err: - if self._handle_database_error(err): - return - _LOGGER.exception( - "Unhandled database error while processing task %s: %s", task, err - ) - except SQLAlchemyError as err: - _LOGGER.exception("SQLAlchemyError error processing task %s: %s", task, err) - - # Reset the session if an SQLAlchemyError (including DatabaseError) - # happens to rollback and recover - self._reopen_event_session() - - def _setup_recorder(self) -> None | int: - """Create connect to the database and get the schema version.""" - tries = 1 - - while tries <= self.db_max_retries: - try: - self._setup_connection() - return migration.get_schema_version(self) - except Exception as err: # pylint: disable=broad-except - _LOGGER.exception( - "Error during connection setup: %s (retrying in %s seconds)", - err, - self.db_retry_wait, - ) - tries += 1 - time.sleep(self.db_retry_wait) - - return None - - @callback - def _async_migration_started(self) -> None: - """Set the migration started event.""" - self.async_migration_event.set() - - def _migrate_schema_and_setup_run(self, current_version: int) -> bool: - """Migrate schema to the latest version.""" - persistent_notification.create( - self.hass, - "System performance will temporarily degrade during the database upgrade. Do not power down or restart the system until the upgrade completes. Integrations that read the database, such as logbook and history, may return inconsistent results until the upgrade completes.", - "Database upgrade in progress", - "recorder_database_migration", - ) - self.hass.add_job(self._async_migration_started) - - try: - migration.migrate_schema(self, current_version) - except exc.DatabaseError as err: - if self._handle_database_error(err): - return True - _LOGGER.exception("Database error during schema migration") - return False - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Error during schema migration") - return False - else: - self._setup_run() - return True - finally: - self.migration_in_progress = False - persistent_notification.dismiss(self.hass, "recorder_database_migration") - - def _lock_database(self, task: DatabaseLockTask) -> None: - @callback - def _async_set_database_locked(task: DatabaseLockTask) -> None: - task.database_locked.set() - - with write_lock_db_sqlite(self): - # Notify that lock is being held, wait until database can be used again. - self.hass.add_job(_async_set_database_locked, task) - while not task.database_unlock.wait(timeout=DB_LOCK_QUEUE_CHECK_TIMEOUT): - if self.queue.qsize() > MAX_QUEUE_BACKLOG * 0.9: - _LOGGER.warning( - "Database queue backlog reached more than 90% of maximum queue " - "length while waiting for backup to finish; recorder will now " - "resume writing to database. The backup can not be trusted and " - "must be restarted" - ) - task.queue_overflow = True - break - _LOGGER.info( - "Database queue backlog reached %d entries during backup", - self.queue.qsize(), - ) - - def _process_one_event(self, event: Event) -> None: - 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: - if event.event_type == EVENT_STATE_CHANGED: - dbevent = Events.from_event(event, event_data=None) - else: - dbevent = Events.from_event(event) - except (TypeError, ValueError): - _LOGGER.warning("Event is not JSON serializable: %s", event) - return - - self.event_session.add(dbevent) - if event.event_type != EVENT_STATE_CHANGED: - return - - 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: - 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) - 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.""" - if isinstance(err.__cause__, sqlite3.DatabaseError): - _LOGGER.exception( - "Unrecoverable sqlite3 database corruption detected: %s", err - ) - self._handle_sqlite_corruption() - 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_has_pending_writes(): - return - tries = 1 - while tries <= self.db_max_retries: - try: - self._commit_event_session() - return - except (exc.InternalError, exc.OperationalError) as err: - _LOGGER.error( - "%s: Error executing query: %s. (retrying in %s seconds)", - INVALIDATED_ERR if err.connection_invalidated else CONNECTIVITY_ERR, - err, - self.db_retry_wait, - ) - if tries == self.db_max_retries: - raise - - tries += 1 - time.sleep(self.db_retry_wait) - - def _commit_event_session(self) -> None: - assert self.event_session is not None - self._commits_without_expire += 1 - - if self._pending_expunge: - self.event_session.flush() - for dbstate in self._pending_expunge: - # Expunge the state so its not expired - # until we use it later for dbstate.old_state - if dbstate in self.event_session: - self.event_session.expunge(dbstate) - self._pending_expunge = [] - self.event_session.commit() - - # We just committed the state attributes to the database - # and we now know the attributes_ids. We can save - # many selects for matching attributes by loading them - # into the LRU cache now. - for state_attr in self._pending_state_attributes.values(): - self._state_attributes_ids[ - state_attr.shared_attrs - ] = state_attr.attributes_id - self._pending_state_attributes = {} - - # 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: - self._commits_without_expire = 0 - self.event_session.expire_all() - - def _handle_sqlite_corruption(self) -> None: - """Handle the sqlite3 database being corrupt.""" - 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() - - def _close_event_session(self) -> None: - """Close the event session.""" - self._old_states = {} - self._state_attributes_ids = {} - self._pending_state_attributes = {} - - if not self.event_session: - return - - try: - self.event_session.rollback() - self.event_session.close() - except SQLAlchemyError as err: - _LOGGER.exception( - "Error while rolling back and closing the event session: %s", err - ) - - def _reopen_event_session(self) -> None: - """Rollback the event session and reopen it after a failure.""" - self._close_event_session() - self._open_event_session() - - def _open_event_session(self) -> None: - """Open the event session.""" - assert self.get_session is not None - self.event_session = self.get_session() - self.event_session.expire_on_commit = False - - def _send_keep_alive(self) -> None: - """Send a keep alive to keep the db connection open.""" - assert self.event_session is not None - _LOGGER.debug("Sending keepalive") - self.event_session.connection().scalar(select([1])) - - @callback - def event_listener(self, event: Event) -> None: - """Listen for new events and put them in the process queue.""" - self.queue.put(EventTask(event)) - - def block_till_done(self) -> None: - """Block till all events processed. - - This is only called in tests. - - This only blocks until the queue is empty - which does not mean the recorder is done. - - Call tests.common's wait_recording_done - after calling this to ensure the data - is in the database. - """ - self._queue_watch.clear() - self.queue.put(WaitTask()) - self._queue_watch.wait() - - async def lock_database(self) -> bool: - """Lock database so it can be backed up safely.""" - if not self.using_sqlite(): - _LOGGER.debug( - "Not a SQLite database or not connected, locking not necessary" - ) - return True - - if self._database_lock_task: - _LOGGER.warning("Database already locked") - return False - - database_locked = asyncio.Event() - task = DatabaseLockTask(database_locked, threading.Event(), False) - self.queue.put(task) - try: - await asyncio.wait_for(database_locked.wait(), timeout=DB_LOCK_TIMEOUT) - except asyncio.TimeoutError as err: - task.database_unlock.set() - raise TimeoutError( - f"Could not lock database within {DB_LOCK_TIMEOUT} seconds." - ) from err - self._database_lock_task = task - return True - - @callback - def unlock_database(self) -> bool: - """Unlock database. - - Returns true if database lock has been held throughout the process. - """ - if not self.using_sqlite(): - _LOGGER.debug( - "Not a SQLite database or not connected, unlocking not necessary" - ) - return True - - if not self._database_lock_task: - _LOGGER.warning("Database currently not locked") - return False - - self._database_lock_task.database_unlock.set() - success = not self._database_lock_task.queue_overflow - - self._database_lock_task = None - - return success - - def _setup_connection(self) -> None: - """Ensure database is ready to fly.""" - kwargs: dict[str, Any] = {} - self._completed_first_database_setup = False - - def setup_recorder_connection( - dbapi_connection: Any, connection_record: Any - ) -> None: - """Dbapi specific connection settings.""" - assert self.engine is not None - setup_connection_for_dialect( - self, - self.engine.dialect.name, - dbapi_connection, - not self._completed_first_database_setup, - ) - self._completed_first_database_setup = True - - if self.db_url == SQLITE_URL_PREFIX or ":memory:" in self.db_url: - kwargs["connect_args"] = {"check_same_thread": False} - 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 - else: - kwargs["echo"] = False - - if self._using_file_sqlite: - validate_or_move_away_sqlite_database(self.db_url) - - 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, future=True)) - _LOGGER.debug("Connected to recorder database") - - @property - def _using_file_sqlite(self) -> bool: - """Short version to check if we are using sqlite3 as a file.""" - return self.db_url != SQLITE_URL_PREFIX and self.db_url.startswith( - SQLITE_URL_PREFIX - ) - - def _close_connection(self) -> None: - """Close the connection.""" - assert self.engine is not None - self.engine.dispose() - self.engine = None - self.get_session = None - - def _setup_run(self) -> None: - """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: - end_incomplete_runs(session, self.run_history.recording_start) - self.run_history.start(session) - self._schedule_compile_missing_statistics(session) - - self._open_event_session() - - def _schedule_compile_missing_statistics(self, session: Session) -> None: - """Add tasks for missing statistics runs.""" - now = dt_util.utcnow() - last_period_minutes = now.minute - now.minute % 5 - last_period = now.replace(minute=last_period_minutes, second=0, microsecond=0) - start = now - timedelta(days=self.keep_days) - start = start.replace(minute=0, second=0, microsecond=0) - - # Find the newest statistics run, if any - if last_run := session.query(func.max(StatisticsRuns.start)).scalar(): - start = max(start, process_timestamp(last_run) + timedelta(minutes=5)) - - # Add tasks - while start < last_period: - end = start + timedelta(minutes=5) - _LOGGER.debug("Compiling missing statistics for %s-%s", start, end) - self.queue.put(StatisticsTask(start)) - start = end - - def _end_session(self) -> None: - """End the recorder session.""" - if self.event_session is None: - return - try: - 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_history.clear() - - def _shutdown(self) -> None: - """Save end time for current run.""" - self.hass.add_job(self._async_stop_listeners) - self._stop_executor() - self._end_session() - self._close_connection() - - @property - def recording(self) -> bool: - """Return if the recorder is recording.""" - return self._event_listener is not None + instance.queue_task(AddRecorderPlatformTask(domain, platform)) diff --git a/homeassistant/components/recorder/const.py b/homeassistant/components/recorder/const.py index 593710a10dd..e558d19b530 100644 --- a/homeassistant/components/recorder/const.py +++ b/homeassistant/components/recorder/const.py @@ -4,11 +4,13 @@ from functools import partial import json from typing import Final +from homeassistant.backports.enum import StrEnum from homeassistant.const import ATTR_ATTRIBUTION, ATTR_RESTORED, ATTR_SUPPORTED_FEATURES from homeassistant.helpers.json import JSONEncoder DATA_INSTANCE = "recorder_instance" SQLITE_URL_PREFIX = "sqlite://" +MYSQLDB_URL_PREFIX = "mysql://" DOMAIN = "recorder" CONF_DB_INTEGRITY_CHECK = "db_integrity_check" @@ -28,3 +30,20 @@ DB_WORKER_PREFIX = "DbWorker" JSON_DUMP: Final = partial(json.dumps, cls=JSONEncoder, separators=(",", ":")) ALL_DOMAIN_EXCLUDE_ATTRS = {ATTR_ATTRIBUTION, ATTR_RESTORED, ATTR_SUPPORTED_FEATURES} + +ATTR_KEEP_DAYS = "keep_days" +ATTR_REPACK = "repack" +ATTR_APPLY_FILTER = "apply_filter" + +KEEPALIVE_TIME = 30 + + +EXCLUDE_ATTRIBUTES = f"{DOMAIN}_exclude_attributes_by_domain" + + +class SupportedDialect(StrEnum): + """Supported dialects.""" + + SQLITE = "sqlite" + MYSQL = "mysql" + POSTGRESQL = "postgresql" diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py new file mode 100644 index 00000000000..7df4cf57e56 --- /dev/null +++ b/homeassistant/components/recorder/core.py @@ -0,0 +1,1114 @@ +"""Support for recording details.""" +from __future__ import annotations + +import asyncio +from collections.abc import Callable, Iterable +import contextlib +from datetime import datetime, timedelta +import logging +import queue +import sqlite3 +import threading +import time +from typing import Any, TypeVar, cast + +from awesomeversion import AwesomeVersion +from lru import LRU # pylint: disable=no-name-in-module +from sqlalchemy import create_engine, event as sqlalchemy_event, exc, func, select +from sqlalchemy.engine import Engine +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.orm.session import Session + +from homeassistant.components import persistent_notification +from homeassistant.const import ( + ATTR_ENTITY_ID, + EVENT_HOMEASSISTANT_FINAL_WRITE, + EVENT_HOMEASSISTANT_STARTED, + EVENT_HOMEASSISTANT_STOP, + EVENT_STATE_CHANGED, + MATCH_ALL, +) +from homeassistant.core import CALLBACK_TYPE, CoreState, Event, HomeAssistant, callback +from homeassistant.helpers.event import ( + async_track_time_change, + async_track_time_interval, + async_track_utc_time_change, +) +from homeassistant.helpers.typing import UNDEFINED, UndefinedType +import homeassistant.util.dt as dt_util + +from . import migration, statistics +from .const import ( + DB_WORKER_PREFIX, + KEEPALIVE_TIME, + MAX_QUEUE_BACKLOG, + MYSQLDB_URL_PREFIX, + SQLITE_URL_PREFIX, + SupportedDialect, +) +from .executor import DBInterruptibleThreadPoolExecutor +from .models import ( + SCHEMA_VERSION, + Base, + EventData, + Events, + StateAttributes, + States, + StatisticData, + StatisticMetaData, + StatisticsRuns, + UnsupportedDialect, + process_timestamp, +) +from .pool import POOL_SIZE, MutexPool, RecorderPool +from .queries import find_shared_attributes_id, find_shared_data_id +from .run_history import RunHistory +from .tasks import ( + AdjustStatisticsTask, + ClearStatisticsTask, + CommitTask, + DatabaseLockTask, + EventTask, + ExternalStatisticsTask, + KeepAliveTask, + PerodicCleanupTask, + PurgeTask, + RecorderTask, + StatisticsTask, + StopTask, + SynchronizeTask, + UpdateStatisticsMetadataTask, + WaitTask, +) +from .util import ( + build_mysqldb_conv, + dburl_to_path, + end_incomplete_runs, + is_second_sunday, + move_away_broken_database, + session_scope, + setup_connection_for_dialect, + validate_or_move_away_sqlite_database, + write_lock_db_sqlite, +) + +_LOGGER = logging.getLogger(__name__) + +T = TypeVar("T") + +DEFAULT_URL = "sqlite:///{hass_config_path}" + +# Controls how often we clean up +# States and Events objects +EXPIRE_AFTER_COMMITS = 120 + +# The number of attribute ids to cache in memory +# +# Based on: +# - The number of overlapping attributes +# - How frequently states with overlapping attributes will change +# - How much memory our low end hardware has +STATE_ATTRIBUTES_ID_CACHE_SIZE = 2048 +EVENT_DATA_ID_CACHE_SIZE = 2048 + +SHUTDOWN_TASK = object() + +COMMIT_TASK = CommitTask() +KEEP_ALIVE_TASK = KeepAliveTask() +WAIT_TASK = WaitTask() + +DB_LOCK_TIMEOUT = 30 +DB_LOCK_QUEUE_CHECK_TIMEOUT = 1 + + +INVALIDATED_ERR = "Database connection invalidated" +CONNECTIVITY_ERR = "Error in database connectivity during commit" + +# Pool size must accommodate Recorder thread + All db executors +MAX_DB_EXECUTOR_WORKERS = POOL_SIZE - 1 + + +class Recorder(threading.Thread): + """A threaded recorder class.""" + + stop_requested: bool + + def __init__( + self, + hass: HomeAssistant, + auto_purge: bool, + auto_repack: bool, + keep_days: int, + commit_interval: int, + uri: str, + db_max_retries: int, + db_retry_wait: int, + entity_filter: Callable[[str], bool], + exclude_t: list[str], + exclude_attributes_by_domain: dict[str, set[str]], + ) -> 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.db_url = uri + self.db_max_retries = db_max_retries + self.db_retry_wait = db_retry_wait + self.engine_version: AwesomeVersion | None = None + self.async_db_ready: asyncio.Future[bool] = asyncio.Future() + self.async_recorder_ready = asyncio.Event() + self._queue_watch = threading.Event() + self.engine: Engine | None = None + self.run_history = RunHistory() + + self.entity_filter = entity_filter + self.exclude_t = exclude_t + + self.schema_version = 0 + self._commits_without_expire = 0 + self._old_states: dict[str, States] = {} + self._state_attributes_ids: LRU = LRU(STATE_ATTRIBUTES_ID_CACHE_SIZE) + self._event_data_ids: LRU = LRU(EVENT_DATA_ID_CACHE_SIZE) + self._pending_state_attributes: dict[str, StateAttributes] = {} + self._pending_event_data: dict[str, EventData] = {} + self._pending_expunge: list[States] = [] + self.event_session: Session | None = None + self._get_session: Callable[[], Session] | None = None + self._completed_first_database_setup: bool | None = None + self.async_migration_event = asyncio.Event() + self.migration_in_progress = False + self._database_lock_task: DatabaseLockTask | None = None + self._db_executor: DBInterruptibleThreadPoolExecutor | None = None + self._exclude_attributes_by_domain = exclude_attributes_by_domain + + self._event_listener: CALLBACK_TYPE | None = None + self._queue_watcher: CALLBACK_TYPE | None = None + 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 + + @property + def backlog(self) -> int: + """Return the number of items in the recorder backlog.""" + return self._queue.qsize() + + @property + def dialect_name(self) -> SupportedDialect | None: + """Return the dialect the recorder uses.""" + with contextlib.suppress(ValueError): + return SupportedDialect(self.engine.dialect.name) if self.engine else None + return None + + @property + def _using_file_sqlite(self) -> bool: + """Short version to check if we are using sqlite3 as a file.""" + return self.db_url != SQLITE_URL_PREFIX and self.db_url.startswith( + SQLITE_URL_PREFIX + ) + + @property + def recording(self) -> bool: + """Return if the recorder is recording.""" + return self._event_listener is not None + + def get_session(self) -> Session: + """Get a new sqlalchemy session.""" + if self._get_session is None: + raise RuntimeError("The database connection has not been established") + return self._get_session() + + def queue_task(self, task: RecorderTask) -> None: + """Add a task to the recorder queue.""" + self._queue.put(task) + + def set_enable(self, enable: bool) -> None: + """Enable or disable recording events and states.""" + self.enabled = enable + + @callback + def async_start_executor(self) -> None: + """Start the executor.""" + self._db_executor = DBInterruptibleThreadPoolExecutor( + thread_name_prefix=DB_WORKER_PREFIX, + max_workers=MAX_DB_EXECUTOR_WORKERS, + shutdown_hook=self._shutdown_pool, + ) + + def _shutdown_pool(self) -> None: + """Close the dbpool connections in the current thread.""" + if self.engine and hasattr(self.engine.pool, "shutdown"): + self.engine.pool.shutdown() + + @callback + def async_initialize(self) -> None: + """Initialize the recorder.""" + self._event_listener = self.hass.bus.async_listen( + MATCH_ALL, + self.event_listener, + run_immediately=True, + ) + self._queue_watcher = async_track_time_interval( + 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_task(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_task(COMMIT_TASK) + + @callback + def async_add_executor_job( + self, target: Callable[..., T], *args: Any + ) -> asyncio.Future[T]: + """Add an executor job from within the event loop.""" + return self.hass.loop.run_in_executor(self._db_executor, target, *args) + + def _stop_executor(self) -> None: + """Stop the executor.""" + assert self._db_executor is not None + self._db_executor.shutdown() + self._db_executor = None + + @callback + def _async_check_queue(self, *_: Any) -> None: + """Periodic check of the queue size to ensure we do not exaust memory. + + The queue grows during migraton or if something really goes wrong. + """ + size = self.backlog + _LOGGER.debug("Recorder queue size is: %s", size) + if size <= MAX_QUEUE_BACKLOG: + return + _LOGGER.error( + "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() + + @callback + def _async_stop_queue_watcher_and_event_listener(self) -> None: + """Stop watching the queue and listening for events.""" + if self._queue_watcher: + self._queue_watcher() + self._queue_watcher = None + if self._event_listener: + 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.""" + if event.event_type in self.exclude_t: + return False + + if (entity_id := event.data.get(ATTR_ENTITY_ID)) is None: + return True + + if isinstance(entity_id, str): + return self.entity_filter(entity_id) + + if isinstance(entity_id, list): + for eid in entity_id: + if self.entity_filter(eid): + return True + return False + + # Unknown what it is. + return True + + def do_adhoc_statistics(self, **kwargs: Any) -> None: + """Trigger an adhoc statistics run.""" + if not (start := kwargs.get("start")): + start = statistics.get_start_time() + self.queue_task(StatisticsTask(start)) + + def _empty_queue(self, event: Event) -> None: + """Empty the queue if its still present at final write.""" + + # If the queue is full of events to be processed because + # the database is so broken that every event results in a retry + # we will never be able to get though the events to shutdown in time. + # + # We drain all the events in the queue and then insert + # an empty one to ensure the next thing the recorder sees + # is a request to shutdown. + while True: + try: + self._queue.get_nowait() + except queue.Empty: + break + self.queue_task(StopTask()) + + async def _async_shutdown(self, event: Event) -> None: + """Shut down the Recorder.""" + if not self._hass_started.done(): + self._hass_started.set_result(SHUTDOWN_TASK) + self.queue_task(StopTask()) + self._async_stop_listeners() + await self.hass.async_add_executor_job(self.join) + + @callback + def _async_hass_started(self, event: Event) -> None: + """Notify that hass has started.""" + self._hass_started.set_result(None) + + @callback + def async_register(self) -> None: + """Post connection initialize.""" + bus = self.hass.bus + bus.async_listen_once(EVENT_HOMEASSISTANT_FINAL_WRITE, self._empty_queue) + bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._async_shutdown) + if self.hass.state == CoreState.running: + self._hass_started.set_result(None) + return + bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, self._async_hass_started) + + @callback + def async_connection_failed(self) -> None: + """Connect failed tasks.""" + self.async_db_ready.set_result(False) + persistent_notification.async_create( + self.hass, + "The recorder could not start, check [the logs](/config/logs)", + "Recorder", + ) + self._async_stop_listeners() + + @callback + def async_connection_success(self) -> None: + """Connect success tasks.""" + self.async_db_ready.set_result(True) + self.async_start_executor() + + @callback + def _async_recorder_ready(self) -> None: + """Finish start and mark recorder ready.""" + self._async_setup_periodic_tasks() + self.async_recorder_ready.set() + + @callback + def async_nightly_tasks(self, now: datetime) -> None: + """Trigger the purge.""" + if self.auto_purge: + # 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_task(PurgeTask(purge_before, repack=repack, apply_filter=False)) + else: + self.queue_task(PerodicCleanupTask()) + + @callback + def async_periodic_statistics(self, now: datetime) -> None: + """Trigger the statistics run. + + Short term statistics run every 5 minutes + """ + start = statistics.get_start_time() + self.queue_task(StatisticsTask(start)) + + @callback + def async_adjust_statistics( + self, statistic_id: str, start_time: datetime, sum_adjustment: float + ) -> None: + """Adjust statistics.""" + self.queue_task(AdjustStatisticsTask(statistic_id, start_time, sum_adjustment)) + + @callback + def async_clear_statistics(self, statistic_ids: list[str]) -> None: + """Clear statistics for a list of statistic_ids.""" + self.queue_task(ClearStatisticsTask(statistic_ids)) + + @callback + def async_update_statistics_metadata( + self, + statistic_id: str, + *, + new_statistic_id: str | UndefinedType = UNDEFINED, + new_unit_of_measurement: str | None | UndefinedType = UNDEFINED, + ) -> None: + """Update statistics metadata for a statistic_id.""" + self.queue_task( + UpdateStatisticsMetadataTask( + statistic_id, new_statistic_id, new_unit_of_measurement + ) + ) + + @callback + def async_external_statistics( + self, metadata: StatisticMetaData, stats: Iterable[StatisticData] + ) -> None: + """Schedule external statistics.""" + self.queue_task(ExternalStatisticsTask(metadata, stats)) + + @callback + def _async_setup_periodic_tasks(self) -> None: + """Prepare periodic tasks.""" + if self.hass.is_stopping or not self._get_session: + # 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 self.dialect_name != SupportedDialect.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 + 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 + self._periodic_listener = async_track_utc_time_change( + self.hass, self.async_periodic_statistics, minute=range(0, 60, 5), second=10 + ) + + async def _async_wait_for_started(self) -> object | None: + """Wait for the hass started future.""" + return await self._hass_started + + def _wait_startup_or_shutdown(self) -> object | None: + """Wait for startup or shutdown before starting.""" + return asyncio.run_coroutine_threadsafe( + self._async_wait_for_started(), self.hass.loop + ).result() + + def run(self) -> None: + """Start processing events to save.""" + current_version = self._setup_recorder() + + if current_version is None: + 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() + else: + self.migration_in_progress = True + + self.hass.add_job(self.async_connection_success) + + # If shutdown happened before Home Assistant finished starting + if self._wait_startup_or_shutdown() is SHUTDOWN_TASK: + self.migration_in_progress = False + # Make sure we cleanly close the run if + # we restart before startup finishes + self._shutdown() + return + + # We wait to start the migration until startup has finished + # since it can be cpu intensive and we do not want it to compete + # 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 + # is reached, we need to reinitialize the listener. + self.hass.add_job(self.async_initialize) + else: + persistent_notification.create( + self.hass, + "The database migration failed, check [the logs](/config/logs)." + "Database Migration Failed", + "recorder_database_migration", + ) + self._shutdown() + return + + _LOGGER.debug("Recorder processing the queue") + self.hass.add_job(self._async_recorder_ready) + self._run_event_loop() + + def _run_event_loop(self) -> None: + """Run the event loop for the recorder.""" + # Use a session for the event read loop + # with a commit every time the event time + # has changed. This reduces the disk io. + 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 + _LOGGER.exception("Error while processing event %s: %s", task, err) + + self._shutdown() + + def _process_one_task_or_recover(self, task: RecorderTask) -> None: + """Process an event, reconnect, or recover a malformed database.""" + try: + # If its not an event, commit everything + # that is pending before running the task + if task.commit_before: + self._commit_event_session_or_retry() + return task.run(self) + except exc.DatabaseError as err: + if self._handle_database_error(err): + return + _LOGGER.exception( + "Unhandled database error while processing task %s: %s", task, err + ) + except SQLAlchemyError as err: + _LOGGER.exception("SQLAlchemyError error processing task %s: %s", task, err) + + # Reset the session if an SQLAlchemyError (including DatabaseError) + # happens to rollback and recover + self._reopen_event_session() + + def _setup_recorder(self) -> None | int: + """Create connect to the database and get the schema version.""" + tries = 1 + + while tries <= self.db_max_retries: + try: + self._setup_connection() + return migration.get_schema_version(self.get_session) + except UnsupportedDialect: + break + except Exception as err: # pylint: disable=broad-except + _LOGGER.exception( + "Error during connection setup: %s (retrying in %s seconds)", + err, + self.db_retry_wait, + ) + tries += 1 + time.sleep(self.db_retry_wait) + + return None + + @callback + def _async_migration_started(self) -> None: + """Set the migration started event.""" + self.async_migration_event.set() + + def _migrate_schema_and_setup_run(self, current_version: int) -> bool: + """Migrate schema to the latest version.""" + persistent_notification.create( + self.hass, + "System performance will temporarily degrade during the database upgrade. Do not power down or restart the system until the upgrade completes. Integrations that read the database, such as logbook and history, may return inconsistent results until the upgrade completes.", + "Database upgrade in progress", + "recorder_database_migration", + ) + self.hass.add_job(self._async_migration_started) + + try: + migration.migrate_schema( + self.hass, self.engine, self.get_session, current_version + ) + except exc.DatabaseError as err: + if self._handle_database_error(err): + return True + _LOGGER.exception("Database error during schema migration") + return False + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Error during schema migration") + return False + else: + self._setup_run() + return True + finally: + self.migration_in_progress = False + persistent_notification.dismiss(self.hass, "recorder_database_migration") + + def _lock_database(self, task: DatabaseLockTask) -> None: + @callback + def _async_set_database_locked(task: DatabaseLockTask) -> None: + task.database_locked.set() + + with write_lock_db_sqlite(self): + # Notify that lock is being held, wait until database can be used again. + self.hass.add_job(_async_set_database_locked, task) + while not task.database_unlock.wait(timeout=DB_LOCK_QUEUE_CHECK_TIMEOUT): + if self.backlog > MAX_QUEUE_BACKLOG * 0.9: + _LOGGER.warning( + "Database queue backlog reached more than 90% of maximum queue " + "length while waiting for backup to finish; recorder will now " + "resume writing to database. The backup can not be trusted and " + "must be restarted" + ) + task.queue_overflow = True + break + _LOGGER.info( + "Database queue backlog reached %d entries during backup", + self.backlog, + ) + + def _process_one_event(self, event: Event) -> None: + if not self.enabled: + return + if event.event_type == EVENT_STATE_CHANGED: + self._process_state_changed_event_into_session(event) + else: + self._process_non_state_changed_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 + with self.event_session.no_autoflush: + if attributes_id := self.event_session.execute( + find_shared_attributes_id(attr_hash, shared_attrs) + ).first(): + return cast(int, attributes_id[0]) + return None + + def _find_shared_data_in_db(self, data_hash: int, shared_data: str) -> int | None: + """Find shared event data 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 + with self.event_session.no_autoflush: + if data_id := self.event_session.execute( + find_shared_data_id(data_hash, shared_data) + ).first(): + return cast(int, data_id[0]) + return None + + def _process_non_state_changed_event_into_session(self, event: Event) -> None: + """Process any event into the session except state changed.""" + assert self.event_session is not None + dbevent = Events.from_event(event) + if not event.data: + self.event_session.add(dbevent) + return + + try: + shared_data = EventData.shared_data_from_event(event) + except (TypeError, ValueError) as ex: + _LOGGER.warning("Event is not JSON serializable: %s: %s", event, ex) + return + + # Matching attributes found in the pending commit + if pending_event_data := self._pending_event_data.get(shared_data): + dbevent.event_data_rel = pending_event_data + # Matching attributes id found in the cache + elif data_id := self._event_data_ids.get(shared_data): + dbevent.data_id = data_id + else: + data_hash = EventData.hash_shared_data(shared_data) + # Matching attributes found in the database + if data_id := self._find_shared_data_in_db(data_hash, shared_data): + self._event_data_ids[shared_data] = dbevent.data_id = data_id + # No matching attributes found, save them in the DB + else: + dbevent_data = EventData(shared_data=shared_data, hash=data_hash) + dbevent.event_data_rel = self._pending_event_data[ + shared_data + ] = dbevent_data + self.event_session.add(dbevent_data) + + self.event_session.add(dbevent) + + def _process_state_changed_event_into_session(self, event: Event) -> None: + """Process a state_changed event into the session.""" + assert self.event_session is not None + 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: + 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) + else: + dbstate.state = None + 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.""" + if isinstance(err.__cause__, sqlite3.DatabaseError): + _LOGGER.exception( + "Unrecoverable sqlite3 database corruption detected: %s", err + ) + self._handle_sqlite_corruption() + 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_has_pending_writes(): + return + tries = 1 + while tries <= self.db_max_retries: + try: + self._commit_event_session() + return + except (exc.InternalError, exc.OperationalError) as err: + _LOGGER.error( + "%s: Error executing query: %s. (retrying in %s seconds)", + INVALIDATED_ERR if err.connection_invalidated else CONNECTIVITY_ERR, + err, + self.db_retry_wait, + ) + if tries == self.db_max_retries: + raise + + tries += 1 + time.sleep(self.db_retry_wait) + + def _commit_event_session(self) -> None: + assert self.event_session is not None + self._commits_without_expire += 1 + + self.event_session.commit() + if self._pending_expunge: + for dbstate in self._pending_expunge: + # Expunge the state so its not expired + # until we use it later for dbstate.old_state + if dbstate in self.event_session: + self.event_session.expunge(dbstate) + self._pending_expunge = [] + + # We just committed the state attributes to the database + # and we now know the attributes_ids. We can save + # many selects for matching attributes by loading them + # into the LRU cache now. + for state_attr in self._pending_state_attributes.values(): + self._state_attributes_ids[ + state_attr.shared_attrs + ] = state_attr.attributes_id + self._pending_state_attributes = {} + for event_data in self._pending_event_data.values(): + self._event_data_ids[event_data.shared_data] = event_data.data_id + self._pending_event_data = {} + + # 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: + self._commits_without_expire = 0 + self.event_session.expire_all() + + def _handle_sqlite_corruption(self) -> None: + """Handle the sqlite3 database being corrupt.""" + 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() + + def _close_event_session(self) -> None: + """Close the event session.""" + self._old_states = {} + self._state_attributes_ids = {} + self._event_data_ids = {} + self._pending_state_attributes = {} + self._pending_event_data = {} + + if not self.event_session: + return + + try: + self.event_session.rollback() + self.event_session.close() + except SQLAlchemyError as err: + _LOGGER.exception( + "Error while rolling back and closing the event session: %s", err + ) + + def _reopen_event_session(self) -> None: + """Rollback the event session and reopen it after a failure.""" + self._close_event_session() + self._open_event_session() + + def _open_event_session(self) -> None: + """Open the event session.""" + self.event_session = self.get_session() + self.event_session.expire_on_commit = False + + def _send_keep_alive(self) -> None: + """Send a keep alive to keep the db connection open.""" + assert self.event_session is not None + _LOGGER.debug("Sending keepalive") + self.event_session.connection().scalar(select([1])) + + @callback + def event_listener(self, event: Event) -> None: + """Listen for new events and put them in the process queue.""" + if self._async_event_filter(event): + self.queue_task(EventTask(event)) + + async def async_block_till_done(self) -> None: + """Async version of block_till_done.""" + event = asyncio.Event() + self.queue_task(SynchronizeTask(event)) + await event.wait() + + def block_till_done(self) -> None: + """Block till all events processed. + + This is only called in tests. + + This only blocks until the queue is empty + which does not mean the recorder is done. + + Call tests.common's wait_recording_done + after calling this to ensure the data + is in the database. + """ + self._queue_watch.clear() + self.queue_task(WAIT_TASK) + self._queue_watch.wait() + + async def lock_database(self) -> bool: + """Lock database so it can be backed up safely.""" + if self.dialect_name != SupportedDialect.SQLITE: + _LOGGER.debug( + "Not a SQLite database or not connected, locking not necessary" + ) + return True + + if self._database_lock_task: + _LOGGER.warning("Database already locked") + return False + + database_locked = asyncio.Event() + task = DatabaseLockTask(database_locked, threading.Event(), False) + self.queue_task(task) + try: + await asyncio.wait_for(database_locked.wait(), timeout=DB_LOCK_TIMEOUT) + except asyncio.TimeoutError as err: + task.database_unlock.set() + raise TimeoutError( + f"Could not lock database within {DB_LOCK_TIMEOUT} seconds." + ) from err + self._database_lock_task = task + return True + + @callback + def unlock_database(self) -> bool: + """Unlock database. + + Returns true if database lock has been held throughout the process. + """ + if self.dialect_name != SupportedDialect.SQLITE: + _LOGGER.debug( + "Not a SQLite database or not connected, unlocking not necessary" + ) + return True + + if not self._database_lock_task: + _LOGGER.warning("Database currently not locked") + return False + + self._database_lock_task.database_unlock.set() + success = not self._database_lock_task.queue_overflow + + self._database_lock_task = None + + return success + + def _setup_connection(self) -> None: + """Ensure database is ready to fly.""" + kwargs: dict[str, Any] = {} + self._completed_first_database_setup = False + + def setup_recorder_connection( + dbapi_connection: Any, connection_record: Any + ) -> None: + """Dbapi specific connection settings.""" + assert self.engine is not None + if version := setup_connection_for_dialect( + self, + self.engine.dialect.name, + dbapi_connection, + not self._completed_first_database_setup, + ): + self.engine_version = version + self._completed_first_database_setup = True + + if self.db_url == SQLITE_URL_PREFIX or ":memory:" in self.db_url: + kwargs["connect_args"] = {"check_same_thread": False} + 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 + elif self.db_url.startswith(MYSQLDB_URL_PREFIX): + # If they have configured MySQLDB but don't have + # the MySQLDB module installed this will throw + # an ImportError which we suppress here since + # sqlalchemy will give them a better error when + # it tried to import it below. + with contextlib.suppress(ImportError): + kwargs["connect_args"] = {"conv": build_mysqldb_conv()} + else: + kwargs["echo"] = False + + if self._using_file_sqlite: + validate_or_move_away_sqlite_database(self.db_url) + + 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, future=True)) + _LOGGER.debug("Connected to recorder database") + + def _close_connection(self) -> None: + """Close the connection.""" + assert self.engine is not None + self.engine.dispose() + self.engine = None + self._get_session = None + + def _setup_run(self) -> None: + """Log the start of the current run and schedule any needed jobs.""" + with session_scope(session=self.get_session()) as session: + end_incomplete_runs(session, self.run_history.recording_start) + self.run_history.start(session) + self._schedule_compile_missing_statistics(session) + + self._open_event_session() + + def _schedule_compile_missing_statistics(self, session: Session) -> None: + """Add tasks for missing statistics runs.""" + now = dt_util.utcnow() + last_period_minutes = now.minute - now.minute % 5 + last_period = now.replace(minute=last_period_minutes, second=0, microsecond=0) + start = now - timedelta(days=self.keep_days) + start = start.replace(minute=0, second=0, microsecond=0) + + # Find the newest statistics run, if any + if last_run := session.query(func.max(StatisticsRuns.start)).scalar(): + start = max(start, process_timestamp(last_run) + timedelta(minutes=5)) + + # Add tasks + while start < last_period: + end = start + timedelta(minutes=5) + _LOGGER.debug("Compiling missing statistics for %s-%s", start, end) + self.queue_task(StatisticsTask(start)) + start = end + + def _end_session(self) -> None: + """End the recorder session.""" + if self.event_session is None: + return + try: + 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_history.clear() + + def _shutdown(self) -> None: + """Save end time for current run.""" + self.hass.add_job(self._async_stop_listeners) + self._stop_executor() + self._end_session() + self._close_connection() diff --git a/homeassistant/components/recorder/filters.py b/homeassistant/components/recorder/filters.py new file mode 100644 index 00000000000..0ceb013d8c5 --- /dev/null +++ b/homeassistant/components/recorder/filters.py @@ -0,0 +1,186 @@ +"""Provide pre-made queries on top of the recorder component.""" +from __future__ import annotations + +from collections.abc import Callable, Iterable +import json +from typing import Any + +from sqlalchemy import JSON, Column, Text, cast, not_, or_ +from sqlalchemy.sql.elements import ClauseList + +from homeassistant.const import CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE +from homeassistant.helpers.entityfilter import CONF_ENTITY_GLOBS +from homeassistant.helpers.typing import ConfigType + +from .models import ENTITY_ID_IN_EVENT, OLD_ENTITY_ID_IN_EVENT, States + +DOMAIN = "history" +HISTORY_FILTERS = "history_filters" + +GLOB_TO_SQL_CHARS = { + ord("*"): "%", + ord("?"): "_", + ord("%"): "\\%", + ord("_"): "\\_", + ord("\\"): "\\\\", +} + +FILTER_TYPES = (CONF_EXCLUDE, CONF_INCLUDE) +FITLER_MATCHERS = (CONF_ENTITIES, CONF_DOMAINS, CONF_ENTITY_GLOBS) + + +def extract_include_exclude_filter_conf(conf: ConfigType) -> dict[str, Any]: + """Extract an include exclude filter from configuration. + + This makes a copy so we do not alter the original data. + """ + return { + filter_type: { + matcher: set(conf.get(filter_type, {}).get(matcher, [])) + for matcher in FITLER_MATCHERS + } + for filter_type in FILTER_TYPES + } + + +def merge_include_exclude_filters( + base_filter: dict[str, Any], add_filter: dict[str, Any] +) -> dict[str, Any]: + """Merge two filters. + + This makes a copy so we do not alter the original data. + """ + return { + filter_type: { + matcher: base_filter[filter_type][matcher] + | add_filter[filter_type][matcher] + for matcher in FITLER_MATCHERS + } + for filter_type in FILTER_TYPES + } + + +def sqlalchemy_filter_from_include_exclude_conf(conf: ConfigType) -> Filters | None: + """Build a sql filter from config.""" + filters = Filters() + if exclude := conf.get(CONF_EXCLUDE): + filters.excluded_entities = exclude.get(CONF_ENTITIES, []) + filters.excluded_domains = exclude.get(CONF_DOMAINS, []) + filters.excluded_entity_globs = exclude.get(CONF_ENTITY_GLOBS, []) + if include := conf.get(CONF_INCLUDE): + filters.included_entities = include.get(CONF_ENTITIES, []) + filters.included_domains = include.get(CONF_DOMAINS, []) + filters.included_entity_globs = include.get(CONF_ENTITY_GLOBS, []) + + return filters if filters.has_config else None + + +class Filters: + """Container for the configured include and exclude filters.""" + + def __init__(self) -> None: + """Initialise the include and exclude filters.""" + self.excluded_entities: Iterable[str] = [] + self.excluded_domains: Iterable[str] = [] + self.excluded_entity_globs: Iterable[str] = [] + + self.included_entities: Iterable[str] = [] + self.included_domains: Iterable[str] = [] + self.included_entity_globs: Iterable[str] = [] + + @property + def has_config(self) -> bool: + """Determine if there is any filter configuration.""" + return bool( + self.excluded_entities + or self.excluded_domains + or self.excluded_entity_globs + or self.included_entities + or self.included_domains + or self.included_entity_globs + ) + + def _generate_filter_for_columns( + self, columns: Iterable[Column], encoder: Callable[[Any], Any] + ) -> ClauseList: + includes = [] + if self.included_domains: + includes.append(_domain_matcher(self.included_domains, columns, encoder)) + if self.included_entities: + includes.append(_entity_matcher(self.included_entities, columns, encoder)) + if self.included_entity_globs: + includes.append( + _globs_to_like(self.included_entity_globs, columns, encoder) + ) + + excludes = [] + if self.excluded_domains: + excludes.append(_domain_matcher(self.excluded_domains, columns, encoder)) + if self.excluded_entities: + excludes.append(_entity_matcher(self.excluded_entities, columns, encoder)) + if self.excluded_entity_globs: + excludes.append( + _globs_to_like(self.excluded_entity_globs, columns, encoder) + ) + + if not includes and not excludes: + return None + + if includes and not excludes: + return or_(*includes).self_group() + + if not includes and excludes: + return not_(or_(*excludes).self_group()) + + return or_(*includes).self_group() & not_(or_(*excludes).self_group()) + + def states_entity_filter(self) -> ClauseList: + """Generate the entity filter query.""" + + def _encoder(data: Any) -> Any: + """Nothing to encode for states since there is no json.""" + return data + + return self._generate_filter_for_columns((States.entity_id,), _encoder) + + def events_entity_filter(self) -> ClauseList: + """Generate the entity filter query.""" + _encoder = json.dumps + return or_( + (ENTITY_ID_IN_EVENT == JSON.NULL) & (OLD_ENTITY_ID_IN_EVENT == JSON.NULL), + self._generate_filter_for_columns( + (ENTITY_ID_IN_EVENT, OLD_ENTITY_ID_IN_EVENT), _encoder + ).self_group(), + ) + + +def _globs_to_like( + glob_strs: Iterable[str], columns: Iterable[Column], encoder: Callable[[Any], Any] +) -> ClauseList: + """Translate glob to sql.""" + return or_( + cast(column, Text()).like( + encoder(glob_str).translate(GLOB_TO_SQL_CHARS), escape="\\" + ) + for glob_str in glob_strs + for column in columns + ) + + +def _entity_matcher( + entity_ids: Iterable[str], columns: Iterable[Column], encoder: Callable[[Any], Any] +) -> ClauseList: + return or_( + cast(column, Text()).in_([encoder(entity_id) for entity_id in entity_ids]) + for column in columns + ) + + +def _domain_matcher( + domains: Iterable[str], columns: Iterable[Column], encoder: Callable[[Any], Any] +) -> ClauseList: + return or_( + cast(column, Text()).like(encoder(f"{domain}.%")) + for domain in domains + for column in columns + ) diff --git a/homeassistant/components/recorder/history.py b/homeassistant/components/recorder/history.py index d221ced3a84..7e8e97eafd4 100644 --- a/homeassistant/components/recorder/history.py +++ b/homeassistant/components/recorder/history.py @@ -2,31 +2,40 @@ from __future__ import annotations from collections import defaultdict -from collections.abc import Iterable, Iterator, MutableMapping +from collections.abc import Callable, Iterable, Iterator, MutableMapping from datetime import datetime from itertools import groupby import logging import time from typing import Any, cast -from sqlalchemy import Column, Text, and_, bindparam, func, or_ -from sqlalchemy.ext import baked +from sqlalchemy import Column, Text, and_, func, lambda_stmt, or_, select +from sqlalchemy.engine.row import Row +from sqlalchemy.orm.query import Query from sqlalchemy.orm.session import Session from sqlalchemy.sql.expression import literal +from sqlalchemy.sql.lambdas import StatementLambdaElement from homeassistant.components import recorder +from homeassistant.components.websocket_api.const import ( + COMPRESSED_STATE_LAST_UPDATED, + COMPRESSED_STATE_STATE, +) from homeassistant.core import HomeAssistant, State, split_entity_id import homeassistant.util.dt as dt_util +from .filters import Filters from .models import ( LazyState, RecorderRuns, StateAttributes, States, + process_datetime_to_timestamp, process_timestamp, process_timestamp_to_utc_isoformat, + row_to_compressed_state, ) -from .util import execute, session_scope +from .util import execute_stmt_lambda_element, session_scope # mypy: allow-untyped-defs, no-check-untyped-defs @@ -59,19 +68,19 @@ BASE_STATES = [ States.last_changed, States.last_updated, ] -BASE_STATES_NO_LAST_UPDATED = [ +BASE_STATES_NO_LAST_CHANGED = [ States.entity_id, States.state, - States.last_changed, - literal(value=None, type_=Text).label("last_updated"), + literal(value=None, type_=Text).label("last_changed"), + States.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, +QUERY_STATE_NO_ATTR_NO_LAST_CHANGED = [ + *BASE_STATES_NO_LAST_CHANGED, literal(value=None, type_=Text).label("attributes"), literal(value=None, type_=Text).label("shared_attrs"), ] @@ -83,8 +92,8 @@ 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, +QUERY_STATES_PRE_SCHEMA_25_NO_LAST_CHANGED = [ + *BASE_STATES_NO_LAST_CHANGED, States.attributes, literal(value=None, type_=Text).label("shared_attrs"), ] @@ -94,83 +103,55 @@ QUERY_STATES = [ States.attributes, StateAttributes.shared_attrs, ] -QUERY_STATES_NO_LAST_UPDATED = [ - *BASE_STATES_NO_LAST_UPDATED, +QUERY_STATES_NO_LAST_CHANGED = [ + *BASE_STATES_NO_LAST_CHANGED, # Remove States.attributes once all attributes are in StateAttributes.shared_attrs States.attributes, StateAttributes.shared_attrs, ] -HISTORY_BAKERY = "recorder_history_bakery" + +def _schema_version(hass: HomeAssistant) -> int: + return recorder.get_instance(hass).schema_version -def query_and_join_attributes( - hass: HomeAssistant, no_attributes: bool -) -> tuple[list[Column], bool]: - """Return the query keys and if StateAttributes should be joined.""" - # If no_attributes was requested we do the query - # without the attributes fields and do not join the - # state_attributes table - if no_attributes: - return QUERY_STATE_NO_ATTR, 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).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 - # join state_attributes - return QUERY_STATES, True +def lambda_stmt_and_join_attributes( + schema_version: int, no_attributes: bool, include_last_changed: bool = True +) -> tuple[StatementLambdaElement, bool]: + """Return the lambda_stmt and if StateAttributes should be joined. - -def bake_query_and_join_attributes( - hass: HomeAssistant, no_attributes: bool, include_last_updated: bool = True -) -> tuple[Any, bool]: - """Return the initial backed query and if StateAttributes should be joined. - - Because these are baked queries the values inside the lambdas need + Because these are lambda_stmt the values inside the lambdas need to be explicitly written out to avoid caching the wrong values. """ - bakery: baked.bakery = hass.data[HISTORY_BAKERY] # If no_attributes was requested we do the query # without the attributes fields and do not join the # state_attributes table if no_attributes: - if include_last_updated: - return bakery(lambda session: session.query(*QUERY_STATE_NO_ATTR)), False + if include_last_changed: + return lambda_stmt(lambda: select(*QUERY_STATE_NO_ATTR)), False return ( - bakery(lambda session: session.query(*QUERY_STATE_NO_ATTR_NO_LAST_UPDATED)), + lambda_stmt(lambda: select(*QUERY_STATE_NO_ATTR_NO_LAST_CHANGED)), 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).schema_version < 25: - if include_last_updated: + if schema_version < 25: + if include_last_changed: return ( - bakery(lambda session: session.query(*QUERY_STATES_PRE_SCHEMA_25)), + lambda_stmt(lambda: select(*QUERY_STATES_PRE_SCHEMA_25)), False, ) return ( - bakery( - lambda session: session.query( - *QUERY_STATES_PRE_SCHEMA_25_NO_LAST_UPDATED - ) - ), + lambda_stmt(lambda: select(*QUERY_STATES_PRE_SCHEMA_25_NO_LAST_CHANGED)), False, ) # Finally if no migration is in progress and no_attributes # was not requested, we query both attributes columns and # join state_attributes - 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: - """Set up the history hooks.""" - hass.data[HISTORY_BAKERY] = baked.bakery() + if include_last_changed: + return lambda_stmt(lambda: select(*QUERY_STATES)), True + return lambda_stmt(lambda: select(*QUERY_STATES_NO_LAST_CHANGED)), True def get_significant_states( @@ -178,11 +159,12 @@ def get_significant_states( start_time: datetime, end_time: datetime | None = None, entity_ids: list[str] | None = None, - filters: Any | None = None, + filters: Filters | None = None, include_start_time_state: bool = True, significant_changes_only: bool = True, minimal_response: bool = False, no_attributes: bool = False, + compressed_state_format: bool = False, ) -> MutableMapping[str, list[State | dict[str, Any]]]: """Wrap get_significant_states_with_session with an sql session.""" with session_scope(hass=hass) as session: @@ -197,84 +179,76 @@ def get_significant_states( significant_changes_only, minimal_response, no_attributes, + compressed_state_format, ) -def _query_significant_states_with_session( - hass: HomeAssistant, - session: Session, +def _ignore_domains_filter(query: Query) -> Query: + """Add a filter to ignore domains we do not fetch history for.""" + return query.filter( + and_( + *[ + ~States.entity_id.like(entity_domain) + for entity_domain in IGNORE_DOMAINS_ENTITY_ID_LIKE + ] + ) + ) + + +def _significant_states_stmt( + schema_version: int, start_time: datetime, - end_time: datetime | None = None, - entity_ids: list[str] | None = None, - filters: Any = None, - significant_changes_only: bool = True, - no_attributes: bool = False, -) -> list[States]: + end_time: datetime | None, + entity_ids: list[str] | None, + filters: Filters | None, + significant_changes_only: bool, + no_attributes: bool, +) -> StatementLambdaElement: """Query the database for significant state changes.""" - if _LOGGER.isEnabledFor(logging.DEBUG): - 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: - if ( - 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 - ) + stmt, join_attributes = lambda_stmt_and_join_attributes( + schema_version, no_attributes, include_last_changed=not significant_changes_only + ) + if ( + entity_ids + and len(entity_ids) == 1 + and significant_changes_only + and split_entity_id(entity_ids[0])[0] not in SIGNIFICANT_DOMAINS + ): + stmt += lambda q: q.filter( + (States.last_changed == States.last_updated) | States.last_changed.is_(None) + ) elif significant_changes_only: - baked_query += lambda q: q.filter( + stmt += lambda q: q.filter( or_( *[ States.entity_id.like(entity_domain) for entity_domain in SIGNIFICANT_DOMAINS_ENTITY_ID_LIKE ], - (States.last_changed == States.last_updated), + ( + (States.last_changed == States.last_updated) + | States.last_changed.is_(None) + ), ) ) - if entity_ids is not None: - baked_query += lambda q: q.filter( - States.entity_id.in_(bindparam("entity_ids", expanding=True)) - ) + if entity_ids: + stmt += lambda q: q.filter(States.entity_id.in_(entity_ids)) else: - baked_query += lambda q: q.filter( - and_( - *[ - ~States.entity_id.like(entity_domain) - for entity_domain in IGNORE_DOMAINS_ENTITY_ID_LIKE - ] - ) - ) - if filters: - filters.bake(baked_query) + stmt += _ignore_domains_filter + if filters and filters.has_config: + entity_filter = filters.states_entity_filter() + stmt += lambda q: q.filter(entity_filter) - baked_query += lambda q: q.filter(States.last_updated > bindparam("start_time")) - if end_time is not None: - baked_query += lambda q: q.filter(States.last_updated < bindparam("end_time")) + stmt += lambda q: q.filter(States.last_updated > start_time) + if end_time: + stmt += lambda q: q.filter(States.last_updated < end_time) if join_attributes: - baked_query += lambda q: q.outerjoin( + stmt += lambda q: q.outerjoin( StateAttributes, States.attributes_id == StateAttributes.attributes_id ) - baked_query += lambda q: q.order_by(States.entity_id, States.last_updated) - - states = execute( - baked_query(session).params( - start_time=start_time, end_time=end_time, entity_ids=entity_ids - ) - ) - - if _LOGGER.isEnabledFor(logging.DEBUG): - elapsed = time.perf_counter() - timer_start - _LOGGER.debug("get_significant_states took %fs", elapsed) - - return states + stmt += lambda q: q.order_by(States.entity_id, States.last_updated) + return stmt def get_significant_states_with_session( @@ -283,11 +257,12 @@ def get_significant_states_with_session( start_time: datetime, end_time: datetime | None = None, entity_ids: list[str] | None = None, - filters: Any = None, + filters: Filters | None = None, include_start_time_state: bool = True, significant_changes_only: bool = True, minimal_response: bool = False, no_attributes: bool = False, + compressed_state_format: bool = False, ) -> MutableMapping[str, list[State | dict[str, Any]]]: """ Return states changes during UTC period start_time - end_time. @@ -301,9 +276,8 @@ def get_significant_states_with_session( 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, + stmt = _significant_states_stmt( + _schema_version(hass), start_time, end_time, entity_ids, @@ -311,6 +285,9 @@ def get_significant_states_with_session( significant_changes_only, no_attributes, ) + states = execute_stmt_lambda_element( + session, stmt, None if entity_ids else start_time, end_time + ) return _sorted_states_to_dict( hass, session, @@ -321,6 +298,7 @@ def get_significant_states_with_session( include_start_time_state, minimal_response, no_attributes, + compressed_state_format, ) @@ -330,7 +308,7 @@ def get_full_significant_states_with_session( start_time: datetime, end_time: datetime | None = None, entity_ids: list[str] | None = None, - filters: Any = None, + filters: Filters | None = None, include_start_time_state: bool = True, significant_changes_only: bool = True, no_attributes: bool = False, @@ -353,6 +331,38 @@ def get_full_significant_states_with_session( ) +def _state_changed_during_period_stmt( + schema_version: int, + start_time: datetime, + end_time: datetime | None, + entity_id: str | None, + no_attributes: bool, + descending: bool, + limit: int | None, +) -> StatementLambdaElement: + stmt, join_attributes = lambda_stmt_and_join_attributes( + schema_version, no_attributes, include_last_changed=False + ) + stmt += lambda q: q.filter( + ((States.last_changed == States.last_updated) | States.last_changed.is_(None)) + & (States.last_updated > start_time) + ) + if end_time: + stmt += lambda q: q.filter(States.last_updated < end_time) + stmt += lambda q: q.filter(States.entity_id == entity_id) + if join_attributes: + stmt += lambda q: q.outerjoin( + StateAttributes, States.attributes_id == StateAttributes.attributes_id + ) + if descending: + stmt += lambda q: q.order_by(States.entity_id, States.last_updated.desc()) + else: + stmt += lambda q: q.order_by(States.entity_id, States.last_updated) + if limit: + stmt += lambda q: q.limit(limit) + return stmt + + def state_changes_during_period( hass: HomeAssistant, start_time: datetime, @@ -364,49 +374,21 @@ def state_changes_during_period( include_start_time_state: bool = True, ) -> MutableMapping[str, list[State]]: """Return states changes during UTC period start_time - end_time.""" + entity_id = entity_id.lower() if entity_id is not None else None + with session_scope(hass=hass) as session: - baked_query, join_attributes = bake_query_and_join_attributes( - hass, no_attributes, include_last_updated=False + stmt = _state_changed_during_period_stmt( + _schema_version(hass), + start_time, + end_time, + entity_id, + no_attributes, + descending, + limit, ) - - baked_query += lambda q: q.filter( - (States.last_changed == States.last_updated) - & (States.last_updated > bindparam("start_time")) + states = execute_stmt_lambda_element( + session, stmt, None if entity_id else start_time, end_time ) - - if end_time is not None: - baked_query += lambda q: q.filter( - States.last_updated < bindparam("end_time") - ) - - if entity_id is not None: - baked_query += lambda q: q.filter_by(entity_id=bindparam("entity_id")) - entity_id = entity_id.lower() - - if join_attributes: - baked_query += lambda q: q.outerjoin( - StateAttributes, States.attributes_id == StateAttributes.attributes_id - ) - - 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(bindparam("limit")) - - states = execute( - baked_query(session).params( - 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 cast( @@ -422,39 +404,37 @@ def state_changes_during_period( ) +def _get_last_state_changes_stmt( + schema_version: int, number_of_states: int, entity_id: str +) -> StatementLambdaElement: + stmt, join_attributes = lambda_stmt_and_join_attributes( + schema_version, False, include_last_changed=False + ) + stmt += lambda q: q.filter( + (States.last_changed == States.last_updated) | States.last_changed.is_(None) + ).filter(States.entity_id == entity_id) + if join_attributes: + stmt += lambda q: q.outerjoin( + StateAttributes, States.attributes_id == StateAttributes.attributes_id + ) + stmt += lambda q: q.order_by(States.entity_id, States.last_updated.desc()).limit( + number_of_states + ) + return stmt + + def get_last_state_changes( hass: HomeAssistant, number_of_states: int, entity_id: str ) -> MutableMapping[str, list[State]]: """Return the last number_of_states.""" start_time = dt_util.utcnow() + entity_id = entity_id.lower() if entity_id is not None else None with session_scope(hass=hass) as session: - baked_query, join_attributes = bake_query_and_join_attributes( - hass, False, include_last_updated=False + stmt = _get_last_state_changes_stmt( + _schema_version(hass), number_of_states, entity_id ) - - baked_query += lambda q: q.filter(States.last_changed == States.last_updated) - - if entity_id is not None: - baked_query += lambda q: q.filter_by(entity_id=bindparam("entity_id")) - entity_id = entity_id.lower() - - if join_attributes: - baked_query += lambda q: q.outerjoin( - StateAttributes, States.attributes_id == StateAttributes.attributes_id - ) - baked_query += lambda q: q.order_by( - States.entity_id, States.last_updated.desc() - ) - - baked_query += lambda q: q.limit(bindparam("number_of_states")) - - states = execute( - baked_query(session).params( - number_of_states=number_of_states, entity_id=entity_id - ) - ) - + states = list(execute_stmt_lambda_element(session, stmt)) entity_ids = [entity_id] if entity_id is not None else None return cast( @@ -470,19 +450,110 @@ def get_last_state_changes( ) -def _get_states_with_session( +def _get_states_for_entites_stmt( + schema_version: int, + run_start: datetime, + utc_point_in_time: datetime, + entity_ids: list[str], + no_attributes: bool, +) -> StatementLambdaElement: + """Baked query to get states for specific entities.""" + stmt, join_attributes = lambda_stmt_and_join_attributes( + schema_version, no_attributes, include_last_changed=True + ) + # We got an include-list of entities, accelerate the query by filtering already + # in the inner query. + stmt += lambda q: q.where( + States.state_id + == ( + select(func.max(States.state_id).label("max_state_id")) + .filter( + (States.last_updated >= run_start) + & (States.last_updated < utc_point_in_time) + ) + .filter(States.entity_id.in_(entity_ids)) + .group_by(States.entity_id) + .subquery() + ).c.max_state_id + ) + if join_attributes: + stmt += lambda q: q.outerjoin( + StateAttributes, (States.attributes_id == StateAttributes.attributes_id) + ) + return stmt + + +def _get_states_for_all_stmt( + schema_version: int, + run_start: datetime, + utc_point_in_time: datetime, + filters: Filters | None, + no_attributes: bool, +) -> StatementLambdaElement: + """Baked query to get states for all entities.""" + stmt, join_attributes = lambda_stmt_and_join_attributes( + schema_version, no_attributes, include_last_changed=True + ) + # We did not get an include-list of entities, query all states in the inner + # query, then filter out unwanted domains as well as applying the custom filter. + # This filtering can't be done in the inner query because the domain column is + # not indexed and we can't control what's in the custom filter. + most_recent_states_by_date = ( + select( + States.entity_id.label("max_entity_id"), + func.max(States.last_updated).label("max_last_updated"), + ) + .filter( + (States.last_updated >= run_start) + & (States.last_updated < utc_point_in_time) + ) + .group_by(States.entity_id) + .subquery() + ) + stmt += lambda q: q.where( + States.state_id + == ( + select(func.max(States.state_id).label("max_state_id")) + .join( + most_recent_states_by_date, + and_( + States.entity_id == most_recent_states_by_date.c.max_entity_id, + States.last_updated + == most_recent_states_by_date.c.max_last_updated, + ), + ) + .group_by(States.entity_id) + .subquery() + ).c.max_state_id, + ) + stmt += _ignore_domains_filter + if filters and filters.has_config: + entity_filter = filters.states_entity_filter() + stmt += lambda q: q.filter(entity_filter) + if join_attributes: + stmt += lambda q: q.outerjoin( + StateAttributes, (States.attributes_id == StateAttributes.attributes_id) + ) + return stmt + + +def _get_rows_with_session( hass: HomeAssistant, session: Session, utc_point_in_time: datetime, entity_ids: list[str] | None = None, run: RecorderRuns | None = None, - filters: Any | None = None, + filters: Filters | None = None, no_attributes: bool = False, -) -> list[State]: +) -> Iterable[Row]: """Return the states at a specific point in time.""" + schema_version = _schema_version(hass) 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 + return execute_stmt_lambda_element( + session, + _get_single_entity_states_stmt( + schema_version, utc_point_in_time, entity_ids[0], no_attributes + ), ) if run is None: @@ -494,117 +565,55 @@ def _get_states_with_session( # We have more than one entity to look at so we need to do a query on states # since the last recorder run started. - query_keys, join_attributes = query_and_join_attributes(hass, no_attributes) - query = session.query(*query_keys) - if entity_ids: - # We got an include-list of entities, accelerate the query by filtering already - # in the inner query. - most_recent_state_ids = ( - session.query( - func.max(States.state_id).label("max_state_id"), - ) - .filter( - (States.last_updated >= run.start) - & (States.last_updated < utc_point_in_time) - ) - .filter(States.entity_id.in_(entity_ids)) + stmt = _get_states_for_entites_stmt( + schema_version, run.start, utc_point_in_time, entity_ids, no_attributes ) - most_recent_state_ids = most_recent_state_ids.group_by(States.entity_id) - most_recent_state_ids = most_recent_state_ids.subquery() - query = query.join( - most_recent_state_ids, - States.state_id == most_recent_state_ids.c.max_state_id, - ) - if join_attributes: - query = query.outerjoin( - StateAttributes, (States.attributes_id == StateAttributes.attributes_id) - ) else: - # We did not get an include-list of entities, query all states in the inner - # query, then filter out unwanted domains as well as applying the custom filter. - # This filtering can't be done in the inner query because the domain column is - # not indexed and we can't control what's in the custom filter. - most_recent_states_by_date = ( - session.query( - States.entity_id.label("max_entity_id"), - func.max(States.last_updated).label("max_last_updated"), - ) - .filter( - (States.last_updated >= run.start) - & (States.last_updated < utc_point_in_time) - ) - .group_by(States.entity_id) - .subquery() + stmt = _get_states_for_all_stmt( + schema_version, run.start, utc_point_in_time, filters, no_attributes ) - most_recent_state_ids = ( - session.query(func.max(States.state_id).label("max_state_id")) - .join( - most_recent_states_by_date, - and_( - States.entity_id == most_recent_states_by_date.c.max_entity_id, - States.last_updated - == most_recent_states_by_date.c.max_last_updated, - ), - ) - .group_by(States.entity_id) - .subquery() - ) - query = query.join( - most_recent_state_ids, - States.state_id == most_recent_state_ids.c.max_state_id, - ) - for entity_domain in IGNORE_DOMAINS_ENTITY_ID_LIKE: - query = query.filter(~States.entity_id.like(entity_domain)) - if filters: - query = filters.apply(query) - if join_attributes: - query = query.outerjoin( - StateAttributes, (States.attributes_id == StateAttributes.attributes_id) - ) - attr_cache: dict[str, dict[str, Any]] = {} - return [LazyState(row, attr_cache) for row in execute(query)] + return execute_stmt_lambda_element(session, stmt) -def _get_single_entity_states_with_session( - hass: HomeAssistant, - session: Session, +def _get_single_entity_states_stmt( + schema_version: int, utc_point_in_time: datetime, entity_id: str, no_attributes: bool = False, -) -> list[State]: +) -> StatementLambdaElement: # 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) - baked_query += lambda q: q.filter( - States.last_updated < bindparam("utc_point_in_time"), - States.entity_id == bindparam("entity_id"), + stmt, join_attributes = lambda_stmt_and_join_attributes( + schema_version, no_attributes, include_last_changed=True + ) + stmt += ( + lambda q: q.filter( + States.last_updated < utc_point_in_time, + States.entity_id == entity_id, + ) + .order_by(States.last_updated.desc()) + .limit(1) ) if join_attributes: - baked_query += lambda q: q.outerjoin( + stmt += lambda q: q.outerjoin( StateAttributes, States.attributes_id == StateAttributes.attributes_id ) - baked_query += lambda q: q.order_by(States.last_updated.desc()) - baked_query += lambda q: q.limit(1) - - query = baked_query(session).params( - utc_point_in_time=utc_point_in_time, entity_id=entity_id - ) - - return [LazyState(row) for row in execute(query)] + return stmt def _sorted_states_to_dict( hass: HomeAssistant, session: Session, - states: Iterable[States], + states: Iterable[Row], start_time: datetime, entity_ids: list[str] | None, - filters: Any = None, + filters: Filters | None = None, include_start_time_state: bool = True, minimal_response: bool = False, no_attributes: bool = False, + compressed_state_format: bool = False, ) -> MutableMapping[str, list[State | dict[str, Any]]]: """Convert SQL results into JSON friendly data structure. @@ -617,6 +626,19 @@ def _sorted_states_to_dict( each list of states, otherwise our graphs won't start on the Y axis correctly. """ + if compressed_state_format: + state_class = row_to_compressed_state + _process_timestamp: Callable[ + [datetime], float | str + ] = process_datetime_to_timestamp + attr_time = COMPRESSED_STATE_LAST_UPDATED + attr_state = COMPRESSED_STATE_STATE + else: + state_class = LazyState # type: ignore[assignment] + _process_timestamp = process_timestamp_to_utc_isoformat + attr_time = LAST_CHANGED_KEY + attr_state = STATE_KEY + 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: @@ -625,27 +647,24 @@ def _sorted_states_to_dict( # Get the states at the start time timer_start = time.perf_counter() + initial_states: dict[str, Row] = {} if include_start_time_state: - for state in _get_states_with_session( - hass, - session, - start_time, - entity_ids, - filters=filters, - no_attributes=no_attributes, - ): - state.last_changed = start_time - state.last_updated = start_time - result[state.entity_id].append(state) + initial_states = { + row.entity_id: row + for row in _get_rows_with_session( + hass, + session, + start_time, + entity_ids, + filters=filters, + no_attributes=no_attributes, + ) + } if _LOGGER.isEnabledFor(logging.DEBUG): elapsed = time.perf_counter() - timer_start _LOGGER.debug("getting %d first datapoints took %fs", len(result), elapsed) - # Called in a tight loop so cache the function - # 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)), @@ -655,11 +674,15 @@ def _sorted_states_to_dict( # Append all changes to it for ent_id, group in states_iter: - ent_results = result[ent_id] attr_cache: dict[str, dict[str, Any]] = {} + prev_state: Column | str + ent_results = result[ent_id] + if row := initial_states.pop(ent_id, None): + prev_state = row.state + ent_results.append(state_class(row, attr_cache, start_time)) 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) + ent_results.extend(state_class(db_state, attr_cache) for db_state in group) continue # With minimal response we only provide a native @@ -669,33 +692,32 @@ def _sorted_states_to_dict( if not ent_results: if (first_state := next(group, None)) is None: continue - ent_results.append(LazyState(first_state, attr_cache)) + prev_state = first_state.state + ent_results.append(state_class(first_state, attr_cache)) - prev_state = ent_results[-1] - assert isinstance(prev_state, LazyState) - initial_state_count = len(ent_results) - - for db_state in group: + for row in group: # With minimal response we do not care about attribute # changes so we can filter out duplicate states - if db_state.state == prev_state.state: + if (state := row.state) == prev_state: continue ent_results.append( { - STATE_KEY: db_state.state, - LAST_CHANGED_KEY: _process_timestamp_to_utc_isoformat( - db_state.last_changed - ), + attr_state: state, + # + # minimal_response only makes sense with last_updated == last_updated + # + # We use last_updated for for last_changed since its the same + # + attr_time: _process_timestamp(row.last_updated), } ) - prev_state = db_state + prev_state = state - if prev_state and len(ent_results) != initial_state_count: - # There was at least one state change - # replace the last minimal state with - # a full state - ent_results[-1] = LazyState(prev_state, attr_cache) + # If there are no states beyond the initial state, + # the state a was never popped from initial_states + for ent_id, row in initial_states.items(): + result[ent_id].append(state_class(row, {}, start_time)) # Filter out the empty lists if some states had 0 results. return {key: val for key, val in result.items() if val} diff --git a/homeassistant/components/recorder/manifest.json b/homeassistant/components/recorder/manifest.json index 0fb44f99ae2..38897c42e1a 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.36", "fnvhash==0.1.0", "lru-dict==1.1.7"], + "requirements": ["sqlalchemy==1.4.37", "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 94614fe8cff..bc636d34b10 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -1,11 +1,13 @@ """Schema migration helpers.""" +from collections.abc import Callable, Iterable import contextlib from datetime import timedelta import logging -from typing import Any +from typing import cast import sqlalchemy from sqlalchemy import ForeignKeyConstraint, MetaData, Table, func, text +from sqlalchemy.engine import Engine from sqlalchemy.exc import ( DatabaseError, InternalError, @@ -13,9 +15,13 @@ from sqlalchemy.exc import ( ProgrammingError, SQLAlchemyError, ) +from sqlalchemy.orm.session import Session from sqlalchemy.schema import AddConstraint, DropConstraint from sqlalchemy.sql.expression import true +from homeassistant.core import HomeAssistant + +from .const import SupportedDialect from .models import ( SCHEMA_VERSION, TABLE_STATES, @@ -27,13 +33,17 @@ from .models import ( StatisticsShortTerm, process_timestamp, ) -from .statistics import delete_duplicates, get_start_time +from .statistics import ( + delete_statistics_duplicates, + delete_statistics_meta_duplicates, + get_start_time, +) from .util import session_scope _LOGGER = logging.getLogger(__name__) -def raise_if_exception_missing_str(ex, match_substrs): +def raise_if_exception_missing_str(ex: Exception, match_substrs: Iterable[str]) -> None: """Raise an exception if the exception and cause do not contain the match substrs.""" lower_ex_strs = [str(ex).lower(), str(ex.__cause__).lower()] for str_sub in match_substrs: @@ -44,10 +54,9 @@ def raise_if_exception_missing_str(ex, match_substrs): raise ex -def get_schema_version(instance: Any) -> int: +def get_schema_version(session_maker: Callable[[], Session]) -> int: """Get the schema version.""" - assert instance.get_session is not None - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: res = ( session.query(SchemaChanges) .order_by(SchemaChanges.change_id.desc()) @@ -61,7 +70,7 @@ def get_schema_version(instance: Any) -> int: "No schema version found. Inspected version: %s", current_version ) - return current_version + return cast(int, current_version) def schema_is_current(current_version: int) -> bool: @@ -69,21 +78,27 @@ def schema_is_current(current_version: int) -> bool: return current_version == SCHEMA_VERSION -def migrate_schema(instance: Any, current_version: int) -> None: +def migrate_schema( + hass: HomeAssistant, + engine: Engine, + session_maker: Callable[[], Session], + current_version: int, +) -> None: """Check if the schema needs to be upgraded.""" - assert instance.get_session is not None _LOGGER.warning("Database is about to upgrade. Schema version: %s", current_version) for version in range(current_version, SCHEMA_VERSION): new_version = version + 1 _LOGGER.info("Upgrading recorder db schema to version %s", new_version) - _apply_update(instance, new_version, current_version) - with session_scope(session=instance.get_session()) as session: + _apply_update(hass, engine, session_maker, new_version, current_version) + with session_scope(session=session_maker()) as session: session.add(SchemaChanges(schema_version=new_version)) _LOGGER.info("Upgrade to version %s done", new_version) -def _create_index(instance, table_name, index_name): +def _create_index( + session_maker: Callable[[], Session], table_name: str, index_name: str +) -> None: """Create an index for the specified table. The index name should match the name given for the index @@ -104,7 +119,7 @@ def _create_index(instance, table_name, index_name): "be patient!", index_name, ) - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: try: connection = session.connection() index.create(connection) @@ -117,7 +132,9 @@ def _create_index(instance, table_name, index_name): _LOGGER.debug("Finished creating %s", index_name) -def _drop_index(instance, table_name, index_name): +def _drop_index( + session_maker: Callable[[], Session], table_name: str, index_name: str +) -> None: """Drop an index from a specified table. There is no universal way to do something like `DROP INDEX IF EXISTS` @@ -132,7 +149,7 @@ def _drop_index(instance, table_name, index_name): success = False # Engines like DB2/Oracle - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: try: connection = session.connection() connection.execute(text(f"DROP INDEX {index_name}")) @@ -143,7 +160,7 @@ def _drop_index(instance, table_name, index_name): # Engines like SQLite, SQL Server if not success: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: try: connection = session.connection() connection.execute( @@ -160,7 +177,7 @@ def _drop_index(instance, table_name, index_name): if not success: # Engines like MySQL, MS Access - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: try: connection = session.connection() connection.execute( @@ -194,7 +211,9 @@ def _drop_index(instance, table_name, index_name): ) -def _add_columns(instance, table_name, columns_def): +def _add_columns( + session_maker: Callable[[], Session], table_name: str, columns_def: list[str] +) -> None: """Add columns to a table.""" _LOGGER.warning( "Adding columns %s to table %s. Note: this can take several " @@ -206,7 +225,7 @@ def _add_columns(instance, table_name, columns_def): columns_def = [f"ADD {col_def}" for col_def in columns_def] - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: try: connection = session.connection() connection.execute( @@ -223,7 +242,7 @@ def _add_columns(instance, table_name, columns_def): _LOGGER.info("Unable to use quick column add. Adding 1 by 1") for column_def in columns_def: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: try: connection = session.connection() connection.execute( @@ -242,9 +261,14 @@ def _add_columns(instance, table_name, columns_def): ) -def _modify_columns(instance, engine, table_name, columns_def): +def _modify_columns( + session_maker: Callable[[], Session], + engine: Engine, + table_name: str, + columns_def: list[str], +) -> None: """Modify columns in a table.""" - if engine.dialect.name == "sqlite": + if engine.dialect.name == SupportedDialect.SQLITE: _LOGGER.debug( "Skipping to modify columns %s in table %s; " "Modifying column length in SQLite is unnecessary, " @@ -262,7 +286,7 @@ def _modify_columns(instance, engine, table_name, columns_def): table_name, ) - if engine.dialect.name == "postgresql": + if engine.dialect.name == SupportedDialect.POSTGRESQL: columns_def = [ "ALTER {column} TYPE {type}".format( **dict(zip(["column", "type"], col_def.split(" ", 1))) @@ -274,7 +298,7 @@ def _modify_columns(instance, engine, table_name, columns_def): else: columns_def = [f"MODIFY {col_def}" for col_def in columns_def] - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: try: connection = session.connection() connection.execute( @@ -289,7 +313,7 @@ def _modify_columns(instance, engine, table_name, columns_def): _LOGGER.info("Unable to use quick column modify. Modifying 1 by 1") for column_def in columns_def: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: try: connection = session.connection() connection.execute( @@ -305,7 +329,9 @@ def _modify_columns(instance, engine, table_name, columns_def): ) -def _update_states_table_with_foreign_key_options(instance, engine): +def _update_states_table_with_foreign_key_options( + session_maker: Callable[[], Session], engine: Engine +) -> None: """Add the options to foreign key constraints.""" inspector = sqlalchemy.inspect(engine) alters = [] @@ -333,7 +359,7 @@ def _update_states_table_with_foreign_key_options(instance, engine): ) for alter in alters: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: try: connection = session.connection() connection.execute(DropConstraint(alter["old_fk"])) @@ -346,7 +372,9 @@ def _update_states_table_with_foreign_key_options(instance, engine): ) -def _drop_foreign_key_constraints(instance, engine, table, columns): +def _drop_foreign_key_constraints( + session_maker: Callable[[], Session], engine: Engine, table: str, columns: list[str] +) -> None: """Drop foreign key constraints for a table on specific columns.""" inspector = sqlalchemy.inspect(engine) drops = [] @@ -364,7 +392,7 @@ def _drop_foreign_key_constraints(instance, engine, table, columns): ) for drop in drops: - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: try: connection = session.connection() connection.execute(DropConstraint(drop)) @@ -376,17 +404,24 @@ def _drop_foreign_key_constraints(instance, engine, table, columns): ) -def _apply_update(instance, new_version, old_version): # noqa: C901 +def _apply_update( # noqa: C901 + hass: HomeAssistant, + engine: Engine, + session_maker: Callable[[], Session], + new_version: int, + old_version: int, +) -> None: """Perform operations to bring schema up to date.""" - engine = instance.engine dialect = engine.dialect.name + big_int = "INTEGER(20)" if dialect == SupportedDialect.MYSQL else "INTEGER" + if new_version == 1: - _create_index(instance, "events", "ix_events_time_fired") + _create_index(session_maker, "events", "ix_events_time_fired") elif new_version == 2: # Create compound start/end index for recorder_runs - _create_index(instance, "recorder_runs", "ix_recorder_runs_start_end") + _create_index(session_maker, "recorder_runs", "ix_recorder_runs_start_end") # Create indexes for states - _create_index(instance, "states", "ix_states_last_updated") + _create_index(session_maker, "states", "ix_states_last_updated") elif new_version == 3: # There used to be a new index here, but it was removed in version 4. pass @@ -396,41 +431,41 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 if old_version == 3: # Remove index that was added in version 3 - _drop_index(instance, "states", "ix_states_created_domain") + _drop_index(session_maker, "states", "ix_states_created_domain") if old_version == 2: # Remove index that was added in version 2 - _drop_index(instance, "states", "ix_states_entity_id_created") + _drop_index(session_maker, "states", "ix_states_entity_id_created") # Remove indexes that were added in version 0 - _drop_index(instance, "states", "states__state_changes") - _drop_index(instance, "states", "states__significant_changes") - _drop_index(instance, "states", "ix_states_entity_id_created") + _drop_index(session_maker, "states", "states__state_changes") + _drop_index(session_maker, "states", "states__significant_changes") + _drop_index(session_maker, "states", "ix_states_entity_id_created") - _create_index(instance, "states", "ix_states_entity_id_last_updated") + _create_index(session_maker, "states", "ix_states_entity_id_last_updated") elif new_version == 5: # Create supporting index for States.event_id foreign key - _create_index(instance, "states", "ix_states_event_id") + _create_index(session_maker, "states", "ix_states_event_id") elif new_version == 6: _add_columns( - instance, + session_maker, "events", ["context_id CHARACTER(36)", "context_user_id CHARACTER(36)"], ) - _create_index(instance, "events", "ix_events_context_id") - _create_index(instance, "events", "ix_events_context_user_id") + _create_index(session_maker, "events", "ix_events_context_id") + _create_index(session_maker, "events", "ix_events_context_user_id") _add_columns( - instance, + session_maker, "states", ["context_id CHARACTER(36)", "context_user_id CHARACTER(36)"], ) - _create_index(instance, "states", "ix_states_context_id") - _create_index(instance, "states", "ix_states_context_user_id") + _create_index(session_maker, "states", "ix_states_context_id") + _create_index(session_maker, "states", "ix_states_context_user_id") elif new_version == 7: - _create_index(instance, "states", "ix_states_entity_id") + _create_index(session_maker, "states", "ix_states_entity_id") elif new_version == 8: - _add_columns(instance, "events", ["context_parent_id CHARACTER(36)"]) - _add_columns(instance, "states", ["old_state_id INTEGER"]) - _create_index(instance, "events", "ix_events_context_parent_id") + _add_columns(session_maker, "events", ["context_parent_id CHARACTER(36)"]) + _add_columns(session_maker, "states", ["old_state_id INTEGER"]) + _create_index(session_maker, "events", "ix_events_context_parent_id") elif new_version == 9: # We now get the context from events with a join # since its always there on state_changed events @@ -440,36 +475,36 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 # and we would have to move to something like # sqlalchemy alembic to make that work # - _drop_index(instance, "states", "ix_states_context_id") - _drop_index(instance, "states", "ix_states_context_user_id") + # no longer dropping ix_states_context_id since its recreated in 28 + _drop_index(session_maker, "states", "ix_states_context_user_id") # This index won't be there if they were not running # nightly but we don't treat that as a critical issue - _drop_index(instance, "states", "ix_states_context_parent_id") + _drop_index(session_maker, "states", "ix_states_context_parent_id") # Redundant keys on composite index: # We already have ix_states_entity_id_last_updated - _drop_index(instance, "states", "ix_states_entity_id") - _create_index(instance, "events", "ix_events_event_type_time_fired") - _drop_index(instance, "events", "ix_events_event_type") + _drop_index(session_maker, "states", "ix_states_entity_id") + _create_index(session_maker, "events", "ix_events_event_type_time_fired") + _drop_index(session_maker, "events", "ix_events_event_type") elif new_version == 10: # Now done in step 11 pass elif new_version == 11: - _create_index(instance, "states", "ix_states_old_state_id") - _update_states_table_with_foreign_key_options(instance, engine) + _create_index(session_maker, "states", "ix_states_old_state_id") + _update_states_table_with_foreign_key_options(session_maker, engine) elif new_version == 12: - if engine.dialect.name == "mysql": - _modify_columns(instance, engine, "events", ["event_data LONGTEXT"]) - _modify_columns(instance, engine, "states", ["attributes LONGTEXT"]) + if engine.dialect.name == SupportedDialect.MYSQL: + _modify_columns(session_maker, engine, "events", ["event_data LONGTEXT"]) + _modify_columns(session_maker, engine, "states", ["attributes LONGTEXT"]) elif new_version == 13: - if engine.dialect.name == "mysql": + if engine.dialect.name == SupportedDialect.MYSQL: _modify_columns( - instance, + session_maker, engine, "events", ["time_fired DATETIME(6)", "created DATETIME(6)"], ) _modify_columns( - instance, + session_maker, engine, "states", [ @@ -479,12 +514,14 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 ], ) elif new_version == 14: - _modify_columns(instance, engine, "events", ["event_type VARCHAR(64)"]) + _modify_columns(session_maker, engine, "events", ["event_type VARCHAR(64)"]) elif new_version == 15: # This dropped the statistics table, done again in version 18. pass elif new_version == 16: - _drop_foreign_key_constraints(instance, engine, TABLE_STATES, ["old_state_id"]) + _drop_foreign_key_constraints( + session_maker, engine, TABLE_STATES, ["old_state_id"] + ) elif new_version == 17: # This dropped the statistics table, done again in version 18. pass @@ -509,13 +546,13 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 elif new_version == 19: # This adds the statistic runs table, insert a fake run to prevent duplicating # statistics. - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: session.add(StatisticsRuns(start=get_start_time())) elif new_version == 20: # This changed the precision of statistics from float to double - if engine.dialect.name in ["mysql", "postgresql"]: + if engine.dialect.name in [SupportedDialect.MYSQL, SupportedDialect.POSTGRESQL]: _modify_columns( - instance, + session_maker, engine, "statistics", [ @@ -528,7 +565,7 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 ) elif new_version == 21: # Try to change the character set of the statistic_meta table - if engine.dialect.name == "mysql": + if engine.dialect.name == SupportedDialect.MYSQL: for table in ("events", "states", "statistics_meta"): _LOGGER.warning( "Updating character set and collation of table %s to utf8mb4. " @@ -537,7 +574,7 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 table, ) with contextlib.suppress(SQLAlchemyError): - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: connection = session.connection() connection.execute( # Using LOCK=EXCLUSIVE to prevent the database from corrupting @@ -572,7 +609,7 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 # Block 5-minute statistics for one hour from the last run, or it will overlap # with existing hourly statistics. Don't block on a database with no existing # statistics. - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: if session.query(Statistics.id).count() and ( last_run_string := session.query( func.max(StatisticsRuns.start) @@ -588,7 +625,7 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 # When querying the database, be careful to only explicitly query for columns # which were present in schema version 21. If querying the table, SQLAlchemy # will refer to future columns. - with session_scope(session=instance.get_session()) as session: + with session_scope(session=session_maker()) as session: for sum_statistic in session.query(StatisticsMeta.id).filter_by( has_sum=true() ): @@ -615,44 +652,94 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 ) elif new_version == 23: # Add name column to StatisticsMeta - _add_columns(instance, "statistics_meta", ["name VARCHAR(255)"]) + _add_columns(session_maker, "statistics_meta", ["name VARCHAR(255)"]) elif new_version == 24: # Recreate statistics indices to block duplicated statistics - _drop_index(instance, "statistics", "ix_statistics_statistic_id_start") + _drop_index(session_maker, "statistics", "ix_statistics_statistic_id_start") _drop_index( - instance, + session_maker, "statistics_short_term", "ix_statistics_short_term_statistic_id_start", ) try: - _create_index(instance, "statistics", "ix_statistics_statistic_id_start") _create_index( - instance, + session_maker, "statistics", "ix_statistics_statistic_id_start" + ) + _create_index( + session_maker, "statistics_short_term", "ix_statistics_short_term_statistic_id_start", ) except DatabaseError: # There may be duplicated statistics entries, delete duplicated statistics # and try again - with session_scope(session=instance.get_session()) as session: - delete_duplicates(instance, session) - _create_index(instance, "statistics", "ix_statistics_statistic_id_start") + with session_scope(session=session_maker()) as session: + delete_statistics_duplicates(hass, session) _create_index( - instance, + session_maker, "statistics", "ix_statistics_statistic_id_start" + ) + _create_index( + session_maker, "statistics_short_term", "ix_statistics_short_term_statistic_id_start", ) elif new_version == 25: - 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") + _add_columns(session_maker, "states", [f"attributes_id {big_int}"]) + _create_index(session_maker, "states", "ix_states_attributes_id") elif new_version == 26: - _create_index(instance, "statistics_runs", "ix_statistics_runs_start") + _create_index(session_maker, "statistics_runs", "ix_statistics_runs_start") + elif new_version == 27: + _add_columns(session_maker, "events", [f"data_id {big_int}"]) + _create_index(session_maker, "events", "ix_events_data_id") + elif new_version == 28: + _add_columns(session_maker, "events", ["origin_idx INTEGER"]) + # We never use the user_id or parent_id index + _drop_index(session_maker, "events", "ix_events_context_user_id") + _drop_index(session_maker, "events", "ix_events_context_parent_id") + _add_columns( + session_maker, + "states", + [ + "origin_idx INTEGER", + "context_id VARCHAR(36)", + "context_user_id VARCHAR(36)", + "context_parent_id VARCHAR(36)", + ], + ) + _create_index(session_maker, "states", "ix_states_context_id") + # Once there are no longer any state_changed events + # in the events table we can drop the index on states.event_id + elif new_version == 29: + # Recreate statistics_meta index to block duplicated statistic_id + _drop_index(session_maker, "statistics_meta", "ix_statistics_meta_statistic_id") + if engine.dialect.name == SupportedDialect.MYSQL: + # Ensure the row format is dynamic or the index + # unique will be too large + with session_scope(session=session_maker()) as session: + connection = session.connection() + # This is safe to run multiple times and fast since the table is small + connection.execute( + text( + "ALTER TABLE statistics_meta ENGINE=InnoDB, ROW_FORMAT=DYNAMIC" + ) + ) + try: + _create_index( + session_maker, "statistics_meta", "ix_statistics_meta_statistic_id" + ) + except DatabaseError: + # There may be duplicated statistics_meta entries, delete duplicates + # and try again + with session_scope(session=session_maker()) as session: + delete_statistics_meta_duplicates(session) + _create_index( + session_maker, "statistics_meta", "ix_statistics_meta_statistic_id" + ) else: raise ValueError(f"No schema migration defined for version {new_version}") -def _inspect_schema_version(session): +def _inspect_schema_version(session: Session) -> int: """Determine the schema version by inspecting the db structure. When the schema version is not present in the db, either db was just @@ -674,4 +761,4 @@ def _inspect_schema_version(session): # Version 1 schema changes not found, this db needs to be migrated. current_version = SchemaChanges(schema_version=0) session.add(current_version) - return current_version.schema_version + return cast(int, current_version.schema_version) diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 5b402dec7a3..70c816c2af5 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -1,13 +1,16 @@ """Models for SQLAlchemy.""" from __future__ import annotations +from collections.abc import Callable from datetime import datetime, timedelta import json import logging from typing import Any, TypedDict, cast, overload +import ciso8601 from fnvhash import fnv1a_32 from sqlalchemy import ( + JSON, BigInteger, Boolean, Column, @@ -17,16 +20,24 @@ from sqlalchemy import ( Identity, Index, Integer, + SmallInteger, String, Text, distinct, + type_coerce, ) -from sqlalchemy.dialects import mysql, oracle, postgresql +from sqlalchemy.dialects import mysql, oracle, postgresql, sqlite from sqlalchemy.engine.row import Row from sqlalchemy.ext.declarative import declared_attr -from sqlalchemy.orm import declarative_base, relationship +from sqlalchemy.orm import aliased, declarative_base, relationship from sqlalchemy.orm.session import Session +from homeassistant.components.websocket_api.const import ( + COMPRESSED_STATE_ATTRIBUTES, + COMPRESSED_STATE_LAST_CHANGED, + COMPRESSED_STATE_LAST_UPDATED, + COMPRESSED_STATE_STATE, +) from homeassistant.const import ( MAX_LENGTH_EVENT_CONTEXT_ID, MAX_LENGTH_EVENT_EVENT_TYPE, @@ -35,7 +46,6 @@ from homeassistant.const import ( MAX_LENGTH_STATE_STATE, ) from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id -from homeassistant.helpers.typing import UNDEFINED, UndefinedType import homeassistant.util.dt as dt_util from .const import ALL_DOMAIN_EXCLUDE_ATTRS, JSON_DUMP @@ -44,13 +54,14 @@ from .const import ALL_DOMAIN_EXCLUDE_ATTRS, JSON_DUMP # pylint: disable=invalid-name Base = declarative_base() -SCHEMA_VERSION = 26 +SCHEMA_VERSION = 29 _LOGGER = logging.getLogger(__name__) DB_TIMEZONE = "+00:00" TABLE_EVENTS = "events" +TABLE_EVENT_DATA = "event_data" TABLE_STATES = "states" TABLE_STATE_ATTRIBUTES = "state_attributes" TABLE_RECORDER_RUNS = "recorder_runs" @@ -62,7 +73,9 @@ TABLE_STATISTICS_SHORT_TERM = "statistics_short_term" ALL_TABLES = [ TABLE_STATES, + TABLE_STATE_ATTRIBUTES, TABLE_EVENTS, + TABLE_EVENT_DATA, TABLE_RECORDER_RUNS, TABLE_SCHEMA_CHANGES, TABLE_STATISTICS, @@ -71,11 +84,37 @@ ALL_TABLES = [ TABLE_STATISTICS_SHORT_TERM, ] +TABLES_TO_CHECK = [ + TABLE_STATES, + TABLE_EVENTS, + TABLE_RECORDER_RUNS, + TABLE_SCHEMA_CHANGES, +] + +LAST_UPDATED_INDEX = "ix_states_last_updated" +ENTITY_ID_LAST_UPDATED_INDEX = "ix_states_entity_id_last_updated" + EMPTY_JSON_OBJECT = "{}" -DATETIME_TYPE = DateTime(timezone=True).with_variant( - mysql.DATETIME(timezone=True, fsp=6), "mysql" +class FAST_PYSQLITE_DATETIME(sqlite.DATETIME): # type: ignore[misc] + """Use ciso8601 to parse datetimes instead of sqlalchemy built-in regex.""" + + def result_processor(self, dialect, coltype): # type: ignore[no-untyped-def] + """Offload the datetime parsing to ciso8601.""" + return lambda value: None if value is None else ciso8601.parse_datetime(value) + + +JSON_VARIENT_CAST = Text().with_variant( + postgresql.JSON(none_as_null=True), "postgresql" +) +JSONB_VARIENT_CAST = Text().with_variant( + postgresql.JSONB(none_as_null=True), "postgresql" +) +DATETIME_TYPE = ( + DateTime(timezone=True) + .with_variant(mysql.DATETIME(timezone=True, fsp=6), "mysql") + .with_variant(FAST_PYSQLITE_DATETIME(), "sqlite") ) DOUBLE_TYPE = ( Float() @@ -85,6 +124,27 @@ DOUBLE_TYPE = ( ) +class JSONLiteral(JSON): # type: ignore[misc] + """Teach SA how to literalize json.""" + + def literal_processor(self, dialect: str) -> Callable[[Any], str]: + """Processor to convert a value to JSON.""" + + def process(value: Any) -> str: + """Dump json.""" + return json.dumps(value) + + return process + + +EVENT_ORIGIN_ORDER = [EventOrigin.local, EventOrigin.remote] +EVENT_ORIGIN_TO_IDX = {origin: idx for idx, origin in enumerate(EVENT_ORIGIN_ORDER)} + + +class UnsupportedDialect(Exception): + """The dialect or its version is not supported.""" + + class Events(Base): # type: ignore[misc,valid-type] """Event history data.""" @@ -98,30 +158,31 @@ class Events(Base): # type: ignore[misc,valid-type] event_id = Column(Integer, Identity(), primary_key=True) event_type = Column(String(MAX_LENGTH_EVENT_EVENT_TYPE)) event_data = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) - origin = Column(String(MAX_LENGTH_EVENT_ORIGIN)) + origin = Column(String(MAX_LENGTH_EVENT_ORIGIN)) # no longer used for new rows + origin_idx = Column(SmallInteger) time_fired = Column(DATETIME_TYPE, index=True) context_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) - context_user_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) - context_parent_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) + context_user_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID)) + context_parent_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID)) + data_id = Column(Integer, ForeignKey("event_data.data_id"), index=True) + event_data_rel = relationship("EventData") def __repr__(self) -> str: """Return string representation of instance for debugging.""" return ( f"" + f"id={self.event_id}, type='{self.event_type}', " + f"origin_idx='{self.origin_idx}', time_fired='{self.time_fired}'" + f", data_id={self.data_id})>" ) @staticmethod - def from_event( - event: Event, event_data: UndefinedType | None = UNDEFINED - ) -> Events: + def from_event(event: Event) -> Events: """Create an event database object from a native event.""" return Events( event_type=event.event_type, - event_data=JSON_DUMP(event.data) if event_data is UNDEFINED else event_data, - origin=str(event.origin.value), + event_data=None, + origin_idx=EVENT_ORIGIN_TO_IDX.get(event.origin), time_fired=event.time_fired, context_id=event.context.id, context_user_id=event.context.user_id, @@ -138,8 +199,10 @@ class Events(Base): # type: ignore[misc,valid-type] try: return Event( self.event_type, - json.loads(self.event_data), - EventOrigin(self.origin), + json.loads(self.event_data) if self.event_data else {}, + EventOrigin(self.origin) + if self.origin + else EVENT_ORIGIN_ORDER[self.origin_idx], process_timestamp(self.time_fired), context=context, ) @@ -149,30 +212,82 @@ class Events(Base): # type: ignore[misc,valid-type] return None +class EventData(Base): # type: ignore[misc,valid-type] + """Event data history.""" + + __table_args__ = ( + {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, + ) + __tablename__ = TABLE_EVENT_DATA + data_id = Column(Integer, Identity(), primary_key=True) + hash = Column(BigInteger, index=True) + # Note that this is not named attributes to avoid confusion with the states table + shared_data = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + @staticmethod + def from_event(event: Event) -> EventData: + """Create object from an event.""" + shared_data = JSON_DUMP(event.data) + return EventData( + shared_data=shared_data, hash=EventData.hash_shared_data(shared_data) + ) + + @staticmethod + def shared_data_from_event(event: Event) -> str: + """Create shared_attrs from an event.""" + return JSON_DUMP(event.data) + + @staticmethod + def hash_shared_data(shared_data: str) -> int: + """Return the hash of json encoded shared data.""" + return cast(int, fnv1a_32(shared_data.encode("utf-8"))) + + def to_native(self) -> dict[str, Any]: + """Convert to an HA state object.""" + try: + return cast(dict[str, Any], json.loads(self.shared_data)) + except ValueError: + _LOGGER.exception("Error converting row to event data: %s", self) + return {} + + class States(Base): # type: ignore[misc,valid-type] """State change history.""" __table_args__ = ( # Used for fetching the state of entities at a specific time # (get_states in history.py) - Index("ix_states_entity_id_last_updated", "entity_id", "last_updated"), + Index(ENTITY_ID_LAST_UPDATED_INDEX, "entity_id", "last_updated"), {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, ) __tablename__ = TABLE_STATES state_id = Column(Integer, Identity(), primary_key=True) entity_id = Column(String(MAX_LENGTH_STATE_ENTITY_ID)) state = Column(String(MAX_LENGTH_STATE_STATE)) - attributes = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) - event_id = Column( + attributes = Column( + Text().with_variant(mysql.LONGTEXT, "mysql") + ) # no longer used for new rows + event_id = Column( # no longer used for new rows Integer, ForeignKey("events.event_id", ondelete="CASCADE"), index=True ) - last_changed = Column(DATETIME_TYPE, default=dt_util.utcnow) + last_changed = Column(DATETIME_TYPE) last_updated = Column(DATETIME_TYPE, default=dt_util.utcnow, index=True) old_state_id = Column(Integer, ForeignKey("states.state_id"), index=True) attributes_id = Column( Integer, ForeignKey("state_attributes.attributes_id"), index=True ) - event = relationship("Events", uselist=False) + context_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) + context_user_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID)) + context_parent_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID)) + origin_idx = Column(SmallInteger) # 0 is local, 1 is remote old_state = relationship("States", remote_side=[state_id]) state_attributes = relationship("StateAttributes") @@ -192,40 +307,60 @@ class States(Base): # type: ignore[misc,valid-type] """Create object from a state_changed event.""" entity_id = event.data["entity_id"] state: State | None = event.data.get("new_state") - dbstate = States(entity_id=entity_id, attributes=None) + dbstate = States( + entity_id=entity_id, + attributes=None, + context_id=event.context.id, + context_user_id=event.context.user_id, + context_parent_id=event.context.parent_id, + origin_idx=EVENT_ORIGIN_TO_IDX.get(event.origin), + ) # None state means the state was removed from the state machine if state is None: dbstate.state = "" - dbstate.last_changed = event.time_fired dbstate.last_updated = event.time_fired + dbstate.last_changed = None + return dbstate + + dbstate.state = state.state + dbstate.last_updated = state.last_updated + if state.last_updated == state.last_changed: + dbstate.last_changed = None else: - dbstate.state = state.state dbstate.last_changed = state.last_changed - dbstate.last_updated = state.last_updated return dbstate def to_native(self, validate_entity_id: bool = True) -> State | None: """Convert to an HA state object.""" + context = Context( + id=self.context_id, + user_id=self.context_user_id, + parent_id=self.context_parent_id, + ) try: - return State( - self.entity_id, - self.state, - # Join the state_attributes table on attributes_id to get the attributes - # for newer states - json.loads(self.attributes) if self.attributes else {}, - process_timestamp(self.last_changed), - process_timestamp(self.last_updated), - # Join the events table on event_id to get the context instead - # as it will always be there for state_changed events - context=Context(id=None), # type: ignore[arg-type] - validate_entity_id=validate_entity_id, - ) + attrs = json.loads(self.attributes) if self.attributes else {} except ValueError: # When json.loads fails _LOGGER.exception("Error converting row to state: %s", self) return None + if self.last_changed is None or self.last_changed == self.last_updated: + last_changed = last_updated = process_timestamp(self.last_updated) + else: + last_updated = process_timestamp(self.last_updated) + last_changed = process_timestamp(self.last_changed) + return State( + self.entity_id, + self.state, + # Join the state_attributes table on attributes_id to get the attributes + # for newer states + attrs, + last_changed, + last_updated, + context=context, + validate_entity_id=validate_entity_id, + ) class StateAttributes(Base): # type: ignore[misc,valid-type] @@ -398,7 +533,7 @@ class StatisticsMeta(Base): # type: ignore[misc,valid-type] ) __tablename__ = TABLE_STATISTICS_META id = Column(Integer, Identity(), primary_key=True) - statistic_id = Column(String(255), index=True) + statistic_id = Column(String(255), index=True, unique=True) source = Column(String(32)) unit_of_measurement = Column(String(255)) has_mean = Column(Boolean) @@ -495,6 +630,26 @@ class StatisticsRuns(Base): # type: ignore[misc,valid-type] ) +EVENT_DATA_JSON = type_coerce( + EventData.shared_data.cast(JSONB_VARIENT_CAST), JSONLiteral(none_as_null=True) +) +OLD_FORMAT_EVENT_DATA_JSON = type_coerce( + Events.event_data.cast(JSONB_VARIENT_CAST), JSONLiteral(none_as_null=True) +) + +SHARED_ATTRS_JSON = type_coerce( + StateAttributes.shared_attrs.cast(JSON_VARIENT_CAST), JSON(none_as_null=True) +) +OLD_FORMAT_ATTRS_JSON = type_coerce( + States.attributes.cast(JSON_VARIENT_CAST), JSON(none_as_null=True) +) + +ENTITY_ID_IN_EVENT: Column = EVENT_DATA_JSON["entity_id"] +OLD_ENTITY_ID_IN_EVENT: Column = OLD_FORMAT_EVENT_DATA_JSON["entity_id"] +DEVICE_ID_IN_EVENT: Column = EVENT_DATA_JSON["device_id"] +OLD_STATE = aliased(States, name="old_state") + + @overload def process_timestamp(ts: None) -> None: ... @@ -536,6 +691,17 @@ def process_timestamp_to_utc_isoformat(ts: datetime | None) -> str | None: return ts.astimezone(dt_util.UTC).isoformat() +def process_datetime_to_timestamp(ts: datetime) -> float: + """Process a datebase datetime to epoch. + + Mirrors the behavior of process_timestamp_to_utc_isoformat + except it returns the epoch time. + """ + if ts.tzinfo is None or ts.tzinfo == dt_util.UTC: + return dt_util.utc_to_timestamp(ts) + return ts.timestamp() + + class LazyState(State): """A lazy version of core State.""" @@ -545,45 +711,30 @@ class LazyState(State): "_last_changed", "_last_updated", "_context", - "_attr_cache", + "attr_cache", ] def __init__( # pylint: disable=super-init-not-called - self, row: Row, attr_cache: dict[str, dict[str, Any]] | None = None + self, + row: Row, + attr_cache: dict[str, dict[str, Any]], + start_time: datetime | None = None, ) -> None: """Init the lazy state.""" self._row = row self.entity_id: str = self._row.entity_id self.state = self._row.state or "" self._attributes: dict[str, Any] | None = None - self._last_changed: datetime | None = None - self._last_updated: datetime | None = None + self._last_changed: datetime | None = start_time + self._last_updated: datetime | None = start_time self._context: Context | None = None - self._attr_cache = attr_cache + self.attr_cache = attr_cache @property # type: ignore[override] def attributes(self) -> dict[str, Any]: # type: ignore[override] """State attributes.""" if self._attributes is None: - source = self._row.shared_attrs or self._row.attributes - if self._attr_cache is not None and ( - attributes := self._attr_cache.get(source) - ): - self._attributes = attributes - return attributes - if source == EMPTY_JSON_OBJECT or source is None: - self._attributes = {} - return self._attributes - try: - self._attributes = json.loads(source) - except ValueError: - # When json.loads fails - _LOGGER.exception( - "Error converting row to state attributes: %s", self._row - ) - self._attributes = {} - if self._attr_cache is not None: - self._attr_cache[source] = self._attributes + self._attributes = decode_attributes_from_row(self._row, self.attr_cache) return self._attributes @attributes.setter @@ -595,7 +746,7 @@ class LazyState(State): def context(self) -> Context: # type: ignore[override] """State context.""" if self._context is None: - self._context = Context(id=None) # type: ignore[arg-type] + self._context = Context(id=None) return self._context @context.setter @@ -607,7 +758,10 @@ class LazyState(State): def last_changed(self) -> datetime: # type: ignore[override] """Last changed datetime.""" if self._last_changed is None: - self._last_changed = process_timestamp(self._row.last_changed) + if (last_changed := self._row.last_changed) is not None: + self._last_changed = process_timestamp(last_changed) + else: + self._last_changed = self.last_updated return self._last_changed @last_changed.setter @@ -619,10 +773,7 @@ class LazyState(State): def last_updated(self) -> datetime: # type: ignore[override] """Last updated datetime.""" if self._last_updated is None: - if (last_updated := self._row.last_updated) is not None: - self._last_updated = process_timestamp(last_updated) - else: - self._last_updated = self.last_changed + self._last_updated = process_timestamp(self._row.last_updated) return self._last_updated @last_updated.setter @@ -638,24 +789,24 @@ class LazyState(State): To be used for JSON serialization. """ if self._last_changed is None and self._last_updated is None: - last_changed_isoformat = process_timestamp_to_utc_isoformat( - self._row.last_changed + last_updated_isoformat = process_timestamp_to_utc_isoformat( + self._row.last_updated ) if ( - self._row.last_updated is None + self._row.last_changed is None or self._row.last_changed == self._row.last_updated ): - last_updated_isoformat = last_changed_isoformat + last_changed_isoformat = last_updated_isoformat else: - last_updated_isoformat = process_timestamp_to_utc_isoformat( - self._row.last_updated + last_changed_isoformat = process_timestamp_to_utc_isoformat( + self._row.last_changed ) else: - last_changed_isoformat = self.last_changed.isoformat() + last_updated_isoformat = self.last_updated.isoformat() if self.last_changed == self.last_updated: - last_updated_isoformat = last_changed_isoformat + last_changed_isoformat = last_updated_isoformat else: - last_updated_isoformat = self.last_updated.isoformat() + last_changed_isoformat = self.last_changed.isoformat() return { "entity_id": self.entity_id, "state": self.state, @@ -672,3 +823,46 @@ class LazyState(State): and self.state == other.state and self.attributes == other.attributes ) + + +def decode_attributes_from_row( + row: Row, attr_cache: dict[str, dict[str, Any]] +) -> dict[str, Any]: + """Decode attributes from a database row.""" + source: str = row.shared_attrs or row.attributes + if (attributes := attr_cache.get(source)) is not None: + return attributes + if not source or source == EMPTY_JSON_OBJECT: + return {} + try: + attr_cache[source] = attributes = json.loads(source) + except ValueError: + _LOGGER.exception("Error converting row to state attributes: %s", source) + attr_cache[source] = attributes = {} + return attributes + + +def row_to_compressed_state( + row: Row, + attr_cache: dict[str, dict[str, Any]], + start_time: datetime | None = None, +) -> dict[str, Any]: + """Convert a database row to a compressed state.""" + comp_state = { + COMPRESSED_STATE_STATE: row.state, + COMPRESSED_STATE_ATTRIBUTES: decode_attributes_from_row(row, attr_cache), + } + if start_time: + comp_state[COMPRESSED_STATE_LAST_UPDATED] = start_time.timestamp() + else: + row_last_updated: datetime = row.last_updated + comp_state[COMPRESSED_STATE_LAST_UPDATED] = process_datetime_to_timestamp( + row_last_updated + ) + if ( + row_changed_changed := row.last_changed + ) and row_last_updated != row_changed_changed: + comp_state[COMPRESSED_STATE_LAST_CHANGED] = process_datetime_to_timestamp( + row_changed_changed + ) + return comp_state diff --git a/homeassistant/components/recorder/pool.py b/homeassistant/components/recorder/pool.py index 027b9bfbc25..52b6b74dfa1 100644 --- a/homeassistant/components/recorder/pool.py +++ b/homeassistant/components/recorder/pool.py @@ -8,6 +8,7 @@ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.pool import NullPool, SingletonThreadPool, StaticPool from homeassistant.helpers.frame import report +from homeassistant.util.async_ import check_loop from .const import DB_WORKER_PREFIX @@ -19,6 +20,10 @@ DEBUG_MUTEX_POOL_TRACE = False POOL_SIZE = 5 +ADVISE_MSG = ( + "Use homeassistant.components.recorder.get_instance(hass).async_add_executor_job()" +) + class RecorderPool(SingletonThreadPool, NullPool): # type: ignore[misc] """A hybrid of NullPool and SingletonThreadPool. @@ -50,7 +55,12 @@ class RecorderPool(SingletonThreadPool, NullPool): # type: ignore[misc] def shutdown(self) -> None: """Close the connection.""" - if self.recorder_or_dbworker and self._conn and (conn := self._conn.current()): + if ( + self.recorder_or_dbworker + and self._conn + and hasattr(self._conn, "current") + and (conn := self._conn.current()) + ): conn.close() def dispose(self) -> None: @@ -62,9 +72,17 @@ class RecorderPool(SingletonThreadPool, NullPool): # type: ignore[misc] def _do_get(self) -> Any: if self.recorder_or_dbworker: return super()._do_get() + check_loop( + self._do_get_db_connection_protected, + strict=True, + advise_msg=ADVISE_MSG, + ) + return self._do_get_db_connection_protected() + + def _do_get_db_connection_protected(self) -> Any: report( "accesses the database without the database executor; " - "Use homeassistant.components.recorder.get_instance(hass).async_add_executor_job() " + f"{ADVISE_MSG} " "for faster database operations", exclude_integrations={"recorder"}, error_if_core=False, diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index d4061a69bab..10136dfb5a6 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -3,26 +3,38 @@ from __future__ import annotations from collections.abc import Callable, Iterable from datetime import datetime -from itertools import zip_longest +from functools import partial +from itertools import islice, zip_longest import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -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 -from .const import MAX_ROWS_TO_PURGE -from .models import ( - Events, - RecorderRuns, - StateAttributes, - States, - StatisticsRuns, - StatisticsShortTerm, +from .const import MAX_ROWS_TO_PURGE, SupportedDialect +from .models import Events, StateAttributes, States +from .queries import ( + attributes_ids_exist_in_states, + attributes_ids_exist_in_states_sqlite, + data_ids_exist_in_events, + data_ids_exist_in_events_sqlite, + delete_event_data_rows, + delete_event_rows, + delete_recorder_runs_rows, + delete_states_attributes_rows, + delete_states_rows, + delete_statistics_runs_rows, + delete_statistics_short_term_rows, + disconnect_states_rows, + find_events_to_purge, + find_latest_statistics_runs_run_id, + find_legacy_event_state_and_attributes_and_data_ids_to_purge, + find_legacy_row, + find_short_term_statistics_to_purge, + find_states_to_purge, + find_statistics_runs_to_purge, ) from .repack import repack_database from .util import retryable_database_job, session_scope @@ -33,9 +45,34 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) +DEFAULT_STATES_BATCHES_PER_PURGE = 20 # We expect ~95% de-dupe rate +DEFAULT_EVENTS_BATCHES_PER_PURGE = 15 # We expect ~92% de-dupe rate + + +def take(take_num: int, iterable: Iterable) -> list[Any]: + """Return first n items of the iterable as a list. + + From itertools recipes + """ + return list(islice(iterable, take_num)) + + +def chunked(iterable: Iterable, chunked_num: int) -> Iterable[Any]: + """Break *iterable* into lists of length *n*. + + From more-itertools + """ + return iter(partial(take, chunked_num, iter(iterable)), []) + + @retryable_database_job("purge") def purge_old_data( - instance: Recorder, purge_before: datetime, repack: bool, apply_filter: bool = False + instance: Recorder, + purge_before: datetime, + repack: bool, + apply_filter: bool = False, + events_batch_size: int = DEFAULT_EVENTS_BATCHES_PER_PURGE, + states_batch_size: int = DEFAULT_STATES_BATCHES_PER_PURGE, ) -> bool: """Purge events and states older than purge_before. @@ -45,38 +82,41 @@ def purge_old_data( "Purging states and events before target %s", purge_before.isoformat(sep=" ", timespec="seconds"), ) - using_sqlite = instance.using_sqlite() + using_sqlite = instance.dialect_name == SupportedDialect.SQLITE - with session_scope(session=instance.get_session()) as session: # type: ignore[misc] + with session_scope(session=instance.get_session()) as session: # Purge a max of MAX_ROWS_TO_PURGE, based on the oldest states or events record - ( - event_ids, - state_ids, - attributes_ids, - ) = _select_event_state_and_attributes_ids_to_purge(session, purge_before) + has_more_to_purge = False + if _purging_legacy_format(session): + _LOGGER.debug( + "Purge running in legacy format as there are states with event_id remaining" + ) + has_more_to_purge |= _purge_legacy_format( + instance, session, purge_before, using_sqlite + ) + else: + _LOGGER.debug( + "Purge running in new format as there are NO states with event_id remaining" + ) + # Once we are done purging legacy rows, we use the new method + has_more_to_purge |= _purge_states_and_attributes_ids( + instance, session, states_batch_size, purge_before, using_sqlite + ) + has_more_to_purge |= _purge_events_and_data_ids( + instance, session, events_batch_size, purge_before, using_sqlite + ) + statistics_runs = _select_statistics_runs_to_purge(session, purge_before) short_term_statistics = _select_short_term_statistics_to_purge( session, purge_before ) - - if state_ids: - _purge_state_ids(instance, session, state_ids) - - if unused_attribute_ids_set := _select_unused_attributes_ids( - session, attributes_ids, using_sqlite - ): - _purge_attributes_ids(instance, session, unused_attribute_ids_set) - - if event_ids: - _purge_event_ids(session, event_ids) - if statistics_runs: _purge_statistics_runs(session, statistics_runs) if short_term_statistics: _purge_short_term_statistics(session, short_term_statistics) - if event_ids or statistics_runs or short_term_statistics: + if has_more_to_purge or statistics_runs or short_term_statistics: # Return false, as we might not be done yet. _LOGGER.debug("Purging hasn't fully completed yet") return False @@ -91,245 +131,132 @@ def purge_old_data( return True -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, 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() +def _purging_legacy_format(session: Session) -> bool: + """Check if there are any legacy event_id linked states rows remaining.""" + return bool(session.execute(find_legacy_row()).scalar()) + + +def _purge_legacy_format( + instance: Recorder, session: Session, purge_before: datetime, using_sqlite: bool +) -> bool: + """Purge rows that are still linked by the event_ids.""" + ( + event_ids, + state_ids, + attributes_ids, + data_ids, + ) = _select_legacy_event_state_and_attributes_and_data_ids_to_purge( + session, purge_before ) - _LOGGER.debug("Selected %s event ids to remove", len(events)) - event_ids = set() + if state_ids: + _purge_state_ids(instance, session, state_ids) + _purge_unused_attributes_ids(instance, session, attributes_ids, using_sqlite) + if event_ids: + _purge_event_ids(session, event_ids) + _purge_unused_data_ids(instance, session, data_ids, using_sqlite) + return bool(event_ids or state_ids or attributes_ids or data_ids) + + +def _purge_states_and_attributes_ids( + instance: Recorder, + session: Session, + states_batch_size: int, + purge_before: datetime, + using_sqlite: bool, +) -> bool: + """Purge states and linked attributes id in a batch. + + Returns true if there are more states to purge. + """ + has_remaining_state_ids_to_purge = True + # There are more states relative to attributes_ids so + # we purge enough state_ids to try to generate a full + # size batch of attributes_ids that will be around the size + # MAX_ROWS_TO_PURGE + attributes_ids_batch: set[int] = set() + for _ in range(states_batch_size): + state_ids, attributes_ids = _select_state_attributes_ids_to_purge( + session, purge_before + ) + if not state_ids: + has_remaining_state_ids_to_purge = False + break + _purge_state_ids(instance, session, state_ids) + attributes_ids_batch = attributes_ids_batch | attributes_ids + + _purge_unused_attributes_ids(instance, session, attributes_ids_batch, using_sqlite) + _LOGGER.debug( + "After purging states and attributes_ids remaining=%s", + has_remaining_state_ids_to_purge, + ) + return has_remaining_state_ids_to_purge + + +def _purge_events_and_data_ids( + instance: Recorder, + session: Session, + events_batch_size: int, + purge_before: datetime, + using_sqlite: bool, +) -> bool: + """Purge states and linked attributes id in a batch. + + Returns true if there are more states to purge. + """ + has_remaining_event_ids_to_purge = True + # There are more events relative to data_ids so + # we purge enough event_ids to try to generate a full + # size batch of data_ids that will be around the size + # MAX_ROWS_TO_PURGE + data_ids_batch: set[int] = set() + for _ in range(events_batch_size): + event_ids, data_ids = _select_event_data_ids_to_purge(session, purge_before) + if not event_ids: + has_remaining_event_ids_to_purge = False + break + _purge_event_ids(session, event_ids) + data_ids_batch = data_ids_batch | data_ids + + _purge_unused_data_ids(instance, session, data_ids_batch, using_sqlite) + _LOGGER.debug( + "After purging event and data_ids remaining=%s", + has_remaining_event_ids_to_purge, + ) + return has_remaining_event_ids_to_purge + + +def _select_state_attributes_ids_to_purge( + session: Session, purge_before: datetime +) -> tuple[set[int], set[int]]: + """Return sets of state and attribute ids to purge.""" state_ids = set() attributes_ids = set() - 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), - ) + for state in session.execute(find_states_to_purge(purge_before)).all(): + state_ids.add(state.state_id) + if state.attributes_id: + attributes_ids.add(state.attributes_id) + _LOGGER.debug( + "Selected %s state ids and %s attributes_ids to remove", + len(state_ids), + len(attributes_ids), ) + return state_ids, attributes_ids + + +def _select_event_data_ids_to_purge( + session: Session, purge_before: datetime +) -> tuple[set[int], set[int]]: + """Return sets of event and data ids to purge.""" + event_ids = set() + data_ids = set() + for event in session.execute(find_events_to_purge(purge_before)).all(): + event_ids.add(event.event_id) + if event.data_id: + data_ids.add(event.data_id) + _LOGGER.debug( + "Selected %s event ids and %s data_ids to remove", len(event_ids), len(data_ids) + ) + return event_ids, data_ids def _select_unused_attributes_ids( @@ -354,9 +281,9 @@ def _select_unused_attributes_ids( # seen_ids = { state[0] - for state in session.query(distinct(States.attributes_id)) - .filter(States.attributes_id.in_(attributes_ids)) - .all() + for state in session.execute( + attributes_ids_exist_in_states_sqlite(attributes_ids) + ).all() } else: # @@ -386,11 +313,11 @@ def _select_unused_attributes_ids( 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) + attrs_id[0] + for attrs_id in session.execute( + attributes_ids_exist_in_states(*attr_ids) # type: ignore[arg-type] ).all() - if state[0] is not None + if attrs_id[0] is not None } to_remove = attributes_ids - seen_ids _LOGGER.debug( @@ -400,20 +327,69 @@ def _select_unused_attributes_ids( return to_remove +def _purge_unused_attributes_ids( + instance: Recorder, + session: Session, + attributes_ids_batch: set[int], + using_sqlite: bool, +) -> None: + if unused_attribute_ids_set := _select_unused_attributes_ids( + session, attributes_ids_batch, using_sqlite + ): + _purge_batch_attributes_ids(instance, session, unused_attribute_ids_set) + + +def _select_unused_event_data_ids( + session: Session, data_ids: set[int], using_sqlite: bool +) -> set[int]: + """Return a set of event data ids that are not used by any events in the database.""" + if not data_ids: + return set() + + # See _select_unused_attributes_ids for why this function + # branches for non-sqlite databases. + if using_sqlite: + seen_ids = { + state[0] + for state in session.execute( + data_ids_exist_in_events_sqlite(data_ids) + ).all() + } + else: + seen_ids = set() + groups = [iter(data_ids)] * 100 + for data_ids_group in zip_longest(*groups, fillvalue=None): + seen_ids |= { + data_id[0] + for data_id in session.execute( + data_ids_exist_in_events(*data_ids_group) # type: ignore[arg-type] + ).all() + if data_id[0] is not None + } + to_remove = data_ids - seen_ids + _LOGGER.debug("Selected %s shared event data to remove", len(to_remove)) + return to_remove + + +def _purge_unused_data_ids( + instance: Recorder, session: Session, data_ids_batch: set[int], using_sqlite: bool +) -> None: + + if unused_data_ids_set := _select_unused_event_data_ids( + session, data_ids_batch, using_sqlite + ): + _purge_batch_data_ids(instance, session, unused_data_ids_set) + + def _select_statistics_runs_to_purge( session: Session, purge_before: datetime ) -> list[int]: """Return a list of statistic runs to purge, but take care to keep the newest run.""" - statistic_runs = ( - session.query(StatisticsRuns.run_id) - .filter(StatisticsRuns.start < purge_before) - .limit(MAX_ROWS_TO_PURGE) - .all() - ) + statistic_runs = session.execute(find_statistics_runs_to_purge(purge_before)).all() statistic_runs_list = [run.run_id for run in statistic_runs] # Exclude the newest statistics run if ( - last_run := session.query(func.max(StatisticsRuns.run_id)).scalar() + last_run := session.execute(find_latest_statistics_runs_run_id()).scalar() ) and last_run in statistic_runs_list: statistic_runs_list.remove(last_run) @@ -425,16 +401,41 @@ def _select_short_term_statistics_to_purge( session: Session, purge_before: datetime ) -> list[int]: """Return a list of short term statistics to purge.""" - statistics = ( - session.query(StatisticsShortTerm.id) - .filter(StatisticsShortTerm.start < purge_before) - .limit(MAX_ROWS_TO_PURGE) - .all() - ) + statistics = session.execute( + find_short_term_statistics_to_purge(purge_before) + ).all() _LOGGER.debug("Selected %s short term statistics to remove", len(statistics)) return [statistic.id for statistic in statistics] +def _select_legacy_event_state_and_attributes_and_data_ids_to_purge( + session: Session, purge_before: datetime +) -> tuple[set[int], set[int], set[int], set[int]]: + """Return a list of event, state, and attribute ids to purge that are linked by the event_id. + + We do not link these anymore since state_change events + do not exist in the events table anymore, however we + still need to be able to purge them. + """ + events = session.execute( + find_legacy_event_state_and_attributes_and_data_ids_to_purge(purge_before) + ).all() + _LOGGER.debug("Selected %s event ids to remove", len(events)) + event_ids = set() + state_ids = set() + attributes_ids = set() + data_ids = set() + 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) + if event.data_id: + data_ids.add(event.data_id) + return event_ids, state_ids, attributes_ids, data_ids + + def _purge_state_ids(instance: Recorder, session: Session, state_ids: set[int]) -> None: """Disconnect states and delete by state id.""" @@ -442,18 +443,10 @@ def _purge_state_ids(instance: Recorder, session: Session, state_ids: set[int]) # the delete does not fail due to a foreign key constraint # since some databases (MSSQL) cannot do the ON DELETE SET NULL # for us. - disconnected_rows = ( - session.query(States) - .filter(States.old_state_id.in_(state_ids)) - .update({"old_state_id": None}, synchronize_session=False) - ) + disconnected_rows = session.execute(disconnect_states_rows(state_ids)) _LOGGER.debug("Updated %s states to remove old_state_id", disconnected_rows) - deleted_rows = ( - session.query(States) - .filter(States.state_id.in_(state_ids)) - .delete(synchronize_session=False) - ) + deleted_rows = session.execute(delete_states_rows(state_ids)) _LOGGER.debug("Deleted %s states", deleted_rows) # Evict eny entries in the old_states cache referring to a purged state @@ -477,6 +470,21 @@ def _evict_purged_states_from_old_states_cache( old_states.pop(old_state_reversed[purged_state_id], None) +def _evict_purged_data_from_data_cache( + instance: Recorder, purged_data_ids: set[int] +) -> None: + """Evict purged data ids from the data ids cache.""" + # Make a map from data_id to the data json + event_data_ids = instance._event_data_ids # pylint: disable=protected-access + event_data_ids_reversed = { + data_id: data for data, data_id in event_data_ids.items() + } + + # Evict any purged data from the event_data_ids cache + for purged_attribute_id in purged_data_ids.intersection(event_data_ids_reversed): + event_data_ids.pop(event_data_ids_reversed[purged_attribute_id], None) + + def _evict_purged_attributes_from_attributes_cache( instance: Recorder, purged_attributes_ids: set[int] ) -> None: @@ -499,29 +507,35 @@ def _evict_purged_attributes_from_attributes_cache( ) -def _purge_attributes_ids( +def _purge_batch_attributes_ids( instance: Recorder, session: Session, attributes_ids: set[int] ) -> None: - """Delete old attributes ids.""" - - deleted_rows = ( - session.query(StateAttributes) - .filter(StateAttributes.attributes_id.in_(attributes_ids)) - .delete(synchronize_session=False) - ) - _LOGGER.debug("Deleted %s attribute states", deleted_rows) + """Delete old attributes ids in batches of MAX_ROWS_TO_PURGE.""" + for attributes_ids_chunk in chunked(attributes_ids, MAX_ROWS_TO_PURGE): + deleted_rows = session.execute( + delete_states_attributes_rows(attributes_ids_chunk) + ) + _LOGGER.debug("Deleted %s attribute states", deleted_rows) # Evict any entries in the state_attributes_ids cache referring to a purged state _evict_purged_attributes_from_attributes_cache(instance, attributes_ids) +def _purge_batch_data_ids( + instance: Recorder, session: Session, data_ids: set[int] +) -> None: + """Delete old event data ids in batches of MAX_ROWS_TO_PURGE.""" + for data_ids_chunk in chunked(data_ids, MAX_ROWS_TO_PURGE): + deleted_rows = session.execute(delete_event_data_rows(data_ids_chunk)) + _LOGGER.debug("Deleted %s data events", deleted_rows) + + # Evict any entries in the event_data_ids cache referring to a purged state + _evict_purged_data_from_data_cache(instance, data_ids) + + def _purge_statistics_runs(session: Session, statistics_runs: list[int]) -> None: """Delete by run_id.""" - deleted_rows = ( - session.query(StatisticsRuns) - .filter(StatisticsRuns.run_id.in_(statistics_runs)) - .delete(synchronize_session=False) - ) + deleted_rows = session.execute(delete_statistics_runs_rows(statistics_runs)) _LOGGER.debug("Deleted %s statistic runs", deleted_rows) @@ -529,21 +543,15 @@ def _purge_short_term_statistics( session: Session, short_term_statistics: list[int] ) -> None: """Delete by id.""" - deleted_rows = ( - session.query(StatisticsShortTerm) - .filter(StatisticsShortTerm.id.in_(short_term_statistics)) - .delete(synchronize_session=False) + deleted_rows = session.execute( + delete_statistics_short_term_rows(short_term_statistics) ) _LOGGER.debug("Deleted %s short term statistics", deleted_rows) def _purge_event_ids(session: Session, event_ids: Iterable[int]) -> None: """Delete by event id.""" - deleted_rows = ( - session.query(Events) - .filter(Events.event_id.in_(event_ids)) - .delete(synchronize_session=False) - ) + deleted_rows = session.execute(delete_event_rows(event_ids)) _LOGGER.debug("Deleted %s events", deleted_rows) @@ -552,11 +560,8 @@ def _purge_old_recorder_runs( ) -> None: """Purge all old recorder runs.""" # Recorder runs is small, no need to batch run it - deleted_rows = ( - session.query(RecorderRuns) - .filter(RecorderRuns.start < purge_before) - .filter(RecorderRuns.run_id != instance.run_history.current.run_id) - .delete(synchronize_session=False) + deleted_rows = session.execute( + delete_recorder_runs_rows(purge_before, instance.run_history.current.run_id) ) _LOGGER.debug("Deleted %s recorder_runs", deleted_rows) @@ -564,7 +569,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() + using_sqlite = instance.dialect_name == SupportedDialect.SQLITE # Check if excluded entity_ids are in database excluded_entity_ids: list[str] = [ @@ -616,22 +621,22 @@ def _purge_filtered_states( unused_attribute_ids_set = _select_unused_attributes_ids( session, {id_ for id_ in attributes_ids if id_ is not None}, using_sqlite ) - _purge_attributes_ids(instance, session, unused_attribute_ids_set) + _purge_batch_attributes_ids(instance, session, unused_attribute_ids_set) def _purge_filtered_events( instance: Recorder, session: Session, excluded_event_types: list[str] ) -> None: """Remove filtered events and linked states.""" - events: list[Events] = ( - session.query(Events.event_id) - .filter(Events.event_type.in_(excluded_event_types)) - .limit(MAX_ROWS_TO_PURGE) - .all() + using_sqlite = instance.dialect_name == SupportedDialect.SQLITE + event_ids, data_ids = zip( + *( + session.query(Events.event_id, Events.data_id) + .filter(Events.event_type.in_(excluded_event_types)) + .limit(MAX_ROWS_TO_PURGE) + .all() + ) ) - event_ids: list[int] = [ - event.event_id for event in events if event.event_id is not None - ] _LOGGER.debug( "Selected %s event_ids to remove that should be filtered", len(event_ids) ) @@ -641,6 +646,10 @@ def _purge_filtered_events( state_ids: set[int] = {state.state_id for state in states} _purge_state_ids(instance, session, state_ids) _purge_event_ids(session, event_ids) + if unused_data_ids_set := _select_unused_event_data_ids( + session, set(data_ids), using_sqlite + ): + _purge_batch_data_ids(instance, session, unused_data_ids_set) if EVENT_STATE_CHANGED in excluded_event_types: session.query(StateAttributes).delete(synchronize_session=False) instance._state_attributes_ids = {} # pylint: disable=protected-access @@ -649,8 +658,8 @@ 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] + using_sqlite = instance.dialect_name == SupportedDialect.SQLITE + with session_scope(session=instance.get_session()) as session: selected_entity_ids: list[str] = [ entity_id for (entity_id,) in session.query(distinct(States.entity_id)).all() diff --git a/homeassistant/components/recorder/queries.py b/homeassistant/components/recorder/queries.py new file mode 100644 index 00000000000..e27d3d692cc --- /dev/null +++ b/homeassistant/components/recorder/queries.py @@ -0,0 +1,642 @@ +"""Queries for the recorder.""" +from __future__ import annotations + +from collections.abc import Iterable +from datetime import datetime + +from sqlalchemy import delete, distinct, func, lambda_stmt, select, union_all, update +from sqlalchemy.sql.lambdas import StatementLambdaElement +from sqlalchemy.sql.selectable import Select + +from .const import MAX_ROWS_TO_PURGE +from .models import ( + EventData, + Events, + RecorderRuns, + StateAttributes, + States, + StatisticsRuns, + StatisticsShortTerm, +) + + +def find_shared_attributes_id( + data_hash: int, shared_attrs: str +) -> StatementLambdaElement: + """Find an attributes_id by hash and shared_attrs.""" + return lambda_stmt( + lambda: select(StateAttributes.attributes_id) + .filter(StateAttributes.hash == data_hash) + .filter(StateAttributes.shared_attrs == shared_attrs) + ) + + +def find_shared_data_id(attr_hash: int, shared_data: str) -> StatementLambdaElement: + """Find a data_id by hash and shared_data.""" + return lambda_stmt( + lambda: select(EventData.data_id) + .filter(EventData.hash == attr_hash) + .filter(EventData.shared_data == shared_data) + ) + + +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 attributes_ids_exist_in_states_sqlite( + attributes_ids: Iterable[int], +) -> StatementLambdaElement: + """Find attributes ids that exist in the states table.""" + return lambda_stmt( + lambda: select(distinct(States.attributes_id)).filter( + States.attributes_id.in_(attributes_ids) + ) + ) + + +def attributes_ids_exist_in_states( + 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 data_ids_exist_in_events_sqlite( + data_ids: Iterable[int], +) -> StatementLambdaElement: + """Find data ids that exist in the events table.""" + return lambda_stmt( + lambda: select(distinct(Events.data_id)).filter(Events.data_id.in_(data_ids)) + ) + + +def _event_data_id_exist(data_id: int | None) -> Select: + """Check if a event data id exists in the events table.""" + return select(func.min(Events.data_id)).where(Events.data_id == data_id) + + +def data_ids_exist_in_events( + id1: int, + id2: int | None, + id3: int | None, + id4: int | None, + id5: int | None, + id6: int | None, + id7: int | None, + id8: int | None, + id9: int | None, + id10: int | None, + id11: int | None, + id12: int | None, + id13: int | None, + id14: int | None, + id15: int | None, + id16: int | None, + id17: int | None, + id18: int | None, + id19: int | None, + id20: int | None, + id21: int | None, + id22: int | None, + id23: int | None, + id24: int | None, + id25: int | None, + id26: int | None, + id27: int | None, + id28: int | None, + id29: int | None, + id30: int | None, + id31: int | None, + id32: int | None, + id33: int | None, + id34: int | None, + id35: int | None, + id36: int | None, + id37: int | None, + id38: int | None, + id39: int | None, + id40: int | None, + id41: int | None, + id42: int | None, + id43: int | None, + id44: int | None, + id45: int | None, + id46: int | None, + id47: int | None, + id48: int | None, + id49: int | None, + id50: int | None, + id51: int | None, + id52: int | None, + id53: int | None, + id54: int | None, + id55: int | None, + id56: int | None, + id57: int | None, + id58: int | None, + id59: int | None, + id60: int | None, + id61: int | None, + id62: int | None, + id63: int | None, + id64: int | None, + id65: int | None, + id66: int | None, + id67: int | None, + id68: int | None, + id69: int | None, + id70: int | None, + id71: int | None, + id72: int | None, + id73: int | None, + id74: int | None, + id75: int | None, + id76: int | None, + id77: int | None, + id78: int | None, + id79: int | None, + id80: int | None, + id81: int | None, + id82: int | None, + id83: int | None, + id84: int | None, + id85: int | None, + id86: int | None, + id87: int | None, + id88: int | None, + id89: int | None, + id90: int | None, + id91: int | None, + id92: int | None, + id93: int | None, + id94: int | None, + id95: int | None, + id96: int | None, + id97: int | None, + id98: int | None, + id99: int | None, + id100: int | None, +) -> StatementLambdaElement: + """Generate the find event data select only once. + + https://docs.sqlalchemy.org/en/14/core/connections.html#quick-guidelines-for-lambdas + """ + return lambda_stmt( + lambda: union_all( + _event_data_id_exist(id1), + _event_data_id_exist(id2), + _event_data_id_exist(id3), + _event_data_id_exist(id4), + _event_data_id_exist(id5), + _event_data_id_exist(id6), + _event_data_id_exist(id7), + _event_data_id_exist(id8), + _event_data_id_exist(id9), + _event_data_id_exist(id10), + _event_data_id_exist(id11), + _event_data_id_exist(id12), + _event_data_id_exist(id13), + _event_data_id_exist(id14), + _event_data_id_exist(id15), + _event_data_id_exist(id16), + _event_data_id_exist(id17), + _event_data_id_exist(id18), + _event_data_id_exist(id19), + _event_data_id_exist(id20), + _event_data_id_exist(id21), + _event_data_id_exist(id22), + _event_data_id_exist(id23), + _event_data_id_exist(id24), + _event_data_id_exist(id25), + _event_data_id_exist(id26), + _event_data_id_exist(id27), + _event_data_id_exist(id28), + _event_data_id_exist(id29), + _event_data_id_exist(id30), + _event_data_id_exist(id31), + _event_data_id_exist(id32), + _event_data_id_exist(id33), + _event_data_id_exist(id34), + _event_data_id_exist(id35), + _event_data_id_exist(id36), + _event_data_id_exist(id37), + _event_data_id_exist(id38), + _event_data_id_exist(id39), + _event_data_id_exist(id40), + _event_data_id_exist(id41), + _event_data_id_exist(id42), + _event_data_id_exist(id43), + _event_data_id_exist(id44), + _event_data_id_exist(id45), + _event_data_id_exist(id46), + _event_data_id_exist(id47), + _event_data_id_exist(id48), + _event_data_id_exist(id49), + _event_data_id_exist(id50), + _event_data_id_exist(id51), + _event_data_id_exist(id52), + _event_data_id_exist(id53), + _event_data_id_exist(id54), + _event_data_id_exist(id55), + _event_data_id_exist(id56), + _event_data_id_exist(id57), + _event_data_id_exist(id58), + _event_data_id_exist(id59), + _event_data_id_exist(id60), + _event_data_id_exist(id61), + _event_data_id_exist(id62), + _event_data_id_exist(id63), + _event_data_id_exist(id64), + _event_data_id_exist(id65), + _event_data_id_exist(id66), + _event_data_id_exist(id67), + _event_data_id_exist(id68), + _event_data_id_exist(id69), + _event_data_id_exist(id70), + _event_data_id_exist(id71), + _event_data_id_exist(id72), + _event_data_id_exist(id73), + _event_data_id_exist(id74), + _event_data_id_exist(id75), + _event_data_id_exist(id76), + _event_data_id_exist(id77), + _event_data_id_exist(id78), + _event_data_id_exist(id79), + _event_data_id_exist(id80), + _event_data_id_exist(id81), + _event_data_id_exist(id82), + _event_data_id_exist(id83), + _event_data_id_exist(id84), + _event_data_id_exist(id85), + _event_data_id_exist(id86), + _event_data_id_exist(id87), + _event_data_id_exist(id88), + _event_data_id_exist(id89), + _event_data_id_exist(id90), + _event_data_id_exist(id91), + _event_data_id_exist(id92), + _event_data_id_exist(id93), + _event_data_id_exist(id94), + _event_data_id_exist(id95), + _event_data_id_exist(id96), + _event_data_id_exist(id97), + _event_data_id_exist(id98), + _event_data_id_exist(id99), + _event_data_id_exist(id100), + ) + ) + + +def disconnect_states_rows(state_ids: Iterable[int]) -> StatementLambdaElement: + """Disconnect states rows.""" + return lambda_stmt( + lambda: update(States) + .where(States.old_state_id.in_(state_ids)) + .values(old_state_id=None) + .execution_options(synchronize_session=False) + ) + + +def delete_states_rows(state_ids: Iterable[int]) -> StatementLambdaElement: + """Delete states rows.""" + return lambda_stmt( + lambda: delete(States) + .where(States.state_id.in_(state_ids)) + .execution_options(synchronize_session=False) + ) + + +def delete_event_data_rows(data_ids: Iterable[int]) -> StatementLambdaElement: + """Delete event_data rows.""" + return lambda_stmt( + lambda: delete(EventData) + .where(EventData.data_id.in_(data_ids)) + .execution_options(synchronize_session=False) + ) + + +def delete_states_attributes_rows( + attributes_ids: Iterable[int], +) -> StatementLambdaElement: + """Delete states_attributes rows.""" + return lambda_stmt( + lambda: delete(StateAttributes) + .where(StateAttributes.attributes_id.in_(attributes_ids)) + .execution_options(synchronize_session=False) + ) + + +def delete_statistics_runs_rows( + statistics_runs: Iterable[int], +) -> StatementLambdaElement: + """Delete statistics_runs rows.""" + return lambda_stmt( + lambda: delete(StatisticsRuns) + .where(StatisticsRuns.run_id.in_(statistics_runs)) + .execution_options(synchronize_session=False) + ) + + +def delete_statistics_short_term_rows( + short_term_statistics: Iterable[int], +) -> StatementLambdaElement: + """Delete statistics_short_term rows.""" + return lambda_stmt( + lambda: delete(StatisticsShortTerm) + .where(StatisticsShortTerm.id.in_(short_term_statistics)) + .execution_options(synchronize_session=False) + ) + + +def delete_event_rows( + event_ids: Iterable[int], +) -> StatementLambdaElement: + """Delete statistics_short_term rows.""" + return lambda_stmt( + lambda: delete(Events) + .where(Events.event_id.in_(event_ids)) + .execution_options(synchronize_session=False) + ) + + +def delete_recorder_runs_rows( + purge_before: datetime, current_run_id: int +) -> StatementLambdaElement: + """Delete recorder_runs rows.""" + return lambda_stmt( + lambda: delete(RecorderRuns) + .filter(RecorderRuns.start < purge_before) + .filter(RecorderRuns.run_id != current_run_id) + .execution_options(synchronize_session=False) + ) + + +def find_events_to_purge(purge_before: datetime) -> StatementLambdaElement: + """Find events to purge.""" + return lambda_stmt( + lambda: select(Events.event_id, Events.data_id) + .filter(Events.time_fired < purge_before) + .limit(MAX_ROWS_TO_PURGE) + ) + + +def find_states_to_purge(purge_before: datetime) -> StatementLambdaElement: + """Find states to purge.""" + return lambda_stmt( + lambda: select(States.state_id, States.attributes_id) + .filter(States.last_updated < purge_before) + .limit(MAX_ROWS_TO_PURGE) + ) + + +def find_short_term_statistics_to_purge( + purge_before: datetime, +) -> StatementLambdaElement: + """Find short term statistics to purge.""" + return lambda_stmt( + lambda: select(StatisticsShortTerm.id) + .filter(StatisticsShortTerm.start < purge_before) + .limit(MAX_ROWS_TO_PURGE) + ) + + +def find_statistics_runs_to_purge( + purge_before: datetime, +) -> StatementLambdaElement: + """Find statistics_runs to purge.""" + return lambda_stmt( + lambda: select(StatisticsRuns.run_id) + .filter(StatisticsRuns.start < purge_before) + .limit(MAX_ROWS_TO_PURGE) + ) + + +def find_latest_statistics_runs_run_id() -> StatementLambdaElement: + """Find the latest statistics_runs run_id.""" + return lambda_stmt(lambda: select(func.max(StatisticsRuns.run_id))) + + +def find_legacy_event_state_and_attributes_and_data_ids_to_purge( + purge_before: datetime, +) -> StatementLambdaElement: + """Find the latest row in the legacy format to purge.""" + return lambda_stmt( + lambda: select( + Events.event_id, Events.data_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) + ) + + +def find_legacy_row() -> StatementLambdaElement: + """Check if there are still states in the table with an event_id.""" + return lambda_stmt(lambda: select(func.max(States.event_id))) diff --git a/homeassistant/components/recorder/repack.py b/homeassistant/components/recorder/repack.py index c272f2827a0..1b1d59df37e 100644 --- a/homeassistant/components/recorder/repack.py +++ b/homeassistant/components/recorder/repack.py @@ -6,6 +6,9 @@ from typing import TYPE_CHECKING from sqlalchemy import text +from .const import SupportedDialect +from .models import ALL_TABLES + if TYPE_CHECKING: from . import Recorder @@ -18,7 +21,7 @@ def repack_database(instance: Recorder) -> None: dialect_name = instance.engine.dialect.name # Execute sqlite command to free up space on disk - if dialect_name == "sqlite": + if dialect_name == SupportedDialect.SQLITE: _LOGGER.debug("Vacuuming SQL DB to free space") with instance.engine.connect() as conn: conn.execute(text("VACUUM")) @@ -26,7 +29,7 @@ def repack_database(instance: Recorder) -> None: return # Execute postgresql vacuum command to free up space on disk - if dialect_name == "postgresql": + if dialect_name == SupportedDialect.POSTGRESQL: _LOGGER.debug("Vacuuming SQL DB to free space") with instance.engine.connect().execution_options( isolation_level="AUTOCOMMIT" @@ -36,9 +39,9 @@ def repack_database(instance: Recorder) -> None: return # Optimize mysql / mariadb tables to free up space on disk - if dialect_name == "mysql": + if dialect_name == SupportedDialect.MYSQL: _LOGGER.debug("Optimizing SQL DB to free space") with instance.engine.connect() as conn: - conn.execute(text("OPTIMIZE TABLE states, events, recorder_runs")) + conn.execute(text(f"OPTIMIZE TABLE {','.join(ALL_TABLES)}")) conn.commit() return diff --git a/homeassistant/components/recorder/run_history.py b/homeassistant/components/recorder/run_history.py index 3a76eef3c83..783aff89c17 100644 --- a/homeassistant/components/recorder/run_history.py +++ b/homeassistant/components/recorder/run_history.py @@ -53,6 +53,13 @@ class RunHistory: """Return the time the recorder started recording states.""" return self._recording_start + @property + def first(self) -> RecorderRuns: + """Get the first run.""" + if runs_by_timestamp := self._run_history.runs_by_timestamp: + return next(iter(runs_by_timestamp.values())) + return self.current + @property def current(self) -> RecorderRuns: """Get the current run.""" diff --git a/homeassistant/components/recorder/services.py b/homeassistant/components/recorder/services.py new file mode 100644 index 00000000000..14337290c9b --- /dev/null +++ b/homeassistant/components/recorder/services.py @@ -0,0 +1,116 @@ +"""Support for recorder services.""" +from __future__ import annotations + +from datetime import timedelta +from typing import cast + +import voluptuous as vol + +from homeassistant.core import HomeAssistant, ServiceCall, callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entityfilter import generate_filter +from homeassistant.helpers.service import async_extract_entity_ids +import homeassistant.util.dt as dt_util + +from .const import ATTR_APPLY_FILTER, ATTR_KEEP_DAYS, ATTR_REPACK, DOMAIN +from .core import Recorder +from .tasks import PurgeEntitiesTask, PurgeTask + +SERVICE_PURGE = "purge" +SERVICE_PURGE_ENTITIES = "purge_entities" +SERVICE_ENABLE = "enable" +SERVICE_DISABLE = "disable" + +SERVICE_PURGE_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_KEEP_DAYS): cv.positive_int, + vol.Optional(ATTR_REPACK, default=False): cv.boolean, + vol.Optional(ATTR_APPLY_FILTER, default=False): cv.boolean, + } +) + +ATTR_DOMAINS = "domains" +ATTR_ENTITY_GLOBS = "entity_globs" + +SERVICE_PURGE_ENTITIES_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_DOMAINS, default=[]): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(ATTR_ENTITY_GLOBS, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + } +).extend(cv.ENTITY_SERVICE_FIELDS) + +SERVICE_ENABLE_SCHEMA = vol.Schema({}) +SERVICE_DISABLE_SCHEMA = vol.Schema({}) + + +@callback +def _async_register_purge_service(hass: HomeAssistant, instance: Recorder) -> None: + async def async_handle_purge_service(service: ServiceCall) -> None: + """Handle calls to the purge service.""" + kwargs = service.data + keep_days = kwargs.get(ATTR_KEEP_DAYS, instance.keep_days) + repack = cast(bool, kwargs[ATTR_REPACK]) + apply_filter = cast(bool, kwargs[ATTR_APPLY_FILTER]) + purge_before = dt_util.utcnow() - timedelta(days=keep_days) + instance.queue_task(PurgeTask(purge_before, repack, apply_filter)) + + hass.services.async_register( + DOMAIN, SERVICE_PURGE, async_handle_purge_service, schema=SERVICE_PURGE_SCHEMA + ) + + +@callback +def _async_register_purge_entities_service( + hass: HomeAssistant, instance: Recorder +) -> None: + async def async_handle_purge_entities_service(service: ServiceCall) -> None: + """Handle calls to the purge entities service.""" + entity_ids = await async_extract_entity_ids(hass, service) + domains = service.data.get(ATTR_DOMAINS, []) + entity_globs = service.data.get(ATTR_ENTITY_GLOBS, []) + entity_filter = generate_filter(domains, list(entity_ids), [], [], entity_globs) + instance.queue_task(PurgeEntitiesTask(entity_filter)) + + hass.services.async_register( + DOMAIN, + SERVICE_PURGE_ENTITIES, + async_handle_purge_entities_service, + schema=SERVICE_PURGE_ENTITIES_SCHEMA, + ) + + +@callback +def _async_register_enable_service(hass: HomeAssistant, instance: Recorder) -> None: + async def async_handle_enable_service(service: ServiceCall) -> None: + instance.set_enable(True) + + hass.services.async_register( + DOMAIN, + SERVICE_ENABLE, + async_handle_enable_service, + schema=SERVICE_ENABLE_SCHEMA, + ) + + +@callback +def _async_register_disable_service(hass: HomeAssistant, instance: Recorder) -> None: + async def async_handle_disable_service(service: ServiceCall) -> None: + instance.set_enable(False) + + hass.services.async_register( + DOMAIN, + SERVICE_DISABLE, + async_handle_disable_service, + schema=SERVICE_DISABLE_SCHEMA, + ) + + +@callback +def async_register_services(hass: HomeAssistant, instance: Recorder) -> None: + """Register recorder services.""" + _async_register_purge_service(hass, instance) + _async_register_purge_entities_service(hass, instance) + _async_register_enable_service(hass, instance) + _async_register_disable_service(hass, instance) diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 1c993b32bb6..4bed39fee4a 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -14,11 +14,12 @@ import re from statistics import mean from typing import TYPE_CHECKING, Any, Literal, overload -from sqlalchemy import bindparam, func +from sqlalchemy import bindparam, func, lambda_stmt, select +from sqlalchemy.engine.row import Row from sqlalchemy.exc import SQLAlchemyError, StatementError -from sqlalchemy.ext import baked from sqlalchemy.orm.session import Session from sqlalchemy.sql.expression import literal_column, true +from sqlalchemy.sql.lambdas import StatementLambdaElement import voluptuous as vol from homeassistant.const import ( @@ -32,13 +33,14 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.storage import STORAGE_DIR +from homeassistant.helpers.typing import UNDEFINED, UndefinedType import homeassistant.util.dt as dt_util import homeassistant.util.pressure as pressure_util import homeassistant.util.temperature as temperature_util from homeassistant.util.unit_system import UnitSystem import homeassistant.util.volume as volume_util -from .const import DATA_INSTANCE, DOMAIN, MAX_ROWS_TO_PURGE +from .const import DATA_INSTANCE, DOMAIN, MAX_ROWS_TO_PURGE, SupportedDialect from .models import ( StatisticData, StatisticMetaData, @@ -50,7 +52,12 @@ from .models import ( process_timestamp, process_timestamp_to_utc_isoformat, ) -from .util import execute, retryable_database_job, session_scope +from .util import ( + execute, + execute_stmt_lambda_element, + retryable_database_job, + session_scope, +) if TYPE_CHECKING: from . import Recorder @@ -120,8 +127,6 @@ QUERY_STATISTIC_META_ID = [ StatisticsMeta.statistic_id, ] -STATISTICS_BAKERY = "recorder_statistics_bakery" - # Convert pressure, temperature and volume statistics from the normalized unit used for # statistics to the unit configured by the user @@ -203,20 +208,12 @@ class ValidationIssue: def async_setup(hass: HomeAssistant) -> None: """Set up the history hooks.""" - hass.data[STATISTICS_BAKERY] = baked.bakery() - def _entity_id_changed(event: Event) -> None: - """Handle entity_id changed.""" - old_entity_id = event.data["old_entity_id"] - entity_id = event.data["entity_id"] - with session_scope(hass=hass) as session: - session.query(StatisticsMeta).filter( - (StatisticsMeta.statistic_id == old_entity_id) - & (StatisticsMeta.source == DOMAIN) - ).update({StatisticsMeta.statistic_id: entity_id}) - - async def _async_entity_id_changed(event: Event) -> None: - await hass.data[DATA_INSTANCE].async_add_executor_job(_entity_id_changed, event) + @callback + def _async_entity_id_changed(event: Event) -> None: + hass.data[DATA_INSTANCE].async_update_statistics_metadata( + event.data["old_entity_id"], new_statistic_id=event.data["entity_id"] + ) @callback def entity_registry_changed_filter(event: Event) -> bool: @@ -377,7 +374,7 @@ def _delete_duplicates_from_table( return (total_deleted_rows, all_non_identical_duplicates) -def delete_duplicates(instance: Recorder, session: Session) -> None: +def delete_statistics_duplicates(hass: HomeAssistant, session: Session) -> None: """Identify and delete duplicated statistics. A backup will be made of duplicated statistics before it is deleted. @@ -391,7 +388,7 @@ def delete_duplicates(instance: Recorder, session: Session) -> None: if non_identical_duplicates: isotime = dt_util.utcnow().isoformat() backup_file_name = f"deleted_statistics.{isotime}.json" - backup_path = instance.hass.config.path(STORAGE_DIR, backup_file_name) + backup_path = hass.config.path(STORAGE_DIR, backup_file_name) os.makedirs(os.path.dirname(backup_path), exist_ok=True) with open(backup_path, "w", encoding="utf8") as backup_file: @@ -415,11 +412,88 @@ def delete_duplicates(instance: Recorder, session: Session) -> None: ) if deleted_short_term_statistics_rows: _LOGGER.warning( - "Deleted duplicated short term statistic rows, please report at " - 'https://github.com/home-assistant/core/issues?q=is%%3Aissue+label%%3A"integration%%3A+recorder"+' + "Deleted duplicated short term statistic rows, please report at %s", + "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+recorder%22", ) +def _find_statistics_meta_duplicates(session: Session) -> list[int]: + """Find duplicated statistics_meta.""" + subquery = ( + session.query( + StatisticsMeta.statistic_id, + literal_column("1").label("is_duplicate"), + ) + .group_by(StatisticsMeta.statistic_id) + .having(func.count() > 1) + .subquery() + ) + query = ( + session.query(StatisticsMeta) + .outerjoin( + subquery, + (subquery.c.statistic_id == StatisticsMeta.statistic_id), + ) + .filter(subquery.c.is_duplicate == 1) + .order_by(StatisticsMeta.statistic_id, StatisticsMeta.id.desc()) + .limit(1000 * MAX_ROWS_TO_PURGE) + ) + duplicates = execute(query) + statistic_id = None + duplicate_ids: list[int] = [] + + if not duplicates: + return duplicate_ids + + for duplicate in duplicates: + if statistic_id != duplicate.statistic_id: + statistic_id = duplicate.statistic_id + continue + duplicate_ids.append(duplicate.id) + + return duplicate_ids + + +def _delete_statistics_meta_duplicates(session: Session) -> int: + """Identify and delete duplicated statistics from a specified table.""" + total_deleted_rows = 0 + while True: + duplicate_ids = _find_statistics_meta_duplicates(session) + if not duplicate_ids: + break + for i in range(0, len(duplicate_ids), MAX_ROWS_TO_PURGE): + deleted_rows = ( + session.query(StatisticsMeta) + .filter(StatisticsMeta.id.in_(duplicate_ids[i : i + MAX_ROWS_TO_PURGE])) + .delete(synchronize_session=False) + ) + total_deleted_rows += deleted_rows + return total_deleted_rows + + +def delete_statistics_meta_duplicates(session: Session) -> None: + """Identify and delete duplicated statistics_meta.""" + deleted_statistics_rows = _delete_statistics_meta_duplicates(session) + if deleted_statistics_rows: + _LOGGER.info( + "Deleted %s duplicated statistics_meta rows", deleted_statistics_rows + ) + + +def _compile_hourly_statistics_summary_mean_stmt( + start_time: datetime, end_time: datetime +) -> StatementLambdaElement: + """Generate the summary mean statement for hourly statistics.""" + stmt = lambda_stmt(lambda: select(*QUERY_STATISTICS_SUMMARY_MEAN)) + stmt += ( + lambda q: q.filter(StatisticsShortTerm.start >= start_time) + .filter(StatisticsShortTerm.start < end_time) + .group_by(StatisticsShortTerm.metadata_id) + .order_by(StatisticsShortTerm.metadata_id) + ) + return stmt + + def compile_hourly_statistics( instance: Recorder, session: Session, start: datetime ) -> None: @@ -434,20 +508,8 @@ def compile_hourly_statistics( # Compute last hour's average, min, max summary: dict[str, StatisticData] = {} - baked_query = instance.hass.data[STATISTICS_BAKERY]( - lambda session: session.query(*QUERY_STATISTICS_SUMMARY_MEAN) - ) - - baked_query += lambda q: q.filter( - StatisticsShortTerm.start >= bindparam("start_time") - ) - baked_query += lambda q: q.filter(StatisticsShortTerm.start < bindparam("end_time")) - baked_query += lambda q: q.group_by(StatisticsShortTerm.metadata_id) - baked_query += lambda q: q.order_by(StatisticsShortTerm.metadata_id) - - stats = execute( - baked_query(session).params(start_time=start_time, end_time=end_time) - ) + stmt = _compile_hourly_statistics_summary_mean_stmt(start_time, end_time) + stats = execute_stmt_lambda_element(session, stmt) if stats: for stat in stats: @@ -460,81 +522,37 @@ def compile_hourly_statistics( } # Get last hour's last sum - if instance._db_supports_row_number: # pylint: disable=[protected-access] - subquery = ( - session.query(*QUERY_STATISTICS_SUMMARY_SUM) - .filter(StatisticsShortTerm.start >= bindparam("start_time")) - .filter(StatisticsShortTerm.start < bindparam("end_time")) - .subquery() - ) - query = ( - session.query(subquery) - .filter(subquery.c.rownum == 1) - .order_by(subquery.c.metadata_id) - ) - stats = execute(query.params(start_time=start_time, end_time=end_time)) + subquery = ( + session.query(*QUERY_STATISTICS_SUMMARY_SUM) + .filter(StatisticsShortTerm.start >= bindparam("start_time")) + .filter(StatisticsShortTerm.start < bindparam("end_time")) + .subquery() + ) + query = ( + session.query(subquery) + .filter(subquery.c.rownum == 1) + .order_by(subquery.c.metadata_id) + ) + stats = execute(query.params(start_time=start_time, end_time=end_time)) - if stats: - for stat in stats: - metadata_id, start, last_reset, state, _sum, _ = stat - if metadata_id in summary: - summary[metadata_id].update( - { - "last_reset": process_timestamp(last_reset), - "state": state, - "sum": _sum, - } - ) - else: - summary[metadata_id] = { - "start": start_time, - "last_reset": process_timestamp(last_reset), - "state": state, - "sum": _sum, - } - else: - baked_query = instance.hass.data[STATISTICS_BAKERY]( - lambda session: session.query(*QUERY_STATISTICS_SUMMARY_SUM_LEGACY) - ) - - baked_query += lambda q: q.filter( - StatisticsShortTerm.start >= bindparam("start_time") - ) - baked_query += lambda q: q.filter( - StatisticsShortTerm.start < bindparam("end_time") - ) - baked_query += lambda q: q.order_by( - StatisticsShortTerm.metadata_id, StatisticsShortTerm.start.desc() - ) - - stats = execute( - baked_query(session).params(start_time=start_time, end_time=end_time) - ) - - if stats: - for metadata_id, group in groupby(stats, lambda stat: stat["metadata_id"]): # type: ignore[no-any-return] - ( - metadata_id, - last_reset, - state, - _sum, - ) = next(group) - if metadata_id in summary: - summary[metadata_id].update( - { - "start": start_time, - "last_reset": process_timestamp(last_reset), - "state": state, - "sum": _sum, - } - ) - else: - summary[metadata_id] = { - "start": start_time, + if stats: + for stat in stats: + metadata_id, start, last_reset, state, _sum, _ = stat + if metadata_id in summary: + summary[metadata_id].update( + { "last_reset": process_timestamp(last_reset), "state": state, "sum": _sum, } + ) + else: + summary[metadata_id] = { + "start": start_time, + "last_reset": process_timestamp(last_reset), + "state": state, + "sum": _sum, + } # Insert compiled hourly statistics in the database for metadata_id, stat in summary.items(): @@ -551,7 +569,7 @@ def compile_statistics(instance: Recorder, start: datetime) -> bool: end = start + timedelta(minutes=5) # Return if we already have 5-minute statistics for the requested period - with session_scope(session=instance.get_session()) as session: # type: ignore[misc] + with session_scope(session=instance.get_session()) as session: if session.query(StatisticsRuns).filter_by(start=start).first(): _LOGGER.debug("Statistics already compiled for %s-%s", start, end) return True @@ -578,7 +596,7 @@ def compile_statistics(instance: Recorder, start: datetime) -> bool: # Insert collected statistics in the database with session_scope( - session=instance.get_session(), # type: ignore[misc] + session=instance.get_session(), exception_filter=_filter_unique_constraint_integrity_error(instance), ) as session: for stats in platform_stats: @@ -669,6 +687,24 @@ def _update_statistics( ) +def _generate_get_metadata_stmt( + statistic_ids: list[str] | tuple[str] | None = None, + statistic_type: Literal["mean"] | Literal["sum"] | None = None, + statistic_source: str | None = None, +) -> StatementLambdaElement: + """Generate a statement to fetch metadata.""" + stmt = lambda_stmt(lambda: select(*QUERY_STATISTIC_META)) + if statistic_ids is not None: + stmt += lambda q: q.where(StatisticsMeta.statistic_id.in_(statistic_ids)) + if statistic_source is not None: + stmt += lambda q: q.where(StatisticsMeta.source == statistic_source) + if statistic_type == "mean": + stmt += lambda q: q.where(StatisticsMeta.has_mean == true()) + elif statistic_type == "sum": + stmt += lambda q: q.where(StatisticsMeta.has_sum == true()) + return stmt + + def get_metadata_with_session( hass: HomeAssistant, session: Session, @@ -686,26 +722,8 @@ def get_metadata_with_session( """ # Fetch metatadata from the database - baked_query = hass.data[STATISTICS_BAKERY]( - lambda session: session.query(*QUERY_STATISTIC_META) - ) - if statistic_ids is not None: - baked_query += lambda q: q.filter( - StatisticsMeta.statistic_id.in_(bindparam("statistic_ids")) - ) - if statistic_source is not None: - baked_query += lambda q: q.filter( - StatisticsMeta.source == bindparam("statistic_source") - ) - if statistic_type == "mean": - baked_query += lambda q: q.filter(StatisticsMeta.has_mean == true()) - elif statistic_type == "sum": - baked_query += lambda q: q.filter(StatisticsMeta.has_sum == true()) - result = execute( - baked_query(session).params( - statistic_ids=statistic_ids, statistic_source=statistic_source - ) - ) + stmt = _generate_get_metadata_stmt(statistic_ids, statistic_type, statistic_source) + result = execute_stmt_lambda_element(session, stmt) if not result: return {} @@ -768,20 +786,33 @@ def _configured_unit(unit: str | None, units: UnitSystem) -> str | None: def clear_statistics(instance: Recorder, statistic_ids: list[str]) -> None: """Clear statistics for a list of statistic_ids.""" - with session_scope(session=instance.get_session()) as session: # type: ignore[misc] + with session_scope(session=instance.get_session()) as session: session.query(StatisticsMeta).filter( StatisticsMeta.statistic_id.in_(statistic_ids) ).delete(synchronize_session=False) def update_statistics_metadata( - instance: Recorder, statistic_id: str, unit_of_measurement: str | None + instance: Recorder, + statistic_id: str, + new_statistic_id: str | None | UndefinedType, + new_unit_of_measurement: str | None | UndefinedType, ) -> None: """Update statistics metadata for a statistic_id.""" - with session_scope(session=instance.get_session()) as session: # type: ignore[misc] - session.query(StatisticsMeta).filter( - StatisticsMeta.statistic_id == statistic_id - ).update({StatisticsMeta.unit_of_measurement: unit_of_measurement}) + if new_unit_of_measurement is not UNDEFINED: + with session_scope(session=instance.get_session()) as session: + session.query(StatisticsMeta).filter( + StatisticsMeta.statistic_id == statistic_id + ).update({StatisticsMeta.unit_of_measurement: new_unit_of_measurement}) + if new_statistic_id is not UNDEFINED: + with session_scope( + session=instance.get_session(), + exception_filter=_filter_unique_constraint_integrity_error(instance), + ) as session: + session.query(StatisticsMeta).filter( + (StatisticsMeta.statistic_id == statistic_id) + & (StatisticsMeta.source == DOMAIN) + ).update({StatisticsMeta.statistic_id: new_statistic_id}) def list_statistic_ids( @@ -852,31 +883,6 @@ def list_statistic_ids( ] -def _statistics_during_period_query( - hass: HomeAssistant, - end_time: datetime | None, - statistic_ids: list[str] | None, - 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 += lambda q: q.filter(table.start >= bindparam("start_time")) - - if end_time is not None: - baked_query += lambda q: q.filter(table.start < bindparam("end_time")) - - if statistic_ids is not None: - baked_query += lambda q: q.filter( - table.metadata_id.in_(bindparam("metadata_ids")) - ) - - baked_query += lambda q: q.order_by(table.metadata_id, table.start) - return baked_query # type: ignore[no-any-return] - - def _reduce_statistics( stats: dict[str, list[dict[str, Any]]], same_period: Callable[[datetime, datetime], bool], @@ -975,6 +981,34 @@ def _reduce_statistics_per_month( return _reduce_statistics(stats, same_month, month_start_end, timedelta(days=31)) +def _statistics_during_period_stmt( + start_time: datetime, + end_time: datetime | None, + statistic_ids: list[str] | None, + metadata_ids: list[int] | None, + table: type[Statistics | StatisticsShortTerm], +) -> StatementLambdaElement: + """Prepare a database query for statistics during a given period. + + This prepares a lambda_stmt query, so we don't insert the parameters yet. + """ + if table == StatisticsShortTerm: + stmt = lambda_stmt(lambda: select(*QUERY_STATISTICS_SHORT_TERM)) + else: + stmt = lambda_stmt(lambda: select(*QUERY_STATISTICS)) + + stmt += lambda q: q.filter(table.start >= start_time) + + if end_time is not None: + stmt += lambda q: q.filter(table.start < end_time) + + if statistic_ids is not None: + stmt += lambda q: q.filter(table.metadata_id.in_(metadata_ids)) + + stmt += lambda q: q.order_by(table.metadata_id, table.start) + return stmt + + def statistics_during_period( hass: HomeAssistant, start_time: datetime, @@ -999,25 +1033,16 @@ 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": - baked_query = bakery( - lambda session: session.query(*QUERY_STATISTICS_SHORT_TERM) - ) table = StatisticsShortTerm else: - baked_query = bakery(lambda session: session.query(*QUERY_STATISTICS)) table = Statistics - baked_query = _statistics_during_period_query( - hass, end_time, statistic_ids, baked_query, table + stmt = _statistics_during_period_stmt( + start_time, end_time, statistic_ids, metadata_ids, table ) + stats = execute_stmt_lambda_element(session, stmt) - stats = execute( - baked_query(session).params( - start_time=start_time, end_time=end_time, metadata_ids=metadata_ids - ) - ) if not stats: return {} # Return statistics combined with metadata @@ -1044,6 +1069,24 @@ def statistics_during_period( return _reduce_statistics_per_month(result) +def _get_last_statistics_stmt( + metadata_id: int, + number_of_stats: int, + table: type[Statistics | StatisticsShortTerm], +) -> StatementLambdaElement: + """Generate a statement for number_of_stats statistics for a given statistic_id.""" + if table == StatisticsShortTerm: + stmt = lambda_stmt(lambda: select(*QUERY_STATISTICS_SHORT_TERM)) + else: + stmt = lambda_stmt(lambda: select(*QUERY_STATISTICS)) + stmt += ( + lambda q: q.filter_by(metadata_id=metadata_id) + .order_by(table.metadata_id, table.start.desc()) + .limit(number_of_stats) + ) + return stmt + + def _get_last_statistics( hass: HomeAssistant, number_of_stats: int, @@ -1058,27 +1101,10 @@ def _get_last_statistics( metadata = get_metadata_with_session(hass, session, statistic_ids=statistic_ids) if not metadata: return {} - - bakery = hass.data[STATISTICS_BAKERY] - if table == StatisticsShortTerm: - baked_query = bakery( - lambda session: session.query(*QUERY_STATISTICS_SHORT_TERM) - ) - else: - 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] + stmt = _get_last_statistics_stmt(metadata_id, number_of_stats, table) + stats = execute_stmt_lambda_element(session, stmt) - baked_query += lambda q: q.order_by(table.metadata_id, table.start.desc()) - - baked_query += lambda q: q.limit(bindparam("number_of_stats")) - - stats = execute( - baked_query(session).params( - number_of_stats=number_of_stats, metadata_id=metadata_id - ) - ) if not stats: return {} @@ -1113,14 +1139,36 @@ def get_last_short_term_statistics( ) +def _latest_short_term_statistics_stmt( + metadata_ids: list[int], +) -> StatementLambdaElement: + """Create the statement for finding the latest short term stat rows.""" + stmt = lambda_stmt(lambda: select(*QUERY_STATISTICS_SHORT_TERM)) + most_recent_statistic_row = ( + select( + StatisticsShortTerm.metadata_id, + func.max(StatisticsShortTerm.start).label("start_max"), + ) + .where(StatisticsShortTerm.metadata_id.in_(metadata_ids)) + .group_by(StatisticsShortTerm.metadata_id) + ).subquery() + stmt += lambda s: s.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), + ) + return stmt + + 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: @@ -1134,24 +1182,8 @@ def get_latest_short_term_statistics( 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), - ) - ) + stmt = _latest_short_term_statistics_stmt(metadata_ids) + stats = execute_stmt_lambda_element(session, stmt) if not stats: return {} @@ -1203,7 +1235,7 @@ def _statistics_at_time( def _sorted_statistics_to_dict( hass: HomeAssistant, session: Session, - stats: list, + stats: Iterable[Row], statistic_ids: list[str] | None, _metadata: dict[str, tuple[int, StatisticMetaData]], convert_units: bool, @@ -1215,7 +1247,7 @@ def _sorted_statistics_to_dict( result: dict = defaultdict(list) units = hass.config.units metadata = dict(_metadata.values()) - need_stat_at_start_time = set() + need_stat_at_start_time: set[int] = set() stats_at_start_time = {} def no_conversion(val: Any, _: Any) -> float | None: @@ -1342,10 +1374,13 @@ def _filter_unique_constraint_integrity_error( dialect_name = instance.engine.dialect.name ignore = False - if dialect_name == "sqlite" and "UNIQUE constraint failed" in str(err): + if ( + dialect_name == SupportedDialect.SQLITE + and "UNIQUE constraint failed" in str(err) + ): ignore = True if ( - dialect_name == "postgresql" + dialect_name == SupportedDialect.POSTGRESQL and hasattr(err.orig, "pgcode") and err.orig.pgcode == "23505" ): @@ -1357,8 +1392,8 @@ def _filter_unique_constraint_integrity_error( if ignore: _LOGGER.warning( - "Blocked attempt to insert duplicated statistic rows, please report at " - 'https://github.com/home-assistant/core/issues?q=is%%3Aissue+label%%3A"integration%%3A+recorder"+', + "Blocked attempt to insert duplicated statistic rows, please report at %s", + "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+recorder%22", exc_info=err, ) @@ -1376,7 +1411,7 @@ def add_external_statistics( """Process an add_external_statistics job.""" with session_scope( - session=instance.get_session(), # type: ignore[misc] + session=instance.get_session(), exception_filter=_filter_unique_constraint_integrity_error(instance), ) as session: old_metadata_dict = get_metadata_with_session( @@ -1403,7 +1438,7 @@ def adjust_statistics( ) -> bool: """Process an add_statistics job.""" - with session_scope(session=instance.get_session()) as session: # type: ignore[misc] + with session_scope(session=instance.get_session()) as session: metadata = get_metadata_with_session( instance.hass, session, statistic_ids=(statistic_id,) ) diff --git a/homeassistant/components/recorder/strings.json b/homeassistant/components/recorder/strings.json new file mode 100644 index 00000000000..9b616372adf --- /dev/null +++ b/homeassistant/components/recorder/strings.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "oldest_recorder_run": "Oldest Run Start Time", + "current_recorder_run": "Current Run Start Time", + "estimated_db_size": "Estimated Database Size (MiB)", + "database_engine": "Database Engine", + "database_version": "Database Version" + } + } +} diff --git a/homeassistant/components/recorder/system_health/__init__.py b/homeassistant/components/recorder/system_health/__init__.py new file mode 100644 index 00000000000..c4bf2c3bb89 --- /dev/null +++ b/homeassistant/components/recorder/system_health/__init__.py @@ -0,0 +1,74 @@ +"""Provide info to system health.""" +from __future__ import annotations + +from typing import Any +from urllib.parse import urlparse + +from homeassistant.components import system_health +from homeassistant.components.recorder.core import Recorder +from homeassistant.components.recorder.util import session_scope +from homeassistant.core import HomeAssistant, callback + +from .. import get_instance +from ..const import SupportedDialect +from .mysql import db_size_bytes as mysql_db_size_bytes +from .postgresql import db_size_bytes as postgresql_db_size_bytes +from .sqlite import db_size_bytes as sqlite_db_size_bytes + +DIALECT_TO_GET_SIZE = { + SupportedDialect.SQLITE: sqlite_db_size_bytes, + SupportedDialect.MYSQL: mysql_db_size_bytes, + SupportedDialect.POSTGRESQL: postgresql_db_size_bytes, +} + + +@callback +def async_register( + hass: HomeAssistant, register: system_health.SystemHealthRegistration +) -> None: + """Register system health callbacks.""" + register.async_register_info(system_health_info) + + +def _get_db_stats(instance: Recorder, database_name: str) -> dict[str, Any]: + """Get the stats about the database.""" + db_stats: dict[str, Any] = {} + with session_scope(session=instance.get_session()) as session: + if ( + (dialect_name := instance.dialect_name) + and (get_size := DIALECT_TO_GET_SIZE.get(dialect_name)) + and (db_bytes := get_size(session, database_name)) + ): + db_stats["estimated_db_size"] = f"{db_bytes/1024/1024:.2f} MiB" + return db_stats + + +@callback +def _async_get_db_engine_info(instance: Recorder) -> dict[str, Any]: + """Get database engine info.""" + db_engine_info: dict[str, Any] = {} + if dialect_name := instance.dialect_name: + db_engine_info["database_engine"] = dialect_name.value + if engine_version := instance.engine_version: + db_engine_info["database_version"] = str(engine_version) + return db_engine_info + + +async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: + """Get info for the info page.""" + instance = get_instance(hass) + + run_history = instance.run_history + database_name = urlparse(instance.db_url).path.lstrip("/") + db_engine_info = _async_get_db_engine_info(instance) + db_stats: dict[str, Any] = {} + + if instance.async_db_ready.done(): + db_stats = await instance.async_add_executor_job( + _get_db_stats, instance, database_name + ) + db_runs = { + "oldest_recorder_run": run_history.first.start, + "current_recorder_run": run_history.current.start, + } + return db_runs | db_stats | db_engine_info diff --git a/homeassistant/components/recorder/system_health/mysql.py b/homeassistant/components/recorder/system_health/mysql.py new file mode 100644 index 00000000000..52ea06f61c3 --- /dev/null +++ b/homeassistant/components/recorder/system_health/mysql.py @@ -0,0 +1,19 @@ +"""Provide info to system health for mysql.""" +from __future__ import annotations + +from sqlalchemy import text +from sqlalchemy.orm.session import Session + + +def db_size_bytes(session: Session, database_name: str) -> float: + """Get the mysql database size.""" + return float( + session.execute( + text( + "SELECT ROUND(SUM(DATA_LENGTH + INDEX_LENGTH), 2) " + "FROM information_schema.TABLES WHERE " + "TABLE_SCHEMA=:database_name" + ), + {"database_name": database_name}, + ).first()[0] + ) diff --git a/homeassistant/components/recorder/system_health/postgresql.py b/homeassistant/components/recorder/system_health/postgresql.py new file mode 100644 index 00000000000..3e0667b1f4f --- /dev/null +++ b/homeassistant/components/recorder/system_health/postgresql.py @@ -0,0 +1,15 @@ +"""Provide info to system health for postgresql.""" +from __future__ import annotations + +from sqlalchemy import text +from sqlalchemy.orm.session import Session + + +def db_size_bytes(session: Session, database_name: str) -> float: + """Get the mysql database size.""" + return float( + session.execute( + text("select pg_database_size(:database_name);"), + {"database_name": database_name}, + ).first()[0] + ) diff --git a/homeassistant/components/recorder/system_health/sqlite.py b/homeassistant/components/recorder/system_health/sqlite.py new file mode 100644 index 00000000000..5a5901d2cb3 --- /dev/null +++ b/homeassistant/components/recorder/system_health/sqlite.py @@ -0,0 +1,17 @@ +"""Provide info to system health for sqlite.""" +from __future__ import annotations + +from sqlalchemy import text +from sqlalchemy.orm.session import Session + + +def db_size_bytes(session: Session, database_name: str) -> float: + """Get the mysql database size.""" + return float( + session.execute( + text( + "SELECT page_count * page_size as size " + "FROM pragma_page_count(), pragma_page_size();" + ) + ).first()[0] + ) diff --git a/homeassistant/components/recorder/tasks.py b/homeassistant/components/recorder/tasks.py new file mode 100644 index 00000000000..5ec83a3cefc --- /dev/null +++ b/homeassistant/components/recorder/tasks.py @@ -0,0 +1,269 @@ +"""Support for recording details.""" +from __future__ import annotations + +import abc +import asyncio +from collections.abc import Callable, Iterable +from dataclasses import dataclass +from datetime import datetime +import threading +from typing import TYPE_CHECKING, Any + +from homeassistant.core import Event +from homeassistant.helpers.typing import UndefinedType + +from . import purge, statistics +from .const import DOMAIN, EXCLUDE_ATTRIBUTES +from .models import StatisticData, StatisticMetaData +from .util import periodic_db_cleanups + +if TYPE_CHECKING: + from .core import Recorder + + +class RecorderTask(abc.ABC): + """ABC for recorder tasks.""" + + commit_before = True + + @abc.abstractmethod + def run(self, instance: Recorder) -> None: + """Handle the task.""" + + +@dataclass +class ClearStatisticsTask(RecorderTask): + """Object to store statistics_ids which for which to remove statistics.""" + + statistic_ids: list[str] + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + statistics.clear_statistics(instance, self.statistic_ids) + + +@dataclass +class UpdateStatisticsMetadataTask(RecorderTask): + """Object to store statistics_id and unit for update of statistics metadata.""" + + statistic_id: str + new_statistic_id: str | None | UndefinedType + new_unit_of_measurement: str | None | UndefinedType + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + statistics.update_statistics_metadata( + instance, + self.statistic_id, + self.new_statistic_id, + self.new_unit_of_measurement, + ) + + +@dataclass +class PurgeTask(RecorderTask): + """Object to store information about purge task.""" + + purge_before: datetime + repack: bool + apply_filter: bool + + def run(self, instance: Recorder) -> None: + """Purge the database.""" + 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. + periodic_db_cleanups(instance) + return + # Schedule a new purge task if this one didn't finish + instance.queue_task( + PurgeTask(self.purge_before, self.repack, self.apply_filter) + ) + + +@dataclass +class PurgeEntitiesTask(RecorderTask): + """Object to store entity information about purge task.""" + + entity_filter: Callable[[str], bool] + + def run(self, instance: Recorder) -> None: + """Purge entities from the database.""" + if purge.purge_entity_data(instance, self.entity_filter): + return + # Schedule a new purge task if this one didn't finish + instance.queue_task(PurgeEntitiesTask(self.entity_filter)) + + +@dataclass +class PerodicCleanupTask(RecorderTask): + """An object to insert into the recorder to trigger cleanup tasks when auto purge is disabled.""" + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + periodic_db_cleanups(instance) + + +@dataclass +class StatisticsTask(RecorderTask): + """An object to insert into the recorder queue to run a statistics task.""" + + start: datetime + + def run(self, instance: Recorder) -> None: + """Run statistics task.""" + if statistics.compile_statistics(instance, self.start): + return + # Schedule a new statistics task if this one didn't finish + instance.queue_task(StatisticsTask(self.start)) + + +@dataclass +class ExternalStatisticsTask(RecorderTask): + """An object to insert into the recorder queue to run an external statistics task.""" + + metadata: StatisticMetaData + statistics: Iterable[StatisticData] + + def run(self, instance: Recorder) -> None: + """Run statistics task.""" + if statistics.add_external_statistics(instance, self.metadata, self.statistics): + return + # Schedule a new statistics task if this one didn't finish + instance.queue_task(ExternalStatisticsTask(self.metadata, self.statistics)) + + +@dataclass +class AdjustStatisticsTask(RecorderTask): + """An object to insert into the recorder queue to run an adjust statistics task.""" + + statistic_id: str + start_time: datetime + sum_adjustment: float + + def run(self, instance: Recorder) -> None: + """Run statistics task.""" + if statistics.adjust_statistics( + instance, + self.statistic_id, + self.start_time, + self.sum_adjustment, + ): + return + # Schedule a new adjust statistics task if this one didn't finish + instance.queue_task( + AdjustStatisticsTask( + self.statistic_id, self.start_time, self.sum_adjustment + ) + ) + + +@dataclass +class WaitTask(RecorderTask): + """An object to insert into the recorder queue to tell it set the _queue_watch event.""" + + commit_before = False + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + instance._queue_watch.set() # pylint: disable=[protected-access] + + +@dataclass +class DatabaseLockTask(RecorderTask): + """An object to insert into the recorder queue to prevent writes to the database.""" + + database_locked: asyncio.Event + database_unlock: threading.Event + queue_overflow: bool + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + instance._lock_database(self) # pylint: disable=[protected-access] + + +@dataclass +class StopTask(RecorderTask): + """An object to insert into the recorder queue to stop the event handler.""" + + commit_before = False + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + instance.stop_requested = True + + +@dataclass +class EventTask(RecorderTask): + """An event to be processed.""" + + event: Event + commit_before = False + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + # pylint: disable-next=[protected-access] + 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) + + +@dataclass +class SynchronizeTask(RecorderTask): + """Ensure all pending data has been committed.""" + + # commit_before is the default + event: asyncio.Event + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + # Does not use a tracked task to avoid + # blocking shutdown if the recorder is broken + instance.hass.loop.call_soon_threadsafe(self.event.set) diff --git a/homeassistant/components/recorder/translations/ca.json b/homeassistant/components/recorder/translations/ca.json new file mode 100644 index 00000000000..4450be81574 --- /dev/null +++ b/homeassistant/components/recorder/translations/ca.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Hora d'inici de l'execuci\u00f3 actual", + "database_engine": "Motor de bases de dades", + "database_version": "Versi\u00f3 de la base de dades", + "estimated_db_size": "Mida estimada de la base de dades (MiB)", + "oldest_recorder_run": "Hora d'inici de l'execuci\u00f3 m\u00e9s antiga" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/de.json b/homeassistant/components/recorder/translations/de.json new file mode 100644 index 00000000000..b05adc3c386 --- /dev/null +++ b/homeassistant/components/recorder/translations/de.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Aktuelle Startzeit der Ausf\u00fchrung", + "database_engine": "Datenbank-Engine", + "database_version": "Datenbankversion", + "estimated_db_size": "Gesch\u00e4tzte Datenbankgr\u00f6\u00dfe (MiB)", + "oldest_recorder_run": "\u00c4lteste Startzeit der Ausf\u00fchrung" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/el.json b/homeassistant/components/recorder/translations/el.json new file mode 100644 index 00000000000..6d541820c55 --- /dev/null +++ b/homeassistant/components/recorder/translations/el.json @@ -0,0 +1,9 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03ce\u03c1\u03b1 \u03ad\u03bd\u03b1\u03c1\u03be\u03b7\u03c2 \u03b5\u03ba\u03c4\u03ad\u03bb\u03b5\u03c3\u03b7\u03c2", + "estimated_db_size": "\u0395\u03ba\u03c4\u03b9\u03bc\u03ce\u03bc\u03b5\u03bd\u03bf \u03bc\u03ad\u03b3\u03b5\u03b8\u03bf\u03c2 \u03b2\u03ac\u03c3\u03b7\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd (MiB)", + "oldest_recorder_run": "\u03a0\u03b1\u03bb\u03b1\u03b9\u03cc\u03c4\u03b5\u03c1\u03b7 \u03ce\u03c1\u03b1 \u03ad\u03bd\u03b1\u03c1\u03be\u03b7\u03c2 \u03b5\u03ba\u03c4\u03ad\u03bb\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/en.json b/homeassistant/components/recorder/translations/en.json new file mode 100644 index 00000000000..c9ceffc7397 --- /dev/null +++ b/homeassistant/components/recorder/translations/en.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Current Run Start Time", + "database_engine": "Database Engine", + "database_version": "Database Version", + "estimated_db_size": "Estimated Database Size (MiB)", + "oldest_recorder_run": "Oldest Run Start Time" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/es.json b/homeassistant/components/recorder/translations/es.json new file mode 100644 index 00000000000..81bcf29d548 --- /dev/null +++ b/homeassistant/components/recorder/translations/es.json @@ -0,0 +1,8 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Hora de inicio de la ejecuci\u00f3n actual", + "estimated_db_size": "Mida estimada de la base de datos (MiB)" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/et.json b/homeassistant/components/recorder/translations/et.json new file mode 100644 index 00000000000..b76e0f2e4da --- /dev/null +++ b/homeassistant/components/recorder/translations/et.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Praegune k\u00e4ivitamise algusaeg", + "database_engine": "Andmebaasi mootor", + "database_version": "Andmebaasi versioon", + "estimated_db_size": "Andmebaasi hinnanguline suurus (MB)", + "oldest_recorder_run": "Vanim k\u00e4ivitamise algusaeg" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/fr.json b/homeassistant/components/recorder/translations/fr.json new file mode 100644 index 00000000000..24896a8e66f --- /dev/null +++ b/homeassistant/components/recorder/translations/fr.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Heure de d\u00e9marrage de l'ex\u00e9cution actuelle", + "database_engine": "Moteur de la base de donn\u00e9es", + "database_version": "Version de la base de donn\u00e9es", + "estimated_db_size": "Taille estim\u00e9e de la base de donn\u00e9es (en Mio)", + "oldest_recorder_run": "Heure de d\u00e9marrage de l'ex\u00e9cution la plus ancienne" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/hu.json b/homeassistant/components/recorder/translations/hu.json new file mode 100644 index 00000000000..323c1d489f1 --- /dev/null +++ b/homeassistant/components/recorder/translations/hu.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Aktu\u00e1lis futtat\u00e1s kezd\u00e9si id\u0151pontja", + "database_engine": "Adatb\u00e1zis motor", + "database_version": "Adatb\u00e1zis verzi\u00f3", + "estimated_db_size": "Az adatb\u00e1zis becs\u00fclt m\u00e9rete (MiB)", + "oldest_recorder_run": "Legr\u00e9gebbi futtat\u00e1s kezd\u00e9si id\u0151pontja" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/id.json b/homeassistant/components/recorder/translations/id.json new file mode 100644 index 00000000000..5f391a24f04 --- /dev/null +++ b/homeassistant/components/recorder/translations/id.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Waktu Mulai Jalankan Saat Ini", + "database_engine": "Mesin Basis Data", + "database_version": "Versi Basis Data", + "estimated_db_size": "Perkiraan Ukuran Basis Data (MiB)", + "oldest_recorder_run": "Waktu Mulai Lari Terlama" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/it.json b/homeassistant/components/recorder/translations/it.json new file mode 100644 index 00000000000..ed2bc900ec6 --- /dev/null +++ b/homeassistant/components/recorder/translations/it.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Ora di inizio esecuzione corrente", + "database_engine": "Motore del database", + "database_version": "Versione del database", + "estimated_db_size": "Dimensione stimata del database (MiB)", + "oldest_recorder_run": "Ora di inizio esecuzione meno recente" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/ja.json b/homeassistant/components/recorder/translations/ja.json new file mode 100644 index 00000000000..e8938f363bf --- /dev/null +++ b/homeassistant/components/recorder/translations/ja.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "\u73fe\u5728\u306e\u5b9f\u884c\u958b\u59cb\u6642\u9593", + "database_engine": "\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30a8\u30f3\u30b8\u30f3", + "database_version": "\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30d0\u30fc\u30b8\u30e7\u30f3", + "estimated_db_size": "\u63a8\u5b9a\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30b5\u30a4\u30ba(MiB)", + "oldest_recorder_run": "\u6700\u3082\u53e4\u3044\u5b9f\u884c\u958b\u59cb\u6642\u9593" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/ko.json b/homeassistant/components/recorder/translations/ko.json new file mode 100644 index 00000000000..18cc73f3789 --- /dev/null +++ b/homeassistant/components/recorder/translations/ko.json @@ -0,0 +1,9 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "\ud604\uc7ac \uc2e4\ud589 \uc2dc\uc791 \uc2dc\uac04", + "estimated_db_size": "\uc608\uc0c1 \ub370\uc774\ud130\ubca0\uc774\uc2a4 \ud06c\uae30(MiB)", + "oldest_recorder_run": "\uac00\uc7a5 \uc624\ub798\ub41c \uc2e4\ud589 \uc2dc\uc791 \uc2dc\uac04" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/nl.json b/homeassistant/components/recorder/translations/nl.json new file mode 100644 index 00000000000..db1f5d6a2b4 --- /dev/null +++ b/homeassistant/components/recorder/translations/nl.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Starttijd huidige run", + "database_engine": "Database-engine", + "database_version": "Databaseversie", + "estimated_db_size": "Geschatte databasegrootte (MiB)", + "oldest_recorder_run": "Starttijd oudste run" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/no.json b/homeassistant/components/recorder/translations/no.json new file mode 100644 index 00000000000..c2a9eae16b5 --- /dev/null +++ b/homeassistant/components/recorder/translations/no.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Gjeldende starttid for kj\u00f8ring", + "database_engine": "Databasemotor", + "database_version": "Database versjon", + "estimated_db_size": "Estimert databasest\u00f8rrelse (MiB)", + "oldest_recorder_run": "Eldste Run Start Time" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/pl.json b/homeassistant/components/recorder/translations/pl.json new file mode 100644 index 00000000000..77c5f2750f7 --- /dev/null +++ b/homeassistant/components/recorder/translations/pl.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Pocz\u0105tek aktualnej sesji rejestratora", + "database_engine": "Silnik bazy danych", + "database_version": "Wersja bazy danych", + "estimated_db_size": "Szacowany rozmiar bazy danych (MiB)", + "oldest_recorder_run": "Pocz\u0105tek najstarszej sesji rejestratora" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/pt-BR.json b/homeassistant/components/recorder/translations/pt-BR.json new file mode 100644 index 00000000000..4ebad0d43df --- /dev/null +++ b/homeassistant/components/recorder/translations/pt-BR.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Hora de in\u00edcio de execu\u00e7\u00e3o atual", + "database_engine": "Motor do banco de dados", + "database_version": "Vers\u00e3o do banco de dados", + "estimated_db_size": "Tamanho estimado do banco de dados (MiB)", + "oldest_recorder_run": "Hora de in\u00edcio de execu\u00e7\u00e3o mais antiga" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/ru.json b/homeassistant/components/recorder/translations/ru.json new file mode 100644 index 00000000000..052d442f15c --- /dev/null +++ b/homeassistant/components/recorder/translations/ru.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "\u0412\u0440\u0435\u043c\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0446\u0438\u043a\u043b\u0430", + "database_engine": "\u0414\u0432\u0438\u0436\u043e\u043a \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445", + "database_version": "\u0412\u0435\u0440\u0441\u0438\u044f \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445", + "estimated_db_size": "\u041f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u043c\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 (MiB)", + "oldest_recorder_run": "\u0412\u0440\u0435\u043c\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0430\u043c\u043e\u0433\u043e \u0441\u0442\u0430\u0440\u043e\u0433\u043e \u0446\u0438\u043a\u043b\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/sv.json b/homeassistant/components/recorder/translations/sv.json new file mode 100644 index 00000000000..bf4d6ccf0a5 --- /dev/null +++ b/homeassistant/components/recorder/translations/sv.json @@ -0,0 +1,8 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Aktuell starttid", + "oldest_recorder_run": "\u00c4ldsta starttid" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/tr.json b/homeassistant/components/recorder/translations/tr.json new file mode 100644 index 00000000000..27606e9ab27 --- /dev/null +++ b/homeassistant/components/recorder/translations/tr.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "Mevcut \u00c7al\u0131\u015ft\u0131rma Ba\u015flang\u0131\u00e7 Zaman\u0131", + "database_engine": "Veritaban\u0131 Altyap\u0131s\u0131", + "database_version": "Veritaban\u0131 S\u00fcr\u00fcm\u00fc", + "estimated_db_size": "Tahmini Veritaban\u0131 Boyutu (MB)", + "oldest_recorder_run": "En Eski \u00c7al\u0131\u015ft\u0131rma Ba\u015flang\u0131\u00e7 Zaman\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/translations/zh-Hant.json b/homeassistant/components/recorder/translations/zh-Hant.json new file mode 100644 index 00000000000..53715af2e92 --- /dev/null +++ b/homeassistant/components/recorder/translations/zh-Hant.json @@ -0,0 +1,11 @@ +{ + "system_health": { + "info": { + "current_recorder_run": "\u76ee\u524d\u57f7\u884c\u958b\u59cb\u6642\u9593", + "database_engine": "\u8cc7\u6599\u5eab\u5f15\u64ce", + "database_version": "\u8cc7\u6599\u5eab\u7248\u672c", + "estimated_db_size": "\u9810\u4f30\u8cc7\u6599\u5eab\u6a94\u6848\u5927\u5c0f(MiB)", + "oldest_recorder_run": "\u6700\u65e9\u57f7\u884c\u958b\u59cb\u6642\u9593" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index b67f4c6d558..843f0e4b185 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -1,58 +1,60 @@ """SQLAlchemy util functions.""" from __future__ import annotations -from collections.abc import Callable, Generator +from collections.abc import Callable, Generator, Iterable from contextlib import contextmanager from datetime import date, datetime, timedelta import functools import logging import os import time -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, TypeVar from awesomeversion import ( AwesomeVersion, AwesomeVersionException, AwesomeVersionStrategy, ) +import ciso8601 from sqlalchemy import text from sqlalchemy.engine.cursor import CursorFetchStrategy +from sqlalchemy.engine.row import Row from sqlalchemy.exc import OperationalError, SQLAlchemyError from sqlalchemy.orm.query import Query from sqlalchemy.orm.session import Session +from sqlalchemy.sql.lambdas import StatementLambdaElement +from typing_extensions import Concatenate, ParamSpec from homeassistant.core import HomeAssistant import homeassistant.util.dt as dt_util -from .const import DATA_INSTANCE, SQLITE_URL_PREFIX +from .const import DATA_INSTANCE, SQLITE_URL_PREFIX, SupportedDialect from .models import ( - ALL_TABLES, TABLE_RECORDER_RUNS, TABLE_SCHEMA_CHANGES, - TABLE_STATISTICS, - TABLE_STATISTICS_META, - TABLE_STATISTICS_RUNS, - TABLE_STATISTICS_SHORT_TERM, + TABLES_TO_CHECK, RecorderRuns, + UnsupportedDialect, process_timestamp, ) if TYPE_CHECKING: from . import Recorder +_RecorderT = TypeVar("_RecorderT", bound="Recorder") +_P = ParamSpec("_P") + _LOGGER = logging.getLogger(__name__) RETRIES = 3 QUERY_RETRY_WAIT = 0.1 SQLITE3_POSTFIXES = ["", "-wal", "-shm"] +DEFAULT_YIELD_STATES_ROWS = 32768 MIN_VERSION_MARIA_DB = AwesomeVersion("10.3.0", AwesomeVersionStrategy.SIMPLEVER) -MIN_VERSION_MARIA_DB_ROWNUM = AwesomeVersion("10.2.0", AwesomeVersionStrategy.SIMPLEVER) MIN_VERSION_MYSQL = AwesomeVersion("8.0.0", AwesomeVersionStrategy.SIMPLEVER) -MIN_VERSION_MYSQL_ROWNUM = AwesomeVersion("5.8.0", AwesomeVersionStrategy.SIMPLEVER) MIN_VERSION_PGSQL = AwesomeVersion("12.0", AwesomeVersionStrategy.SIMPLEVER) MIN_VERSION_SQLITE = AwesomeVersion("3.31.0", AwesomeVersionStrategy.SIMPLEVER) -MIN_VERSION_SQLITE_ROWNUM = AwesomeVersion("3.25.0", AwesomeVersionStrategy.SIMPLEVER) # This is the maximum time after the recorder ends the session # before we no longer consider startup to be a "restart" and we @@ -119,7 +121,7 @@ def commit(session: Session, work: Any) -> bool: def execute( qry: Query, to_native: bool = False, validate_entity_ids: bool = True -) -> list: +) -> list[Row]: """Query the database and convert the objects to HA native form. This method also retries a few times in the case of stale connections. @@ -162,7 +164,39 @@ def execute( raise time.sleep(QUERY_RETRY_WAIT) - assert False # unreachable + assert False # unreachable # pragma: no cover + + +def execute_stmt_lambda_element( + session: Session, + stmt: StatementLambdaElement, + start_time: datetime | None = None, + end_time: datetime | None = None, + yield_per: int | None = DEFAULT_YIELD_STATES_ROWS, +) -> Iterable[Row]: + """Execute a StatementLambdaElement. + + If the time window passed is greater than one day + the execution method will switch to yield_per to + reduce memory pressure. + + It is not recommended to pass a time window + when selecting non-ranged rows (ie selecting + specific entities) since they are usually faster + with .all(). + """ + executed = session.execute(stmt) + use_all = not start_time or ((end_time or dt_util.utcnow()) - start_time).days <= 1 + for tryno in range(0, RETRIES): + try: + return executed.all() if use_all else executed.yield_per(yield_per) # type: ignore[no-any-return] + except SQLAlchemyError as err: + _LOGGER.error("Error executing query: %s", err) + if tryno == RETRIES - 1: + raise + time.sleep(QUERY_RETRY_WAIT) + + assert False # unreachable # pragma: no cover def validate_or_move_away_sqlite_database(dburl: str) -> bool: @@ -209,15 +243,7 @@ def last_run_was_recently_clean(cursor: CursorFetchStrategy) -> bool: def basic_sanity_check(cursor: CursorFetchStrategy) -> bool: """Check tables to make sure select does not fail.""" - for table in ALL_TABLES: - # The statistics tables may not be present in old databases - if table in [ - TABLE_STATISTICS, - TABLE_STATISTICS_META, - TABLE_STATISTICS_RUNS, - TABLE_STATISTICS_SHORT_TERM, - ]: - continue + for table in TABLES_TO_CHECK: if table in (TABLE_RECORDER_RUNS, TABLE_SCHEMA_CHANGES): cursor.execute(f"SELECT * FROM {table};") # nosec # not injection else: @@ -300,29 +326,31 @@ def query_on_connection(dbapi_connection: Any, statement: str) -> Any: return result -def _warn_unsupported_dialect(dialect_name: str) -> None: +def _fail_unsupported_dialect(dialect_name: str) -> None: """Warn about unsupported database version.""" - _LOGGER.warning( + _LOGGER.error( "Database %s is not supported; Home Assistant supports %s. " - "Starting with Home Assistant 2022.2 this will prevent the recorder from " - "starting. Please migrate your database to a supported software before then", + "Starting with Home Assistant 2022.6 this prevents the recorder from " + "starting. Please migrate your database to a supported software", dialect_name, "MariaDB ≥ 10.3, MySQL ≥ 8.0, PostgreSQL ≥ 12, SQLite ≥ 3.31.0", ) + raise UnsupportedDialect -def _warn_unsupported_version( +def _fail_unsupported_version( server_version: str, dialect_name: str, minimum_version: str ) -> None: """Warn about unsupported database version.""" - _LOGGER.warning( + _LOGGER.error( "Version %s of %s is not supported; minimum supported version is %s. " - "Starting with Home Assistant 2022.2 this will prevent the recorder from " - "starting. Please upgrade your database software before then", + "Starting with Home Assistant 2022.6 this prevents the recorder from " + "starting. Please upgrade your database software", server_version, dialect_name, minimum_version, ) + raise UnsupportedDialect def _extract_version_from_server_response( @@ -339,17 +367,39 @@ def _extract_version_from_server_response( return None +def _datetime_or_none(value: str) -> datetime | None: + """Fast version of mysqldb DateTime_or_None. + + https://github.com/PyMySQL/mysqlclient/blob/v2.1.0/MySQLdb/times.py#L66 + """ + try: + return ciso8601.parse_datetime(value) + except ValueError: + return None + + +def build_mysqldb_conv() -> dict: + """Build a MySQLDB conv dict that uses cisco8601 to parse datetimes.""" + # Late imports since we only call this if they are using mysqldb + from MySQLdb.constants import ( # pylint: disable=import-outside-toplevel,import-error + FIELD_TYPE, + ) + from MySQLdb.converters import ( # pylint: disable=import-outside-toplevel,import-error + conversions, + ) + + return {**conversions, FIELD_TYPE.DATETIME: _datetime_or_none} + + def setup_connection_for_dialect( instance: Recorder, dialect_name: str, dbapi_connection: Any, first_connection: bool, -) -> None: +) -> AwesomeVersion | None: """Execute statements needed for dialect connection.""" - # Returns False if the the connection needs to be setup - # on the next connection, returns True if the connection - # never needs to be setup again. - if dialect_name == "sqlite": + version: AwesomeVersion | None = None + if dialect_name == SupportedDialect.SQLITE: if first_connection: old_isolation = dbapi_connection.isolation_level dbapi_connection.isolation_level = None @@ -362,22 +412,28 @@ def setup_connection_for_dialect( version_string = result[0][0] version = _extract_version_from_server_response(version_string) - if version and version < MIN_VERSION_SQLITE_ROWNUM: - instance._db_supports_row_number = ( # pylint: disable=[protected-access] - False - ) if not version or version < MIN_VERSION_SQLITE: - _warn_unsupported_version( + _fail_unsupported_version( version or version_string, "SQLite", MIN_VERSION_SQLITE ) - # approximately 8MiB of memory - execute_on_connection(dbapi_connection, "PRAGMA cache_size = -8192") + # The upper bound on the cache size is approximately 16MiB of memory + execute_on_connection(dbapi_connection, "PRAGMA cache_size = -16384") + + # + # Enable FULL synchronous if they have a commit interval of 0 + # or NORMAL if they do not. + # + # https://sqlite.org/pragma.html#pragma_synchronous + # The synchronous=NORMAL setting is a good choice for most applications running in WAL mode. + # + synchronous = "NORMAL" if instance.commit_interval else "FULL" + execute_on_connection(dbapi_connection, f"PRAGMA synchronous={synchronous}") # enable support for foreign keys execute_on_connection(dbapi_connection, "PRAGMA foreign_keys=ON") - elif dialect_name == "mysql": + elif dialect_name == SupportedDialect.MYSQL: execute_on_connection(dbapi_connection, "SET session wait_timeout=28800") if first_connection: result = query_on_connection(dbapi_connection, "SELECT VERSION()") @@ -386,37 +442,31 @@ def setup_connection_for_dialect( is_maria_db = "mariadb" in version_string.lower() if is_maria_db: - if version and version < MIN_VERSION_MARIA_DB_ROWNUM: - instance._db_supports_row_number = ( # pylint: disable=[protected-access] - False - ) if not version or version < MIN_VERSION_MARIA_DB: - _warn_unsupported_version( + _fail_unsupported_version( version or version_string, "MariaDB", MIN_VERSION_MARIA_DB ) else: - if version and version < MIN_VERSION_MYSQL_ROWNUM: - instance._db_supports_row_number = ( # pylint: disable=[protected-access] - False - ) if not version or version < MIN_VERSION_MYSQL: - _warn_unsupported_version( + _fail_unsupported_version( version or version_string, "MySQL", MIN_VERSION_MYSQL ) - elif dialect_name == "postgresql": + elif dialect_name == SupportedDialect.POSTGRESQL: if first_connection: # server_version_num was added in 2006 result = query_on_connection(dbapi_connection, "SHOW server_version") version_string = result[0][0] version = _extract_version_from_server_response(version_string) if not version or version < MIN_VERSION_PGSQL: - _warn_unsupported_version( + _fail_unsupported_version( version or version_string, "PostgreSQL", MIN_VERSION_PGSQL ) else: - _warn_unsupported_dialect(dialect_name) + _fail_unsupported_dialect(dialect_name) + + return version def end_incomplete_runs(session: Session, start_time: datetime) -> None: @@ -430,21 +480,28 @@ def end_incomplete_runs(session: Session, start_time: datetime) -> None: session.add(run) -def retryable_database_job(description: str) -> Callable: +def retryable_database_job( + description: str, +) -> Callable[ + [Callable[Concatenate[_RecorderT, _P], bool]], + Callable[Concatenate[_RecorderT, _P], bool], +]: """Try to execute a database job. The job should return True if it finished, and False if it needs to be rescheduled. """ - def decorator(job: Callable[[Any], bool]) -> Callable: + def decorator( + job: Callable[Concatenate[_RecorderT, _P], bool] + ) -> Callable[Concatenate[_RecorderT, _P], bool]: @functools.wraps(job) - def wrapper(instance: Recorder, *args: Any, **kwargs: Any) -> bool: + def wrapper(instance: _RecorderT, *args: _P.args, **kwargs: _P.kwargs) -> bool: try: return job(instance, *args, **kwargs) except OperationalError as err: assert instance.engine is not None if ( - instance.engine.dialect.name == "mysql" + instance.engine.dialect.name == SupportedDialect.MYSQL and err.orig.args[0] in RETRYABLE_MYSQL_ERRORS ): _LOGGER.info( @@ -470,7 +527,7 @@ def periodic_db_cleanups(instance: Recorder) -> None: These cleanups will happen nightly or after any purge. """ assert instance.engine is not None - if instance.engine.dialect.name == "sqlite": + if instance.engine.dialect.name == SupportedDialect.SQLITE: # Execute sqlite to create a wal checkpoint and free up disk space _LOGGER.debug("WAL checkpoint") with instance.engine.connect() as connection: diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index 585641665af..d0499fbf9cb 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -103,7 +103,7 @@ def ws_update_statistics_metadata( ) -> None: """Update statistics metadata for a statistic_id.""" hass.data[DATA_INSTANCE].async_update_statistics_metadata( - msg["statistic_id"], msg["unit_of_measurement"] + msg["statistic_id"], new_unit_of_measurement=msg["unit_of_measurement"] ) connection.send_result(msg["id"]) @@ -148,7 +148,7 @@ def ws_info( """Return status of the recorder.""" instance: Recorder = hass.data[DATA_INSTANCE] - backlog = instance.queue.qsize() if instance and instance.queue else None + backlog = instance.backlog if instance else None migration_in_progress = async_migration_in_progress(hass) recording = instance.recording if instance else False thread_alive = instance.is_alive() if instance else False diff --git a/homeassistant/components/remote/device_action.py b/homeassistant/components/remote/device_action.py index a337f3275eb..09c540b5e01 100644 --- a/homeassistant/components/remote/device_action.py +++ b/homeassistant/components/remote/device_action.py @@ -19,7 +19,7 @@ async def async_call_action_from_config( hass: HomeAssistant, config: ConfigType, variables: TemplateVarsType, - context: Context, + context: Context | None, ) -> None: """Change state based on configuration.""" await toggle_entity.async_call_action_from_config( diff --git a/homeassistant/components/remote/device_trigger.py b/homeassistant/components/remote/device_trigger.py index c358d86b176..127f07827e2 100644 --- a/homeassistant/components/remote/device_trigger.py +++ b/homeassistant/components/remote/device_trigger.py @@ -1,8 +1,6 @@ """Provides device triggers for remotes.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -36,7 +34,7 @@ async def async_attach_trigger( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/remote/translations/ca.json b/homeassistant/components/remote/translations/ca.json index ae9184b8ea5..6bf9dc141e0 100644 --- a/homeassistant/components/remote/translations/ca.json +++ b/homeassistant/components/remote/translations/ca.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} s'enc\u00e9n o s'apaga", - "toggled": "{entity_name} s'activa o es desactiva", "turned_off": "{entity_name} s'ha apagat", "turned_on": "{entity_name} s'ha engegat" } diff --git a/homeassistant/components/remote/translations/cs.json b/homeassistant/components/remote/translations/cs.json index c2afd70cb95..6907021e393 100644 --- a/homeassistant/components/remote/translations/cs.json +++ b/homeassistant/components/remote/translations/cs.json @@ -10,7 +10,6 @@ "is_on": "{entity_name} je zapnuto" }, "trigger_type": { - "toggled": "{entity_name} zapnuto nebo vypnuto", "turned_off": "{entity_name} vypnuto", "turned_on": "{entity_name} zapnuto" } diff --git a/homeassistant/components/remote/translations/de.json b/homeassistant/components/remote/translations/de.json index d0ad3d23ba2..8c34008da15 100644 --- a/homeassistant/components/remote/translations/de.json +++ b/homeassistant/components/remote/translations/de.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} ein- oder ausgeschaltet", - "toggled": "{entity_name} ein- oder ausgeschaltet", "turned_off": "{entity_name} ausgeschaltet", "turned_on": "{entity_name} eingeschaltet" } diff --git a/homeassistant/components/remote/translations/el.json b/homeassistant/components/remote/translations/el.json index 431d2e8af79..828f8b7fcb9 100644 --- a/homeassistant/components/remote/translations/el.json +++ b/homeassistant/components/remote/translations/el.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "\u03a4\u03bf {entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", - "toggled": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_off": "{entity_name} \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5" } diff --git a/homeassistant/components/remote/translations/en.json b/homeassistant/components/remote/translations/en.json index a232919b38a..2574de7e74d 100644 --- a/homeassistant/components/remote/translations/en.json +++ b/homeassistant/components/remote/translations/en.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} turned on or off", - "toggled": "{entity_name} turned on or off", "turned_off": "{entity_name} turned off", "turned_on": "{entity_name} turned on" } diff --git a/homeassistant/components/remote/translations/es.json b/homeassistant/components/remote/translations/es.json index dfa90cb1cc8..31e68384b8b 100644 --- a/homeassistant/components/remote/translations/es.json +++ b/homeassistant/components/remote/translations/es.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} activado o desactivado", - "toggled": "{entity_name} activado o desactivado", "turned_off": "{entity_name} desactivado", "turned_on": "{entity_name} activado" } diff --git a/homeassistant/components/remote/translations/et.json b/homeassistant/components/remote/translations/et.json index 12918472f33..aea377b6cb0 100644 --- a/homeassistant/components/remote/translations/et.json +++ b/homeassistant/components/remote/translations/et.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} l\u00fclitus sisse v\u00f5i v\u00e4lja", - "toggled": "{entity_name} l\u00fclitus sisse v\u00f5i v\u00e4lja", "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", "turned_on": "{entity_name} l\u00fclitus sisse" } diff --git a/homeassistant/components/remote/translations/fr.json b/homeassistant/components/remote/translations/fr.json index 1560c52cba0..6b81eea5a41 100644 --- a/homeassistant/components/remote/translations/fr.json +++ b/homeassistant/components/remote/translations/fr.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", - "toggled": "{entity_name} activ\u00e9 ou d\u00e9sactiv\u00e9", "turned_off": "{entity_name} s'est \u00e9teint", "turned_on": "{entity_name} s'est allum\u00e9" } diff --git a/homeassistant/components/remote/translations/he.json b/homeassistant/components/remote/translations/he.json index acdd036b2cf..6e4857c217c 100644 --- a/homeassistant/components/remote/translations/he.json +++ b/homeassistant/components/remote/translations/he.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05db\u05d5\u05d1\u05d4", - "toggled": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05db\u05d5\u05d1\u05d4", "turned_off": "{entity_name} \u05db\u05d5\u05d1\u05d4", "turned_on": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc" } diff --git a/homeassistant/components/remote/translations/hu.json b/homeassistant/components/remote/translations/hu.json index 52b24f79f08..039e67efb6c 100644 --- a/homeassistant/components/remote/translations/hu.json +++ b/homeassistant/components/remote/translations/hu.json @@ -11,7 +11,6 @@ }, "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/remote/translations/id.json b/homeassistant/components/remote/translations/id.json index 34eaa019be2..1853288f1ef 100644 --- a/homeassistant/components/remote/translations/id.json +++ b/homeassistant/components/remote/translations/id.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} diaktifkan atau dinonaktifkan", - "toggled": "{entity_name} diaktifkan atau dinonaktifkan", "turned_off": "{entity_name} dimatikan", "turned_on": "{entity_name} dinyalakan" } diff --git a/homeassistant/components/remote/translations/it.json b/homeassistant/components/remote/translations/it.json index 381894615d3..51fbdc6b755 100644 --- a/homeassistant/components/remote/translations/it.json +++ b/homeassistant/components/remote/translations/it.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} attivata o disattivata", - "toggled": "{entity_name} attiva o disattiva", "turned_off": "{entity_name} disattivato", "turned_on": "{entity_name} attivato" } diff --git a/homeassistant/components/remote/translations/ja.json b/homeassistant/components/remote/translations/ja.json index 304a47ed6d5..5c541a16e90 100644 --- a/homeassistant/components/remote/translations/ja.json +++ b/homeassistant/components/remote/translations/ja.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u304c\u30aa\u30f3\u307e\u305f\u306f\u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", - "toggled": "{entity_name} \u304c\u30aa\u30f3\u307e\u305f\u306f\u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "turned_off": "{entity_name} \u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "turned_on": "{entity_name} \u30aa\u30f3\u306b\u306a\u3063\u3066\u3044\u307e\u3059" } diff --git a/homeassistant/components/remote/translations/nl.json b/homeassistant/components/remote/translations/nl.json index 47ba3d7eda7..0c866181814 100644 --- a/homeassistant/components/remote/translations/nl.json +++ b/homeassistant/components/remote/translations/nl.json @@ -6,12 +6,11 @@ "turn_on": "{entity_name} inschakelen" }, "condition_type": { - "is_off": "{entity_name} staat uit", - "is_on": "{entity_name} staat aan" + "is_off": "{entity_name} is uitgeschakeld", + "is_on": "{entity_name} is ingeschakeld" }, "trigger_type": { "changed_states": "{entity_name} in- of uitgeschakeld", - "toggled": "{entity_name} in- of uitgeschakeld", "turned_off": "{entity_name} uitgeschakeld", "turned_on": "{entity_name} ingeschakeld" } diff --git a/homeassistant/components/remote/translations/no.json b/homeassistant/components/remote/translations/no.json index e563a6aa7d8..b6eba880981 100644 --- a/homeassistant/components/remote/translations/no.json +++ b/homeassistant/components/remote/translations/no.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} sl\u00e5tt p\u00e5 eller av", - "toggled": "{entity_name} sl\u00e5tt p\u00e5 eller av", "turned_off": "{entity_name} sl\u00e5tt av", "turned_on": "{entity_name} sl\u00e5tt p\u00e5" } diff --git a/homeassistant/components/remote/translations/pl.json b/homeassistant/components/remote/translations/pl.json index 2aaaf6dbd01..3c538ee2d03 100644 --- a/homeassistant/components/remote/translations/pl.json +++ b/homeassistant/components/remote/translations/pl.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", - "toggled": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "turned_off": "nast\u0105pi wy\u0142\u0105czenie {entity_name}", "turned_on": "nast\u0105pi w\u0142\u0105czenie {entity_name}" } diff --git a/homeassistant/components/remote/translations/pt-BR.json b/homeassistant/components/remote/translations/pt-BR.json index 15d9b0d7c76..c2cbe8991c9 100644 --- a/homeassistant/components/remote/translations/pt-BR.json +++ b/homeassistant/components/remote/translations/pt-BR.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} ligado ou desligado", - "toggled": "{entity_name} ligado ou desligado", "turned_off": "{entity_name} for desligado", "turned_on": "{entity_name} for ligado" } diff --git a/homeassistant/components/remote/translations/ru.json b/homeassistant/components/remote/translations/ru.json index fe1407af417..4224eb7f2b5 100644 --- a/homeassistant/components/remote/translations/ru.json +++ b/homeassistant/components/remote/translations/ru.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", - "toggled": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "turned_off": "{entity_name} \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "turned_on": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f" } diff --git a/homeassistant/components/remote/translations/sv.json b/homeassistant/components/remote/translations/sv.json index eeb5d486d25..1b6584c5bf8 100644 --- a/homeassistant/components/remote/translations/sv.json +++ b/homeassistant/components/remote/translations/sv.json @@ -9,7 +9,6 @@ "is_on": "{entity_name} \u00e4r p\u00e5" }, "trigger_type": { - "toggled": "{entity_name} slogs p\u00e5 eller av", "turned_off": "{entity_name} st\u00e4ngdes av", "turned_on": "{entity_name} slogs p\u00e5" } diff --git a/homeassistant/components/remote/translations/tr.json b/homeassistant/components/remote/translations/tr.json index 0b4fde944de..dc8fbf1ecad 100644 --- a/homeassistant/components/remote/translations/tr.json +++ b/homeassistant/components/remote/translations/tr.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} a\u00e7\u0131ld\u0131 veya kapat\u0131ld\u0131", - "toggled": "{entity_name} a\u00e7\u0131ld\u0131 veya kapat\u0131ld\u0131", "turned_off": "{entity_name} kapat\u0131ld\u0131", "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" } diff --git a/homeassistant/components/remote/translations/zh-Hans.json b/homeassistant/components/remote/translations/zh-Hans.json index b6c5d88102f..f6c509d4a08 100644 --- a/homeassistant/components/remote/translations/zh-Hans.json +++ b/homeassistant/components/remote/translations/zh-Hans.json @@ -10,7 +10,6 @@ "is_on": "{entity_name} \u5df2\u6253\u5f00" }, "trigger_type": { - "toggled": "{entity_name} \u88ab\u6253\u5f00\u6216\u5173\u95ed", "turned_off": "{entity_name} \u88ab\u5173\u95ed", "turned_on": "{entity_name} \u88ab\u6253\u5f00" } diff --git a/homeassistant/components/remote/translations/zh-Hant.json b/homeassistant/components/remote/translations/zh-Hant.json index e8a07049e29..259121d7a4f 100644 --- a/homeassistant/components/remote/translations/zh-Hant.json +++ b/homeassistant/components/remote/translations/zh-Hant.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name}\u5df2\u958b\u555f\u6216\u95dc\u9589", - "toggled": "{entity_name}\u5df2\u958b\u555f\u6216\u95dc\u9589", "turned_off": "{entity_name}\u5df2\u95dc\u9589", "turned_on": "{entity_name}\u5df2\u958b\u555f" } diff --git a/homeassistant/components/renault/translations/es.json b/homeassistant/components/renault/translations/es.json index cf0f88983e0..1d8b5f447e3 100644 --- a/homeassistant/components/renault/translations/es.json +++ b/homeassistant/components/renault/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "kamereon_no_account": "No se pudo encontrar la cuenta de Kamereon.", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, diff --git a/homeassistant/components/renault/translations/nl.json b/homeassistant/components/renault/translations/nl.json index c2e02b03166..1d2066dbdfc 100644 --- a/homeassistant/components/renault/translations/nl.json +++ b/homeassistant/components/renault/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Account is al geconfigureerd", "kamereon_no_account": "Kan Kamereon-account niet vinden.", - "reauth_successful": "Opnieuw verifi\u00ebren is gelukt" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_credentials": "Ongeldige authenticatie" @@ -20,7 +20,7 @@ "password": "Wachtwoord" }, "description": "Werk uw wachtwoord voor {gebruikersnaam} bij", - "title": "Integratie opnieuw verifi\u00ebren" + "title": "Integratie herauthenticeren" }, "user": { "data": { diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index c10075bbb79..b517abafc86 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -24,13 +24,11 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import Event, HomeAssistant, ServiceCall, callback -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.device_registry import ( - EVENT_DEVICE_REGISTRY_UPDATED, - DeviceEntry, - DeviceRegistry, +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.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity @@ -171,9 +169,7 @@ async def async_setup_internal(hass, entry: ConfigEntry): devices = _get_device_lookup(config[CONF_DEVICES]) pt2262_devices: list[str] = [] - device_registry: DeviceRegistry = ( - await hass.helpers.device_registry.async_get_registry() - ) + device_registry = dr.async_get(hass) # Declare the Handle event @callback @@ -214,7 +210,7 @@ async def async_setup_internal(hass, entry: ConfigEntry): event_data[ATTR_DEVICE_ID] = device_entry.id # Callback to HA registered components. - hass.helpers.dispatcher.async_dispatcher_send(SIGNAL_EVENT, event, device_id) + async_dispatcher_send(hass, SIGNAL_EVENT, event, device_id) # Signal event to any other listeners hass.bus.async_fire(EVENT_RFXTRX_EVENT, event_data) @@ -265,7 +261,7 @@ async def async_setup_internal(hass, entry: ConfigEntry): _remove_device(device_id) entry.async_on_unload( - hass.bus.async_listen(EVENT_DEVICE_REGISTRY_UPDATED, _updated_device) + hass.bus.async_listen(dr.EVENT_DEVICE_REGISTRY_UPDATED, _updated_device) ) def _shutdown_rfxtrx(event): @@ -445,7 +441,7 @@ def get_device_tuple_from_identifiers( async def async_remove_config_entry_device( - hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry ) -> bool: """Remove config entry from a device. diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index 8fce56564a4..61d01b8d533 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -24,16 +24,10 @@ from homeassistant.const import ( CONF_TYPE, ) from homeassistant.core import callback -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.device_registry import ( - DeviceEntry, - DeviceRegistry, - async_entries_for_config_entry, - async_get_registry as async_get_device_registry, -) -from homeassistant.helpers.entity_registry import ( - async_entries_for_device, - async_get_registry as async_get_entity_registry, +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + entity_registry as er, ) from . import ( @@ -80,8 +74,8 @@ def none_or_int(value, base): class OptionsFlow(config_entries.OptionsFlow): """Handle Rfxtrx options.""" - _device_registry: DeviceRegistry - _device_entries: list[DeviceEntry] + _device_registry: dr.DeviceRegistry + _device_entries: list[dr.DeviceEntry] def __init__(self, config_entry: ConfigEntry) -> None: """Initialize rfxtrx options flow.""" @@ -135,8 +129,8 @@ class OptionsFlow(config_entries.OptionsFlow): return self.async_create_entry(title="", data={}) - device_registry = await async_get_device_registry(self.hass) - device_entries = async_entries_for_config_entry( + device_registry = dr.async_get(self.hass) + device_entries = dr.async_entries_for_config_entry( device_registry, self._config_entry.entry_id ) self._device_registry = device_registry @@ -320,8 +314,8 @@ class OptionsFlow(config_entries.OptionsFlow): old_device_id = "_".join(x for x in old_device_data[CONF_DEVICE_ID]) new_device_id = "_".join(x for x in new_device_data[CONF_DEVICE_ID]) - entity_registry = await async_get_entity_registry(self.hass) - entity_entries = async_entries_for_device( + entity_registry = er.async_get(self.hass) + entity_entries = er.async_entries_for_device( entity_registry, old_device, include_disabled_entities=True ) entity_migration_map = {} diff --git a/homeassistant/components/rfxtrx/device_action.py b/homeassistant/components/rfxtrx/device_action.py index 37fb39cb499..e8fc8c6707d 100644 --- a/homeassistant/components/rfxtrx/device_action.py +++ b/homeassistant/components/rfxtrx/device_action.py @@ -9,6 +9,7 @@ from homeassistant.components.device_automation.exceptions import ( from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_TYPE from homeassistant.core import Context, HomeAssistant import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from . import DATA_RFXOBJECT, DOMAIN from .helpers import async_get_device_object @@ -37,7 +38,9 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: +async def async_get_actions( + hass: HomeAssistant, device_id: str +) -> list[dict[str, str]]: """List device actions for RFXCOM RFXtrx devices.""" try: @@ -48,8 +51,8 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: actions = [] for action_type in ACTION_TYPES: if hasattr(device, action_type): - values = getattr(device, ACTION_SELECTION[action_type], {}) - for value in values.values(): + data: dict[int, str] = getattr(device, ACTION_SELECTION[action_type], {}) + for value in data.values(): actions.append( { CONF_DEVICE_ID: device_id, @@ -69,7 +72,9 @@ def _get_commands(hass, device_id, action_type): return commands, send_fun -async def async_validate_action_config(hass, config): +async def async_validate_action_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: """Validate config.""" config = ACTION_SCHEMA(config) commands, _ = _get_commands(hass, config[CONF_DEVICE_ID], config[CONF_TYPE]) @@ -84,7 +89,10 @@ async def async_validate_action_config(hass, config): async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/rfxtrx/device_trigger.py b/homeassistant/components/rfxtrx/device_trigger.py index 9ab10ca7f2b..196377dd60f 100644 --- a/homeassistant/components/rfxtrx/device_trigger.py +++ b/homeassistant/components/rfxtrx/device_trigger.py @@ -47,13 +47,15 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: +async def async_get_triggers( + hass: HomeAssistant, device_id: str +) -> list[dict[str, str]]: """List device triggers for RFXCOM RFXtrx devices.""" device = async_get_device_object(hass, device_id) triggers = [] for conf_type in TRIGGER_TYPES: - data = getattr(device, TRIGGER_SELECTION[conf_type], {}) + data: dict[int, str] = getattr(device, TRIGGER_SELECTION[conf_type], {}) for command in data.values(): triggers.append( { @@ -67,7 +69,9 @@ async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: return triggers -async def async_validate_trigger_config(hass, config): +async def async_validate_trigger_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: """Validate config.""" config = TRIGGER_SCHEMA(config) diff --git a/homeassistant/components/rfxtrx/manifest.json b/homeassistant/components/rfxtrx/manifest.json index edbf5c8556c..cfe1049c888 100644 --- a/homeassistant/components/rfxtrx/manifest.json +++ b/homeassistant/components/rfxtrx/manifest.json @@ -2,7 +2,7 @@ "domain": "rfxtrx", "name": "RFXCOM RFXtrx", "documentation": "https://www.home-assistant.io/integrations/rfxtrx", - "requirements": ["pyRFXtrx==0.28.0"], + "requirements": ["pyRFXtrx==0.29.0"], "codeowners": ["@danielhiversen", "@elupus", "@RobBie1221"], "config_flow": true, "iot_class": "local_push", diff --git a/homeassistant/components/rfxtrx/translations/bg.json b/homeassistant/components/rfxtrx/translations/bg.json index 9a7983491d6..c03ec99553e 100644 --- a/homeassistant/components/rfxtrx/translations/bg.json +++ b/homeassistant/components/rfxtrx/translations/bg.json @@ -19,6 +19,9 @@ "device": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" }, "title": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + }, + "setup_serial_manual_path": { + "title": "\u041f\u044a\u0442" } } }, diff --git a/homeassistant/components/rfxtrx/translations/ca.json b/homeassistant/components/rfxtrx/translations/ca.json index a8a4f958cb6..edcd3a7e558 100644 --- a/homeassistant/components/rfxtrx/translations/ca.json +++ b/homeassistant/components/rfxtrx/translations/ca.json @@ -61,8 +61,7 @@ "debug": "Activa la depuraci\u00f3", "device": "Selecciona el dispositiu a configurar", "event_code": "Introdueix el codi de l'esdeveniment a afegir", - "protocols": "Protocols", - "remove_device": "Selecciona el dispositiu a eliminar" + "protocols": "Protocols" }, "title": "Opcions de Rfxtrx" }, @@ -71,11 +70,9 @@ "command_off": "Valor dels bits de dades per a l'ordre off", "command_on": "Valor dels bits de dades per a l'ordre ON", "data_bit": "Nombre de bits de dades", - "fire_event": "Activa l'esdeveniment de dispositiu", "off_delay": "Retard off", "off_delay_enabled": "Activa el retard off", "replace_device": "Selecciona el dispositiu a substituir", - "signal_repetitions": "Nombre de repeticions del senyal", "venetian_blind_mode": "Mode persiana veneciana" }, "title": "Configuraci\u00f3 de les opcions del dispositiu" diff --git a/homeassistant/components/rfxtrx/translations/cs.json b/homeassistant/components/rfxtrx/translations/cs.json index 706d4499fb2..17fea91836c 100644 --- a/homeassistant/components/rfxtrx/translations/cs.json +++ b/homeassistant/components/rfxtrx/translations/cs.json @@ -50,18 +50,15 @@ "automatic_add": "Povolit automatick\u00e9 p\u0159id\u00e1n\u00ed", "debug": "Povolit lad\u011bn\u00ed", "device": "Vyberte za\u0159\u00edzen\u00ed, kter\u00e9 chcete nastavit", - "event_code": "Zadejte k\u00f3d ud\u00e1losti, kterou chcete p\u0159idat", - "remove_device": "Vyberte za\u0159\u00edzen\u00ed, kter\u00e9 chcete odstranit" + "event_code": "Zadejte k\u00f3d ud\u00e1losti, kterou chcete p\u0159idat" }, "title": "Mo\u017enosti Rfxtrx" }, "set_device_options": { "data": { - "fire_event": "Povolit ud\u00e1lost za\u0159\u00edzen\u00ed", "off_delay": "Zpo\u017ed\u011bn\u00ed vypnut\u00ed", "off_delay_enabled": "Povolit zpo\u017ed\u011bn\u00ed vypnut\u00ed", - "replace_device": "Vyberte za\u0159\u00edzen\u00ed, kter\u00e9 chcete vym\u011bnit", - "signal_repetitions": "Po\u010det opakov\u00e1n\u00ed sign\u00e1lu" + "replace_device": "Vyberte za\u0159\u00edzen\u00ed, kter\u00e9 chcete vym\u011bnit" }, "title": "Nastaven\u00ed mo\u017enost\u00ed za\u0159\u00edzen\u00ed" } diff --git a/homeassistant/components/rfxtrx/translations/de.json b/homeassistant/components/rfxtrx/translations/de.json index a80bde85b0b..445a93b7e76 100644 --- a/homeassistant/components/rfxtrx/translations/de.json +++ b/homeassistant/components/rfxtrx/translations/de.json @@ -61,8 +61,7 @@ "debug": "Debugging aktivieren", "device": "Zu konfigurierendes Ger\u00e4t ausw\u00e4hlen", "event_code": "Ereigniscode zum Hinzuf\u00fcgen eingeben", - "protocols": "Protokolle", - "remove_device": "Zu l\u00f6schendes Ger\u00e4t ausw\u00e4hlen" + "protocols": "Protokolle" }, "title": "Rfxtrx Optionen" }, @@ -71,11 +70,9 @@ "command_off": "Datenbitwert f\u00fcr den Befehl \"aus\"", "command_on": "Datenbitwert f\u00fcr den Befehl \"ein\"", "data_bit": "Anzahl der Datenbits", - "fire_event": "Ger\u00e4teereignis aktivieren", "off_delay": "Ausschaltverz\u00f6gerung", "off_delay_enabled": "Ausschaltverz\u00f6gerung aktivieren", "replace_device": "W\u00e4hle ein Ger\u00e4t aus, das ersetzt werden soll", - "signal_repetitions": "Anzahl der Signalwiederholungen", "venetian_blind_mode": "Jalousie-Modus" }, "title": "Ger\u00e4teoptionen konfigurieren" diff --git a/homeassistant/components/rfxtrx/translations/el.json b/homeassistant/components/rfxtrx/translations/el.json index 8d55c7252a0..950982df50a 100644 --- a/homeassistant/components/rfxtrx/translations/el.json +++ b/homeassistant/components/rfxtrx/translations/el.json @@ -61,8 +61,7 @@ "debug": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03c3\u03c6\u03b1\u03bb\u03bc\u03ac\u03c4\u03c9\u03bd", "device": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd", "event_code": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7", - "protocols": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03b1", - "remove_device": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae" + "protocols": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03b1" }, "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 Rfxtrx" }, @@ -71,11 +70,9 @@ "command_off": "\u03a4\u03b9\u03bc\u03ae bit \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae off", "command_on": "\u03a4\u03b9\u03bc\u03ae bit \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae on", "data_bit": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 bit \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd", - "fire_event": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", "off_delay": "\u039a\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2", "off_delay_enabled": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2", "replace_device": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03b1\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", - "signal_repetitions": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b5\u03c0\u03b1\u03bd\u03b1\u03bb\u03ae\u03c8\u03b5\u03c9\u03bd \u03c3\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2", "venetian_blind_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b2\u03b5\u03bd\u03b5\u03c4\u03c3\u03b9\u03ac\u03bd\u03b9\u03ba\u03b7\u03c2 \u03c0\u03b5\u03c1\u03c3\u03af\u03b4\u03b1\u03c2" }, "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" diff --git a/homeassistant/components/rfxtrx/translations/en.json b/homeassistant/components/rfxtrx/translations/en.json index af042719f77..9a509246f5a 100644 --- a/homeassistant/components/rfxtrx/translations/en.json +++ b/homeassistant/components/rfxtrx/translations/en.json @@ -61,8 +61,7 @@ "debug": "Enable debugging", "device": "Select device to configure", "event_code": "Enter event code to add", - "protocols": "Protocols", - "remove_device": "Select device to delete" + "protocols": "Protocols" }, "title": "Rfxtrx Options" }, @@ -71,11 +70,9 @@ "command_off": "Data bits value for command off", "command_on": "Data bits value for command on", "data_bit": "Number of data bits", - "fire_event": "Enable device event", "off_delay": "Off delay", "off_delay_enabled": "Enable off delay", "replace_device": "Select device to replace", - "signal_repetitions": "Number of signal repetitions", "venetian_blind_mode": "Venetian blind mode" }, "title": "Configure device options" diff --git a/homeassistant/components/rfxtrx/translations/es.json b/homeassistant/components/rfxtrx/translations/es.json index fa45fe8a777..997a6b0a0bf 100644 --- a/homeassistant/components/rfxtrx/translations/es.json +++ b/homeassistant/components/rfxtrx/translations/es.json @@ -61,7 +61,7 @@ "debug": "Activar la depuraci\u00f3n", "device": "Seleccionar dispositivo para configurar", "event_code": "Introducir el c\u00f3digo de evento para a\u00f1adir", - "remove_device": "Selecciona el dispositivo a eliminar" + "protocols": "Protocolos" }, "title": "Opciones de Rfxtrx" }, @@ -70,11 +70,9 @@ "command_off": "Valor de bits de datos para comando apagado", "command_on": "Valor de bits de datos para comando activado", "data_bit": "N\u00famero de bits de datos", - "fire_event": "Habilitar evento del dispositivo", "off_delay": "Retraso de apagado", "off_delay_enabled": "Activar retardo de apagado", "replace_device": "Seleccione el dispositivo que desea reemplazar", - "signal_repetitions": "N\u00famero de repeticiones de la se\u00f1al", "venetian_blind_mode": "Modo de persiana veneciana" }, "title": "Configurar las opciones del dispositivo" diff --git a/homeassistant/components/rfxtrx/translations/et.json b/homeassistant/components/rfxtrx/translations/et.json index 2c4a36b1664..eff49a8a6c4 100644 --- a/homeassistant/components/rfxtrx/translations/et.json +++ b/homeassistant/components/rfxtrx/translations/et.json @@ -61,8 +61,7 @@ "debug": "Luba silumine", "device": "Vali seadistatav seade", "event_code": "Sisesta lisatava s\u00fcndmuse kood", - "protocols": "Protokollid", - "remove_device": "Vali eemaldatav seade" + "protocols": "Protokollid" }, "title": "Rfxtrx valikud" }, @@ -71,11 +70,9 @@ "command_off": "V\u00e4ljal\u00fclitamise k\u00e4su andmebittide v\u00e4\u00e4rtus", "command_on": "Sissel\u00fclitamise k\u00e4su andmebittide v\u00e4\u00e4rtus", "data_bit": "Andmebittide arv", - "fire_event": "Luba seadme s\u00fcndmus", "off_delay": "V\u00e4ljal\u00fclitamise viivitus", "off_delay_enabled": "Luba v\u00e4ljal\u00fclitusviivitus", "replace_device": "Vali asendav seade", - "signal_repetitions": "Signaali korduste arv", "venetian_blind_mode": "Ribikardinate juhtimine" }, "title": "Seadista seadme valikud" diff --git a/homeassistant/components/rfxtrx/translations/fr.json b/homeassistant/components/rfxtrx/translations/fr.json index 5eaff1ce406..9e31b450ce7 100644 --- a/homeassistant/components/rfxtrx/translations/fr.json +++ b/homeassistant/components/rfxtrx/translations/fr.json @@ -66,8 +66,7 @@ "debug": "Activer le d\u00e9bogage", "device": "S\u00e9lectionnez l'appareil \u00e0 configurer", "event_code": "Entrez le code d'\u00e9v\u00e9nement \u00e0 ajouter", - "protocols": "Protocoles", - "remove_device": "S\u00e9lectionnez l'appareil \u00e0 supprimer" + "protocols": "Protocoles" }, "title": "Options Rfxtrx" }, @@ -76,11 +75,9 @@ "command_off": "Valeur des bits de donn\u00e9es pour la commande \u00e9teindre", "command_on": "Valeur des bits de donn\u00e9es pour la commande allumer", "data_bit": "Nombre de bits de donn\u00e9es", - "fire_event": "Activer les \u00e9v\u00e9nements d'appareil", "off_delay": "D\u00e9lai d'arr\u00eat", "off_delay_enabled": "Activer le d\u00e9lai d'arr\u00eat", "replace_device": "S\u00e9lectionnez l'appareil \u00e0 remplacer", - "signal_repetitions": "Nombre de r\u00e9p\u00e9titions du signal", "venetian_blind_mode": "Mode store v\u00e9nitien" }, "title": "Configurer les options de l'appareil" diff --git a/homeassistant/components/rfxtrx/translations/hu.json b/homeassistant/components/rfxtrx/translations/hu.json index a6f1c925fbf..158411ef618 100644 --- a/homeassistant/components/rfxtrx/translations/hu.json +++ b/homeassistant/components/rfxtrx/translations/hu.json @@ -66,8 +66,7 @@ "debug": "Enged\u00e9lyezze a hibakeres\u00e9st", "device": "V\u00e1lassza ki a konfigur\u00e1lni k\u00edv\u00e1nt eszk\u00f6zt", "event_code": "\u00cdrja be a hozz\u00e1adni k\u00edv\u00e1nt esem\u00e9ny k\u00f3dj\u00e1t", - "protocols": "Protokollok", - "remove_device": "V\u00e1lassza ki a t\u00f6r\u00f6lni k\u00edv\u00e1nt eszk\u00f6zt" + "protocols": "Protokollok" }, "title": "Rfxtrx opci\u00f3k" }, @@ -76,11 +75,9 @@ "command_off": "Adatbitek \u00e9rt\u00e9ke a parancs kikapcsol\u00e1s\u00e1hoz", "command_on": "Adatbitek \u00e9rt\u00e9ke a parancshoz", "data_bit": "Adatbitek sz\u00e1ma", - "fire_event": "Eszk\u00f6zesem\u00e9ny enged\u00e9lyez\u00e9se", "off_delay": "Kikapcsol\u00e1si k\u00e9sleltet\u00e9s", "off_delay_enabled": "Kikapcsol\u00e1si k\u00e9sleltet\u00e9s enged\u00e9lyez\u00e9se", "replace_device": "V\u00e1lassza ki a cser\u00e9lni k\u00edv\u00e1nt eszk\u00f6zt", - "signal_repetitions": "A jelism\u00e9tl\u00e9sek sz\u00e1ma", "venetian_blind_mode": "Velencei red\u0151ny \u00fczemm\u00f3d" }, "title": "Konfigur\u00e1lja az eszk\u00f6z be\u00e1ll\u00edt\u00e1sait" diff --git a/homeassistant/components/rfxtrx/translations/id.json b/homeassistant/components/rfxtrx/translations/id.json index cea00e08f9c..fa39b4c4a2e 100644 --- a/homeassistant/components/rfxtrx/translations/id.json +++ b/homeassistant/components/rfxtrx/translations/id.json @@ -61,8 +61,7 @@ "debug": "Aktifkan debugging", "device": "Pilih perangkat untuk dikonfigurasi", "event_code": "Masukkan kode event untuk ditambahkan", - "protocols": "Protokol", - "remove_device": "Pilih perangkat yang akan dihapus" + "protocols": "Protokol" }, "title": "Opsi Rfxtrx" }, @@ -71,11 +70,9 @@ "command_off": "Nilai bit data untuk perintah mematikan", "command_on": "Nilai bit data untuk perintah menyalakan", "data_bit": "Jumlah bit data", - "fire_event": "Aktifkan event perangkat", "off_delay": "Penundaan mematikan", "off_delay_enabled": "Aktifkan penundaan mematikan", "replace_device": "Pilih perangkat yang akan diganti", - "signal_repetitions": "Jumlah pengulangan sinyal", "venetian_blind_mode": "Mode penutup kerai Venesia" }, "title": "Konfigurasi opsi perangkat" diff --git a/homeassistant/components/rfxtrx/translations/it.json b/homeassistant/components/rfxtrx/translations/it.json index 5e5f07a013f..bcf9cebd99d 100644 --- a/homeassistant/components/rfxtrx/translations/it.json +++ b/homeassistant/components/rfxtrx/translations/it.json @@ -66,8 +66,7 @@ "debug": "Attiva il debug", "device": "Seleziona il dispositivo da configurare", "event_code": "Inserire il codice dell'evento da aggiungere", - "protocols": "Protocolli", - "remove_device": "Seleziona il dispositivo da eliminare" + "protocols": "Protocolli" }, "title": "Opzioni Rfxtrx" }, @@ -76,11 +75,9 @@ "command_off": "Valore dei bit di dati per il comando disattivato", "command_on": "Valore dei bit di dati per il comando attivato", "data_bit": "Numero di bit di dati", - "fire_event": "Abilita evento dispositivo", "off_delay": "Ritardo di spegnimento", "off_delay_enabled": "Attiva il ritardo di spegnimento", "replace_device": "Seleziona il dispositivo da sostituire", - "signal_repetitions": "Numero di ripetizioni del segnale", "venetian_blind_mode": "Modalit\u00e0 veneziana" }, "title": "Configura le opzioni del dispositivo" diff --git a/homeassistant/components/rfxtrx/translations/ja.json b/homeassistant/components/rfxtrx/translations/ja.json index cb79cc60a8e..9b22d34af58 100644 --- a/homeassistant/components/rfxtrx/translations/ja.json +++ b/homeassistant/components/rfxtrx/translations/ja.json @@ -61,8 +61,7 @@ "debug": "\u30c7\u30d0\u30c3\u30b0\u306e\u6709\u52b9\u5316", "device": "\u8a2d\u5b9a\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e", "event_code": "\u30a4\u30d9\u30f3\u30c8\u30b3\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u8ffd\u52a0", - "protocols": "\u30d7\u30ed\u30c8\u30b3\u30eb", - "remove_device": "\u524a\u9664\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u306e\u9078\u629e" + "protocols": "\u30d7\u30ed\u30c8\u30b3\u30eb" }, "title": "Rfxtrx\u306e\u30aa\u30d7\u30b7\u30e7\u30f3" }, @@ -71,11 +70,9 @@ "command_off": "\u30b3\u30de\u30f3\u30c9\u30aa\u30d5\u306e\u30c7\u30fc\u30bf\u30d3\u30c3\u30c8\u5024", "command_on": "\u30b3\u30de\u30f3\u30c9\u30aa\u30f3\u306e\u30c7\u30fc\u30bf\u30d3\u30c3\u30c8\u5024", "data_bit": "\u30c7\u30fc\u30bf\u30d3\u30c3\u30c8\u6570", - "fire_event": "\u30c7\u30d0\u30a4\u30b9 \u30a4\u30d9\u30f3\u30c8\u3092\u6709\u52b9\u306b\u3059\u308b", "off_delay": "\u30aa\u30d5\u9045\u5ef6", "off_delay_enabled": "\u30aa\u30d5\u9045\u5ef6\u3092\u6709\u52b9\u306b\u3059\u308b", "replace_device": "\u4ea4\u63db\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e", - "signal_repetitions": "\u4fe1\u53f7\u306e\u30ea\u30d4\u30fc\u30c8\u6570", "venetian_blind_mode": "Venetian blind\u30e2\u30fc\u30c9" }, "title": "\u30c7\u30d0\u30a4\u30b9\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u8a2d\u5b9a" diff --git a/homeassistant/components/rfxtrx/translations/ko.json b/homeassistant/components/rfxtrx/translations/ko.json index 891926083dd..0434e5b75ab 100644 --- a/homeassistant/components/rfxtrx/translations/ko.json +++ b/homeassistant/components/rfxtrx/translations/ko.json @@ -50,8 +50,7 @@ "automatic_add": "\uc790\ub3d9 \ucd94\uac00 \ud65c\uc131\ud654\ud558\uae30", "debug": "\ub514\ubc84\uae45 \ud65c\uc131\ud654\ud558\uae30", "device": "\uad6c\uc131\ud560 \uae30\uae30 \uc120\ud0dd\ud558\uae30", - "event_code": "\ucd94\uac00\ud560 \uc774\ubca4\ud2b8 \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", - "remove_device": "\uc0ad\uc81c\ud560 \uae30\uae30 \uc120\ud0dd\ud558\uae30" + "event_code": "\ucd94\uac00\ud560 \uc774\ubca4\ud2b8 \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "title": "Rfxtrx \uc635\uc158" }, @@ -60,11 +59,9 @@ "command_off": "\ub044\uae30 \uba85\ub839\uc5d0 \ub300\ud55c \ub370\uc774\ud130 \ube44\ud2b8 \uac12", "command_on": "\ucf1c\uae30 \uba85\ub839\uc5d0 \ub300\ud55c \ub370\uc774\ud130 \ube44\ud2b8 \uac12", "data_bit": "\ub370\uc774\ud130 \ube44\ud2b8 \uc218", - "fire_event": "\uae30\uae30 \uc774\ubca4\ud2b8 \ud65c\uc131\ud654\ud558\uae30", "off_delay": "\uc790\ub3d9 \uaebc\uc9c4 \uc0c1\ud0dc", "off_delay_enabled": "\uc790\ub3d9 \uaebc\uc9c4 \uc0c1\ud0dc(Off Delay) \ud65c\uc131\ud654\ud558\uae30", "replace_device": "\uad50\uccb4\ud560 \uae30\uae30 \uc120\ud0dd\ud558\uae30", - "signal_repetitions": "\uc2e0\ud638 \ubc18\ubcf5 \ud69f\uc218", "venetian_blind_mode": "\ubca0\ub124\uc2dc\uc548 \ube14\ub77c\uc778\ub4dc \ubaa8\ub4dc" }, "title": "\uae30\uae30 \uc635\uc158 \uad6c\uc131\ud558\uae30" diff --git a/homeassistant/components/rfxtrx/translations/lb.json b/homeassistant/components/rfxtrx/translations/lb.json index f5441eeb298..82535926bd4 100644 --- a/homeassistant/components/rfxtrx/translations/lb.json +++ b/homeassistant/components/rfxtrx/translations/lb.json @@ -47,8 +47,7 @@ "automatic_add": "Aktiv\u00e9ier automatesch dob\u00e4isetzen", "debug": "Aktiv\u00e9ier Debuggen", "device": "Wiel een Apparat fir ze konfigur\u00e9ieren", - "event_code": "Evenement Code aginn fir dob\u00e4izesetzen", - "remove_device": "Wiel een Apparat fir ze l\u00e4schen" + "event_code": "Evenement Code aginn fir dob\u00e4izesetzen" }, "title": "Rfxtrx Optioune" }, @@ -57,11 +56,9 @@ "command_off": "Data bits W\u00e4ert fir Kommando aus", "command_on": "Data bits W\u00e4ert fir Kommando un", "data_bit": "Unzuel vun Data Bits", - "fire_event": "Aktiv\u00e9ier Apparat Evenement", "off_delay": "Aus Verz\u00f6gerung", "off_delay_enabled": "Aktiv\u00e9iert Aus Verz\u00f6gerung", - "replace_device": "Wiel een Apparat fir ze ersetzen", - "signal_repetitions": "Zuel vu Signalwidderhuelungen" + "replace_device": "Wiel een Apparat fir ze ersetzen" }, "title": "Apparat Optioune konfigur\u00e9ieren" } diff --git a/homeassistant/components/rfxtrx/translations/nl.json b/homeassistant/components/rfxtrx/translations/nl.json index e9b4c1a6c04..ee5e8b4a463 100644 --- a/homeassistant/components/rfxtrx/translations/nl.json +++ b/homeassistant/components/rfxtrx/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "error": { "cannot_connect": "Kan geen verbinding maken" @@ -61,8 +61,7 @@ "debug": "Foutopsporing inschakelen", "device": "Selecteer het apparaat om te configureren", "event_code": "Voer de gebeurteniscode in om toe te voegen", - "protocols": "Protocollen", - "remove_device": "Apparaat selecteren dat u wilt verwijderen" + "protocols": "Protocollen" }, "title": "Rfxtrx-opties" }, @@ -71,11 +70,9 @@ "command_off": "Waarde gegevensbits voor commando uit", "command_on": "Waarde gegevensbits voor commando aan", "data_bit": "Aantal databits", - "fire_event": "Schakel apparaatgebeurtenis in", "off_delay": "Uitschakelvertraging", "off_delay_enabled": "Schakel uitschakelvertraging in", "replace_device": "Selecteer apparaat dat u wilt vervangen", - "signal_repetitions": "Aantal signaalherhalingen", "venetian_blind_mode": "Venetiaanse jaloezie modus" }, "title": "Configureer apparaatopties" diff --git a/homeassistant/components/rfxtrx/translations/no.json b/homeassistant/components/rfxtrx/translations/no.json index 62a658d4067..acdca83016e 100644 --- a/homeassistant/components/rfxtrx/translations/no.json +++ b/homeassistant/components/rfxtrx/translations/no.json @@ -61,8 +61,7 @@ "debug": "Aktiver feils\u00f8king", "device": "Velg enhet du vil konfigurere", "event_code": "Angi hendelseskode for \u00e5 legge til", - "protocols": "Protokoller", - "remove_device": "Velg enhet du vil slette" + "protocols": "Protokoller" }, "title": "[%key:component::rfxtrx::title%] alternativer" }, @@ -71,11 +70,9 @@ "command_off": "Databiter-verdi for kommando av", "command_on": "Databiter-verdi for kommando p\u00e5", "data_bit": "Antall databiter", - "fire_event": "Aktiver enhetshendelse", "off_delay": "Av forsinkelse", "off_delay_enabled": "Aktiver av forsinkelse", "replace_device": "Velg enheten du vil erstatte", - "signal_repetitions": "Antall signalrepetisjoner", "venetian_blind_mode": "Persiennemodus" }, "title": "Konfigurer enhetsalternativer" diff --git a/homeassistant/components/rfxtrx/translations/pl.json b/homeassistant/components/rfxtrx/translations/pl.json index b9f83ded473..c081baeafff 100644 --- a/homeassistant/components/rfxtrx/translations/pl.json +++ b/homeassistant/components/rfxtrx/translations/pl.json @@ -72,8 +72,7 @@ "debug": "W\u0142\u0105cz debugowanie", "device": "Wybierz urz\u0105dzenie do skonfigurowania", "event_code": "Podaj kod zdarzenia do dodania", - "protocols": "Protoko\u0142y", - "remove_device": "Wybierz urz\u0105dzenie do usuni\u0119cia" + "protocols": "Protoko\u0142y" }, "title": "Opcje Rfxtrx" }, @@ -82,11 +81,9 @@ "command_off": "Warto\u015b\u0107 bit\u00f3w danych dla komendy \"wy\u0142\u0105cz\" (off)", "command_on": "Warto\u015b\u0107 bit\u00f3w danych dla komendy \"w\u0142\u0105cz\" (on)", "data_bit": "Liczba bit\u00f3w danych", - "fire_event": "W\u0142\u0105cz zdarzenia na urz\u0105dzeniu", "off_delay": "Op\u00f3\u017anienie stanu \"off\"", "off_delay_enabled": "W\u0142\u0105cz op\u00f3\u017anienie stanu \"off\"", "replace_device": "Wybierz urz\u0105dzenie do zast\u0105pienia", - "signal_repetitions": "Liczba powt\u00f3rze\u0144 sygna\u0142u", "venetian_blind_mode": "Tryb \u017caluzji weneckich" }, "title": "Konfiguracja opcji urz\u0105dzenia" diff --git a/homeassistant/components/rfxtrx/translations/pt-BR.json b/homeassistant/components/rfxtrx/translations/pt-BR.json index 83252586d6a..a43b7e15a2b 100644 --- a/homeassistant/components/rfxtrx/translations/pt-BR.json +++ b/homeassistant/components/rfxtrx/translations/pt-BR.json @@ -61,8 +61,7 @@ "debug": "Habilitar a depura\u00e7\u00e3o", "device": "Selecione o dispositivo para configurar", "event_code": "Insira o c\u00f3digo do evento para adicionar", - "protocols": "Protocolos", - "remove_device": "Selecione o dispositivo para excluir" + "protocols": "Protocolos" }, "title": "Op\u00e7\u00f5es de Rfxtrx" }, @@ -71,11 +70,9 @@ "command_off": "Valor de bits de dados para comando desligado", "command_on": "Valor de bits de dados para comando ligado", "data_bit": "N\u00famero de bits de dados", - "fire_event": "Ativar evento do dispositivo", "off_delay": "Atraso de desligamento", "off_delay_enabled": "Ativar atraso de desligamento", "replace_device": "Selecione o dispositivo para substituir", - "signal_repetitions": "N\u00famero de repeti\u00e7\u00f5es de sinal", "venetian_blind_mode": "Modo de persianas" }, "title": "Configurar op\u00e7\u00f5es do dispositivo" diff --git a/homeassistant/components/rfxtrx/translations/ru.json b/homeassistant/components/rfxtrx/translations/ru.json index cb0fa979197..74dc2e218c9 100644 --- a/homeassistant/components/rfxtrx/translations/ru.json +++ b/homeassistant/components/rfxtrx/translations/ru.json @@ -61,8 +61,7 @@ "debug": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u043e\u0442\u043b\u0430\u0434\u043a\u0438", "device": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", "event_code": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0441\u043e\u0431\u044b\u0442\u0438\u044f", - "protocols": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u044b", - "remove_device": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f" + "protocols": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u044b" }, "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438" }, @@ -71,11 +70,9 @@ "command_off": "\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0431\u0438\u0442\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", "command_on": "\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0431\u0438\u0442\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", "data_bit": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0431\u0438\u0442 \u0434\u0430\u043d\u043d\u044b\u0445", - "fire_event": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", "off_delay": "\u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", "off_delay_enabled": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0443 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", "replace_device": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0434\u043b\u044f \u0437\u0430\u043c\u0435\u043d\u044b", - "signal_repetitions": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0432 \u0441\u0438\u0433\u043d\u0430\u043b\u0430", "venetian_blind_mode": "\u0420\u0435\u0436\u0438\u043c \u0432\u0435\u043d\u0435\u0446\u0438\u0430\u043d\u0441\u043a\u0438\u0445 \u0436\u0430\u043b\u044e\u0437\u0438" }, "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" diff --git a/homeassistant/components/rfxtrx/translations/sv.json b/homeassistant/components/rfxtrx/translations/sv.json index bdafd86c9a0..6cab9bd0b37 100644 --- a/homeassistant/components/rfxtrx/translations/sv.json +++ b/homeassistant/components/rfxtrx/translations/sv.json @@ -43,8 +43,7 @@ "automatic_add": "Aktivera automatisk till\u00e4gg av enheter", "debug": "Aktivera fels\u00f6kning", "device": "V\u00e4lj enhet att konfigurera", - "event_code": "Ange h\u00e4ndelsekod att l\u00e4gga till", - "remove_device": "V\u00e4lj enhet som ska tas bort" + "event_code": "Ange h\u00e4ndelsekod att l\u00e4gga till" }, "title": "Rfxtrx-alternativ" }, @@ -53,11 +52,9 @@ "command_off": "Databitv\u00e4rde f\u00f6r av-kommando", "command_on": "Databitv\u00e4rde f\u00f6r p\u00e5-kommando", "data_bit": "Antal databitar", - "fire_event": "Aktivera enhetsh\u00e4ndelse", "off_delay": "Avst\u00e4ngningsf\u00f6rdr\u00f6jning", "off_delay_enabled": "Aktivera avst\u00e4ngningsf\u00f6rdr\u00f6jning", - "replace_device": "V\u00e4lj enhet att ers\u00e4tta", - "signal_repetitions": "Antal signalrepetitioner" + "replace_device": "V\u00e4lj enhet att ers\u00e4tta" }, "title": "Konfigurera enhetsalternativ" } diff --git a/homeassistant/components/rfxtrx/translations/tr.json b/homeassistant/components/rfxtrx/translations/tr.json index fd29e034e31..999536c6b31 100644 --- a/homeassistant/components/rfxtrx/translations/tr.json +++ b/homeassistant/components/rfxtrx/translations/tr.json @@ -66,8 +66,7 @@ "debug": "Hata ay\u0131klamay\u0131 etkinle\u015ftir", "device": "Yap\u0131land\u0131rmak i\u00e7in cihaz\u0131 se\u00e7in", "event_code": "Eklemek i\u00e7in etkinlik kodunu girin", - "protocols": "Protokoller", - "remove_device": "Silinecek cihaz\u0131 se\u00e7in" + "protocols": "Protokoller" }, "title": "Rfxtrx Se\u00e7enekleri" }, @@ -76,11 +75,9 @@ "command_off": "Komut kapatma i\u00e7in veri bitleri de\u011feri", "command_on": "Komut i\u00e7in veri bitleri de\u011feri", "data_bit": "Veri biti say\u0131s\u0131", - "fire_event": "Cihaz etkinli\u011fini etkinle\u015ftir", "off_delay": "Kapanma gecikmesi", "off_delay_enabled": "Kapatma gecikmesini etkinle\u015ftir", "replace_device": "De\u011fi\u015ftirilecek cihaz\u0131 se\u00e7in", - "signal_repetitions": "Sinyal tekrar\u0131 say\u0131s\u0131", "venetian_blind_mode": "Jaluzi modu" }, "title": "Cihaz se\u00e7eneklerini yap\u0131land\u0131r\u0131n" diff --git a/homeassistant/components/rfxtrx/translations/uk.json b/homeassistant/components/rfxtrx/translations/uk.json index 65cca65679c..42ab18699ee 100644 --- a/homeassistant/components/rfxtrx/translations/uk.json +++ b/homeassistant/components/rfxtrx/translations/uk.json @@ -50,8 +50,7 @@ "automatic_add": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f", "debug": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043d\u0430\u043b\u0430\u0433\u043e\u0434\u0436\u0435\u043d\u043d\u044f", "device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f", - "event_code": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0456\u0457", - "remove_device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0434\u043b\u044f \u0432\u0438\u0434\u0430\u043b\u0435\u043d\u043d\u044f" + "event_code": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0456\u0457" }, "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" }, @@ -60,11 +59,9 @@ "command_off": "\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0431\u0456\u0442\u0456\u0432 \u0434\u0430\u043d\u0438\u0445 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f", "command_on": "\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0431\u0456\u0442\u0456\u0432 \u0434\u0430\u043d\u0438\u0445 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f", "data_bit": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0431\u0456\u0442\u0456\u0432 \u0434\u0430\u043d\u0438\u0445", - "fire_event": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u043f\u043e\u0434\u0456\u0457 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", "off_delay": "\u0417\u0430\u0442\u0440\u0438\u043c\u043a\u0430 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f", "off_delay_enabled": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0437\u0430\u0442\u0440\u0438\u043c\u043a\u0443 \u0432\u0438\u043c\u0438\u043a\u0430\u043d\u043d\u044f", "replace_device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0434\u043b\u044f \u0437\u0430\u043c\u0456\u043d\u0438", - "signal_repetitions": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0432 \u0441\u0438\u0433\u043d\u0430\u043b\u0443", "venetian_blind_mode": "\u0420\u0435\u0436\u0438\u043c \u0436\u0430\u043b\u044e\u0437\u0456" }, "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" diff --git a/homeassistant/components/rfxtrx/translations/zh-Hant.json b/homeassistant/components/rfxtrx/translations/zh-Hant.json index 3f84f0a5f98..e477e6628ae 100644 --- a/homeassistant/components/rfxtrx/translations/zh-Hant.json +++ b/homeassistant/components/rfxtrx/translations/zh-Hant.json @@ -61,8 +61,7 @@ "debug": "\u958b\u555f\u9664\u932f", "device": "\u9078\u64c7\u6240\u8981\u8a2d\u5b9a\u7684\u88dd\u7f6e", "event_code": "\u8f38\u5165\u4e8b\u4ef6\u4ee3\u78bc\u4ee5\u65b0\u589e", - "protocols": "\u901a\u8a0a\u5354\u5b9a", - "remove_device": "\u9078\u64c7\u88dd\u7f6e\u4ee5\u522a\u9664" + "protocols": "\u901a\u8a0a\u5354\u5b9a" }, "title": "Rfxtrx \u9078\u9805" }, @@ -71,11 +70,9 @@ "command_off": "\u547d\u4ee4\u95dc\u9589\u7684\u8cc7\u6599\u4f4d\u5143\u503c", "command_on": "\u547d\u4ee4\u958b\u555f\u7684\u8cc7\u6599\u4f4d\u5143\u503c", "data_bit": "\u8cc7\u6599\u4f4d\u5143\u6578", - "fire_event": "\u958b\u555f\u88dd\u7f6e\u4e8b\u4ef6", "off_delay": "\u5ef6\u9072", "off_delay_enabled": "\u958b\u555f\u5ef6\u9072", "replace_device": "\u9078\u64c7\u88dd\u7f6e\u4ee5\u53d6\u4ee3", - "signal_repetitions": "\u8a0a\u865f\u91cd\u8907\u6b21\u6578", "venetian_blind_mode": "\u767e\u8449\u7a97\u6a21\u5f0f" }, "title": "\u8a2d\u5b9a\u88dd\u7f6e\u9078\u9805" diff --git a/homeassistant/components/ridwell/translations/nl.json b/homeassistant/components/ridwell/translations/nl.json index afec8a578f5..0bd599ccf19 100644 --- a/homeassistant/components/ridwell/translations/nl.json +++ b/homeassistant/components/ridwell/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie", @@ -14,7 +14,7 @@ "password": "Wachtwoord" }, "description": "Voer het wachtwoord voor {username} opnieuw in:", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index 5d834976370..0d8f87eef3c 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -16,6 +16,7 @@ from ring_doorbell import Auth, Ring from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform, __version__ from homeassistant.core import HomeAssistant, ServiceCall, callback +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType from homeassistant.util.async_ import run_callback_threadsafe @@ -36,6 +37,7 @@ PLATFORMS = [ Platform.SENSOR, Platform.SWITCH, Platform.CAMERA, + Platform.SIREN, ] @@ -145,6 +147,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 + + class GlobalDataUpdater: """Data storage for single API endpoint.""" diff --git a/homeassistant/components/ring/siren.py b/homeassistant/components/ring/siren.py new file mode 100644 index 00000000000..b83d3e7b2ae --- /dev/null +++ b/homeassistant/components/ring/siren.py @@ -0,0 +1,51 @@ +"""This component provides HA Siren support for Ring Chimes.""" +import logging +from typing import Any + +from ring_doorbell.const import CHIME_TEST_SOUND_KINDS, KIND_DING + +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 + +from . import DOMAIN +from .entity import RingEntityMixin + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Create the sirens for the Ring devices.""" + devices = hass.data[DOMAIN][config_entry.entry_id]["devices"] + sirens = [] + + for device in devices["chimes"]: + sirens.append(RingChimeSiren(config_entry, device)) + + async_add_entities(sirens) + + +class RingChimeSiren(RingEntityMixin, SirenEntity): + """Creates a siren to play the test chimes of a Chime device.""" + + def __init__(self, config_entry: ConfigEntry, device) -> None: + """Initialize a Ring Chime siren.""" + super().__init__(config_entry.entry_id, device) + # Entity class attributes + self._attr_name = f"{self._device.name} Siren" + self._attr_unique_id = f"{self._device.id}-siren" + self._attr_available_tones = CHIME_TEST_SOUND_KINDS + self._attr_supported_features = ( + SirenEntityFeature.TURN_ON | SirenEntityFeature.TONES + ) + + def turn_on(self, **kwargs: Any) -> None: + """Play the test sound on a Ring Chime device.""" + tone = kwargs.get(ATTR_TONE) or KIND_DING + + self._device.test_sound(kind=tone) diff --git a/homeassistant/components/risco/sensor.py b/homeassistant/components/risco/sensor.py index 3f3dc221fac..6038c2911c9 100644 --- a/homeassistant/components/risco/sensor.py +++ b/homeassistant/components/risco/sensor.py @@ -3,6 +3,7 @@ from homeassistant.components.binary_sensor import DOMAIN as BS_DOMAIN from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import dt as dt_util @@ -75,9 +76,7 @@ class RiscoSensor(CoordinatorEntity, SensorEntity): async def async_added_to_hass(self): """When entity is added to hass.""" - self._entity_registry = ( - await self.hass.helpers.entity_registry.async_get_registry() - ) + self._entity_registry = er.async_get(self.hass) self.async_on_remove( self.coordinator.async_add_listener(self._refresh_from_coordinator) ) diff --git a/homeassistant/components/risco/translations/es.json b/homeassistant/components/risco/translations/es.json index 68e7031c102..6f85fe0c4ab 100644 --- a/homeassistant/components/risco/translations/es.json +++ b/homeassistant/components/risco/translations/es.json @@ -33,7 +33,7 @@ "init": { "data": { "code_arm_required": "Requiere un c\u00f3digo PIN para armar", - "code_disarm_required": "Requiere un c\u00f3digo PIN para desarmar", + "code_disarm_required": "Requiere un c\u00f3digo PIN para desactivar", "scan_interval": "Con qu\u00e9 frecuencia sondear Risco (en segundos)" }, "title": "Configurar opciones" diff --git a/homeassistant/components/risco/translations/nl.json b/homeassistant/components/risco/translations/nl.json index fdf1ee5ad23..acab8c172c3 100644 --- a/homeassistant/components/risco/translations/nl.json +++ b/homeassistant/components/risco/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, @@ -12,7 +12,7 @@ "user": { "data": { "password": "Wachtwoord", - "pin": "PIN-code", + "pin": "Pincode", "username": "Gebruikersnaam" } } @@ -32,8 +32,8 @@ }, "init": { "data": { - "code_arm_required": "PIN-code vereist om in te schakelen", - "code_disarm_required": "PIN-code vereist om uit te schakelen", + "code_arm_required": "Pincode vereist om in te schakelen", + "code_disarm_required": "Pincode vereist om uit te schakelen", "scan_interval": "Hoe vaak moet Risco worden ververst (in seconden)" }, "title": "Configureer opties" diff --git a/homeassistant/components/risco/translations/sk.json b/homeassistant/components/risco/translations/sk.json index 5ada995aa6e..3f464b4046d 100644 --- a/homeassistant/components/risco/translations/sk.json +++ b/homeassistant/components/risco/translations/sk.json @@ -3,5 +3,14 @@ "error": { "invalid_auth": "Neplatn\u00e9 overenie" } + }, + "options": { + "step": { + "risco_to_ha": { + "data": { + "A": "Skupina A" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/roku/helpers.py b/homeassistant/components/roku/helpers.py index 26fdb53c935..f5a68f44ab8 100644 --- a/homeassistant/components/roku/helpers.py +++ b/homeassistant/components/roku/helpers.py @@ -13,7 +13,7 @@ from .entity import RokuEntity _LOGGER = logging.getLogger(__name__) -_T = TypeVar("_T", bound=RokuEntity) +_RokuEntityT = TypeVar("_RokuEntityT", bound=RokuEntity) _P = ParamSpec("_P") @@ -25,14 +25,21 @@ def format_channel_name(channel_number: str, channel_name: str | None = None) -> return channel_number -def roku_exception_handler(ignore_timeout: bool = False) -> Callable[..., Callable]: +def roku_exception_handler( + ignore_timeout: bool = False, +) -> Callable[ + [Callable[Concatenate[_RokuEntityT, _P], Awaitable[Any]]], + Callable[Concatenate[_RokuEntityT, _P], Coroutine[Any, Any, None]], +]: """Decorate Roku calls to handle Roku exceptions.""" def decorator( - func: Callable[Concatenate[_T, _P], Awaitable[None]], # type: ignore[misc] - ) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]: # type: ignore[misc] + func: Callable[Concatenate[_RokuEntityT, _P], Awaitable[Any]], + ) -> Callable[Concatenate[_RokuEntityT, _P], Coroutine[Any, Any, None]]: @wraps(func) - async def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: + async def wrapper( + self: _RokuEntityT, *args: _P.args, **kwargs: _P.kwargs + ) -> None: try: await func(self, *args, **kwargs) except RokuConnectionTimeoutError as error: diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index e6fe0d7dcf5..a47432694dd 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -384,7 +384,9 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): # Handle media_source if media_source.is_media_source_id(media_id): - sourced_media = await media_source.async_resolve_media(self.hass, media_id) + sourced_media = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_type = MEDIA_TYPE_URL media_id = sourced_media.url mime_type = sourced_media.mime_type diff --git a/homeassistant/components/roku/translations/ca.json b/homeassistant/components/roku/translations/ca.json index be84d78fbff..df9d6fe48c7 100644 --- a/homeassistant/components/roku/translations/ca.json +++ b/homeassistant/components/roku/translations/ca.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "Vols configurar {name}?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "Vols configurar {name}?", - "title": "Roku" + "description": "Vols configurar {name}?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/cs.json b/homeassistant/components/roku/translations/cs.json index 6914a519285..ee5ad4d600a 100644 --- a/homeassistant/components/roku/translations/cs.json +++ b/homeassistant/components/roku/translations/cs.json @@ -11,12 +11,7 @@ "flow_title": "Roku: {name}", "step": { "discovery_confirm": { - "description": "Chcete nastavit {name}?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "Chcete nastavit {name}?", - "title": "Roku" + "description": "Chcete nastavit {name}?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/de.json b/homeassistant/components/roku/translations/de.json index 5ff560809c8..25972162593 100644 --- a/homeassistant/components/roku/translations/de.json +++ b/homeassistant/components/roku/translations/de.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "M\u00f6chtest du {name} einrichten?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "M\u00f6chtest du {name} einrichten?", - "title": "Roku" + "description": "M\u00f6chtest du {name} einrichten?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/el.json b/homeassistant/components/roku/translations/el.json index 91087c73a58..bf498993096 100644 --- a/homeassistant/components/roku/translations/el.json +++ b/homeassistant/components/roku/translations/el.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};", - "title": "Roku" + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/en.json b/homeassistant/components/roku/translations/en.json index 192b9b23085..a4d931a8bcf 100644 --- a/homeassistant/components/roku/translations/en.json +++ b/homeassistant/components/roku/translations/en.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "Do you want to set up {name}?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "Do you want to set up {name}?", - "title": "Roku" + "description": "Do you want to set up {name}?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/es-419.json b/homeassistant/components/roku/translations/es-419.json index 00b69c53c72..bff168d0084 100644 --- a/homeassistant/components/roku/translations/es-419.json +++ b/homeassistant/components/roku/translations/es-419.json @@ -9,10 +9,6 @@ }, "flow_title": "Roku: {name}", "step": { - "ssdp_confirm": { - "description": "\u00bfDesea configurar {name}?", - "title": "Roku" - }, "user": { "data": { "host": "Host o direcci\u00f3n IP" diff --git a/homeassistant/components/roku/translations/es.json b/homeassistant/components/roku/translations/es.json index 189a4aec179..817f1d970cc 100644 --- a/homeassistant/components/roku/translations/es.json +++ b/homeassistant/components/roku/translations/es.json @@ -8,15 +8,10 @@ "error": { "cannot_connect": "Fallo al conectar" }, - "flow_title": "Roku: {name}", + "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "\u00bfQuieres configurar {name} ?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "\u00bfQuieres configurar {name}?", - "title": "Roku" + "description": "\u00bfQuieres configurar {name} ?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/et.json b/homeassistant/components/roku/translations/et.json index bb496ab8716..78ce0995ebd 100644 --- a/homeassistant/components/roku/translations/et.json +++ b/homeassistant/components/roku/translations/et.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "Kas soovid seadistada {name}?", - "title": "" - }, - "ssdp_confirm": { - "description": "Kas soovid seadistada {name}?", - "title": "" + "description": "Kas soovid seadistada {name}?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/fr.json b/homeassistant/components/roku/translations/fr.json index 64a928de567..2d5ab38f828 100644 --- a/homeassistant/components/roku/translations/fr.json +++ b/homeassistant/components/roku/translations/fr.json @@ -15,16 +15,13 @@ "one": "Vide", "other": "Vide" }, - "description": "Voulez-vous configurer {name} ?", - "title": "Roku" + "description": "Voulez-vous configurer {name} ?" }, "ssdp_confirm": { "data": { "one": "Vide", "other": "Vide" - }, - "description": "Voulez-vous configurer {name} ?", - "title": "Roku" + } }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/hu.json b/homeassistant/components/roku/translations/hu.json index c28b0a712ea..e38c9ada483 100644 --- a/homeassistant/components/roku/translations/hu.json +++ b/homeassistant/components/roku/translations/hu.json @@ -15,16 +15,13 @@ "one": "Egy", "other": "Egy\u00e9b" }, - "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name}?", - "title": "Roku felfedezve" + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name}?" }, "ssdp_confirm": { "data": { "one": "\u00dcres", "other": "\u00dcres" - }, - "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name}?", - "title": "Roku" + } }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/id.json b/homeassistant/components/roku/translations/id.json index 3a227e80eaf..69d40e4aabb 100644 --- a/homeassistant/components/roku/translations/id.json +++ b/homeassistant/components/roku/translations/id.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "Ingin menyiapkan {name}?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "Ingin menyiapkan {name}?", - "title": "Roku" + "description": "Ingin menyiapkan {name}?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/it.json b/homeassistant/components/roku/translations/it.json index 9f42c49c74f..4ebf0784669 100644 --- a/homeassistant/components/roku/translations/it.json +++ b/homeassistant/components/roku/translations/it.json @@ -15,16 +15,13 @@ "one": "Vuoto", "other": "Vuoti" }, - "description": "Vuoi configurare {name}?", - "title": "Roku" + "description": "Vuoi configurare {name}?" }, "ssdp_confirm": { "data": { "one": "Vuoto", "other": "Vuoti" - }, - "description": "Vuoi impostare {name}?", - "title": "Roku" + } }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/ja.json b/homeassistant/components/roku/translations/ja.json index 65f2ac2272a..59026c4d204 100644 --- a/homeassistant/components/roku/translations/ja.json +++ b/homeassistant/components/roku/translations/ja.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "{name} \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "{name} \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f", - "title": "Roku" + "description": "{name} \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/ko.json b/homeassistant/components/roku/translations/ko.json index cb127234601..93fb292c458 100644 --- a/homeassistant/components/roku/translations/ko.json +++ b/homeassistant/components/roku/translations/ko.json @@ -11,12 +11,7 @@ "flow_title": "Roku: {name}", "step": { "discovery_confirm": { - "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Roku" + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/lb.json b/homeassistant/components/roku/translations/lb.json index 04ad814c6b4..0a293006b9b 100644 --- a/homeassistant/components/roku/translations/lb.json +++ b/homeassistant/components/roku/translations/lb.json @@ -10,12 +10,7 @@ "flow_title": "Roku: {name}", "step": { "discovery_confirm": { - "description": "Soll {name} konfigur\u00e9iert ginn?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "Soll {name} konfigur\u00e9iert ginn?", - "title": "Roku" + "description": "Soll {name} konfigur\u00e9iert ginn?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/nl.json b/homeassistant/components/roku/translations/nl.json index 6bf3435a19b..e809c854060 100644 --- a/homeassistant/components/roku/translations/nl.json +++ b/homeassistant/components/roku/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "unknown": "Onverwachte fout" }, "error": { @@ -15,16 +15,13 @@ "one": "Een", "other": "Ander" }, - "description": "Wilt u {name} instellen?", - "title": "Roku" + "description": "Wilt u {name} instellen?" }, "ssdp_confirm": { "data": { "one": "Leeg", "other": "Leeg" - }, - "description": "Wilt u {name} instellen?", - "title": "Roku" + } }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/no.json b/homeassistant/components/roku/translations/no.json index 1bbd5bbea86..8449aaabfdd 100644 --- a/homeassistant/components/roku/translations/no.json +++ b/homeassistant/components/roku/translations/no.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "Vil du konfigurere {name}?", - "title": "" - }, - "ssdp_confirm": { - "description": "Vil du sette opp {name} ?", - "title": "" + "description": "Vil du konfigurere {name}?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/pl.json b/homeassistant/components/roku/translations/pl.json index 41ea348543e..59fb1aaa673 100644 --- a/homeassistant/components/roku/translations/pl.json +++ b/homeassistant/components/roku/translations/pl.json @@ -17,8 +17,7 @@ "one": "jeden", "other": "inne" }, - "description": "Czy chcesz skonfigurowa\u0107 {name}?", - "title": "Roku" + "description": "Czy chcesz skonfigurowa\u0107 {name}?" }, "ssdp_confirm": { "data": { @@ -26,9 +25,7 @@ "many": "wiele", "one": "jeden", "other": "inne" - }, - "description": "Czy chcesz skonfigurowa\u0107 {name}?", - "title": "Roku" + } }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/pt-BR.json b/homeassistant/components/roku/translations/pt-BR.json index 408bbc915c0..1970fbd0886 100644 --- a/homeassistant/components/roku/translations/pt-BR.json +++ b/homeassistant/components/roku/translations/pt-BR.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "Deseja configurar {name}?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "Voc\u00ea quer configurar o {name}?", - "title": "Roku" + "description": "Deseja configurar {name}?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/pt.json b/homeassistant/components/roku/translations/pt.json index e67de509456..1d4aea3279b 100644 --- a/homeassistant/components/roku/translations/pt.json +++ b/homeassistant/components/roku/translations/pt.json @@ -9,9 +9,6 @@ }, "flow_title": "Roku: {name}", "step": { - "ssdp_confirm": { - "title": "Roku" - }, "user": { "data": { "host": "Servidor" diff --git a/homeassistant/components/roku/translations/ru.json b/homeassistant/components/roku/translations/ru.json index 4ba55bc8e1a..76ac3a7f188 100644 --- a/homeassistant/components/roku/translations/ru.json +++ b/homeassistant/components/roku/translations/ru.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?", - "title": "Roku" + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/sl.json b/homeassistant/components/roku/translations/sl.json index 4a198b3b5c9..bb39edf9753 100644 --- a/homeassistant/components/roku/translations/sl.json +++ b/homeassistant/components/roku/translations/sl.json @@ -15,9 +15,7 @@ "one": "ena", "other": "drugo", "two": "dva" - }, - "description": "Ali \u017eelite nastaviti {name}?", - "title": "Roku" + } }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/sv.json b/homeassistant/components/roku/translations/sv.json index 524e7753548..4272f65b2ae 100644 --- a/homeassistant/components/roku/translations/sv.json +++ b/homeassistant/components/roku/translations/sv.json @@ -5,10 +5,6 @@ }, "flow_title": "Roku: {name}", "step": { - "ssdp_confirm": { - "description": "Vill du konfigurera {name}?", - "title": "Roku" - }, "user": { "data": { "host": "V\u00e4rd" diff --git a/homeassistant/components/roku/translations/tr.json b/homeassistant/components/roku/translations/tr.json index 3e930a6c1b2..23cc6e56bd5 100644 --- a/homeassistant/components/roku/translations/tr.json +++ b/homeassistant/components/roku/translations/tr.json @@ -15,16 +15,13 @@ "one": "Bo\u015f", "other": "Bo\u015f" }, - "description": "{name} kurmak istiyor musunuz?", - "title": "Roku" + "description": "{name} kurmak istiyor musunuz?" }, "ssdp_confirm": { "data": { "one": "Bo\u015f", "other": "Bo\u015f" - }, - "description": "{name} kurmak istiyor musunuz?", - "title": "Roku" + } }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/uk.json b/homeassistant/components/roku/translations/uk.json index b7db8875f8e..8fd1ae7f630 100644 --- a/homeassistant/components/roku/translations/uk.json +++ b/homeassistant/components/roku/translations/uk.json @@ -10,12 +10,7 @@ "flow_title": "Roku: {name}", "step": { "discovery_confirm": { - "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?", - "title": "Roku" + "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?" }, "user": { "data": { diff --git a/homeassistant/components/roku/translations/zh-Hant.json b/homeassistant/components/roku/translations/zh-Hant.json index 5cfe9232301..13c5886b46d 100644 --- a/homeassistant/components/roku/translations/zh-Hant.json +++ b/homeassistant/components/roku/translations/zh-Hant.json @@ -11,12 +11,7 @@ "flow_title": "{name}", "step": { "discovery_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f", - "title": "Roku" - }, - "ssdp_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f", - "title": "Roku" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, "user": { "data": { diff --git a/homeassistant/components/roomba/translations/bg.json b/homeassistant/components/roomba/translations/bg.json index 74075c671a1..3da613d9394 100644 --- a/homeassistant/components/roomba/translations/bg.json +++ b/homeassistant/components/roomba/translations/bg.json @@ -4,9 +4,6 @@ "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "step": { - "init": { - "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e" - }, "link_manual": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u0430" @@ -18,8 +15,7 @@ }, "user": { "data": { - "host": "\u0425\u043e\u0441\u0442", - "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + "host": "\u0425\u043e\u0441\u0442" }, "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e" } diff --git a/homeassistant/components/roomba/translations/ca.json b/homeassistant/components/roomba/translations/ca.json index ba23e30e3f0..de0f673e971 100644 --- a/homeassistant/components/roomba/translations/ca.json +++ b/homeassistant/components/roomba/translations/ca.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "Amfitri\u00f3" - }, - "description": "Selecciona un/a Roomba o Braava.", - "title": "Connexi\u00f3 autom\u00e0tica amb el dispositiu" - }, "link": { "description": "Mant\u00e9 premut el bot\u00f3 d'inici a {name} fins que el dispositiu emeti un so (aproximadament dos segons) despr\u00e9s, envia en els seg\u00fcents 30 segons.", "title": "Recupera la contrasenya" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Amfitri\u00f3" }, "description": "No s'ha descobert cap Roomba o Braava a la teva xarxa.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "Cont\u00ednua", - "delay": "Retard", - "host": "Amfitri\u00f3", - "password": "Contrasenya" + "host": "Amfitri\u00f3" }, "description": "Selecciona un/a Roomba o Braava.", "title": "Connexi\u00f3 autom\u00e0tica amb el dispositiu" diff --git a/homeassistant/components/roomba/translations/cs.json b/homeassistant/components/roomba/translations/cs.json index d94d39f8136..0d42bf1b5e5 100644 --- a/homeassistant/components/roomba/translations/cs.json +++ b/homeassistant/components/roomba/translations/cs.json @@ -9,11 +9,6 @@ }, "flow_title": "iRobot {name} ({host})", "step": { - "init": { - "data": { - "host": "Hostitel" - } - }, "link_manual": { "data": { "password": "Heslo" @@ -26,9 +21,7 @@ }, "user": { "data": { - "delay": "Zpo\u017ed\u011bn\u00ed", - "host": "Hostitel", - "password": "Heslo" + "host": "Hostitel" }, "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed" } diff --git a/homeassistant/components/roomba/translations/da.json b/homeassistant/components/roomba/translations/da.json index 2a8057ffc95..97ea58b2c51 100644 --- a/homeassistant/components/roomba/translations/da.json +++ b/homeassistant/components/roomba/translations/da.json @@ -2,9 +2,6 @@ "config": { "step": { "user": { - "data": { - "delay": "Forsinkelse" - }, "title": "Tilslut automatisk til enheden" } } diff --git a/homeassistant/components/roomba/translations/de.json b/homeassistant/components/roomba/translations/de.json index ae4ef7d9dc7..1717c07a735 100644 --- a/homeassistant/components/roomba/translations/de.json +++ b/homeassistant/components/roomba/translations/de.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "Host" - }, - "description": "W\u00e4hle einen Roomba oder Braava aus.", - "title": "Automatisch mit dem Ger\u00e4t verbinden" - }, "link": { "description": "Halte die Home-Taste von {name} gedr\u00fcckt, bis das Ger\u00e4t einen Ton erzeugt (ca. zwei Sekunden) und sende die Best\u00e4tigung innerhalb von 30 Sekunden ab.", "title": "Passwort abrufen" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Host" }, "description": "Es wurde kein Roomba oder Braava in deinem Netzwerk entdeckt.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "Kontinuierlich", - "delay": "Verz\u00f6gerung", - "host": "Host", - "password": "Passwort" + "host": "Host" }, "description": "W\u00e4hle einen Roomba oder Braava aus.", "title": "Automatisch mit dem Ger\u00e4t verbinden" diff --git a/homeassistant/components/roomba/translations/el.json b/homeassistant/components/roomba/translations/el.json index e59ce426deb..cb8683f2be2 100644 --- a/homeassistant/components/roomba/translations/el.json +++ b/homeassistant/components/roomba/translations/el.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Roomba \u03ae Braava.", - "title": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" - }, "link": { "description": "\u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b1\u03b9 \u03ba\u03c1\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c0\u03b1\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf \u03c4\u03bf \u03c0\u03bb\u03ae\u03ba\u03c4\u03c1\u03bf Home \u03c3\u03c4\u03bf {name} \u03bc\u03ad\u03c7\u03c1\u03b9 \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bd\u03b1 \u03c0\u03b1\u03c1\u03ac\u03b3\u03b5\u03b9 \u03ad\u03bd\u03b1\u03bd \u03ae\u03c7\u03bf (\u03c0\u03b5\u03c1\u03af\u03c0\u03bf\u03c5 \u03b4\u03cd\u03bf \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1) \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c5\u03c0\u03bf\u03b2\u03ac\u03bb\u03b5\u03c4\u03b5 \u03b5\u03bd\u03c4\u03cc\u03c2 30 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03bf\u03bb\u03ad\u03c0\u03c4\u03c9\u03bd.", "title": "\u0391\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u0394\u03b5\u03bd \u03ad\u03c7\u03bf\u03c5\u03bd \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03c5\u03c6\u03b8\u03b5\u03af Roomba \u03ae Braava \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03cc \u03c3\u03b1\u03c2.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "\u03a3\u03c5\u03bd\u03b5\u03c7\u03ae\u03c2", - "delay": "\u039a\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7", - "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" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Roomba \u03ae Braava.", "title": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index facd127985a..703ccebbb11 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "Host" - }, - "description": "Select a Roomba or Braava.", - "title": "Automatically connect to the device" - }, "link": { "description": "Press and hold the Home button on {name} until the device generates a sound (about two seconds), then submit within 30 seconds.", "title": "Retrieve Password" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Host" }, "description": "No Roomba or Braava have been discovered on your network.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "Continuous", - "delay": "Delay", - "host": "Host", - "password": "Password" + "host": "Host" }, "description": "Select a Roomba or Braava.", "title": "Automatically connect to the device" diff --git a/homeassistant/components/roomba/translations/es-419.json b/homeassistant/components/roomba/translations/es-419.json index 6d0295f7ce0..4fe61c8950c 100644 --- a/homeassistant/components/roomba/translations/es-419.json +++ b/homeassistant/components/roomba/translations/es-419.json @@ -6,11 +6,7 @@ "step": { "user": { "data": { - "blid": "BLID", - "continuous": "Continuo", - "delay": "Retraso", - "host": "Nombre de host o direcci\u00f3n IP", - "password": "Contrase\u00f1a" + "host": "Nombre de host o direcci\u00f3n IP" }, "description": "Actualmente recuperar el BLID y la contrase\u00f1a es un proceso manual. Siga los pasos descritos en la documentaci\u00f3n en: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "title": "Conectarse al dispositivo" diff --git a/homeassistant/components/roomba/translations/es.json b/homeassistant/components/roomba/translations/es.json index 315c8bda096..f2664baee31 100644 --- a/homeassistant/components/roomba/translations/es.json +++ b/homeassistant/components/roomba/translations/es.json @@ -11,13 +11,6 @@ }, "flow_title": "iRobot {name} ({host})", "step": { - "init": { - "data": { - "host": "Host" - }, - "description": "Selecciona una Roomba o Braava.", - "title": "Conectar autom\u00e1ticamente con el dispositivo" - }, "link": { "description": "Mant\u00e9n pulsado el bot\u00f3n Inicio en {name} hasta que el dispositivo genere un sonido (aproximadamente dos segundos).", "title": "Recuperar la contrase\u00f1a" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Host" }, "description": "No se ha descubierto ning\u00fan dispositivo Roomba ni Braava en tu red.", @@ -39,14 +31,10 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "Continuo", - "delay": "Retardo", - "host": "Host", - "password": "Contrase\u00f1a" + "host": "Host" }, "description": "Actualmente recuperar el BLID y la contrase\u00f1a es un proceso manual. Sigue los pasos descritos en la documentaci\u00f3n en: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", - "title": "Conectarse al dispositivo" + "title": "Conexi\u00f3n autom\u00e1tica con el dispositivo" } } }, diff --git a/homeassistant/components/roomba/translations/et.json b/homeassistant/components/roomba/translations/et.json index 43715399ef1..2cc599ca8ee 100644 --- a/homeassistant/components/roomba/translations/et.json +++ b/homeassistant/components/roomba/translations/et.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ( {host} )", "step": { - "init": { - "data": { - "host": "Host" - }, - "description": "Vali Roomba v\u00f5i Braava seade.", - "title": "\u00dchenda seadmega automaatselt" - }, "link": { "description": "Vajuta ja hoia all seadme {name} nuppu Home kuni seade teeb piiksu (umbes kaks sekundit), edasta 30 sekundi jooksul.", "title": "Hangi salas\u00f5na" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "", "host": "Host" }, "description": "V\u00f5rgus ei tuvastatud \u00fchtegi Roomba ega Braava seadet.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "", - "continuous": "Pidev", - "delay": "Viivitus", - "host": "", - "password": "Salas\u00f5na" + "host": "" }, "description": "Vali Roomba v\u00f5i Braava seade", "title": "\u00dchenda seadmega automaatselt" diff --git a/homeassistant/components/roomba/translations/fr.json b/homeassistant/components/roomba/translations/fr.json index 5cbb1090cc0..0f00f6fae61 100644 --- a/homeassistant/components/roomba/translations/fr.json +++ b/homeassistant/components/roomba/translations/fr.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "H\u00f4te" - }, - "description": "S\u00e9lectionnez un Roomba ou un Braava.", - "title": "Se connecter automatiquement \u00e0 l'appareil" - }, "link": { "description": "Appuyez sur le bouton Accueil et maintenez-le enfonc\u00e9 jusqu'\u00e0 ce que l'appareil \u00e9mette un son (environ deux secondes).", "title": "R\u00e9cup\u00e9rer le mot de passe" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "H\u00f4te" }, "description": "Aucun Roomba ou Braava n'a \u00e9t\u00e9 d\u00e9couvert sur votre r\u00e9seau.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "En continu", - "delay": "D\u00e9lai", - "host": "H\u00f4te", - "password": "Mot de passe" + "host": "H\u00f4te" }, "description": "La r\u00e9cup\u00e9ration du BLID et du mot de passe est actuellement un processus manuel. Veuillez suivre les \u00e9tapes d\u00e9crites dans la documentation \u00e0 l'adresse: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "title": "Se connecter automatiquement \u00e0 l'appareil" diff --git a/homeassistant/components/roomba/translations/he.json b/homeassistant/components/roomba/translations/he.json index 4520671eedb..d61ade0479a 100644 --- a/homeassistant/components/roomba/translations/he.json +++ b/homeassistant/components/roomba/translations/he.json @@ -9,11 +9,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "\u05de\u05d0\u05e8\u05d7" - } - }, "link": { "title": "\u05d0\u05d7\u05d6\u05e8 \u05e1\u05d9\u05e1\u05de\u05d4" }, @@ -31,8 +26,7 @@ }, "user": { "data": { - "host": "\u05de\u05d0\u05e8\u05d7", - "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + "host": "\u05de\u05d0\u05e8\u05d7" }, "description": "\u05d1\u05d7\u05d9\u05e8\u05ea Roomba \u05d0\u05d5 Braava." } diff --git a/homeassistant/components/roomba/translations/hu.json b/homeassistant/components/roomba/translations/hu.json index dd1c74cc0b6..e109b6e7043 100644 --- a/homeassistant/components/roomba/translations/hu.json +++ b/homeassistant/components/roomba/translations/hu.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "C\u00edm" - }, - "description": "V\u00e1lasszon egy Roomba vagy Braava k\u00e9sz\u00fcl\u00e9ket.", - "title": "Automatikus csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" - }, "link": { "description": "Nyomja meg \u00e9s tartsa lenyomva a {name} Home gombj\u00e1t, am\u00edg az eszk\u00f6z hangot ad (kb. k\u00e9t m\u00e1sodperc), majd engedje el 30 m\u00e1sodpercen bel\u00fcl.", "title": "Jelsz\u00f3 lek\u00e9r\u00e9se" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "C\u00edm" }, "description": "A h\u00e1l\u00f3zaton egyetlen Roomba vagy Braava sem ker\u00fclt el\u0151.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "Folyamatos", - "delay": "K\u00e9sleltet\u00e9s", - "host": "C\u00edm", - "password": "Jelsz\u00f3" + "host": "C\u00edm" }, "description": "V\u00e1lasszon Roomba-t vagy Braava-t.", "title": "Automatikus csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" diff --git a/homeassistant/components/roomba/translations/id.json b/homeassistant/components/roomba/translations/id.json index 1ade232fd70..9b8e9ef8bbe 100644 --- a/homeassistant/components/roomba/translations/id.json +++ b/homeassistant/components/roomba/translations/id.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "Host" - }, - "description": "Pilih Roomba atau Braava.", - "title": "Sambungkan secara otomatis ke perangkat" - }, "link": { "description": "Tekan dan tahan tombol Home pada {name} hingga perangkat mengeluarkan suara (sekitar dua detik), lalu kirim dalam waktu 30 detik.", "title": "Ambil Kata Sandi" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Host" }, "description": "Tidak ada Roomba atau Braava yang ditemukan di jaringan Anda.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "Terus menerus", - "delay": "Tunda", - "host": "Host", - "password": "Kata Sandi" + "host": "Host" }, "description": "Pilih Roomba atau Braava.", "title": "Sambungkan secara otomatis ke perangkat" diff --git a/homeassistant/components/roomba/translations/it.json b/homeassistant/components/roomba/translations/it.json index 4be1be53540..b87a7cb8ef6 100644 --- a/homeassistant/components/roomba/translations/it.json +++ b/homeassistant/components/roomba/translations/it.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "Host" - }, - "description": "Seleziona un Roomba o un Braava.", - "title": "Connettiti automaticamente al dispositivo" - }, "link": { "description": "Tieni premuto il pulsante Home su {name} fino a quando il dispositivo non genera un suono (circa due secondi), quindi invialo entro 30 secondi.", "title": "Recupera password" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Host" }, "description": "Nessun Roomba o Braava \u00e8 stato rilevato sulla rete.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "Continuo", - "delay": "Ritardo", - "host": "Host", - "password": "Password" + "host": "Host" }, "description": "Seleziona un Roomba o un Braava.", "title": "Connetti automaticamente al dispositivo" diff --git a/homeassistant/components/roomba/translations/ja.json b/homeassistant/components/roomba/translations/ja.json index d7018e544ce..4cbdfd2a8bb 100644 --- a/homeassistant/components/roomba/translations/ja.json +++ b/homeassistant/components/roomba/translations/ja.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "\u30db\u30b9\u30c8" - }, - "description": "\u30eb\u30f3\u30d0\u307e\u305f\u306f\u30d6\u30e9\u30fc\u30d0\u3092\u9078\u629e\u3057\u307e\u3059\u3002", - "title": "\u81ea\u52d5\u7684\u306b\u30c7\u30d0\u30a4\u30b9\u306b\u63a5\u7d9a\u3059\u308b" - }, "link": { "description": "{name} \u306e\u30db\u30fc\u30e0\u30dc\u30bf\u30f3\u3092\u3001\u30c7\u30d0\u30a4\u30b9\u304c\u97f3\u3092\u51fa\u3059\u307e\u3067(\u7d042\u79d2)\u62bc\u3057\u7d9a\u3051\u300130\u79d2\u4ee5\u5185\u306b\u9001\u4fe1(submit)\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "title": "\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u53d6\u5f97" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "\u30db\u30b9\u30c8" }, "description": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u3001\u30eb\u30f3\u30d0\u3084\u30d6\u30e9\u30fc\u30d0\u304c\u767a\u898b\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "\u9023\u7d9a", - "delay": "\u9045\u5ef6", - "host": "\u30db\u30b9\u30c8", - "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" + "host": "\u30db\u30b9\u30c8" }, "description": "\u30eb\u30f3\u30d0\u307e\u305f\u306f\u30d6\u30e9\u30fc\u30d0\u3092\u9078\u629e\u3057\u307e\u3059\u3002", "title": "\u81ea\u52d5\u7684\u306b\u30c7\u30d0\u30a4\u30b9\u306b\u63a5\u7d9a\u3059\u308b" diff --git a/homeassistant/components/roomba/translations/ko.json b/homeassistant/components/roomba/translations/ko.json index bb33287c9b8..70ebc3b6ef4 100644 --- a/homeassistant/components/roomba/translations/ko.json +++ b/homeassistant/components/roomba/translations/ko.json @@ -11,13 +11,6 @@ }, "flow_title": "\uc544\uc774\ub85c\ubd07: {name} ({host})", "step": { - "init": { - "data": { - "host": "\ud638\uc2a4\ud2b8" - }, - "description": "\ub8f8\ubc14 \ub610\ub294 \ube0c\ub77c\ubc14\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", - "title": "\uae30\uae30\uc5d0 \uc790\ub3d9\uc73c\ub85c \uc5f0\uacb0\ud558\uae30" - }, "link": { "description": "\uae30\uae30\uc5d0\uc11c \uc18c\ub9ac\uac00 \ub0a0 \ub54c\uae4c\uc9c0 {name}\uc758 \ud648 \ubc84\ud2bc\uc744 \uae38\uac8c \ub204\ub978 \ub2e4\uc74c(\uc57d 2\ucd08) 30\ucd08 \uc774\ub0b4\uc5d0 \ud655\uc778 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.", "title": "\ube44\ubc00\ubc88\ud638 \uac00\uc838\uc624\uae30" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "\ud638\uc2a4\ud2b8" }, "description": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ub8f8\ubc14 \ub610\ub294 \ube0c\ub77c\ubc14\uac00 \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. BLID\ub294 `iRobot-` \ub610\ub294 `Roomba-` \ub4a4\uc5d0 \uc788\ub294 \uae30\uae30 \ud638\uc2a4\ud2b8 \uc774\ub984\uc758 \uc77c\ubd80\uc785\ub2c8\ub2e4. \uad00\ub828 \ubb38\uc11c\uc5d0 \uc124\uba85\ub41c \ub2e8\uacc4\ub97c \ub530\ub77c\uc8fc\uc138\uc694: {auth_help_url}", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "\uc5f0\uc18d", - "delay": "\uc9c0\uc5f0", - "host": "\ud638\uc2a4\ud2b8", - "password": "\ube44\ubc00\ubc88\ud638" + "host": "\ud638\uc2a4\ud2b8" }, "description": "\ud604\uc7ac BLID \ubc0f \ube44\ubc00\ubc88\ud638\ub294 \uc218\ub3d9\uc73c\ub85c \uac00\uc838\uc640\uc57c\ud569\ub2c8\ub2e4. \ub2e4\uc74c \uad00\ub828 \ubb38\uc11c\uc5d0 \ub098\uc640 \uc788\ub294 \ub2e8\uacc4\ub97c \ub530\ub77c\uc8fc\uc138\uc694: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "title": "\uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" diff --git a/homeassistant/components/roomba/translations/lb.json b/homeassistant/components/roomba/translations/lb.json index 5578a7710db..b2c0b85d16a 100644 --- a/homeassistant/components/roomba/translations/lb.json +++ b/homeassistant/components/roomba/translations/lb.json @@ -8,13 +8,6 @@ }, "flow_title": "iRobot {name} ({host})", "step": { - "init": { - "data": { - "host": "Host" - }, - "description": "Ee Roomba oder Bravaa auswielen.", - "title": "Automatesch mam Apparat verbannen" - }, "link": { "title": "Passwuert ausliesen" }, @@ -26,18 +19,13 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Host" }, "title": "Manuell mam Apparat verbannen" }, "user": { "data": { - "blid": "BLID", - "continuous": "Kontinu\u00e9ierlech", - "delay": "Delai", - "host": "Host", - "password": "Passwuert" + "host": "Host" }, "description": "De Prozess fir BLID an Passwuert opzeruffen ass fir de Moment manuell. Folleg w.e.g. de Schr\u00ebtt d\u00e9i an der Dokumentatioun op https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials beschriwwe sinn.", "title": "Mam Apparat verbannen" diff --git a/homeassistant/components/roomba/translations/nl.json b/homeassistant/components/roomba/translations/nl.json index bbb4fdbe765..68e18d7db93 100644 --- a/homeassistant/components/roomba/translations/nl.json +++ b/homeassistant/components/roomba/translations/nl.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "Host" - }, - "description": "Kies een Roomba of Braava.", - "title": "Automatisch verbinding maken met het apparaat" - }, "link": { "description": "Houd de Home-knop op {name} ingedrukt totdat het apparaat een geluid genereert (ongeveer twee seconden), bevestig vervolgens binnen 30 seconden.", "title": "Wachtwoord opvragen" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Host" }, "description": "Er is geen Roomba of Braava ontdekt op uw netwerk.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "Doorlopend", - "delay": "Vertraging", - "host": "Host", - "password": "Wachtwoord" + "host": "Host" }, "description": "Kies een Roomba of Braava.", "title": "Automatisch verbinding maken met het apparaat" diff --git a/homeassistant/components/roomba/translations/no.json b/homeassistant/components/roomba/translations/no.json index 3a5d95eb006..f378bfb127c 100644 --- a/homeassistant/components/roomba/translations/no.json +++ b/homeassistant/components/roomba/translations/no.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "Vert" - }, - "description": "Velg en Roomba eller Braava", - "title": "Koble automatisk til enheten" - }, "link": { "description": "Trykk og hold nede Hjem-knappen p\u00e5 {name} til enheten genererer en lyd (omtrent to sekunder), og send deretter innen 30 sekunder.", "title": "Hent passord" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "", "host": "Vert" }, "description": "Ingen Roomba eller Braava er oppdaget p\u00e5 nettverket ditt.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "Blid", - "continuous": "Kontinuerlige", - "delay": "Forsinkelse", - "host": "Vert", - "password": "Passord" + "host": "Vert" }, "description": "Velg en Roomba eller Braava.", "title": "Koble automatisk til enheten" diff --git a/homeassistant/components/roomba/translations/pl.json b/homeassistant/components/roomba/translations/pl.json index d2d76bee5ff..a3440c97ed2 100644 --- a/homeassistant/components/roomba/translations/pl.json +++ b/homeassistant/components/roomba/translations/pl.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "Nazwa hosta lub adres IP" - }, - "description": "Wybierz Roomb\u0119 lub Braava", - "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.", "title": "Odzyskiwanie has\u0142a" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Nazwa hosta lub adres IP" }, "description": "W Twojej sieci nie wykryto urz\u0105dzenia Roomba ani Braava.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "Ci\u0105g\u0142y", - "delay": "Op\u00f3\u017anienie", - "host": "Nazwa hosta lub adres IP", - "password": "Has\u0142o" + "host": "Nazwa hosta lub adres IP" }, "description": "Wybierz Roomb\u0119 lub Braava", "title": "Automatyczne po\u0142\u0105czenie z urz\u0105dzeniem" diff --git a/homeassistant/components/roomba/translations/pt-BR.json b/homeassistant/components/roomba/translations/pt-BR.json index ee0a29eac89..4db4e885ac7 100644 --- a/homeassistant/components/roomba/translations/pt-BR.json +++ b/homeassistant/components/roomba/translations/pt-BR.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ( {host} )", "step": { - "init": { - "data": { - "host": "Nome do host" - }, - "description": "Selecione um Roomba ou Braava.", - "title": "Conecte-se automaticamente ao dispositivo" - }, "link": { "description": "Pressione e segure o bot\u00e3o Home em {name} at\u00e9 que o dispositivo gere um som (cerca de dois segundos) e envie em 30 segundos.", "title": "Recuperar Senha" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Nome do host" }, "description": "Nenhum Roomba ou Braava foi descoberto em sua rede.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "Cont\u00ednuo", - "delay": "Atraso", - "host": "Nome do host", - "password": "Senha" + "host": "Nome do host" }, "description": "Selecione um Roomba ou Braava.", "title": "Conecte-se ao dispositivo" diff --git a/homeassistant/components/roomba/translations/pt.json b/homeassistant/components/roomba/translations/pt.json index 6036e870e6c..84fe0402f15 100644 --- a/homeassistant/components/roomba/translations/pt.json +++ b/homeassistant/components/roomba/translations/pt.json @@ -13,10 +13,7 @@ }, "user": { "data": { - "continuous": "Cont\u00ednuo", - "delay": "Atraso", - "host": "Servidor", - "password": "Palavra-passe" + "host": "Servidor" }, "title": "Conectar ao dispositivo" } diff --git a/homeassistant/components/roomba/translations/ru.json b/homeassistant/components/roomba/translations/ru.json index a8b31297f04..643da09744c 100644 --- a/homeassistant/components/roomba/translations/ru.json +++ b/homeassistant/components/roomba/translations/ru.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "\u0425\u043e\u0441\u0442" - }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u044b\u043b\u0435\u0441\u043e\u0441 \u0438\u0437 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 Roomba \u0438\u043b\u0438 Braava.", - "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" - }, "link": { "description": "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u0438 \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0439\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 Home \u043d\u0430 {name}, \u043f\u043e\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u0438\u0437\u0434\u0430\u0441\u0442 \u0437\u0432\u0443\u043a (\u043e\u043a\u043e\u043b\u043e \u0434\u0432\u0443\u0445 \u0441\u0435\u043a\u0443\u043d\u0434). \u0417\u0430\u0442\u0435\u043c \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 30 \u0441\u0435\u043a\u0443\u043d\u0434 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \"\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c\".", "title": "\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u044f" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "\u0425\u043e\u0441\u0442" }, "description": "\u0412 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 Roomba \u0438\u043b\u0438 Braava.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "\u041d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c", - "delay": "\u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 (\u0441\u0435\u043a.)", - "host": "\u0425\u043e\u0441\u0442", - "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + "host": "\u0425\u043e\u0441\u0442" }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u044b\u043b\u0435\u0441\u043e\u0441 \u0438\u0437 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 Roomba \u0438\u043b\u0438 Braava.", "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" diff --git a/homeassistant/components/roomba/translations/sl.json b/homeassistant/components/roomba/translations/sl.json index 9ad90ab82aa..ac046ac6ca7 100644 --- a/homeassistant/components/roomba/translations/sl.json +++ b/homeassistant/components/roomba/translations/sl.json @@ -6,11 +6,7 @@ "step": { "user": { "data": { - "blid": "BLID", - "continuous": "Nenehno", - "delay": "Zamik", - "host": "Ime gostitelja ali naslov IP", - "password": "Geslo" + "host": "Ime gostitelja ali naslov IP" }, "description": "Trenutno je pridobivanje BLID-a in gesla ro\u010dni postopek. Upo\u0161tevajte korake opisane v dokumentaciji na: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "title": "Pove\u017eite se z napravo" diff --git a/homeassistant/components/roomba/translations/sv.json b/homeassistant/components/roomba/translations/sv.json index ee1f8972ef9..e2c491df80b 100644 --- a/homeassistant/components/roomba/translations/sv.json +++ b/homeassistant/components/roomba/translations/sv.json @@ -6,11 +6,7 @@ "step": { "user": { "data": { - "blid": "BLID", - "continuous": "Kontinuerlig", - "delay": "F\u00f6rdr\u00f6jning", - "host": "V\u00e4rdnamn eller IP-adress", - "password": "L\u00f6senord" + "host": "V\u00e4rdnamn eller IP-adress" }, "title": "Anslut till enheten" } diff --git a/homeassistant/components/roomba/translations/tr.json b/homeassistant/components/roomba/translations/tr.json index 97b4b61ada2..ecdaa8b97be 100644 --- a/homeassistant/components/roomba/translations/tr.json +++ b/homeassistant/components/roomba/translations/tr.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "Ana Bilgisayar" - }, - "description": "Roomba veya Braava'y\u0131 se\u00e7in.", - "title": "Cihaza otomatik olarak ba\u011flan" - }, "link": { "description": "Cihaz bir ses \u00e7\u0131karana kadar (yakla\u015f\u0131k iki saniye) {name} \u00fczerindeki Ana Sayfa d\u00fc\u011fmesini bas\u0131l\u0131 tutun, ard\u0131ndan 30 saniye i\u00e7inde g\u00f6nderin.", "title": "\u015eifre Al" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "Sunucu" }, "description": "A\u011f\u0131n\u0131zda Roomba veya Braava bulunamad\u0131.", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "S\u00fcrekli", - "delay": "Gecikme", - "host": "Sunucu", - "password": "Parola" + "host": "Sunucu" }, "description": "Roomba veya Braava'y\u0131 se\u00e7in.", "title": "Cihaza ba\u011flan\u0131n" diff --git a/homeassistant/components/roomba/translations/uk.json b/homeassistant/components/roomba/translations/uk.json index 833a35f62f3..722b1127051 100644 --- a/homeassistant/components/roomba/translations/uk.json +++ b/homeassistant/components/roomba/translations/uk.json @@ -6,11 +6,7 @@ "step": { "user": { "data": { - "blid": "BLID", - "continuous": "\u0411\u0435\u0437\u043f\u0435\u0440\u0435\u0440\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", - "delay": "\u0417\u0430\u0442\u0440\u0438\u043c\u043a\u0430 (\u0441\u0435\u043a.)", - "host": "\u0425\u043e\u0441\u0442", - "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + "host": "\u0425\u043e\u0441\u0442" }, "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438, \u0449\u043e\u0431 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u044f\u043a \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 BLID \u0456 \u043f\u0430\u0440\u043e\u043b\u044c:\nhttps://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" diff --git a/homeassistant/components/roomba/translations/zh-Hant.json b/homeassistant/components/roomba/translations/zh-Hant.json index c17607e8be4..d0c59422625 100644 --- a/homeassistant/components/roomba/translations/zh-Hant.json +++ b/homeassistant/components/roomba/translations/zh-Hant.json @@ -11,13 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "init": { - "data": { - "host": "\u4e3b\u6a5f\u7aef" - }, - "description": "\u9078\u64c7 Roomba \u6216 Braava\u3002", - "title": "\u81ea\u52d5\u9023\u7dda\u81f3\u88dd\u7f6e" - }, "link": { "description": "\u8acb\u6309\u4f4f {name} \u4e0a\u7684 Home \u9375\u76f4\u5230\u88dd\u7f6e\u767c\u51fa\u8072\u97f3\uff08\u7d04\u5169\u79d2\uff09\uff0c\u7136\u5f8c\u65bc 30 \u79d2\u5167\u50b3\u9001\u3002", "title": "\u91cd\u7f6e\u5bc6\u78bc" @@ -31,7 +24,6 @@ }, "manual": { "data": { - "blid": "BLID", "host": "\u4e3b\u6a5f\u7aef" }, "description": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Roomba \u6216 Braava\u3002", @@ -39,11 +31,7 @@ }, "user": { "data": { - "blid": "BLID", - "continuous": "\u9023\u7e8c", - "delay": "\u5ef6\u9072", - "host": "\u4e3b\u6a5f\u7aef", - "password": "\u5bc6\u78bc" + "host": "\u4e3b\u6a5f\u7aef" }, "description": "\u9078\u64c7 Roomba \u6216 Braava\u3002", "title": "\u81ea\u52d5\u9023\u7dda\u81f3\u88dd\u7f6e" diff --git a/homeassistant/components/roon/translations/bg.json b/homeassistant/components/roon/translations/bg.json index 36b9a0b4bff..54c68cff37a 100644 --- a/homeassistant/components/roon/translations/bg.json +++ b/homeassistant/components/roon/translations/bg.json @@ -8,9 +8,10 @@ "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, "step": { - "user": { + "fallback": { "data": { - "host": "\u0425\u043e\u0441\u0442" + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" } } } diff --git a/homeassistant/components/roon/translations/ca.json b/homeassistant/components/roon/translations/ca.json index 3f601ded490..860ad4cff38 100644 --- a/homeassistant/components/roon/translations/ca.json +++ b/homeassistant/components/roon/translations/ca.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "Amfitri\u00f3" - }, - "description": "No s'ha pogut descobrir el servidor Roon, introdueix el nom d'amfitri\u00f3 o la IP." } } } diff --git a/homeassistant/components/roon/translations/cs.json b/homeassistant/components/roon/translations/cs.json index 20187f9d4d2..150b2f5f74a 100644 --- a/homeassistant/components/roon/translations/cs.json +++ b/homeassistant/components/roon/translations/cs.json @@ -11,11 +11,6 @@ "link": { "description": "Mus\u00edte povolit Home Assistant v Roon. Po kliknut\u00ed na Odeslat p\u0159ejd\u011bte do aplikace Roon Core, otev\u0159ete Nastaven\u00ed a na z\u00e1lo\u017ece Roz\u0161\u00ed\u0159en\u00ed povolte Home Assistant.", "title": "Autorizujte HomeAssistant v Roon" - }, - "user": { - "data": { - "host": "Hostitel" - } } } } diff --git a/homeassistant/components/roon/translations/de.json b/homeassistant/components/roon/translations/de.json index e41fe77c3cd..377cae8946f 100644 --- a/homeassistant/components/roon/translations/de.json +++ b/homeassistant/components/roon/translations/de.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "Host" - }, - "description": "Roon-Server konnte nicht gefunden werden, bitte gib den Hostnamen oder die IP ein." } } } diff --git a/homeassistant/components/roon/translations/el.json b/homeassistant/components/roon/translations/el.json index 63cda0821b1..1216ff032a0 100644 --- a/homeassistant/components/roon/translations/el.json +++ b/homeassistant/components/roon/translations/el.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" - }, - "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 \u03ae \u03c4\u03b7\u03bd IP \u03c3\u03b1\u03c2." } } } diff --git a/homeassistant/components/roon/translations/en.json b/homeassistant/components/roon/translations/en.json index b0affedc026..859f8c7307b 100644 --- a/homeassistant/components/roon/translations/en.json +++ b/homeassistant/components/roon/translations/en.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "Host" - }, - "description": "Could not discover Roon server, please enter your the Hostname or IP." } } } diff --git a/homeassistant/components/roon/translations/es.json b/homeassistant/components/roon/translations/es.json index e38b7691f40..098ba60234a 100644 --- a/homeassistant/components/roon/translations/es.json +++ b/homeassistant/components/roon/translations/es.json @@ -8,15 +8,16 @@ "unknown": "Error inesperado" }, "step": { + "fallback": { + "data": { + "host": "Host", + "port": "Puerto" + }, + "description": "No se ha podrido descubrir el servidor Roon, introduce el anfitri\u00f3n y el puerto." + }, "link": { "description": "Debes autorizar Home Assistant en Roon. Despu\u00e9s de pulsar en Enviar, ve a la aplicaci\u00f3n Roon Core, abre Configuraci\u00f3n y activa HomeAssistant en la pesta\u00f1a Extensiones.", "title": "Autorizar HomeAssistant en Roon" - }, - "user": { - "data": { - "host": "Host" - }, - "description": "Introduce el nombre de Host o IP del servidor Roon." } } } diff --git a/homeassistant/components/roon/translations/et.json b/homeassistant/components/roon/translations/et.json index d091f8e4064..3a7484680ea 100644 --- a/homeassistant/components/roon/translations/et.json +++ b/homeassistant/components/roon/translations/et.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "" - }, - "description": "Rooni serverit ei leitud. Sisesta oma Rooni serveri hostinimi v\u00f5i IP." } } } diff --git a/homeassistant/components/roon/translations/fr.json b/homeassistant/components/roon/translations/fr.json index 7af98de188a..279ef703c59 100644 --- a/homeassistant/components/roon/translations/fr.json +++ b/homeassistant/components/roon/translations/fr.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "H\u00f4te" - }, - "description": "Veuillez entrer votre nom d\u2019h\u00f4te ou votre adresse IP sur votre serveur Roon." } } } diff --git a/homeassistant/components/roon/translations/he.json b/homeassistant/components/roon/translations/he.json index 05781910772..0c0d772740b 100644 --- a/homeassistant/components/roon/translations/he.json +++ b/homeassistant/components/roon/translations/he.json @@ -16,12 +16,6 @@ }, "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." - }, - "user": { - "data": { - "host": "\u05de\u05d0\u05e8\u05d7" - }, - "description": "\u05dc\u05d0 \u05d4\u05d9\u05ea\u05d4 \u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \u05dc\u05d2\u05dc\u05d5\u05ea \u05d0\u05ea \u05e9\u05e8\u05ea Roon, \u05d4\u05d6\u05df \u05d0\u05ea \u05e9\u05dd \u05d4\u05de\u05d0\u05e8\u05d7 \u05d0\u05d5 \u05d4-IP." } } } diff --git a/homeassistant/components/roon/translations/hu.json b/homeassistant/components/roon/translations/hu.json index 3a9bcf3f522..aa536aeeddd 100644 --- a/homeassistant/components/roon/translations/hu.json +++ b/homeassistant/components/roon/translations/hu.json @@ -20,10 +20,6 @@ "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", "one": "\u00dcres", "other": "\u00dcres" } diff --git a/homeassistant/components/roon/translations/id.json b/homeassistant/components/roon/translations/id.json index 85c1519e7be..cb3aa92df8c 100644 --- a/homeassistant/components/roon/translations/id.json +++ b/homeassistant/components/roon/translations/id.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "Host" - }, - "description": "Tidak dapat menemukan server Roon, masukkan Nama Host atau IP Anda." } } } diff --git a/homeassistant/components/roon/translations/it.json b/homeassistant/components/roon/translations/it.json index 3236ebd0bb2..9a232281803 100644 --- a/homeassistant/components/roon/translations/it.json +++ b/homeassistant/components/roon/translations/it.json @@ -20,10 +20,6 @@ "title": "Autorizza HomeAssistant in Roon" }, "user": { - "data": { - "host": "Host" - }, - "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 400d982d27c..12a1b074916 100644 --- a/homeassistant/components/roon/translations/ja.json +++ b/homeassistant/components/roon/translations/ja.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "\u30db\u30b9\u30c8" - }, - "description": "Roon server\u3092\u691c\u51fa\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3001 \u30db\u30b9\u30c8\u540d\u307e\u305f\u306f\u3001IP\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" } } } diff --git a/homeassistant/components/roon/translations/ko.json b/homeassistant/components/roon/translations/ko.json index a55249417af..96b9bf88e95 100644 --- a/homeassistant/components/roon/translations/ko.json +++ b/homeassistant/components/roon/translations/ko.json @@ -8,15 +8,16 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "fallback": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8" + }, + "description": "Roon \uc11c\ubc84\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8 \uc774\ub984\uacfc \ud3ec\ud2b8\ub97c \uc785\ub825\ud558\uc2ed\uc2dc\uc624." + }, "link": { "description": "Roon\uc5d0\uc11c Home Assistant\ub97c \uc778\uc99d\ud574\uc8fc\uc5b4\uc57c \ud569\ub2c8\ub2e4. \ud655\uc778\uc744 \ud074\ub9ad\ud55c \ud6c4 Roon Core \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \uc124\uc815\uc744 \uc5f4\uace0 \ud655\uc7a5 \ud0ed\uc5d0\uc11c Home Assistant\ub97c \ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694.", "title": "Roon\uc5d0\uc11c HomeAssistant \uc778\uc99d\ud558\uae30" - }, - "user": { - "data": { - "host": "\ud638\uc2a4\ud2b8" - }, - "description": "Roon \uc11c\ubc84\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/roon/translations/lb.json b/homeassistant/components/roon/translations/lb.json index f5722ee75bb..6121d31b3ab 100644 --- a/homeassistant/components/roon/translations/lb.json +++ b/homeassistant/components/roon/translations/lb.json @@ -11,12 +11,6 @@ "link": { "description": "Du muss Home Assistant am Roon autoris\u00e9ieren. Nodeems Du op ofsch\u00e9cke geklickt hues, g\u00e9i an d'Roon Applikatioun, an d'Astellungen an aktiv\u00e9ier HomeAssistant an den Extensiounen.", "title": "HomeAssistant am Roon erlaaben" - }, - "user": { - "data": { - "host": "Host" - }, - "description": "G\u00ebff den Numm oder IP-Adress vun dengem Roon Server un." } } } diff --git a/homeassistant/components/roon/translations/nl.json b/homeassistant/components/roon/translations/nl.json index 9aafadc918b..ad07fb6bc15 100644 --- a/homeassistant/components/roon/translations/nl.json +++ b/homeassistant/components/roon/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Apparaat is al toegevoegd" + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "invalid_auth": "Ongeldige authencatie", + "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "Host" - }, - "description": "Kon de Roon-server niet vinden, voer de hostnaam of het IP-adres in." } } } diff --git a/homeassistant/components/roon/translations/no.json b/homeassistant/components/roon/translations/no.json index 11eb60a04ce..4d44ef15e87 100644 --- a/homeassistant/components/roon/translations/no.json +++ b/homeassistant/components/roon/translations/no.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "Vert" - }, - "description": "Kunne ikke oppdage Roon-serveren. Angi vertsnavnet eller IP-adressen." } } } diff --git a/homeassistant/components/roon/translations/pl.json b/homeassistant/components/roon/translations/pl.json index b45a7fcb562..068153e2572 100644 --- a/homeassistant/components/roon/translations/pl.json +++ b/homeassistant/components/roon/translations/pl.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "Nazwa hosta lub adres IP" - }, - "description": "Nie wykryto serwera Roon, wprowad\u017a nazw\u0119 hosta lub adres IP." } } } diff --git a/homeassistant/components/roon/translations/pt-BR.json b/homeassistant/components/roon/translations/pt-BR.json index 85628df4a7c..6875841568d 100644 --- a/homeassistant/components/roon/translations/pt-BR.json +++ b/homeassistant/components/roon/translations/pt-BR.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "Nome do host" - }, - "description": "Por favor, digite seu hostname ou IP do servidor Roon." } } } diff --git a/homeassistant/components/roon/translations/pt.json b/homeassistant/components/roon/translations/pt.json index 6e36ff769cd..1e12fdcfcba 100644 --- a/homeassistant/components/roon/translations/pt.json +++ b/homeassistant/components/roon/translations/pt.json @@ -6,13 +6,6 @@ "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" - }, - "step": { - "user": { - "data": { - "host": "Servidor" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/roon/translations/ru.json b/homeassistant/components/roon/translations/ru.json index cc89dc2d2dd..17a44c95cf9 100644 --- a/homeassistant/components/roon/translations/ru.json +++ b/homeassistant/components/roon/translations/ru.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "\u0425\u043e\u0441\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 \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0445\u043e\u0441\u0442\u0430 \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441." } } } diff --git a/homeassistant/components/roon/translations/tr.json b/homeassistant/components/roon/translations/tr.json index c18df90b9b8..a05dfed70f1 100644 --- a/homeassistant/components/roon/translations/tr.json +++ b/homeassistant/components/roon/translations/tr.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "Ana Bilgisayar" - }, - "description": "Roon sunucusu bulunamad\u0131, l\u00fctfen Ana Bilgisayar Ad\u0131n\u0131z\u0131 veya IP'nizi girin." } } } diff --git a/homeassistant/components/roon/translations/uk.json b/homeassistant/components/roon/translations/uk.json index a892e24692d..cdc6f369d43 100644 --- a/homeassistant/components/roon/translations/uk.json +++ b/homeassistant/components/roon/translations/uk.json @@ -11,12 +11,6 @@ "link": { "description": "\u041f\u0456\u0441\u043b\u044f \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u00ab\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438\u00bb \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0432 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Roon Core, \u0432\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 \u00ab\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u00bb \u0456 \u0443\u0432\u0456\u043c\u043a\u043d\u0456\u0442\u044c HomeAssistant \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u0446\u0456 \u00ab\u0420\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u043d\u044f\u00bb.", "title": "Roon" - }, - "user": { - "data": { - "host": "\u0425\u043e\u0441\u0442" - }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043d\u0430\u0437\u0432\u0443 \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Roon" } } } diff --git a/homeassistant/components/roon/translations/zh-Hant.json b/homeassistant/components/roon/translations/zh-Hant.json index f887b2d9847..8ec7bdf4276 100644 --- a/homeassistant/components/roon/translations/zh-Hant.json +++ b/homeassistant/components/roon/translations/zh-Hant.json @@ -18,12 +18,6 @@ "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" - }, - "user": { - "data": { - "host": "\u4e3b\u6a5f\u7aef" - }, - "description": "\u627e\u4e0d\u5230 Roon \u4f3a\u670d\u5668\uff0c\u8acb\u8f38\u5165\u4e3b\u6a5f\u540d\u7a31\u6216 IP\u3002" } } } diff --git a/homeassistant/components/rpi_gpio/__init__.py b/homeassistant/components/rpi_gpio/__init__.py deleted file mode 100644 index 95e3ded1c64..00000000000 --- a/homeassistant/components/rpi_gpio/__init__.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Support for controlling GPIO pins of a Raspberry Pi.""" -import logging - -from RPi import GPIO # pylint: disable=import-error - -from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, - Platform, -) -from homeassistant.core import HomeAssistant -from homeassistant.helpers.typing import ConfigType - -DOMAIN = "rpi_gpio" -PLATFORMS = [ - Platform.BINARY_SENSOR, - Platform.COVER, - Platform.SWITCH, -] - -_LOGGER = logging.getLogger(__name__) - - -def setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the Raspberry PI GPIO component.""" - _LOGGER.warning( - "The Raspberry Pi GPIO integration is deprecated and will be removed " - "in Home Assistant Core 2022.6; this integration is removed under " - "Architectural Decision Record 0019, more information can be found here: " - "https://github.com/home-assistant/architecture/blob/master/adr/0019-GPIO.md" - ) - - def cleanup_gpio(event): - """Stuff to do before stopping.""" - GPIO.cleanup() - - def prepare_gpio(event): - """Stuff to do when Home Assistant starts.""" - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio) - - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio) - GPIO.setmode(GPIO.BCM) - return True - - -def setup_output(port): - """Set up a GPIO as output.""" - GPIO.setup(port, GPIO.OUT) - - -def setup_input(port, pull_mode): - """Set up a GPIO as input.""" - GPIO.setup(port, GPIO.IN, GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP) - - -def write_output(port, value): - """Write a value to a GPIO.""" - GPIO.output(port, value) - - -def read_input(port): - """Read a value from a GPIO.""" - return GPIO.input(port) - - -def edge_detect(port, event_callback, bounce): - """Add detection for RISING and FALLING events.""" - GPIO.add_event_detect(port, GPIO.BOTH, callback=event_callback, bouncetime=bounce) diff --git a/homeassistant/components/rpi_gpio/binary_sensor.py b/homeassistant/components/rpi_gpio/binary_sensor.py deleted file mode 100644 index e183b463e45..00000000000 --- a/homeassistant/components/rpi_gpio/binary_sensor.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Support for binary sensor using RPi GPIO.""" -from __future__ import annotations - -import asyncio - -import voluptuous as vol - -from homeassistant.components import rpi_gpio -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity -from homeassistant.const import DEVICE_DEFAULT_NAME -from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import setup_reload_service -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from . import DOMAIN, PLATFORMS - -CONF_BOUNCETIME = "bouncetime" -CONF_INVERT_LOGIC = "invert_logic" -CONF_PORTS = "ports" -CONF_PULL_MODE = "pull_mode" - -DEFAULT_BOUNCETIME = 50 -DEFAULT_INVERT_LOGIC = False -DEFAULT_PULL_MODE = "UP" - -_SENSORS_SCHEMA = vol.Schema({cv.positive_int: cv.string}) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_PORTS): _SENSORS_SCHEMA, - vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int, - vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, - vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string, - } -) - - -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the Raspberry PI GPIO devices.""" - setup_reload_service(hass, DOMAIN, PLATFORMS) - - pull_mode = config[CONF_PULL_MODE] - bouncetime = config[CONF_BOUNCETIME] - invert_logic = config[CONF_INVERT_LOGIC] - - binary_sensors = [] - ports = config[CONF_PORTS] - for port_num, port_name in ports.items(): - binary_sensors.append( - RPiGPIOBinarySensor( - port_name, port_num, pull_mode, bouncetime, invert_logic - ) - ) - add_entities(binary_sensors, True) - - -class RPiGPIOBinarySensor(BinarySensorEntity): - """Represent a binary sensor that uses Raspberry Pi GPIO.""" - - async def async_read_gpio(self): - """Read state from GPIO.""" - await asyncio.sleep(float(self._bouncetime) / 1000) - self._state = await self.hass.async_add_executor_job( - rpi_gpio.read_input, self._port - ) - self.async_write_ha_state() - - def __init__(self, name, port, pull_mode, bouncetime, invert_logic): - """Initialize the RPi binary sensor.""" - self._name = name or DEVICE_DEFAULT_NAME - self._port = port - self._pull_mode = pull_mode - self._bouncetime = bouncetime - self._invert_logic = invert_logic - self._state = None - - rpi_gpio.setup_input(self._port, self._pull_mode) - - def edge_detected(port): - """Edge detection handler.""" - self.hass.add_job(self.async_read_gpio) - - rpi_gpio.edge_detect(self._port, edge_detected, self._bouncetime) - - @property - def should_poll(self): - """No polling needed.""" - return False - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def is_on(self): - """Return the state of the entity.""" - return self._state != self._invert_logic - - def update(self): - """Update the GPIO state.""" - self._state = rpi_gpio.read_input(self._port) diff --git a/homeassistant/components/rpi_gpio/cover.py b/homeassistant/components/rpi_gpio/cover.py deleted file mode 100644 index e4b07d3c577..00000000000 --- a/homeassistant/components/rpi_gpio/cover.py +++ /dev/null @@ -1,139 +0,0 @@ -"""Support for controlling a Raspberry Pi cover.""" -from __future__ import annotations - -from time import sleep - -import voluptuous as vol - -from homeassistant.components import rpi_gpio -from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity -from homeassistant.const import CONF_COVERS, CONF_NAME -from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import setup_reload_service -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from . import DOMAIN, PLATFORMS - -CONF_RELAY_PIN = "relay_pin" -CONF_RELAY_TIME = "relay_time" -CONF_STATE_PIN = "state_pin" -CONF_STATE_PULL_MODE = "state_pull_mode" -CONF_INVERT_STATE = "invert_state" -CONF_INVERT_RELAY = "invert_relay" - -DEFAULT_RELAY_TIME = 0.2 -DEFAULT_STATE_PULL_MODE = "UP" -DEFAULT_INVERT_STATE = False -DEFAULT_INVERT_RELAY = False -_COVERS_SCHEMA = vol.All( - cv.ensure_list, - [ - vol.Schema( - { - CONF_NAME: cv.string, - CONF_RELAY_PIN: cv.positive_int, - CONF_STATE_PIN: cv.positive_int, - } - ) - ], -) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_COVERS): _COVERS_SCHEMA, - vol.Optional(CONF_STATE_PULL_MODE, default=DEFAULT_STATE_PULL_MODE): cv.string, - vol.Optional(CONF_RELAY_TIME, default=DEFAULT_RELAY_TIME): cv.positive_int, - vol.Optional(CONF_INVERT_STATE, default=DEFAULT_INVERT_STATE): cv.boolean, - vol.Optional(CONF_INVERT_RELAY, default=DEFAULT_INVERT_RELAY): cv.boolean, - } -) - - -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the RPi cover platform.""" - setup_reload_service(hass, DOMAIN, PLATFORMS) - - relay_time = config[CONF_RELAY_TIME] - state_pull_mode = config[CONF_STATE_PULL_MODE] - invert_state = config[CONF_INVERT_STATE] - invert_relay = config[CONF_INVERT_RELAY] - covers = [] - covers_conf = config[CONF_COVERS] - - for cover in covers_conf: - covers.append( - RPiGPIOCover( - cover[CONF_NAME], - cover[CONF_RELAY_PIN], - cover[CONF_STATE_PIN], - state_pull_mode, - relay_time, - invert_state, - invert_relay, - ) - ) - add_entities(covers) - - -class RPiGPIOCover(CoverEntity): - """Representation of a Raspberry GPIO cover.""" - - def __init__( - self, - name, - relay_pin, - state_pin, - state_pull_mode, - relay_time, - invert_state, - invert_relay, - ): - """Initialize the cover.""" - self._name = name - self._state = False - self._relay_pin = relay_pin - self._state_pin = state_pin - self._state_pull_mode = state_pull_mode - self._relay_time = relay_time - self._invert_state = invert_state - self._invert_relay = invert_relay - rpi_gpio.setup_output(self._relay_pin) - rpi_gpio.setup_input(self._state_pin, self._state_pull_mode) - rpi_gpio.write_output(self._relay_pin, 0 if self._invert_relay else 1) - - @property - def name(self): - """Return the name of the cover if any.""" - return self._name - - def update(self): - """Update the state of the cover.""" - self._state = rpi_gpio.read_input(self._state_pin) - - @property - def is_closed(self): - """Return true if cover is closed.""" - return self._state != self._invert_state - - def _trigger(self): - """Trigger the cover.""" - rpi_gpio.write_output(self._relay_pin, 1 if self._invert_relay else 0) - sleep(self._relay_time) - rpi_gpio.write_output(self._relay_pin, 0 if self._invert_relay else 1) - - def close_cover(self, **kwargs): - """Close the cover.""" - if not self.is_closed: - self._trigger() - - def open_cover(self, **kwargs): - """Open the cover.""" - if self.is_closed: - self._trigger() diff --git a/homeassistant/components/rpi_gpio/manifest.json b/homeassistant/components/rpi_gpio/manifest.json deleted file mode 100644 index f8db41b1a31..00000000000 --- a/homeassistant/components/rpi_gpio/manifest.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "domain": "rpi_gpio", - "name": "Raspberry Pi GPIO", - "documentation": "https://www.home-assistant.io/integrations/rpi_gpio", - "requirements": ["RPi.GPIO==0.7.1a4"], - "codeowners": [], - "iot_class": "local_push", - "loggers": ["RPi"] -} diff --git a/homeassistant/components/rpi_gpio/services.yaml b/homeassistant/components/rpi_gpio/services.yaml deleted file mode 100644 index 1858c5a9fa2..00000000000 --- a/homeassistant/components/rpi_gpio/services.yaml +++ /dev/null @@ -1,3 +0,0 @@ -reload: - name: Reload - description: Reload all rpi_gpio entities. diff --git a/homeassistant/components/rpi_gpio/switch.py b/homeassistant/components/rpi_gpio/switch.py deleted file mode 100644 index 040edd912c5..00000000000 --- a/homeassistant/components/rpi_gpio/switch.py +++ /dev/null @@ -1,88 +0,0 @@ -"""Allows to configure a switch using RPi GPIO.""" -from __future__ import annotations - -import voluptuous as vol - -from homeassistant.components import rpi_gpio -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity -from homeassistant.const import DEVICE_DEFAULT_NAME -from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.reload import setup_reload_service -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from . import DOMAIN, PLATFORMS - -CONF_PULL_MODE = "pull_mode" -CONF_PORTS = "ports" -CONF_INVERT_LOGIC = "invert_logic" - -DEFAULT_INVERT_LOGIC = False - -_SWITCHES_SCHEMA = vol.Schema({cv.positive_int: cv.string}) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_PORTS): _SWITCHES_SCHEMA, - vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, - } -) - - -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the Raspberry PI GPIO devices.""" - setup_reload_service(hass, DOMAIN, PLATFORMS) - - invert_logic = config[CONF_INVERT_LOGIC] - - switches = [] - ports = config[CONF_PORTS] - for port, name in ports.items(): - switches.append(RPiGPIOSwitch(name, port, invert_logic)) - add_entities(switches) - - -class RPiGPIOSwitch(SwitchEntity): - """Representation of a Raspberry Pi GPIO.""" - - def __init__(self, name, port, invert_logic): - """Initialize the pin.""" - self._name = name or DEVICE_DEFAULT_NAME - self._port = port - self._invert_logic = invert_logic - self._state = False - rpi_gpio.setup_output(self._port) - rpi_gpio.write_output(self._port, 1 if self._invert_logic else 0) - - @property - def name(self): - """Return the name of the switch.""" - return self._name - - @property - def should_poll(self): - """No polling needed.""" - return False - - @property - def is_on(self): - """Return true if device is on.""" - return self._state - - def turn_on(self, **kwargs): - """Turn the device on.""" - rpi_gpio.write_output(self._port, 0 if self._invert_logic else 1) - self._state = True - self.schedule_update_ha_state() - - def turn_off(self, **kwargs): - """Turn the device off.""" - rpi_gpio.write_output(self._port, 1 if self._invert_logic else 0) - self._state = False - self.schedule_update_ha_state() diff --git a/homeassistant/components/rpi_power/config_flow.py b/homeassistant/components/rpi_power/config_flow.py index ed8a45822b0..97814c2a866 100644 --- a/homeassistant/components/rpi_power/config_flow.py +++ b/homeassistant/components/rpi_power/config_flow.py @@ -36,6 +36,8 @@ class RPiPowerFlow(DiscoveryFlowHandler[Awaitable[bool]], domain=DOMAIN): self, data: dict[str, Any] | None = None ) -> FlowResult: """Handle a flow initialized by onboarding.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") has_devices = await self._discovery_function(self.hass) if not has_devices: diff --git a/homeassistant/components/rpi_power/translations/nl.json b/homeassistant/components/rpi_power/translations/nl.json index d9e42ef11f3..5529aa39f20 100644 --- a/homeassistant/components/rpi_power/translations/nl.json +++ b/homeassistant/components/rpi_power/translations/nl.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } }, diff --git a/homeassistant/components/rtsp_to_webrtc/translations/nl.json b/homeassistant/components/rtsp_to_webrtc/translations/nl.json index 6b3344f2b6b..6454c48c22f 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/nl.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "server_failure": "RtsPtoWebRTC-server retourneerde een fout. Raadpleeg de logboeken voor meer informatie.", "server_unreachable": "Kan niet communiceren met de RTSPtoWebRTC-server. Controleer logboeken voor meer informatie.", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "invalid_url": "Moet een geldige RTSPtoWebRTC server URL zijn, b.v. https://example.com", diff --git a/homeassistant/components/ruckus_unleashed/__init__.py b/homeassistant/components/ruckus_unleashed/__init__.py index 6ea3b736dcd..2c8c8bb108d 100644 --- a/homeassistant/components/ruckus_unleashed/__init__.py +++ b/homeassistant/components/ruckus_unleashed/__init__.py @@ -44,7 +44,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: system_info = await hass.async_add_executor_job(ruckus.system_info) - registry = await device_registry.async_get_registry(hass) + registry = device_registry.async_get(hass) ap_info = await hass.async_add_executor_job(ruckus.ap_info) for device in ap_info[API_AP][API_ID].values(): registry.async_get_or_create( diff --git a/homeassistant/components/ruckus_unleashed/device_tracker.py b/homeassistant/components/ruckus_unleashed/device_tracker.py index 562f4835547..67c86f3bb51 100644 --- a/homeassistant/components/ruckus_unleashed/device_tracker.py +++ b/homeassistant/components/ruckus_unleashed/device_tracker.py @@ -38,7 +38,7 @@ async def async_setup_entry( coordinator.async_add_listener(router_update) ) - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) restore_entities(registry, coordinator, entry, async_add_entities, tracked) diff --git a/homeassistant/components/ruckus_unleashed/translations/sk.json b/homeassistant/components/ruckus_unleashed/translations/sk.json index 5ada995aa6e..dbe0480911b 100644 --- a/homeassistant/components/ruckus_unleashed/translations/sk.json +++ b/homeassistant/components/ruckus_unleashed/translations/sk.json @@ -2,6 +2,13 @@ "config": { "error": { "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "username": "U\u017e\u00edvate\u013esk\u00e9 meno" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/bg.json b/homeassistant/components/sabnzbd/translations/bg.json new file mode 100644 index 00000000000..a9d07e52b39 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/bg.json @@ -0,0 +1,18 @@ +{ + "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" + }, + "step": { + "user": { + "data": { + "api_key": "API \u043a\u043b\u044e\u0447", + "name": "\u0418\u043c\u0435", + "path": "\u041f\u044a\u0442", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/ca.json b/homeassistant/components/sabnzbd/translations/ca.json new file mode 100644 index 00000000000..d1947bb05f5 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/ca.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_api_key": "Clau API inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "name": "Nom", + "path": "Ruta", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/es.json b/homeassistant/components/sabnzbd/translations/es.json new file mode 100644 index 00000000000..f38e28a3287 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/es.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Ha fallado la conexi\u00f3n", + "invalid_api_key": "Clave API inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "api_key": "Clave API", + "name": "Nombre", + "path": "Ruta", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/he.json b/homeassistant/components/sabnzbd/translations/he.json new file mode 100644 index 00000000000..1574a5e7719 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/he.json @@ -0,0 +1,18 @@ +{ + "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" + }, + "step": { + "user": { + "data": { + "api_key": "\u05de\u05e4\u05ea\u05d7 API", + "name": "\u05e9\u05dd", + "path": "\u05e0\u05ea\u05d9\u05d1", + "url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/ja.json b/homeassistant/components/sabnzbd/translations/ja.json new file mode 100644 index 00000000000..737bbfe4140 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/ja.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc" + }, + "step": { + "user": { + "data": { + "api_key": "API\u30ad\u30fc", + "name": "\u540d\u524d", + "path": "\u30d1\u30b9", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/ko.json b/homeassistant/components/sabnzbd/translations/ko.json new file mode 100644 index 00000000000..86887876ff5 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/ko.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/ru.json b/homeassistant/components/sabnzbd/translations/ru.json new file mode 100644 index 00000000000..68e5f255f96 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/ru.json @@ -0,0 +1,18 @@ +{ + "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." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "path": "\u041f\u0443\u0442\u044c", + "url": "URL-\u0430\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netgear/translations/sk.json b/homeassistant/components/sabnzbd/translations/sk.json similarity index 70% rename from homeassistant/components/netgear/translations/sk.json rename to homeassistant/components/sabnzbd/translations/sk.json index ea85ad39b4a..9d5ee388dc3 100644 --- a/homeassistant/components/netgear/translations/sk.json +++ b/homeassistant/components/sabnzbd/translations/sk.json @@ -3,7 +3,7 @@ "step": { "user": { "data": { - "port": "Port (volite\u013en\u00fd)" + "api_key": "API k\u013e\u00fa\u010d" } } } diff --git a/homeassistant/components/sabnzbd/translations/sv.json b/homeassistant/components/sabnzbd/translations/sv.json new file mode 100644 index 00000000000..61847ff0eb4 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/sv.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Det gick inte att ansluta", + "invalid_api_key": "Ogiltig API-nyckel" + }, + "step": { + "user": { + "data": { + "api_key": "API Nyckel", + "name": "Namn", + "path": "S\u00f6kv\u00e4g" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/tr.json b/homeassistant/components/sabnzbd/translations/tr.json new file mode 100644 index 00000000000..f238072b6da --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "name": "Ad", + "path": "Yol", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/__init__.py b/homeassistant/components/samsungtv/__init__.py index a7b8f7d1aec..b870aab62d4 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -26,6 +26,7 @@ from homeassistant.const import ( ) from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr, entity_registry as er import homeassistant.helpers.config_validation as cv from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.typing import ConfigType @@ -313,11 +314,11 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> # 1 -> 2: Unique ID format changed, so delete and re-import: if version == 1: - dev_reg = await hass.helpers.device_registry.async_get_registry() - dev_reg.async_clear_config_entry(config_entry) + dev_reg = dr.async_get(hass) + dev_reg.async_clear_config_entry(config_entry.entry_id) - en_reg = await hass.helpers.entity_registry.async_get_registry() - en_reg.async_clear_config_entry(config_entry) + en_reg = er.async_get(hass) + en_reg.async_clear_config_entry(config_entry.entry_id) version = config_entry.version = 2 hass.config_entries.async_update_entry(config_entry) diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index c3201a493eb..fe0b102647a 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -307,9 +307,7 @@ class SamsungTVLegacyBridge(SamsungTVBridge): if self._remote is None: # We need to create a new instance to reconnect. try: - LOGGER.debug( - "Create SamsungTVLegacyBridge for %s (%s)", CONF_NAME, self.host - ) + LOGGER.debug("Create SamsungTVLegacyBridge for %s", self.host) self._remote = Remote(self.config.copy()) # This is only happening when the auth was switched to DENY # A removed auth will lead to socket timeout because waiting for auth popup is just an open socket diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index adcec0e8b2b..bfa2482f617 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -167,6 +167,8 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): updates = {CONF_HOST: self._host} if self._mac: updates[CONF_MAC] = self._mac + if self._model: + updates[CONF_MODEL] = self._model if self._ssdp_rendering_control_location: updates[ CONF_SSDP_RENDERING_CONTROL_LOCATION @@ -380,10 +382,12 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): != self._ssdp_main_tv_agent_location ) update_mac = self._mac and not data.get(CONF_MAC) + update_model = self._model and not data.get(CONF_MODEL) if ( update_ssdp_rendering_control_location or update_ssdp_main_tv_agent_location or update_mac + or update_model ): if update_ssdp_rendering_control_location: data[ @@ -395,6 +399,8 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ] = self._ssdp_main_tv_agent_location if update_mac: data[CONF_MAC] = self._mac + if update_model: + data[CONF_MODEL] = self._model entry_kw_args["data"] = data if not entry_kw_args: return None @@ -408,7 +414,8 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return entry - async def _async_start_discovery_with_mac_address(self) -> None: + @callback + def _async_start_discovery_with_mac_address(self) -> None: """Start discovery.""" assert self._host is not None if (entry := self._async_update_existing_matching_entry()) and entry.unique_id: @@ -485,7 +492,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): LOGGER.debug("Samsung device found via DHCP: %s", discovery_info) self._mac = discovery_info.macaddress self._host = discovery_info.ip - await self._async_start_discovery_with_mac_address() + self._async_start_discovery_with_mac_address() await self._async_set_device_unique_id() self.context["title_placeholders"] = {"device": self._title} return await self.async_step_confirm() @@ -497,7 +504,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): LOGGER.debug("Samsung device found via ZEROCONF: %s", discovery_info) self._mac = format_mac(discovery_info.properties["deviceid"]) self._host = discovery_info.host - await self._async_start_discovery_with_mac_address() + self._async_start_discovery_with_mac_address() await self._async_set_device_unique_id() self.context["title_placeholders"] = {"device": self._title} return await self.async_step_confirm() diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 9391fe5e311..fd97eb12e54 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.29.0" + "async-upnp-client==0.30.1" ], "ssdp": [ { diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index 6a884c59a87..cf3bfcd64a1 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -197,12 +197,15 @@ class SamsungTVDevice(MediaPlayerEntity): """Update state of device.""" if self._auth_failed or self.hass.is_stopping: return + old_state = self._attr_state if self._power_off_in_progress(): self._attr_state = STATE_OFF else: self._attr_state = ( STATE_ON if await self._bridge.async_is_on() else STATE_OFF ) + if self._attr_state != old_state: + LOGGER.debug("TV %s state updated to %s", self._host, self._attr_state) if self._attr_state != STATE_ON: if self._dmr_device and self._dmr_device.is_subscribed: diff --git a/homeassistant/components/samsungtv/translations/bg.json b/homeassistant/components/samsungtv/translations/bg.json index 2246b4ad954..56d85b8ff5d 100644 --- a/homeassistant/components/samsungtv/translations/bg.json +++ b/homeassistant/components/samsungtv/translations/bg.json @@ -11,9 +11,6 @@ }, "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}." }, diff --git a/homeassistant/components/samsungtv/translations/ca.json b/homeassistant/components/samsungtv/translations/ca.json index 95738ae65d9..ae4ead3f8e8 100644 --- a/homeassistant/components/samsungtv/translations/ca.json +++ b/homeassistant/components/samsungtv/translations/ca.json @@ -6,7 +6,6 @@ "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.", "cannot_connect": "Ha fallat la connexi\u00f3", "id_missing": "El dispositiu Samsung no t\u00e9 cap n\u00famero de s\u00e8rie.", - "missing_config_entry": "Aquest dispositiu Samsung no t\u00e9 cap entrada de configuraci\u00f3.", "not_supported": "Actualment aquest dispositiu Samsung no \u00e9s compatible.", "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament", "unknown": "Error inesperat" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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." }, "encrypted_pairing": { "description": "Introdueix el PIN que es mostra a {device}." diff --git a/homeassistant/components/samsungtv/translations/cs.json b/homeassistant/components/samsungtv/translations/cs.json index 4c2241d4caa..8de7b1f9e13 100644 --- a/homeassistant/components/samsungtv/translations/cs.json +++ b/homeassistant/components/samsungtv/translations/cs.json @@ -16,8 +16,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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." }, "encrypted_pairing": { "description": "Zadejte pros\u00edm PIN zobrazen\u00fd na {device}." diff --git a/homeassistant/components/samsungtv/translations/da.json b/homeassistant/components/samsungtv/translations/da.json index d41a62c5e7b..f335dbb9b29 100644 --- a/homeassistant/components/samsungtv/translations/da.json +++ b/homeassistant/components/samsungtv/translations/da.json @@ -9,8 +9,7 @@ "flow_title": "Samsung-tv: {model}", "step": { "confirm": { - "description": "Vil du konfigurere Samsung-tv {device}? Hvis du aldrig har oprettet forbindelse til Home Assistant f\u00f8r, b\u00f8r du se en popup p\u00e5 dit tv, der beder om godkendelse. Manuelle konfigurationer for dette tv vil blive overskrevet.", - "title": "Samsung-tv" + "description": "Vil du konfigurere Samsung-tv {device}? Hvis du aldrig har oprettet forbindelse til Home Assistant f\u00f8r, b\u00f8r du se en popup p\u00e5 dit tv, der beder om godkendelse. Manuelle konfigurationer for dette tv vil blive overskrevet." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/de.json b/homeassistant/components/samsungtv/translations/de.json index bfacbcd4b3c..7668a940f99 100644 --- a/homeassistant/components/samsungtv/translations/de.json +++ b/homeassistant/components/samsungtv/translations/de.json @@ -6,7 +6,6 @@ "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.", "cannot_connect": "Verbindung fehlgeschlagen", "id_missing": "Dieses Samsung-Ger\u00e4t hat keine Seriennummer.", - "missing_config_entry": "Dieses Samsung-Ger\u00e4t hat keinen Konfigurationseintrag.", "not_supported": "Dieses Samsung TV-Ger\u00e4t wird derzeit nicht unterst\u00fctzt.", "reauth_successful": "Die erneute Authentifizierung war erfolgreich", "unknown": "Unerwarteter Fehler" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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." }, "encrypted_pairing": { "description": "Bitte gib die PIN ein, die auf {device} angezeigt wird." diff --git a/homeassistant/components/samsungtv/translations/el.json b/homeassistant/components/samsungtv/translations/el.json index 0e6c4df03bc..4388073f70b 100644 --- a/homeassistant/components/samsungtv/translations/el.json +++ b/homeassistant/components/samsungtv/translations/el.json @@ -6,7 +6,6 @@ "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.", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "id_missing": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Samsung \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 SerialNumber.", - "missing_config_entry": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Samsung \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2.", "not_supported": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Samsung \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae.", "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", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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." }, "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}." diff --git a/homeassistant/components/samsungtv/translations/en.json b/homeassistant/components/samsungtv/translations/en.json index 827f9992e46..c4e0e181090 100644 --- a/homeassistant/components/samsungtv/translations/en.json +++ b/homeassistant/components/samsungtv/translations/en.json @@ -6,7 +6,6 @@ "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" @@ -18,8 +17,7 @@ "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.", - "title": "Samsung TV" + "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." }, "encrypted_pairing": { "description": "Please enter the PIN displayed on {device}." diff --git a/homeassistant/components/samsungtv/translations/es-419.json b/homeassistant/components/samsungtv/translations/es-419.json index 179965fda80..c1e5110c967 100644 --- a/homeassistant/components/samsungtv/translations/es-419.json +++ b/homeassistant/components/samsungtv/translations/es-419.json @@ -9,8 +9,7 @@ "flow_title": "Televisi\u00f3n Samsung: {model}", "step": { "confirm": { - "description": "\u00bfDesea configurar la televisi\u00f3n Samsung {device}? Si nunca conect\u00f3 Home Assistant antes, deber\u00eda ver una ventana emergente en su televisor pidiendo autorizaci\u00f3n. Las configuraciones manuales para este televisor se sobrescribir\u00e1n.", - "title": "Samsung TV" + "description": "\u00bfDesea configurar la televisi\u00f3n Samsung {device}? Si nunca conect\u00f3 Home Assistant antes, deber\u00eda ver una ventana emergente en su televisor pidiendo autorizaci\u00f3n. Las configuraciones manuales para este televisor se sobrescribir\u00e1n." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/es.json b/homeassistant/components/samsungtv/translations/es.json index 42b0f794e7a..1c2a81cb7c0 100644 --- a/homeassistant/components/samsungtv/translations/es.json +++ b/homeassistant/components/samsungtv/translations/es.json @@ -6,23 +6,28 @@ "auth_missing": "Home Assistant no est\u00e1 autorizado para conectarse a este televisor Samsung. Revisa la configuraci\u00f3n de tu televisor para autorizar a Home Assistant.", "cannot_connect": "No se pudo conectar", "id_missing": "Este dispositivo Samsung no tiene un n\u00famero de serie.", - "missing_config_entry": "Este dispositivo de Samsung no est\u00e1 configurado.", "not_supported": "Esta televisi\u00f3n Samsung actualmente no es compatible.", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente", "unknown": "Error inesperado" }, "error": { - "auth_missing": "Home Assistant no est\u00e1 autorizado para conectarse a este televisor Samsung. Revisa la configuraci\u00f3n de tu televisor para autorizar a Home Assistant." + "auth_missing": "Home Assistant no est\u00e1 autorizado para conectarse a este televisor Samsung. Revisa la configuraci\u00f3n de tu televisor para autorizar a Home Assistant.", + "invalid_pin": "El PIN es inv\u00e1lido; vuelve a intentarlo." }, "flow_title": "{device}", "step": { "confirm": { - "description": "\u00bfQuieres configurar {device}? Si nunca la has conectado a Home Assistant antes deber\u00edas ver una ventana en tu TV pidiendo autorizaci\u00f3n.", - "title": "Samsung TV" + "description": "\u00bfQuieres configurar {device}? Si nunca la has conectado a Home Assistant antes deber\u00edas ver una ventana en tu TV pidiendo autorizaci\u00f3n." + }, + "encrypted_pairing": { + "description": "Introduce el PIN que se muestra en {device}." }, "reauth_confirm": { "description": "Despu\u00e9s de enviarlo, acepte la ventana emergente en {device} solicitando autorizaci\u00f3n dentro de los 30 segundos." }, + "reauth_confirm_encrypted": { + "description": "Introduce el PIN que se muestra en {device}." + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/samsungtv/translations/et.json b/homeassistant/components/samsungtv/translations/et.json index 94e9db78ec2..e2dcf966ae9 100644 --- a/homeassistant/components/samsungtv/translations/et.json +++ b/homeassistant/components/samsungtv/translations/et.json @@ -6,7 +6,6 @@ "auth_missing": "Home Assistantil pole selle Samsungi teleriga \u00fchenduse loomiseks luba. Home Assistanti autoriseerimiseks kontrolli oma teleri seadeid.", "cannot_connect": "\u00dchendamine nurjus", "id_missing": "Sellel Samsungi seadmel puudub seerianumber.", - "missing_config_entry": "Sellel Samsungi seadmel puudub seadekirje.", "not_supported": "Seda Samsungi seadet praegu ei toetata.", "reauth_successful": "Taastuvastamine \u00f5nnestus", "unknown": "Tundmatu t\u00f5rge" @@ -18,8 +17,7 @@ "flow_title": "{devicel}", "step": { "confirm": { - "description": "Kas soovid seadistada {devicel} ? Kui seda pole kunagi enne Home Assistantiga \u00fchendatud, n\u00e4ed oma teleris h\u00fcpikakent, mis k\u00fcsib tuvastamist.", - "title": "" + "description": "Kas soovid seadistada {devicel} ? Kui seda pole kunagi enne Home Assistantiga \u00fchendatud, n\u00e4ed oma teleris h\u00fcpikakent, mis k\u00fcsib tuvastamist." }, "encrypted_pairing": { "description": "Sisesta seadmes {device} kuvatav PIN-kood." diff --git a/homeassistant/components/samsungtv/translations/fr.json b/homeassistant/components/samsungtv/translations/fr.json index 438336f2818..1d651f9ce79 100644 --- a/homeassistant/components/samsungtv/translations/fr.json +++ b/homeassistant/components/samsungtv/translations/fr.json @@ -6,7 +6,6 @@ "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.", "cannot_connect": "\u00c9chec de connexion", "id_missing": "Cet appareil Samsung n'a pas de num\u00e9ro de s\u00e9rie.", - "missing_config_entry": "Cet appareil Samsung n'a pas d'entr\u00e9e de configuration.", "not_supported": "Ce t\u00e9l\u00e9viseur Samsung n'est actuellement pas pris en charge.", "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", "unknown": "Erreur inattendue" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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." }, "encrypted_pairing": { "description": "Veuillez saisir le code PIN affich\u00e9 sur {device}." diff --git a/homeassistant/components/samsungtv/translations/he.json b/homeassistant/components/samsungtv/translations/he.json index 9f62de51b8c..2904c4c3012 100644 --- a/homeassistant/components/samsungtv/translations/he.json +++ b/homeassistant/components/samsungtv/translations/he.json @@ -13,9 +13,6 @@ }, "flow_title": "{device}", "step": { - "confirm": { - "title": "\u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05d4 \u05e9\u05dc \u05e1\u05de\u05e1\u05d5\u05e0\u05d2" - }, "user": { "data": { "host": "\u05de\u05d0\u05e8\u05d7", diff --git a/homeassistant/components/samsungtv/translations/hu.json b/homeassistant/components/samsungtv/translations/hu.json index dc42596e290..b53f50ff74a 100644 --- a/homeassistant/components/samsungtv/translations/hu.json +++ b/homeassistant/components/samsungtv/translations/hu.json @@ -6,7 +6,6 @@ "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.", - "missing_config_entry": "Ez a Samsung eszk\u00f6z nem rendelkezik konfigur\u00e1ci\u00f3s bejegyz\u00e9ssel.", "not_supported": "Ez a Samsung k\u00e9sz\u00fcl\u00e9k jelenleg nem t\u00e1mogatott.", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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." }, "encrypted_pairing": { "description": "K\u00e9rj\u00fck, adja meg az eszk\u00f6z\u00f6n megjelen\u0151 PIN-k\u00f3dot: {device}" diff --git a/homeassistant/components/samsungtv/translations/id.json b/homeassistant/components/samsungtv/translations/id.json index 394cfc41ae1..9ed190df2b7 100644 --- a/homeassistant/components/samsungtv/translations/id.json +++ b/homeassistant/components/samsungtv/translations/id.json @@ -6,20 +6,18 @@ "auth_missing": "Home Assistant tidak diizinkan untuk tersambung ke TV Samsung ini. Periksa Manajer Perangkat Eksternal TV Anda untuk mengotorisasi Home Assistant.", "cannot_connect": "Gagal terhubung", "id_missing": "Perangkat Samsung ini tidak memiliki SerialNumber.", - "missing_config_entry": "Perangkat Samsung ini tidak memiliki entri konfigurasi.", "not_supported": "Perangkat TV Samsung ini saat ini tidak didukung.", "reauth_successful": "Autentikasi ulang berhasil", "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.", - "invalid_pin": "PIN tidak valid, silakan coba lagi." + "invalid_pin": "PIN tidak valid, coba lagi." }, "flow_title": "{device}", "step": { "confirm": { - "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" + "description": "Apakah Anda ingin menyiapkan {device}? Jika Anda belum pernah menyambungkan Home Assistant sebelumnya, Anda akan melihat dialog di TV yang meminta otorisasi." }, "encrypted_pairing": { "description": "Masukkan PIN yang ditampilkan di {device} ." diff --git a/homeassistant/components/samsungtv/translations/it.json b/homeassistant/components/samsungtv/translations/it.json index 6bfc26fb445..2f96d7566a6 100644 --- a/homeassistant/components/samsungtv/translations/it.json +++ b/homeassistant/components/samsungtv/translations/it.json @@ -6,7 +6,6 @@ "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.", "cannot_connect": "Impossibile connettersi", "id_missing": "Questo dispositivo Samsung non ha un SerialNumber.", - "missing_config_entry": "Questo dispositivo Samsung non ha una voce di configurazione.", "not_supported": "Questo dispositivo Samsung non \u00e8 attualmente supportato.", "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", "unknown": "Errore imprevisto" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "description": "Vuoi configurare {device}? Se non hai mai collegato Home Assistant, dovresti vedere un popup sulla tua TV che chiede l'autorizzazione." }, "encrypted_pairing": { "description": "Digita il PIN visualizzato su {device}." diff --git a/homeassistant/components/samsungtv/translations/ja.json b/homeassistant/components/samsungtv/translations/ja.json index d1b5670e25c..6b0cae1ba4d 100644 --- a/homeassistant/components/samsungtv/translations/ja.json +++ b/homeassistant/components/samsungtv/translations/ja.json @@ -6,7 +6,6 @@ "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", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "id_missing": "\u3053\u306eSamsung\u30c7\u30d0\u30a4\u30b9\u306b\u306f\u30b7\u30ea\u30a2\u30eb\u756a\u53f7\u304c\u3042\u308a\u307e\u305b\u3093\u3002", - "missing_config_entry": "\u3053\u306eSamsung\u30c7\u30d0\u30a4\u30b9\u306b\u306f\u69cb\u6210\u30a8\u30f3\u30c8\u30ea\u30fc\u304c\u3042\u308a\u307e\u305b\u3093\u3002", "not_supported": "\u3053\u306eSamsung\u30c7\u30d0\u30a4\u30b9\u306f\u73fe\u5728\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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" }, "encrypted_pairing": { "description": "{device} \u306b\u8868\u793a\u3055\u308c\u3066\u3044\u308bPIN\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" diff --git a/homeassistant/components/samsungtv/translations/ko.json b/homeassistant/components/samsungtv/translations/ko.json index 4f62bbf6237..5a3c64f120b 100644 --- a/homeassistant/components/samsungtv/translations/ko.json +++ b/homeassistant/components/samsungtv/translations/ko.json @@ -5,13 +5,24 @@ "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "auth_missing": "Home Assistant\uac00 \ud574\ub2f9 \uc0bc\uc131 TV\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc788\ub294 \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. TV \uc124\uc815\uc744 \ud655\uc778\ud558\uc5ec Home Assistant\ub97c \uc2b9\uc778\ud574\uc8fc\uc138\uc694.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "not_supported": "\uc774 \uc0bc\uc131 TV \ubaa8\ub378\uc740 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." + "id_missing": "\ud574\ub2f9 \uc0bc\uc131 \uae30\uae30\uc5d0 \uc77c\ub828\ubc88\ud638\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", + "not_supported": "\uc774 \uc0bc\uc131 TV \ubaa8\ub378\uc740 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "auth_missing": "Home Assistant\uac00 \ud574\ub2f9 \uc0bc\uc131 TV\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc788\ub294 \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. TV \uc124\uc815\uc744 \ud655\uc778\ud558\uc5ec Home Assistant\ub97c \uc2b9\uc778\ud574\uc8fc\uc138\uc694." }, "flow_title": "\uc0bc\uc131 TV: {model}", "step": { "confirm": { - "description": "{device} \uc0bc\uc131 TV\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? Home Assistant\ub97c \uc5f0\uacb0\ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV\uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4. \uc774 TV\uc758 \uc218\ub3d9\uc73c\ub85c \uad6c\uc131\ub41c \ub0b4\uc6a9\uc740 \ub36e\uc5b4\uc50c\uc6cc\uc9d1\ub2c8\ub2e4.", - "title": "\uc0bc\uc131 TV" + "description": "{device} \uc0bc\uc131 TV\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? Home Assistant\ub97c \uc5f0\uacb0\ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV\uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4. \uc774 TV\uc758 \uc218\ub3d9\uc73c\ub85c \uad6c\uc131\ub41c \ub0b4\uc6a9\uc740 \ub36e\uc5b4\uc50c\uc6cc\uc9d1\ub2c8\ub2e4." + }, + "pairing": { + "description": "{device} \uc0bc\uc131 TV\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? Home Assistant\ub97c \uc5f0\uacb0\ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV\uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4. \uc774 TV\uc758 \uc218\ub3d9\uc73c\ub85c \uad6c\uc131\ub41c \ub0b4\uc6a9\uc740 \ub36e\uc5b4\uc50c\uc6cc\uc9d1\ub2c8\ub2e4." + }, + "reauth_confirm": { + "description": "\ud655\uc778 \ud6c4 30\ucd08 \uc774\ub0b4\uc5d0 \uc2b9\uc778\uc744 \uc694\uccad\ud558\ub294 {device} \ud31d\uc5c5\uc744 \uc218\ub77d\ud558\uac70\ub098 PIN\uc744 \uc785\ub825\ud558\uc138\uc694." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/lb.json b/homeassistant/components/samsungtv/translations/lb.json index 0740ab119a4..732507d3bb1 100644 --- a/homeassistant/components/samsungtv/translations/lb.json +++ b/homeassistant/components/samsungtv/translations/lb.json @@ -10,8 +10,7 @@ "flow_title": "Samsnung TV:{model}", "step": { "confirm": { - "description": "W\u00ebllt dir de Samsung TV {device} ariichten?. Falls dir Home Assistant nach ni domat verbonnen hutt misst den TV eng Meldung mat enger Authentifiz\u00e9ierung uweisen. Manuell Konfiguratioun fir d\u00ebse TV g\u00ebtt iwwerschriwwen.", - "title": "Samsnung TV" + "description": "W\u00ebllt dir de Samsung TV {device} ariichten?. Falls dir Home Assistant nach ni domat verbonnen hutt misst den TV eng Meldung mat enger Authentifiz\u00e9ierung uweisen. Manuell Konfiguratioun fir d\u00ebse TV g\u00ebtt iwwerschriwwen." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/nl.json b/homeassistant/components/samsungtv/translations/nl.json index b75f9b5970e..4b69c8e812c 100644 --- a/homeassistant/components/samsungtv/translations/nl.json +++ b/homeassistant/components/samsungtv/translations/nl.json @@ -2,13 +2,12 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "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.", "cannot_connect": "Kan geen verbinding maken", "id_missing": "Dit Samsung-apparaat heeft geen serienummer.", - "missing_config_entry": "Dit Samsung-apparaat heeft geen configuratie-invoer.", "not_supported": "Deze Samsung TV wordt momenteel niet ondersteund.", - "reauth_successful": "Herauthenticatie was succesvol", + "reauth_successful": "Herauthenticatie geslaagd", "unknown": "Onverwachte fout" }, "error": { @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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" }, "encrypted_pairing": { "description": "Voer de pincode in die wordt weergegeven op {device} ." diff --git a/homeassistant/components/samsungtv/translations/no.json b/homeassistant/components/samsungtv/translations/no.json index 2125b407f29..3b515608128 100644 --- a/homeassistant/components/samsungtv/translations/no.json +++ b/homeassistant/components/samsungtv/translations/no.json @@ -6,7 +6,6 @@ "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.", "cannot_connect": "Tilkobling mislyktes", "id_missing": "Denne Samsung-enheten har ikke serienummer.", - "missing_config_entry": "Denne Samsung -enheten har ingen konfigurasjonsoppf\u00f8ring.", "not_supported": "Denne Samsung-enheten st\u00f8ttes forel\u00f8pig ikke.", "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket", "unknown": "Uventet feil" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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": "" + "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." }, "encrypted_pairing": { "description": "Vennligst skriv inn PIN-koden som vises p\u00e5 {device} ." diff --git a/homeassistant/components/samsungtv/translations/pl.json b/homeassistant/components/samsungtv/translations/pl.json index 015f048dc60..f61c0eb8ced 100644 --- a/homeassistant/components/samsungtv/translations/pl.json +++ b/homeassistant/components/samsungtv/translations/pl.json @@ -6,7 +6,6 @@ "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.", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "id_missing": "To urz\u0105dzenie Samsung nie ma numeru seryjnego.", - "missing_config_entry": "To urz\u0105dzenie Samsung nie ma wpisu konfiguracyjnego.", "not_supported": "To urz\u0105dzenie Samsung nie jest obecnie obs\u0142ugiwane", "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119", "unknown": "Nieoczekiwany b\u0142\u0105d" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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." }, "encrypted_pairing": { "description": "Wprowad\u017a kod PIN wy\u015bwietlony na {device}." diff --git a/homeassistant/components/samsungtv/translations/pt-BR.json b/homeassistant/components/samsungtv/translations/pt-BR.json index dbb4b50fe93..c1480bb93c9 100644 --- a/homeassistant/components/samsungtv/translations/pt-BR.json +++ b/homeassistant/components/samsungtv/translations/pt-BR.json @@ -6,7 +6,6 @@ "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.", "cannot_connect": "Falha ao conectar", "id_missing": "Este dispositivo Samsung n\u00e3o possui um SerialNumber.", - "missing_config_entry": "Este dispositivo Samsung n\u00e3o tem uma entrada de configura\u00e7\u00e3o.", "not_supported": "Este dispositivo Samsung n\u00e3o \u00e9 compat\u00edvel no momento.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", "unknown": "Erro inesperado" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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." }, "encrypted_pairing": { "description": "Insira o PIN exibido em {device}." diff --git a/homeassistant/components/samsungtv/translations/pt.json b/homeassistant/components/samsungtv/translations/pt.json index 15e61d23627..b2cd242c7da 100644 --- a/homeassistant/components/samsungtv/translations/pt.json +++ b/homeassistant/components/samsungtv/translations/pt.json @@ -7,9 +7,6 @@ }, "flow_title": "TV Samsung: {model}", "step": { - "confirm": { - "title": "TV Samsung" - }, "user": { "data": { "host": "Servidor", diff --git a/homeassistant/components/samsungtv/translations/ru.json b/homeassistant/components/samsungtv/translations/ru.json index ee73070cf0e..5d6a3cb8e12 100644 --- a/homeassistant/components/samsungtv/translations/ru.json +++ b/homeassistant/components/samsungtv/translations/ru.json @@ -6,7 +6,6 @@ "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.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "id_missing": "\u0423 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Samsung \u043d\u0435\u0442 \u0441\u0435\u0440\u0438\u0439\u043d\u043e\u0433\u043e \u043d\u043e\u043c\u0435\u0440\u0430.", - "missing_config_entry": "\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Samsung.", "not_supported": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Samsung \u0432 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\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.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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." }, "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}." diff --git a/homeassistant/components/samsungtv/translations/sl.json b/homeassistant/components/samsungtv/translations/sl.json index 41536f72b16..5ec6f1aca76 100644 --- a/homeassistant/components/samsungtv/translations/sl.json +++ b/homeassistant/components/samsungtv/translations/sl.json @@ -9,8 +9,7 @@ "flow_title": "Samsung TV: {model}", "step": { "confirm": { - "description": "Vnesite podatke o televizorju Samsung {device}. \u010ce \u0161e nikoli niste povezali Home Assistant, bi morali na televizorju videli pojavno okno, ki zahteva va\u0161e dovoljenje. Ro\u010dna konfiguracija za ta TV bo prepisana.", - "title": "Samsung TV" + "description": "Vnesite podatke o televizorju Samsung {device}. \u010ce \u0161e nikoli niste povezali Home Assistant, bi morali na televizorju videli pojavno okno, ki zahteva va\u0161e dovoljenje. Ro\u010dna konfiguracija za ta TV bo prepisana." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/sv.json b/homeassistant/components/samsungtv/translations/sv.json index 38b3bea8a22..e9c0803c865 100644 --- a/homeassistant/components/samsungtv/translations/sv.json +++ b/homeassistant/components/samsungtv/translations/sv.json @@ -11,8 +11,7 @@ "flow_title": "{device}", "step": { "confirm": { - "description": "Vill du st\u00e4lla in Samsung TV {device}? Om du aldrig har anslutit Home Assistant innan du ska se ett popup-f\u00f6nster p\u00e5 tv:n och be om auktorisering. Manuella konfigurationer f\u00f6r den h\u00e4r TV:n skrivs \u00f6ver.", - "title": "Samsung TV" + "description": "Vill du st\u00e4lla in Samsung TV {device}? Om du aldrig har anslutit Home Assistant innan du ska se ett popup-f\u00f6nster p\u00e5 tv:n och be om auktorisering. Manuella konfigurationer f\u00f6r den h\u00e4r TV:n skrivs \u00f6ver." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/tr.json b/homeassistant/components/samsungtv/translations/tr.json index 668b321e26d..172bd0e093e 100644 --- a/homeassistant/components/samsungtv/translations/tr.json +++ b/homeassistant/components/samsungtv/translations/tr.json @@ -6,7 +6,6 @@ "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.", "cannot_connect": "Ba\u011flanma hatas\u0131", "id_missing": "Bu Samsung cihaz\u0131n\u0131n Seri Numaras\u0131 yok.", - "missing_config_entry": "Bu Samsung cihaz\u0131nda bir yap\u0131land\u0131rma giri\u015fi yok.", "not_supported": "Bu Samsung TV cihaz\u0131 \u015fu anda desteklenmiyor.", "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", "unknown": "Beklenmeyen hata" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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." }, "encrypted_pairing": { "description": "L\u00fctfen {device} \u00fczerinde g\u00f6r\u00fcnt\u00fclenen PIN'i girin." diff --git a/homeassistant/components/samsungtv/translations/uk.json b/homeassistant/components/samsungtv/translations/uk.json index f6aa504ccc8..ae3f98e3aac 100644 --- a/homeassistant/components/samsungtv/translations/uk.json +++ b/homeassistant/components/samsungtv/translations/uk.json @@ -10,8 +10,7 @@ "flow_title": "Samsung TV: {model}", "step": { "confirm": { - "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Samsung {device}? \u042f\u043a\u0449\u043e \u0446\u0435\u0439 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0440\u0430\u043d\u0456\u0448\u0435 \u043d\u0435 \u0431\u0443\u0432 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0434\u043e Home Assistant, \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 \u043c\u0430\u0454 \u0437'\u044f\u0432\u0438\u0442\u0438\u0441\u044f \u0441\u043f\u043b\u0438\u0432\u0430\u044e\u0447\u0435 \u0432\u0456\u043a\u043d\u043e \u0456\u0437 \u0437\u0430\u043f\u0438\u0442\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457. \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430, \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0456 \u0432\u0440\u0443\u0447\u043d\u0443, \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u043d\u0456.", - "title": "\u0422\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Samsung" + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Samsung {device}? \u042f\u043a\u0449\u043e \u0446\u0435\u0439 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0440\u0430\u043d\u0456\u0448\u0435 \u043d\u0435 \u0431\u0443\u0432 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0434\u043e Home Assistant, \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 \u043c\u0430\u0454 \u0437'\u044f\u0432\u0438\u0442\u0438\u0441\u044f \u0441\u043f\u043b\u0438\u0432\u0430\u044e\u0447\u0435 \u0432\u0456\u043a\u043d\u043e \u0456\u0437 \u0437\u0430\u043f\u0438\u0442\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457. \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430, \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0456 \u0432\u0440\u0443\u0447\u043d\u0443, \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u043d\u0456." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/zh-Hans.json b/homeassistant/components/samsungtv/translations/zh-Hans.json index da6a5c3c9ba..3a18da7fa94 100644 --- a/homeassistant/components/samsungtv/translations/zh-Hans.json +++ b/homeassistant/components/samsungtv/translations/zh-Hans.json @@ -16,8 +16,7 @@ "flow_title": "{device}", "step": { "confirm": { - "description": "\u662f\u5426\u8981\u914d\u7f6e {device} ?\n\u5982\u679c\u60a8\u4e4b\u524d\u4ece\u672a\u8fde\u63a5\u8fc7 Home Assistant \uff0c\u60a8\u5c06\u4f1a\u5728\u8be5\u7535\u89c6\u4e0a\u770b\u5230\u8bf7\u6c42\u6388\u6743\u7684\u5f39\u7a97\u3002", - "title": "\u4e09\u661f\u7535\u89c6" + "description": "\u662f\u5426\u8981\u914d\u7f6e {device} ?\n\u5982\u679c\u60a8\u4e4b\u524d\u4ece\u672a\u8fde\u63a5\u8fc7 Home Assistant \uff0c\u60a8\u5c06\u4f1a\u5728\u8be5\u7535\u89c6\u4e0a\u770b\u5230\u8bf7\u6c42\u6388\u6743\u7684\u5f39\u7a97\u3002" }, "reauth_confirm": { "description": "\u63d0\u4ea4\u4fe1\u606f\u540e\uff0c\u8bf7\u5728 30 \u79d2\u5185\u5728 {device} \u540c\u610f\u83b7\u53d6\u76f8\u5173\u6388\u6743\u3002" diff --git a/homeassistant/components/samsungtv/translations/zh-Hant.json b/homeassistant/components/samsungtv/translations/zh-Hant.json index 8f30090dd24..da3d0db877a 100644 --- a/homeassistant/components/samsungtv/translations/zh-Hant.json +++ b/homeassistant/components/samsungtv/translations/zh-Hant.json @@ -6,7 +6,6 @@ "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", "cannot_connect": "\u9023\u7dda\u5931\u6557", "id_missing": "\u4e09\u661f\u88dd\u7f6e\u4e26\u672a\u5305\u542b\u5e8f\u865f\u3002", - "missing_config_entry": "\u6b64\u4e09\u661f\u88dd\u7f6e\u4e26\u672a\u5305\u542b\u8a2d\u5b9a\u3002", "not_supported": "\u4e0d\u652f\u63f4\u6b64\u6b3e\u4e09\u661f\u88dd\u7f6e\u3002", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" @@ -18,8 +17,7 @@ "flow_title": "{device}", "step": { "confirm": { - "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" + "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" }, "encrypted_pairing": { "description": "\u8acb\u8f38\u5165\u986f\u793a\u65bc {device} \u4e0a\u7684 PIN \u78bc\u3002" diff --git a/homeassistant/components/scrape/manifest.json b/homeassistant/components/scrape/manifest.json index bf5865206e4..b1ccbb354a9 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.11.1"], + "requirements": ["beautifulsoup4==4.11.1", "lxml==4.8.0"], "after_dependencies": ["rest"], "codeowners": ["@fabaff"], "iot_class": "cloud_polling" diff --git a/homeassistant/components/scrape/sensor.py b/homeassistant/components/scrape/sensor.py index 8f2a672ef06..e15f7c5ba97 100644 --- a/homeassistant/components/scrape/sensor.py +++ b/homeassistant/components/scrape/sensor.py @@ -154,7 +154,7 @@ class ScrapeSensor(SensorEntity): def _extract_value(self) -> Any: """Parse the html extraction in the executor.""" - raw_data = BeautifulSoup(self.rest.data, "html.parser") + raw_data = BeautifulSoup(self.rest.data, "lxml") _LOGGER.debug(raw_data) try: diff --git a/homeassistant/components/script/logbook.py b/homeassistant/components/script/logbook.py index f75584540d3..7fcbad07479 100644 --- a/homeassistant/components/script/logbook.py +++ b/homeassistant/components/script/logbook.py @@ -1,4 +1,10 @@ """Describe logbook events.""" +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_CONTEXT_ID, + LOGBOOK_ENTRY_ENTITY_ID, + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME from homeassistant.core import callback @@ -14,9 +20,10 @@ def async_describe_events(hass, async_describe_event): """Describe the logbook event.""" data = event.data return { - "name": data.get(ATTR_NAME), - "message": "started", - "entity_id": data.get(ATTR_ENTITY_ID), + LOGBOOK_ENTRY_NAME: data.get(ATTR_NAME), + LOGBOOK_ENTRY_MESSAGE: "started", + LOGBOOK_ENTRY_ENTITY_ID: data.get(ATTR_ENTITY_ID), + LOGBOOK_ENTRY_CONTEXT_ID: event.context_id, } async_describe_event(DOMAIN, EVENT_SCRIPT_STARTED, async_describe_logbook_event) diff --git a/homeassistant/components/season/translations/sensor.fy.json b/homeassistant/components/season/translations/sensor.fy.json new file mode 100644 index 00000000000..f0990e02749 --- /dev/null +++ b/homeassistant/components/season/translations/sensor.fy.json @@ -0,0 +1,7 @@ +{ + "state": { + "season__season__": { + "summer": "Simmer" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/season/translations/sensor.sk.json b/homeassistant/components/season/translations/sensor.sk.json new file mode 100644 index 00000000000..6a1f5dd293b --- /dev/null +++ b/homeassistant/components/season/translations/sensor.sk.json @@ -0,0 +1,7 @@ +{ + "state": { + "season__season__": { + "spring": "Jar" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/select/device_action.py b/homeassistant/components/select/device_action.py index f4c4d7883d0..1212b9dea6b 100644 --- a/homeassistant/components/select/device_action.py +++ b/homeassistant/components/select/device_action.py @@ -15,7 +15,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import get_capability -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from .const import ATTR_OPTION, ATTR_OPTIONS, CONF_OPTION, DOMAIN, SERVICE_SELECT_OPTION @@ -34,7 +34,7 @@ async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Select devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) return [ { CONF_DEVICE_ID: device_id, @@ -48,7 +48,10 @@ async def async_get_actions( async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" await hass.services.async_call( diff --git a/homeassistant/components/select/device_condition.py b/homeassistant/components/select/device_condition.py index ed48f2afb15..6e6a3c704b3 100644 --- a/homeassistant/components/select/device_condition.py +++ b/homeassistant/components/select/device_condition.py @@ -38,7 +38,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Select devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) return [ { CONF_CONDITION: "device", diff --git a/homeassistant/components/select/device_trigger.py b/homeassistant/components/select/device_trigger.py index 79e1c4a221f..574acfc6893 100644 --- a/homeassistant/components/select/device_trigger.py +++ b/homeassistant/components/select/device_trigger.py @@ -1,8 +1,6 @@ """Provides device triggers for Select.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -47,9 +45,9 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Select devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) return [ { CONF_PLATFORM: "device", diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index aaf3630ae19..2e0ed4622e8 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -80,6 +80,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except (SenseAuthenticationException, SenseMFARequiredException) as err: _LOGGER.warning("Sense authentication expired") raise ConfigEntryAuthFailed(err) from err + except SENSE_TIMEOUT_EXCEPTIONS as err: + raise ConfigEntryNotReady( + str(err) or "Timed out during authentication" + ) from err sense_devices_data = SenseDevicesData() try: diff --git a/homeassistant/components/sense/binary_sensor.py b/homeassistant/components/sense/binary_sensor.py index aa895da166f..10399fa8e2b 100644 --- a/homeassistant/components/sense/binary_sensor.py +++ b/homeassistant/components/sense/binary_sensor.py @@ -8,9 +8,9 @@ from homeassistant.components.binary_sensor import ( from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION 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 homeassistant.helpers.entity_registry import async_get_registry from .const import ( ATTRIBUTION, @@ -50,7 +50,7 @@ async def async_setup_entry( async def _migrate_old_unique_ids(hass, devices): - registry = await async_get_registry(hass) + registry = er.async_get(hass) for device in devices: # Migration of old not so unique ids old_entity_id = registry.async_get_entity_id( diff --git a/homeassistant/components/sense/translations/es.json b/homeassistant/components/sense/translations/es.json index ca77c97900b..5621988c60b 100644 --- a/homeassistant/components/sense/translations/es.json +++ b/homeassistant/components/sense/translations/es.json @@ -12,7 +12,8 @@ "reauth_validate": { "data": { "password": "Contrase\u00f1a" - } + }, + "description": "La integraci\u00f3n Sense debe volver a autenticar la cuenta {email}." }, "user": { "data": { diff --git a/homeassistant/components/sense/translations/ko.json b/homeassistant/components/sense/translations/ko.json index 269d8a76fea..b28f41f0d64 100644 --- a/homeassistant/components/sense/translations/ko.json +++ b/homeassistant/components/sense/translations/ko.json @@ -15,6 +15,9 @@ "password": "\ube44\ubc00\ubc88\ud638" }, "title": "Sense Energy Monitor\uc5d0 \uc5f0\uacb0\ud558\uae30" + }, + "validation": { + "title": "Sense \ub2e4\ub2e8\uacc4 \uc778\uc99d" } } } diff --git a/homeassistant/components/sense/translations/nl.json b/homeassistant/components/sense/translations/nl.json index 11b1c07350c..3558f4a0c07 100644 --- a/homeassistant/components/sense/translations/nl.json +++ b/homeassistant/components/sense/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -15,7 +15,7 @@ "password": "Wachtwoord" }, "description": "De Sense-integratie moet uw account {email} opnieuw verifi\u00ebren.", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { diff --git a/homeassistant/components/senseme/translations/sk.json b/homeassistant/components/senseme/translations/sk.json new file mode 100644 index 00000000000..ffc3e13321e --- /dev/null +++ b/homeassistant/components/senseme/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepodarilo sa pripoji\u0165." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensibo/__init__.py b/homeassistant/components/sensibo/__init__.py index ab8e4e85d39..dc02e9ee686 100644 --- a/homeassistant/components/sensibo/__init__.py +++ b/homeassistant/components/sensibo/__init__.py @@ -26,12 +26,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload Sensibo config entry.""" - if await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): del hass.data[DOMAIN][entry.entry_id] if not hass.data[DOMAIN]: del hass.data[DOMAIN] - return True - return False + return unload_ok async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 907105b5384..c1e690cd28a 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -178,7 +178,7 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): ) if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: - return + raise ValueError("No target temperature provided") if temperature == self.target_temperature: return @@ -192,7 +192,9 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): temperature = self.device_data.temp_list[0] else: - return + raise ValueError( + f"Target temperature has to be one off {str(self.device_data.temp_list)}" + ) await self._async_set_ac_state_property("targetTemperature", int(temperature)) diff --git a/homeassistant/components/sensibo/number.py b/homeassistant/components/sensibo/number.py index 69d9237da7a..fc18e28f1a3 100644 --- a/homeassistant/components/sensibo/number.py +++ b/homeassistant/components/sensibo/number.py @@ -29,7 +29,7 @@ class SensiboNumberEntityDescription( """Class describing Sensibo Number entities.""" -NUMBER_TYPES = ( +DEVICE_NUMBER_TYPES = ( SensiboNumberEntityDescription( key="calibration_temp", remote_key="temperature", @@ -65,7 +65,7 @@ async def async_setup_entry( async_add_entities( SensiboNumber(coordinator, device_id, description) for device_id, device_data in coordinator.data.parsed.items() - for description in NUMBER_TYPES + for description in DEVICE_NUMBER_TYPES ) diff --git a/homeassistant/components/sensibo/select.py b/homeassistant/components/sensibo/select.py index c442fbc1374..56b8fbac4fd 100644 --- a/homeassistant/components/sensibo/select.py +++ b/homeassistant/components/sensibo/select.py @@ -29,7 +29,7 @@ class SensiboSelectEntityDescription( """Class describing Sensibo Number entities.""" -SELECT_TYPES = ( +DEVICE_SELECT_TYPES = ( SensiboSelectEntityDescription( key="horizontalSwing", remote_key="horizontal_swing_mode", @@ -57,7 +57,7 @@ async def async_setup_entry( async_add_entities( SensiboSelect(coordinator, device_id, description) for device_id, device_data in coordinator.data.parsed.items() - for description in SELECT_TYPES + for description in DEVICE_SELECT_TYPES if description.key in device_data.full_features ) diff --git a/homeassistant/components/sensibo/sensor.py b/homeassistant/components/sensibo/sensor.py index 307cfac6003..7ac871a61eb 100644 --- a/homeassistant/components/sensibo/sensor.py +++ b/homeassistant/components/sensibo/sensor.py @@ -99,7 +99,7 @@ MOTION_SENSOR_TYPES: tuple[SensiboMotionSensorEntityDescription, ...] = ( value_fn=lambda data: data.temperature, ), ) -DEVICE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = ( +PURE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = ( SensiboDeviceSensorEntityDescription( key="pm25", device_class=SensorDeviceClass.PM25, @@ -137,8 +137,8 @@ async def async_setup_entry( entities.extend( SensiboDeviceSensor(coordinator, device_id, description) for device_id, device_data in coordinator.data.parsed.items() - for description in DEVICE_SENSOR_TYPES - if getattr(device_data, description.key) is not None + for description in PURE_SENSOR_TYPES + if device_data.model == "pure" ) async_add_entities(entities) diff --git a/homeassistant/components/sensibo/translations/bg.json b/homeassistant/components/sensibo/translations/bg.json index e600bafdb4a..1435a77a2fb 100644 --- a/homeassistant/components/sensibo/translations/bg.json +++ b/homeassistant/components/sensibo/translations/bg.json @@ -16,8 +16,7 @@ }, "user": { "data": { - "api_key": "API \u043a\u043b\u044e\u0447", - "name": "\u0418\u043c\u0435" + "api_key": "API \u043a\u043b\u044e\u0447" } } } diff --git a/homeassistant/components/sensibo/translations/ca.json b/homeassistant/components/sensibo/translations/ca.json index f062af9e519..1634e0b8e40 100644 --- a/homeassistant/components/sensibo/translations/ca.json +++ b/homeassistant/components/sensibo/translations/ca.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "Clau API", - "name": "Nom" + "api_key": "Clau API" } } } diff --git a/homeassistant/components/sensibo/translations/cs.json b/homeassistant/components/sensibo/translations/cs.json index 8e25544e301..fcfba839932 100644 --- a/homeassistant/components/sensibo/translations/cs.json +++ b/homeassistant/components/sensibo/translations/cs.json @@ -9,8 +9,7 @@ "step": { "user": { "data": { - "api_key": "Kl\u00ed\u010d API", - "name": "Jm\u00e9no" + "api_key": "Kl\u00ed\u010d API" } } } diff --git a/homeassistant/components/sensibo/translations/de.json b/homeassistant/components/sensibo/translations/de.json index 9e3b02c726c..d5900f0f25e 100644 --- a/homeassistant/components/sensibo/translations/de.json +++ b/homeassistant/components/sensibo/translations/de.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "API-Schl\u00fcssel", - "name": "Name" + "api_key": "API-Schl\u00fcssel" } } } diff --git a/homeassistant/components/sensibo/translations/el.json b/homeassistant/components/sensibo/translations/el.json index baa2545a7e0..7fd7ee26f97 100644 --- a/homeassistant/components/sensibo/translations/el.json +++ b/homeassistant/components/sensibo/translations/el.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" } } } diff --git a/homeassistant/components/sensibo/translations/en.json b/homeassistant/components/sensibo/translations/en.json index 1b5d7cd9214..7b7c8aab7f1 100644 --- a/homeassistant/components/sensibo/translations/en.json +++ b/homeassistant/components/sensibo/translations/en.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "API Key", - "name": "Name" + "api_key": "API Key" } } } diff --git a/homeassistant/components/sensibo/translations/es.json b/homeassistant/components/sensibo/translations/es.json index f4ff854c502..b91e1b63f9b 100644 --- a/homeassistant/components/sensibo/translations/es.json +++ b/homeassistant/components/sensibo/translations/es.json @@ -4,13 +4,21 @@ "already_configured": "La cuenta ya est\u00e1 configurada" }, "error": { - "cannot_connect": "No se pudo conectar" + "cannot_connect": "No se pudo conectar", + "incorrect_api_key": "Clave API inv\u00e1lida para la cuenta seleccionada", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "no_devices": "No se ha descubierto ning\u00fan dispositivo", + "no_username": "No se pudo obtener el nombre de usuario" }, "step": { + "reauth_confirm": { + "data": { + "api_key": "Clave API" + } + }, "user": { "data": { - "api_key": "Clave API", - "name": "Nombre" + "api_key": "Clave API" } } } diff --git a/homeassistant/components/sensibo/translations/et.json b/homeassistant/components/sensibo/translations/et.json index de5a158c1ad..b216ecc3260 100644 --- a/homeassistant/components/sensibo/translations/et.json +++ b/homeassistant/components/sensibo/translations/et.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "API v\u00f5ti", - "name": "Nimi" + "api_key": "API v\u00f5ti" } } } diff --git a/homeassistant/components/sensibo/translations/fr.json b/homeassistant/components/sensibo/translations/fr.json index 09e2e5e5045..f674ad2c9b2 100644 --- a/homeassistant/components/sensibo/translations/fr.json +++ b/homeassistant/components/sensibo/translations/fr.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "Cl\u00e9 d'API", - "name": "Nom" + "api_key": "Cl\u00e9 d'API" } } } diff --git a/homeassistant/components/sensibo/translations/he.json b/homeassistant/components/sensibo/translations/he.json index 3486bd3c646..29f4fc720da 100644 --- a/homeassistant/components/sensibo/translations/he.json +++ b/homeassistant/components/sensibo/translations/he.json @@ -16,8 +16,7 @@ }, "user": { "data": { - "api_key": "\u05de\u05e4\u05ea\u05d7 API", - "name": "\u05e9\u05dd" + "api_key": "\u05de\u05e4\u05ea\u05d7 API" } } } diff --git a/homeassistant/components/sensibo/translations/hu.json b/homeassistant/components/sensibo/translations/hu.json index 065a7b982ed..14c7cd756d0 100644 --- a/homeassistant/components/sensibo/translations/hu.json +++ b/homeassistant/components/sensibo/translations/hu.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "API kulcs", - "name": "Elnevez\u00e9s" + "api_key": "API kulcs" } } } diff --git a/homeassistant/components/sensibo/translations/id.json b/homeassistant/components/sensibo/translations/id.json index 479edabe69b..dfaf05cca33 100644 --- a/homeassistant/components/sensibo/translations/id.json +++ b/homeassistant/components/sensibo/translations/id.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "Kunci API", - "name": "Nama" + "api_key": "Kunci API" } } } diff --git a/homeassistant/components/sensibo/translations/it.json b/homeassistant/components/sensibo/translations/it.json index c10adf2cf38..912bfee5114 100644 --- a/homeassistant/components/sensibo/translations/it.json +++ b/homeassistant/components/sensibo/translations/it.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "Chiave API", - "name": "Nome" + "api_key": "Chiave API" } } } diff --git a/homeassistant/components/sensibo/translations/ja.json b/homeassistant/components/sensibo/translations/ja.json index 859722fbe73..4c74b6412ac 100644 --- a/homeassistant/components/sensibo/translations/ja.json +++ b/homeassistant/components/sensibo/translations/ja.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "API\u30ad\u30fc", - "name": "\u540d\u524d" + "api_key": "API\u30ad\u30fc" } } } diff --git a/homeassistant/components/sensibo/translations/ko.json b/homeassistant/components/sensibo/translations/ko.json new file mode 100644 index 00000000000..7f03247bb3d --- /dev/null +++ b/homeassistant/components/sensibo/translations/ko.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "no_devices": "\uac80\uc0c9\ub41c \uae30\uae30 \uc5c6\uc74c" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensibo/translations/nl.json b/homeassistant/components/sensibo/translations/nl.json index cc6529c3540..fe04b6b64a8 100644 --- a/homeassistant/components/sensibo/translations/nl.json +++ b/homeassistant/components/sensibo/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "API-sleutel", - "name": "Naam" + "api_key": "API-sleutel" } } } diff --git a/homeassistant/components/sensibo/translations/no.json b/homeassistant/components/sensibo/translations/no.json index 9d3098ccd9e..43f17f52f2e 100644 --- a/homeassistant/components/sensibo/translations/no.json +++ b/homeassistant/components/sensibo/translations/no.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "API-n\u00f8kkel", - "name": "Navn" + "api_key": "API-n\u00f8kkel" } } } diff --git a/homeassistant/components/sensibo/translations/pl.json b/homeassistant/components/sensibo/translations/pl.json index 022aaf52039..830ab3399b9 100644 --- a/homeassistant/components/sensibo/translations/pl.json +++ b/homeassistant/components/sensibo/translations/pl.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "Klucz API", - "name": "Nazwa" + "api_key": "Klucz API" } } } diff --git a/homeassistant/components/sensibo/translations/pt-BR.json b/homeassistant/components/sensibo/translations/pt-BR.json index ff0ac4883ba..89b8984ac49 100644 --- a/homeassistant/components/sensibo/translations/pt-BR.json +++ b/homeassistant/components/sensibo/translations/pt-BR.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "Chave da API", - "name": "Nome" + "api_key": "Chave da API" } } } diff --git a/homeassistant/components/sensibo/translations/ru.json b/homeassistant/components/sensibo/translations/ru.json index 96574822758..aafef088706 100644 --- a/homeassistant/components/sensibo/translations/ru.json +++ b/homeassistant/components/sensibo/translations/ru.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "\u041a\u043b\u044e\u0447 API", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + "api_key": "\u041a\u043b\u044e\u0447 API" } } } diff --git a/homeassistant/components/sensibo/translations/sk.json b/homeassistant/components/sensibo/translations/sk.json index 694f006218b..d2401d8b7a3 100644 --- a/homeassistant/components/sensibo/translations/sk.json +++ b/homeassistant/components/sensibo/translations/sk.json @@ -1,10 +1,12 @@ { "config": { + "error": { + "incorrect_api_key": "Neplatn\u00fd API k\u013e\u00fa\u010d pre vybran\u00fd \u00fa\u010det" + }, "step": { "user": { "data": { - "api_key": "API k\u013e\u00fa\u010d", - "name": "N\u00e1zov" + "api_key": "API k\u013e\u00fa\u010d" } } } diff --git a/homeassistant/components/sensibo/translations/tr.json b/homeassistant/components/sensibo/translations/tr.json index b08f683b200..fbe466cfd8d 100644 --- a/homeassistant/components/sensibo/translations/tr.json +++ b/homeassistant/components/sensibo/translations/tr.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "API Anahtar\u0131", - "name": "Ad" + "api_key": "API Anahtar\u0131" } } } diff --git a/homeassistant/components/sensibo/translations/zh-Hant.json b/homeassistant/components/sensibo/translations/zh-Hant.json index 39cc7dc9662..c2639ea2ab9 100644 --- a/homeassistant/components/sensibo/translations/zh-Hant.json +++ b/homeassistant/components/sensibo/translations/zh-Hant.json @@ -19,8 +19,7 @@ }, "user": { "data": { - "api_key": "API \u91d1\u9470", - "name": "\u540d\u7a31" + "api_key": "API \u91d1\u9470" } } } diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index 70a265e3b25..808d6367cdf 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -15,16 +15,16 @@ 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 import ( + condition, + config_validation as cv, + entity_registry as er, +) 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 ATTR_STATE_CLASS, DOMAIN, SensorDeviceClass @@ -141,10 +141,10 @@ async def async_get_conditions( ) -> list[dict[str, str]]: """List device conditions.""" conditions: list[dict[str, str]] = [] - entity_registry = await async_get_registry(hass) + entity_registry = er.async_get(hass) entries = [ entry - for entry in async_entries_for_device(entity_registry, device_id) + for entry in er.async_entries_for_device(entity_registry, device_id) if entry.domain == DOMAIN ] @@ -195,7 +195,9 @@ def async_condition_from_config( return condition.async_numeric_state_from_config(numeric_state_config) -async def async_get_condition_capabilities(hass, config): +async def async_get_condition_capabilities( + hass: HomeAssistant, config: ConfigType +) -> dict[str, vol.Schema]: """List condition capabilities.""" try: unit_of_measurement = get_unit_of_measurement(hass, config[CONF_ENTITY_ID]) diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index f90022cf5f3..741c0281e0d 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -1,6 +1,10 @@ """Provides device triggers for sensors.""" import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, @@ -15,14 +19,15 @@ from homeassistant.const import ( CONF_FOR, CONF_TYPE, ) +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_registry as er 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 homeassistant.helpers.typing import ConfigType from . import ATTR_STATE_CLASS, DOMAIN, SensorDeviceClass @@ -135,7 +140,12 @@ TRIGGER_SCHEMA = vol.All( ) -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" numeric_state_config = { numeric_state_trigger.CONF_PLATFORM: "numeric_state", @@ -156,14 +166,16 @@ async def async_attach_trigger(hass, config, action, automation_info): ) -async def async_get_triggers(hass, device_id): +async def async_get_triggers( + hass: HomeAssistant, device_id: str +) -> list[dict[str, str]]: """List device triggers.""" - triggers = [] - entity_registry = await hass.helpers.entity_registry.async_get_registry() + triggers: list[dict[str, str]] = [] + entity_registry = er.async_get(hass) entries = [ entry - for entry in async_entries_for_device(entity_registry, device_id) + for entry in er.async_entries_for_device(entity_registry, device_id) if entry.domain == DOMAIN ] @@ -193,7 +205,9 @@ async def async_get_triggers(hass, device_id): return triggers -async def async_get_trigger_capabilities(hass, config): +async def async_get_trigger_capabilities( + hass: HomeAssistant, config: ConfigType +) -> dict[str, vol.Schema]: """List trigger capabilities.""" try: unit_of_measurement = get_unit_of_measurement(hass, config[CONF_ENTITY_ID]) diff --git a/homeassistant/components/sensor/translations/ko.json b/homeassistant/components/sensor/translations/ko.json index 69ff6adb5e7..b01e5d95b9e 100644 --- a/homeassistant/components/sensor/translations/ko.json +++ b/homeassistant/components/sensor/translations/ko.json @@ -27,6 +27,7 @@ "power": "{entity_name}\uc758 \uc18c\ube44 \uc804\ub825\uc774 \ubcc0\ud560 \ub54c", "power_factor": "{entity_name}\uc758 \uc5ed\ub960\uc774 \ubcc0\ud560 \ub54c", "pressure": "{entity_name}\uc758 \uc555\ub825\uc774 \ubcc0\ud560 \ub54c", + "reactive_power": "{entity_name} \ubb34\ud6a8 \uc804\ub825\uc774 \ubcc0\ud560 \ub54c", "signal_strength": "{entity_name}\uc758 \uc2e0\ud638 \uac15\ub3c4\uac00 \ubcc0\ud560 \ub54c", "temperature": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ubcc0\ud560 \ub54c", "value": "{entity_name}\uc758 \uac12\uc774 \ubcc0\ud560 \ub54c", diff --git a/homeassistant/components/sensor/translations/sk.json b/homeassistant/components/sensor/translations/sk.json index 8f3507f56c1..f86600559a4 100644 --- a/homeassistant/components/sensor/translations/sk.json +++ b/homeassistant/components/sensor/translations/sk.json @@ -1,4 +1,13 @@ { + "device_automation": { + "condition_type": { + "is_voltage": "S\u00fa\u010dasn\u00e9 nap\u00e4tie {entity_name}" + }, + "trigger_type": { + "current": "{entity_name} pr\u00fad sa zmen\u00ed", + "voltage": "{entity_name} zmen\u00ed nap\u00e4tie" + } + }, "state": { "_": { "off": "Neakt\u00edvny", diff --git a/homeassistant/components/sentry/__init__.py b/homeassistant/components/sentry/__init__.py index 7ea26c04810..3037f1dc374 100644 --- a/homeassistant/components/sentry/__init__.py +++ b/homeassistant/components/sentry/__init__.py @@ -16,8 +16,9 @@ from homeassistant.const import ( __version__ as current_version, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers import config_validation as cv, entity_platform, instance_id from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.system_info import async_get_system_info from homeassistant.loader import Integration, async_get_custom_components from .const import ( @@ -67,8 +68,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Additional/extra data collection channel = get_channel(current_version) - huuid = await hass.helpers.instance_id.async_get() - system_info = await hass.helpers.system_info.async_get_system_info() + huuid = await instance_id.async_get(hass) + system_info = await async_get_system_info(hass) custom_components = await async_get_custom_components(hass) tracing = {} @@ -79,7 +80,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ), } - sentry_sdk.init( # pylint: disable=abstract-class-instantiated + # pylint: disable-next=abstract-class-instantiated + sentry_sdk.init( dsn=entry.data[CONF_DSN], environment=entry.options.get(CONF_ENVIRONMENT), integrations=[sentry_logging, AioHttpIntegration(), SqlalchemyIntegration()], @@ -99,7 +101,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def update_system_info(now): nonlocal system_info - system_info = await hass.helpers.system_info.async_get_system_info() + system_info = await async_get_system_info(hass) # Update system info every hour async_call_later(hass, 3600, update_system_info) diff --git a/homeassistant/components/sentry/manifest.json b/homeassistant/components/sentry/manifest.json index 1096edd4e36..b76318f046d 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.10"], + "requirements": ["sentry-sdk==1.5.12"], "codeowners": ["@dcramer", "@frenck"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/sentry/translations/af.json b/homeassistant/components/sentry/translations/af.json deleted file mode 100644 index 7db651b6b96..00000000000 --- a/homeassistant/components/sentry/translations/af.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "Sentry" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/ca.json b/homeassistant/components/sentry/translations/ca.json index d83abb16f1e..b353a3a5a0e 100644 --- a/homeassistant/components/sentry/translations/ca.json +++ b/homeassistant/components/sentry/translations/ca.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "Sentry DSN" - }, - "description": "Introdueix el DSN de Sentry", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/cs.json b/homeassistant/components/sentry/translations/cs.json index 28a2d603dd0..815d7a9bd09 100644 --- a/homeassistant/components/sentry/translations/cs.json +++ b/homeassistant/components/sentry/translations/cs.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "DSN" - }, - "description": "Zadejte sv\u00e9 Sentry DSN", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/da.json b/homeassistant/components/sentry/translations/da.json index 689bdad67ac..8a6f6788302 100644 --- a/homeassistant/components/sentry/translations/da.json +++ b/homeassistant/components/sentry/translations/da.json @@ -3,12 +3,6 @@ "error": { "bad_dsn": "Ugyldigt DSN", "unknown": "Uventet fejl" - }, - "step": { - "user": { - "description": "Indtast dit Sentry-DSN", - "title": "Sentry" - } } } } \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/de.json b/homeassistant/components/sentry/translations/de.json index d408af0d885..9af3d633924 100644 --- a/homeassistant/components/sentry/translations/de.json +++ b/homeassistant/components/sentry/translations/de.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "Sentry-DSN" - }, - "description": "Gib deine Sentry-DSN ein", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/el.json b/homeassistant/components/sentry/translations/el.json index fd64b2140e2..d416005a9c2 100644 --- a/homeassistant/components/sentry/translations/el.json +++ b/homeassistant/components/sentry/translations/el.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "DSN" - }, - "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf DSN \u03c4\u03bf\u03c5 Sentry \u03c3\u03b1\u03c2", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/en.json b/homeassistant/components/sentry/translations/en.json index 16bdac6b510..e989d4811fd 100644 --- a/homeassistant/components/sentry/translations/en.json +++ b/homeassistant/components/sentry/translations/en.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "Sentry DSN" - }, - "description": "Enter your Sentry DSN", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/es-419.json b/homeassistant/components/sentry/translations/es-419.json index dd6866f7610..6e6269f693b 100644 --- a/homeassistant/components/sentry/translations/es-419.json +++ b/homeassistant/components/sentry/translations/es-419.json @@ -3,12 +3,6 @@ "error": { "bad_dsn": "DSN inv\u00e1lido", "unknown": "Error inesperado" - }, - "step": { - "user": { - "description": "Ingrese su DSN Sentry", - "title": "Centinela" - } } } } \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/es.json b/homeassistant/components/sentry/translations/es.json index a2b5cc31f9a..0bb2f70720c 100644 --- a/homeassistant/components/sentry/translations/es.json +++ b/homeassistant/components/sentry/translations/es.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "DSN" - }, - "description": "Introduzca su DSN Sentry", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/et.json b/homeassistant/components/sentry/translations/et.json index 01386fa97e6..9e414a9d935 100644 --- a/homeassistant/components/sentry/translations/et.json +++ b/homeassistant/components/sentry/translations/et.json @@ -11,9 +11,7 @@ "user": { "data": { "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 6850bfe8a71..6f041e4acdd 100644 --- a/homeassistant/components/sentry/translations/fr.json +++ b/homeassistant/components/sentry/translations/fr.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "DSN Sentry" - }, - "description": "Entrez votre DSN Sentry", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/hu.json b/homeassistant/components/sentry/translations/hu.json index 0535657c3d0..5be91c081b7 100644 --- a/homeassistant/components/sentry/translations/hu.json +++ b/homeassistant/components/sentry/translations/hu.json @@ -11,9 +11,7 @@ "user": { "data": { "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 5f81eb1a445..1acc9574e82 100644 --- a/homeassistant/components/sentry/translations/id.json +++ b/homeassistant/components/sentry/translations/id.json @@ -11,9 +11,7 @@ "user": { "data": { "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 10a0d1bad2b..24738be06b8 100644 --- a/homeassistant/components/sentry/translations/it.json +++ b/homeassistant/components/sentry/translations/it.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "DSN Sentry" - }, - "description": "Inserisci il tuo DSN Sentry", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/ja.json b/homeassistant/components/sentry/translations/ja.json index 13ce3b4a5ff..8ac8ebf58a1 100644 --- a/homeassistant/components/sentry/translations/ja.json +++ b/homeassistant/components/sentry/translations/ja.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "DSN" - }, - "description": "Sentry DSN\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/ko.json b/homeassistant/components/sentry/translations/ko.json index 92e26b30c42..efd5e558edb 100644 --- a/homeassistant/components/sentry/translations/ko.json +++ b/homeassistant/components/sentry/translations/ko.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "DSN" - }, - "description": "Sentry DSN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/lb.json b/homeassistant/components/sentry/translations/lb.json index 3041a7eb156..8ab1be71190 100644 --- a/homeassistant/components/sentry/translations/lb.json +++ b/homeassistant/components/sentry/translations/lb.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "DSN" - }, - "description": "Gitt \u00e4r Sentry DSN un", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/nl.json b/homeassistant/components/sentry/translations/nl.json index 67be245ffde..0397ac0f609 100644 --- a/homeassistant/components/sentry/translations/nl.json +++ b/homeassistant/components/sentry/translations/nl.json @@ -11,9 +11,7 @@ "user": { "data": { "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 c3bb4acd26e..11e358f4908 100644 --- a/homeassistant/components/sentry/translations/no.json +++ b/homeassistant/components/sentry/translations/no.json @@ -11,9 +11,7 @@ "user": { "data": { "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 be5d008dcff..37489c9b619 100644 --- a/homeassistant/components/sentry/translations/pl.json +++ b/homeassistant/components/sentry/translations/pl.json @@ -11,9 +11,7 @@ "user": { "data": { "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 c489de8ee6d..39a7448716e 100644 --- a/homeassistant/components/sentry/translations/pt-BR.json +++ b/homeassistant/components/sentry/translations/pt-BR.json @@ -11,9 +11,7 @@ "user": { "data": { "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 6a7192a5385..903ca88bc6b 100644 --- a/homeassistant/components/sentry/translations/ru.json +++ b/homeassistant/components/sentry/translations/ru.json @@ -11,9 +11,7 @@ "user": { "data": { "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/sl.json b/homeassistant/components/sentry/translations/sl.json index 5a448c5b673..57990b75063 100644 --- a/homeassistant/components/sentry/translations/sl.json +++ b/homeassistant/components/sentry/translations/sl.json @@ -3,12 +3,6 @@ "error": { "bad_dsn": "Neveljaven DSN", "unknown": "Nepri\u010dakovana napaka" - }, - "step": { - "user": { - "description": "Vpi\u0161ite va\u0161 Sentry DSN", - "title": "Sentry" - } } } } \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/sv.json b/homeassistant/components/sentry/translations/sv.json index d04ea24b72a..45c4508ff9e 100644 --- a/homeassistant/components/sentry/translations/sv.json +++ b/homeassistant/components/sentry/translations/sv.json @@ -3,12 +3,6 @@ "error": { "bad_dsn": "Ogiltig DSN", "unknown": "Ov\u00e4ntat fel" - }, - "step": { - "user": { - "description": "Ange din Sentry DSN", - "title": "Sentry" - } } } } \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/tr.json b/homeassistant/components/sentry/translations/tr.json index 6369f727d17..a32bf17f843 100644 --- a/homeassistant/components/sentry/translations/tr.json +++ b/homeassistant/components/sentry/translations/tr.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "Sentry DSN" - }, - "description": "Sentry DSN'nizi girin", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/uk.json b/homeassistant/components/sentry/translations/uk.json index 124ac4543ed..4246abba8dc 100644 --- a/homeassistant/components/sentry/translations/uk.json +++ b/homeassistant/components/sentry/translations/uk.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "DSN" - }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448 DSN Sentry", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/sentry/translations/zh-Hant.json b/homeassistant/components/sentry/translations/zh-Hant.json index 6d4615db892..99330fb4f12 100644 --- a/homeassistant/components/sentry/translations/zh-Hant.json +++ b/homeassistant/components/sentry/translations/zh-Hant.json @@ -11,9 +11,7 @@ "user": { "data": { "dsn": "Sentry DSN" - }, - "description": "\u8f38\u5165 Sentry DSN", - "title": "Sentry" + } } } }, diff --git a/homeassistant/components/senz/__init__.py b/homeassistant/components/senz/__init__.py index b6fc2422888..08aa26fa3c5 100644 --- a/homeassistant/components/senz/__init__.py +++ b/homeassistant/components/senz/__init__.py @@ -4,10 +4,14 @@ from __future__ import annotations from datetime import timedelta import logging -from aiosenz import AUTHORIZATION_ENDPOINT, SENZAPI, TOKEN_ENDPOINT, Thermostat +from aiosenz import SENZAPI, Thermostat from httpx import RequestError import voluptuous as vol +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, Platform from homeassistant.core import HomeAssistant @@ -20,7 +24,6 @@ from homeassistant.helpers import ( 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 @@ -29,14 +32,17 @@ 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, - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) @@ -52,17 +58,21 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if DOMAIN not in config: return True - config_flow.OAuth2FlowHandler.async_register_implementation( + await async_import_client_credential( hass, - config_entry_oauth2_flow.LocalOAuth2Implementation( - hass, - DOMAIN, + DOMAIN, + ClientCredential( config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET], - AUTHORIZATION_ENDPOINT, - TOKEN_ENDPOINT, ), ) + _LOGGER.warning( + "Configuration of SENZ integration in YAML is deprecated " + "and will be removed in a future release; Your existing OAuth " + "Application Credentials have been imported into the UI " + "automatically and can be safely removed from your " + "configuration.yaml file" + ) return True diff --git a/homeassistant/components/senz/application_credentials.py b/homeassistant/components/senz/application_credentials.py new file mode 100644 index 00000000000..205f00ff33f --- /dev/null +++ b/homeassistant/components/senz/application_credentials.py @@ -0,0 +1,14 @@ +"""Application credentials platform for senz.""" + +from aiosenz import AUTHORIZATION_ENDPOINT, TOKEN_ENDPOINT + +from homeassistant.components.application_credentials import AuthorizationServer +from homeassistant.core import HomeAssistant + + +async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer: + """Return authorization server.""" + return AuthorizationServer( + authorize_url=AUTHORIZATION_ENDPOINT, + token_url=TOKEN_ENDPOINT, + ) diff --git a/homeassistant/components/senz/manifest.json b/homeassistant/components/senz/manifest.json index e9b6165cb26..937a20d8482 100644 --- a/homeassistant/components/senz/manifest.json +++ b/homeassistant/components/senz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/senz", "requirements": ["aiosenz==1.0.0"], - "dependencies": ["auth"], + "dependencies": ["application_credentials"], "codeowners": ["@milanmeu"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/senz/translations/bg.json b/homeassistant/components/senz/translations/bg.json index ddd6040e9cf..a99746433a0 100644 --- a/homeassistant/components/senz/translations/bg.json +++ b/homeassistant/components/senz/translations/bg.json @@ -1,7 +1,13 @@ { "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" + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u043c\u0435\u0442\u043e\u0434 \u0437\u0430 \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" + } } } } \ No newline at end of file diff --git a/homeassistant/components/senz/translations/de.json b/homeassistant/components/senz/translations/de.json index 16c6d8a883d..ffbc7bb458f 100644 --- a/homeassistant/components/senz/translations/de.json +++ b/homeassistant/components/senz/translations/de.json @@ -4,16 +4,16 @@ "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.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", "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" + "default": "Erfolgreich authentifiziert" }, "step": { "pick_implementation": { - "title": "Art der Anmeldung ausw\u00e4hlen" + "title": "W\u00e4hle die Authentifizierungsmethode" } } } diff --git a/homeassistant/components/senz/translations/es.json b/homeassistant/components/senz/translations/es.json new file mode 100644 index 00000000000..f81af28f4d9 --- /dev/null +++ b/homeassistant/components/senz/translations/es.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "La cuenta ya est\u00e1 configurada", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", + "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n.", + "no_url_available": "No hay ninguna URL disponible. Para m\u00e1s informaci\u00f3n sobre este error, [consulta la secci\u00f3n de ayuda]({docs_url})", + "oauth_error": "Se han recibido datos token inv\u00e1lidos." + }, + "create_entry": { + "default": "Autenticaci\u00f3n exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e9todo de autenticaci\u00f3n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/ko.json b/homeassistant/components/senz/translations/ko.json new file mode 100644 index 00000000000..c3f709296a5 --- /dev/null +++ b/homeassistant/components/senz/translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "oauth_error": "\uc798\ubabb\ub41c \ud1a0\ud070 \ub370\uc774\ud130\ub97c \ubc1b\uc558\uc2b5\ub2c8\ub2e4." + }, + "create_entry": { + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/nl.json b/homeassistant/components/senz/translations/nl.json index 0f50cb0f918..7f1eaccf89c 100644 --- a/homeassistant/components/senz/translations/nl.json +++ b/homeassistant/components/senz/translations/nl.json @@ -2,14 +2,14 @@ "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." + "already_in_progress": "De configuratie is momenteel al bezig", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", + "oauth_error": "Ongeldige tokengegevens ontvangen." }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index b5a83dfd747..caee1d72c38 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.1.0"], + "requirements": ["pillow==9.1.1"], "codeowners": ["@fabaff"], "iot_class": "local_polling" } diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 36fabaa7337..12cdcc0680c 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -19,7 +19,11 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import aiohttp_client, config_validation as cv +from homeassistant.helpers import ( + aiohttp_client, + config_validation as cv, + entity_registry as er, +) from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_call_later from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -234,7 +238,7 @@ class SeventeenTrackPackageSensor(SensorEntity): """Remove entity itself.""" await self.async_remove(force_remove=True) - reg = await self.hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(self.hass) entity_id = reg.async_get_entity_id( "sensor", "seventeentrack", diff --git a/homeassistant/components/sharkiq/translations/nl.json b/homeassistant/components/sharkiq/translations/nl.json index 3acfdbdf074..c76fc24ea92 100644 --- a/homeassistant/components/sharkiq/translations/nl.json +++ b/homeassistant/components/sharkiq/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Account is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", - "reauth_successful": "Herauthenticatie was succesvol", + "reauth_successful": "Herauthenticatie geslaagd", "unknown": "Onverwachte fout" }, "error": { @@ -14,7 +14,7 @@ "step": { "reauth": { "data": { - "password": "Paswoord", + "password": "Wachtwoord", "username": "Gebruikersnaam" } }, diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 41a9e68fbdd..4551fee5590 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -182,7 +182,7 @@ async def async_setup_block_entry(hass: HomeAssistant, entry: ConfigEntry) -> bo data["model"] = device.settings["device"]["type"] hass.config_entries.async_update_entry(entry, data=data) - hass.async_create_task(async_block_device_setup(hass, entry, device)) + async_block_device_setup(hass, entry, device) if sleep_period == 0: # Not a sleeping device, finish setup @@ -197,7 +197,7 @@ async def async_setup_block_entry(hass: HomeAssistant, entry: ConfigEntry) -> bo except OSError as err: raise ConfigEntryNotReady(str(err) or "Error during device setup") from err - await async_block_device_setup(hass, entry, device) + async_block_device_setup(hass, entry, device) elif sleep_period is None or device_entry is None: # Need to get sleep info or first time sleeping device setup, wait for device hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = device @@ -208,12 +208,13 @@ async def async_setup_block_entry(hass: HomeAssistant, entry: ConfigEntry) -> bo else: # Restore sensors for sleeping device LOGGER.debug("Setting up offline block device %s", entry.title) - await async_block_device_setup(hass, entry, device) + async_block_device_setup(hass, entry, device) return True -async def async_block_device_setup( +@callback +def async_block_device_setup( hass: HomeAssistant, entry: ConfigEntry, device: BlockDevice ) -> None: """Set up a block based device that is online.""" diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index a6cde0c4670..b33947ad6b7 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -235,12 +235,12 @@ async def async_setup_entry( ) -> None: """Set up sensors for device.""" if get_device_entry_gen(config_entry) == 2: - return await async_setup_entry_rpc( + return async_setup_entry_rpc( hass, config_entry, async_add_entities, RPC_SENSORS, RpcBinarySensor ) if config_entry.data[CONF_SLEEP_PERIOD]: - await async_setup_entry_attribute_entities( + async_setup_entry_attribute_entities( hass, config_entry, async_add_entities, @@ -249,7 +249,7 @@ async def async_setup_entry( _build_block_description, ) else: - await async_setup_entry_attribute_entities( + async_setup_entry_attribute_entities( hass, config_entry, async_add_entities, @@ -257,7 +257,7 @@ async def async_setup_entry( BlockBinarySensor, _build_block_description, ) - await async_setup_entry_rest( + async_setup_entry_rest( hass, config_entry, async_add_entities, diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index ffd5e012ef7..453d67f39a7 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -50,14 +50,13 @@ async def async_setup_entry( ][BLOCK] if wrapper.device.initialized: - await async_setup_climate_entities(async_add_entities, wrapper) + async_setup_climate_entities(async_add_entities, wrapper) else: - await async_restore_climate_entities( - hass, config_entry, async_add_entities, wrapper - ) + async_restore_climate_entities(hass, config_entry, async_add_entities, wrapper) -async def async_setup_climate_entities( +@callback +def async_setup_climate_entities( async_add_entities: AddEntitiesCallback, wrapper: BlockDeviceWrapper, ) -> None: @@ -79,7 +78,8 @@ async def async_setup_climate_entities( async_add_entities([BlockSleepingClimate(wrapper, sensor_block, device_block)]) -async def async_restore_climate_entities( +@callback +def async_restore_climate_entities( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, @@ -87,7 +87,7 @@ async def async_restore_climate_entities( ) -> None: """Restore sleeping climate devices.""" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = entity_registry.async_get(hass) entries = entity_registry.async_entries_for_config_entry( ent_reg, config_entry.entry_id ) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index abcfe689e93..41e0bd3031a 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -58,10 +58,12 @@ async def validate_input( options, ) await rpc_device.shutdown() + assert rpc_device.shelly + return { "title": get_rpc_device_name(rpc_device), CONF_SLEEP_PERIOD: 0, - "model": rpc_device.model, + "model": rpc_device.shelly.get("model"), "gen": 2, } @@ -119,21 +121,21 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" - except KeyError: - errors["base"] = "firmware_not_fully_provisioned" except Exception: # pylint: disable=broad-except LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - return self.async_create_entry( - title=device_info["title"], - data={ - **user_input, - CONF_SLEEP_PERIOD: device_info[CONF_SLEEP_PERIOD], - "model": device_info["model"], - "gen": device_info["gen"], - }, - ) + if device_info["model"]: + return self.async_create_entry( + title=device_info["title"], + data={ + **user_input, + CONF_SLEEP_PERIOD: device_info[CONF_SLEEP_PERIOD], + "model": device_info["model"], + "gen": device_info["gen"], + }, + ) + errors["base"] = "firmware_not_fully_provisioned" return self.async_show_form( step_id="user", data_schema=HOST_SCHEMA, errors=errors @@ -162,22 +164,22 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "cannot_connect" except aioshelly.exceptions.JSONRPCError: errors["base"] = "cannot_connect" - except KeyError: - errors["base"] = "firmware_not_fully_provisioned" except Exception: # pylint: disable=broad-except LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - return self.async_create_entry( - title=device_info["title"], - data={ - **user_input, - CONF_HOST: self.host, - CONF_SLEEP_PERIOD: device_info[CONF_SLEEP_PERIOD], - "model": device_info["model"], - "gen": device_info["gen"], - }, - ) + if device_info["model"]: + return self.async_create_entry( + title=device_info["title"], + data={ + **user_input, + CONF_HOST: self.host, + CONF_SLEEP_PERIOD: device_info[CONF_SLEEP_PERIOD], + "model": device_info["model"], + "gen": device_info["gen"], + }, + ) + errors["base"] = "firmware_not_fully_provisioned" else: user_input = {} @@ -223,8 +225,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): try: self.device_info = await validate_input(self.hass, self.host, self.info, {}) - except KeyError: - LOGGER.debug("Shelly host %s firmware not fully provisioned", self.host) except HTTP_CONNECT_ERRORS: return self.async_abort(reason="cannot_connect") @@ -235,7 +235,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -> FlowResult: """Handle discovery confirm.""" errors: dict[str, str] = {} - try: + + if not self.device_info["model"]: + errors["base"] = "firmware_not_fully_provisioned" + model = "Shelly" + else: + model = get_model_name(self.info) if user_input is not None: return self.async_create_entry( title=self.device_info["title"], @@ -246,15 +251,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): "gen": self.device_info["gen"], }, ) - except KeyError: - errors["base"] = "firmware_not_fully_provisioned" - else: self._set_confirm_only() return self.async_show_form( step_id="confirm_discovery", description_placeholders={ - "model": get_model_name(self.info), + "model": model, "host": self.host, }, errors=errors, diff --git a/homeassistant/components/shelly/cover.py b/homeassistant/components/shelly/cover.py index 9bc61f9415f..e28fe22a528 100644 --- a/homeassistant/components/shelly/cover.py +++ b/homeassistant/components/shelly/cover.py @@ -28,12 +28,13 @@ async def async_setup_entry( ) -> None: """Set up switches for device.""" if get_device_entry_gen(config_entry) == 2: - return await async_setup_rpc_entry(hass, config_entry, async_add_entities) + return async_setup_rpc_entry(hass, config_entry, async_add_entities) - return await async_setup_block_entry(hass, config_entry, async_add_entities) + return async_setup_block_entry(hass, config_entry, async_add_entities) -async def async_setup_block_entry( +@callback +def async_setup_block_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, @@ -48,7 +49,8 @@ async def async_setup_block_entry( async_add_entities(BlockShellyCover(wrapper, block) for block in blocks) -async def async_setup_rpc_entry( +@callback +def async_setup_rpc_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, diff --git a/homeassistant/components/shelly/device_trigger.py b/homeassistant/components/shelly/device_trigger.py index 3e839507127..0f9fc55ed71 100644 --- a/homeassistant/components/shelly/device_trigger.py +++ b/homeassistant/components/shelly/device_trigger.py @@ -1,7 +1,7 @@ """Provides device triggers for Shelly.""" from __future__ import annotations -from typing import Any, Final +from typing import Final import voluptuous as vol @@ -54,7 +54,7 @@ TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend( def append_input_triggers( - triggers: list[dict[str, Any]], + triggers: list[dict[str, str]], input_triggers: list[tuple[str, str]], device_id: str, ) -> None: @@ -72,8 +72,8 @@ def append_input_triggers( async def async_validate_trigger_config( - hass: HomeAssistant, config: dict[str, Any] -) -> dict[str, Any]: + hass: HomeAssistant, config: ConfigType +) -> ConfigType: """Validate config.""" config = TRIGGER_SCHEMA(config) @@ -108,9 +108,9 @@ async def async_validate_trigger_config( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Shelly devices.""" - triggers: list[dict[str, Any]] = [] + triggers: list[dict[str, str]] = [] if rpc_wrapper := get_rpc_device_wrapper(hass, device_id): input_triggers = get_rpc_input_triggers(rpc_wrapper.device) diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index f544770722f..8cba4d2804e 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -46,7 +46,8 @@ from .utils import ( ) -async def async_setup_entry_attribute_entities( +@callback +def async_setup_entry_attribute_entities( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, @@ -62,11 +63,11 @@ async def async_setup_entry_attribute_entities( ][BLOCK] if wrapper.device.initialized: - await async_setup_block_attribute_entities( + async_setup_block_attribute_entities( hass, async_add_entities, wrapper, sensors, sensor_class ) else: - await async_restore_block_attribute_entities( + async_restore_block_attribute_entities( hass, config_entry, async_add_entities, @@ -77,7 +78,8 @@ async def async_setup_entry_attribute_entities( ) -async def async_setup_block_attribute_entities( +@callback +def async_setup_block_attribute_entities( hass: HomeAssistant, async_add_entities: AddEntitiesCallback, wrapper: BlockDeviceWrapper, @@ -105,7 +107,7 @@ async def async_setup_block_attribute_entities( ): domain = sensor_class.__module__.split(".")[-1] unique_id = f"{wrapper.mac}-{block.description}-{sensor_id}" - await async_remove_shelly_entity(hass, domain, unique_id) + async_remove_shelly_entity(hass, domain, unique_id) else: blocks.append((block, sensor_id, description)) @@ -120,7 +122,8 @@ async def async_setup_block_attribute_entities( ) -async def async_restore_block_attribute_entities( +@callback +def async_restore_block_attribute_entities( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, @@ -134,7 +137,7 @@ async def async_restore_block_attribute_entities( """Restore block attributes entities.""" entities = [] - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = entity_registry.async_get(hass) entries = entity_registry.async_entries_for_config_entry( ent_reg, config_entry.entry_id ) @@ -158,7 +161,8 @@ async def async_restore_block_attribute_entities( async_add_entities(entities) -async def async_setup_entry_rpc( +@callback +def async_setup_entry_rpc( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, @@ -192,7 +196,7 @@ async def async_setup_entry_rpc( ): domain = sensor_class.__module__.split(".")[-1] unique_id = f"{wrapper.mac}-{key}-{sensor_id}" - await async_remove_shelly_entity(hass, domain, unique_id) + async_remove_shelly_entity(hass, domain, unique_id) else: if description.use_polling_wrapper: entities.append( @@ -207,7 +211,8 @@ async def async_setup_entry_rpc( async_add_entities(entities) -async def async_setup_entry_rest( +@callback +def async_setup_entry_rest( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 3530241f102..79db9c509f4 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -65,12 +65,13 @@ async def async_setup_entry( ) -> None: """Set up lights for device.""" if get_device_entry_gen(config_entry) == 2: - return await async_setup_rpc_entry(hass, config_entry, async_add_entities) + return async_setup_rpc_entry(hass, config_entry, async_add_entities) - return await async_setup_block_entry(hass, config_entry, async_add_entities) + return async_setup_block_entry(hass, config_entry, async_add_entities) -async def async_setup_block_entry( +@callback +def async_setup_block_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, @@ -92,7 +93,7 @@ async def async_setup_block_entry( blocks.append(block) assert wrapper.device.shelly unique_id = f"{wrapper.mac}-{block.type}_{block.channel}" - await async_remove_shelly_entity(hass, "switch", unique_id) + async_remove_shelly_entity(hass, "switch", unique_id) if not blocks: return @@ -100,7 +101,8 @@ async def async_setup_block_entry( async_add_entities(BlockShellyLight(wrapper, block) for block in blocks) -async def async_setup_rpc_entry( +@callback +def async_setup_rpc_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, @@ -116,7 +118,7 @@ async def async_setup_rpc_entry( switch_ids.append(id_) unique_id = f"{wrapper.mac}-switch:{id_}" - await async_remove_shelly_entity(hass, "switch", unique_id) + async_remove_shelly_entity(hass, "switch", unique_id) if not switch_ids: return @@ -380,6 +382,9 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): class RpcShellyLight(ShellyRpcEntity, LightEntity): """Entity that controls a light on RPC based Shelly devices.""" + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + def __init__(self, wrapper: RpcDeviceWrapper, id_: int) -> None: """Initialize light.""" super().__init__(wrapper, f"switch:{id_}") diff --git a/homeassistant/components/shelly/logbook.py b/homeassistant/components/shelly/logbook.py index d4278e3e98e..a91f4e1cf56 100644 --- a/homeassistant/components/shelly/logbook.py +++ b/homeassistant/components/shelly/logbook.py @@ -3,6 +3,10 @@ from __future__ import annotations from collections.abc import Callable +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) from homeassistant.const import ATTR_DEVICE_ID from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.typing import EventType @@ -48,8 +52,8 @@ def async_describe_events( input_name = f"{device_name} channel {channel}" return { - "name": "Shelly", - "message": f"'{click_type}' click event for {input_name} Input was fired.", + LOGBOOK_ENTRY_NAME: "Shelly", + LOGBOOK_ENTRY_MESSAGE: f"'{click_type}' click event for {input_name} Input was fired", } async_describe_event(DOMAIN, EVENT_SHELLY_CLICK, async_describe_shelly_click_event) diff --git a/homeassistant/components/shelly/number.py b/homeassistant/components/shelly/number.py index bfac4cd4033..dcedc32602d 100644 --- a/homeassistant/components/shelly/number.py +++ b/homeassistant/components/shelly/number.py @@ -81,7 +81,7 @@ async def async_setup_entry( return if config_entry.data[CONF_SLEEP_PERIOD]: - await async_setup_entry_attribute_entities( + async_setup_entry_attribute_entities( hass, config_entry, async_add_entities, diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 7e19b9724d7..92c19734414 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -394,12 +394,12 @@ async def async_setup_entry( ) -> None: """Set up sensors for device.""" if get_device_entry_gen(config_entry) == 2: - return await async_setup_entry_rpc( + return async_setup_entry_rpc( hass, config_entry, async_add_entities, RPC_SENSORS, RpcSensor ) if config_entry.data[CONF_SLEEP_PERIOD]: - await async_setup_entry_attribute_entities( + async_setup_entry_attribute_entities( hass, config_entry, async_add_entities, @@ -408,7 +408,7 @@ async def async_setup_entry( _build_block_description, ) else: - await async_setup_entry_attribute_entities( + async_setup_entry_attribute_entities( hass, config_entry, async_add_entities, @@ -416,7 +416,7 @@ async def async_setup_entry( BlockSensor, _build_block_description, ) - await async_setup_entry_rest( + async_setup_entry_rest( hass, config_entry, async_add_entities, REST_SENSORS, RestSensor ) diff --git a/homeassistant/components/shelly/switch.py b/homeassistant/components/shelly/switch.py index 9114587a910..d65568d0a2a 100644 --- a/homeassistant/components/shelly/switch.py +++ b/homeassistant/components/shelly/switch.py @@ -29,12 +29,13 @@ async def async_setup_entry( ) -> None: """Set up switches for device.""" if get_device_entry_gen(config_entry) == 2: - return await async_setup_rpc_entry(hass, config_entry, async_add_entities) + return async_setup_rpc_entry(hass, config_entry, async_add_entities) - return await async_setup_block_entry(hass, config_entry, async_add_entities) + return async_setup_block_entry(hass, config_entry, async_add_entities) -async def async_setup_block_entry( +@callback +def async_setup_block_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, @@ -59,7 +60,7 @@ async def async_setup_block_entry( relay_blocks.append(block) unique_id = f"{wrapper.mac}-{block.type}_{block.channel}" - await async_remove_shelly_entity(hass, "light", unique_id) + async_remove_shelly_entity(hass, "light", unique_id) if not relay_blocks: return @@ -67,7 +68,8 @@ async def async_setup_block_entry( async_add_entities(BlockRelaySwitch(wrapper, block) for block in relay_blocks) -async def async_setup_rpc_entry( +@callback +def async_setup_rpc_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, @@ -84,7 +86,7 @@ async def async_setup_rpc_entry( switch_ids.append(id_) unique_id = f"{wrapper.mac}-switch:{id_}" - await async_remove_shelly_entity(hass, "light", unique_id) + async_remove_shelly_entity(hass, "light", unique_id) if not switch_ids: return diff --git a/homeassistant/components/shelly/translations/ca.json b/homeassistant/components/shelly/translations/ca.json index 6430bdcf6a6..a9b1347e62e 100644 --- a/homeassistant/components/shelly/translations/ca.json +++ b/homeassistant/components/shelly/translations/ca.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", + "firmware_not_fully_provisioned": "Dispositiu no totalment aprovisionat. Posa't en contacte amb l'assist\u00e8ncia de Shelly", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/shelly/translations/de.json b/homeassistant/components/shelly/translations/de.json index a8d8bdbdf99..6d4c0e92110 100644 --- a/homeassistant/components/shelly/translations/de.json +++ b/homeassistant/components/shelly/translations/de.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", + "firmware_not_fully_provisioned": "Das Ger\u00e4t ist nicht vollst\u00e4ndig eingerichtet. Bitte kontaktiere den Shelly Support", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/shelly/translations/el.json b/homeassistant/components/shelly/translations/el.json index e83971e34fe..a5680f9343a 100644 --- a/homeassistant/components/shelly/translations/el.json +++ b/homeassistant/components/shelly/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", + "firmware_not_fully_provisioned": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03bb\u03ae\u03c1\u03c9\u03c2 \u03b5\u03c6\u03bf\u03b4\u03b9\u03b1\u03c3\u03bc\u03ad\u03bd\u03b7. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03c4\u03b7\u03c2 Shelly", "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" }, diff --git a/homeassistant/components/shelly/translations/es.json b/homeassistant/components/shelly/translations/es.json index 6f5c86417d4..25cbb8eb30b 100644 --- a/homeassistant/components/shelly/translations/es.json +++ b/homeassistant/components/shelly/translations/es.json @@ -42,7 +42,7 @@ "double": "Pulsaci\u00f3n doble de {subtype}", "double_push": "Pulsaci\u00f3n doble de {subtype}", "long": "Pulsaci\u00f3n larga de {subtype}", - "long_push": "Pulsaci\u00f3n larga de {subtype}", + "long_push": "{subtype} pulsado durante un rato", "long_single": "Pulsaci\u00f3n larga de {subtype} seguida de una pulsaci\u00f3n simple", "single": "Pulsaci\u00f3n simple de {subtype}", "single_long": "Pulsaci\u00f3n simple de {subtype} seguida de una pulsaci\u00f3n larga", diff --git a/homeassistant/components/shelly/translations/et.json b/homeassistant/components/shelly/translations/et.json index 7db0eaad4ac..e248ee9eba4 100644 --- a/homeassistant/components/shelly/translations/et.json +++ b/homeassistant/components/shelly/translations/et.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "\u00dchendamine nurjus", + "firmware_not_fully_provisioned": "Seade pole t\u00e4ielikult toetatud. V\u00f5ta \u00fchendust Shelly toega", "invalid_auth": "Tuvastamine nurjus", "unknown": "Tundmatu viga" }, diff --git a/homeassistant/components/shelly/translations/fr.json b/homeassistant/components/shelly/translations/fr.json index cd19af62bad..3ed78b53b63 100644 --- a/homeassistant/components/shelly/translations/fr.json +++ b/homeassistant/components/shelly/translations/fr.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion", + "firmware_not_fully_provisioned": "L'appareil n'est pas enti\u00e8rement provisionn\u00e9. Veuillez contacter le support Shelly", "invalid_auth": "Authentification non valide", "unknown": "Erreur inattendue" }, diff --git a/homeassistant/components/shelly/translations/hu.json b/homeassistant/components/shelly/translations/hu.json index bfaf591d7c2..18f53ca232d 100644 --- a/homeassistant/components/shelly/translations/hu.json +++ b/homeassistant/components/shelly/translations/hu.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "firmware_not_fully_provisioned": "Az eszk\u00f6z nincs teljesen be\u00fczemelve. K\u00e9rj\u00fck, vegye fel a kapcsolatot a Shelly \u00fcgyf\u00e9lszolg\u00e1lat\u00e1val", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, diff --git a/homeassistant/components/shelly/translations/id.json b/homeassistant/components/shelly/translations/id.json index 9ed75694de0..59af237d5d3 100644 --- a/homeassistant/components/shelly/translations/id.json +++ b/homeassistant/components/shelly/translations/id.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "Gagal terhubung", + "firmware_not_fully_provisioned": "Perangkat belum sepenuhnya disediakan. Hubungi dukungan Shelly.", "invalid_auth": "Autentikasi tidak valid", "unknown": "Kesalahan yang tidak diharapkan" }, diff --git a/homeassistant/components/shelly/translations/it.json b/homeassistant/components/shelly/translations/it.json index c004141cac4..7bd26761ad8 100644 --- a/homeassistant/components/shelly/translations/it.json +++ b/homeassistant/components/shelly/translations/it.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "Impossibile connettersi", + "firmware_not_fully_provisioned": "Dispositivo non completamente supportato. Si prega di contattare il supporto Shelly", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/shelly/translations/ja.json b/homeassistant/components/shelly/translations/ja.json index 12a97c8508a..748e70fd32a 100644 --- a/homeassistant/components/shelly/translations/ja.json +++ b/homeassistant/components/shelly/translations/ja.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "firmware_not_fully_provisioned": "\u30c7\u30d0\u30a4\u30b9\u304c\u5b8c\u5168\u306b\u30d7\u30ed\u30d3\u30b8\u30e7\u30cb\u30f3\u30b0(provisioned)\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002Shelly\u30b5\u30dd\u30fc\u30c8\u306b\u9023\u7d61\u3057\u3066\u304f\u3060\u3055\u3044", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, diff --git a/homeassistant/components/shelly/translations/no.json b/homeassistant/components/shelly/translations/no.json index fe7d30c098f..072d0bf4ee7 100644 --- a/homeassistant/components/shelly/translations/no.json +++ b/homeassistant/components/shelly/translations/no.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "Tilkobling mislyktes", + "firmware_not_fully_provisioned": "Enheten er ikke fullstendig klargjort. Vennligst kontakt Shelly support", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/shelly/translations/pl.json b/homeassistant/components/shelly/translations/pl.json index d1c658d7816..71d7f047e5f 100644 --- a/homeassistant/components/shelly/translations/pl.json +++ b/homeassistant/components/shelly/translations/pl.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "firmware_not_fully_provisioned": "Urz\u0105dzenie nie jest w pe\u0142ni udost\u0119pnione. Skontaktuj si\u0119 z pomoc\u0105 techniczn\u0105 Shelly.", "invalid_auth": "Niepoprawne uwierzytelnienie", "unknown": "Nieoczekiwany b\u0142\u0105d" }, diff --git a/homeassistant/components/shelly/translations/pt-BR.json b/homeassistant/components/shelly/translations/pt-BR.json index 8b8ec5ea020..125334b8d28 100644 --- a/homeassistant/components/shelly/translations/pt-BR.json +++ b/homeassistant/components/shelly/translations/pt-BR.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "Falha ao conectar", + "firmware_not_fully_provisioned": "Dispositivo n\u00e3o totalmente provisionado. Entre em contato com o suporte da Shelly", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, diff --git a/homeassistant/components/shelly/translations/ru.json b/homeassistant/components/shelly/translations/ru.json index d3f38aa9eeb..b80e58aa905 100644 --- a/homeassistant/components/shelly/translations/ru.json +++ b/homeassistant/components/shelly/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.", + "firmware_not_fully_provisioned": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u043d\u0435 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u0432 \u0441\u043b\u0443\u0436\u0431\u0443 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 Shelly", "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." }, diff --git a/homeassistant/components/shelly/translations/tr.json b/homeassistant/components/shelly/translations/tr.json index 0a70a067690..fac805e5134 100644 --- a/homeassistant/components/shelly/translations/tr.json +++ b/homeassistant/components/shelly/translations/tr.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", + "firmware_not_fully_provisioned": "Cihaz tam olarak sa\u011flanmad\u0131. L\u00fctfen Shelly deste\u011fiyle ileti\u015fime ge\u00e7in", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "unknown": "Beklenmeyen hata" }, diff --git a/homeassistant/components/shelly/translations/zh-Hant.json b/homeassistant/components/shelly/translations/zh-Hant.json index 62d1bd18850..a2727fcc933 100644 --- a/homeassistant/components/shelly/translations/zh-Hant.json +++ b/homeassistant/components/shelly/translations/zh-Hant.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", + "firmware_not_fully_provisioned": "\u88dd\u7f6e\u4e26\u672a\u5b8c\u5168\u652f\u63f4\uff0c\u8acb\u806f\u7e6b Shelly \u5c0b\u6c42\u652f\u63f4", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 77c09283fbf..6dfc2fb3be8 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -11,7 +11,7 @@ from aioshelly.rpc_device import RpcDevice from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import device_registry, singleton +from homeassistant.helpers import device_registry, entity_registry, singleton from homeassistant.helpers.typing import EventType from homeassistant.util.dt import utcnow @@ -30,11 +30,12 @@ from .const import ( ) -async def async_remove_shelly_entity( +@callback +def async_remove_shelly_entity( hass: HomeAssistant, domain: str, unique_id: str ) -> None: """Remove a Shelly entity.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = entity_registry.async_get(hass) entity_id = entity_reg.async_get_entity_id(domain, DOMAIN, unique_id) if entity_id: LOGGER.debug("Removing entity: %s", entity_id) diff --git a/homeassistant/components/shiftr/manifest.json b/homeassistant/components/shiftr/manifest.json index e3d27b6b4fc..932571a977e 100644 --- a/homeassistant/components/shiftr/manifest.json +++ b/homeassistant/components/shiftr/manifest.json @@ -3,7 +3,7 @@ "name": "shiftr.io", "documentation": "https://www.home-assistant.io/integrations/shiftr", "requirements": ["paho-mqtt==1.6.1"], - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "cloud_push", "loggers": ["paho"] } diff --git a/homeassistant/components/shopping_list/translations/nl.json b/homeassistant/components/shopping_list/translations/nl.json index e8de5fbae1d..6eb39857046 100644 --- a/homeassistant/components/shopping_list/translations/nl.json +++ b/homeassistant/components/shopping_list/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/sia/translations/ar.json b/homeassistant/components/sia/translations/ar.json index ebf7325b114..54e29ccfa5d 100644 --- a/homeassistant/components/sia/translations/ar.json +++ b/homeassistant/components/sia/translations/ar.json @@ -5,6 +5,5 @@ "title": "\u0625\u0646\u0634\u0627\u0621 \u0627\u062a\u0635\u0627\u0644 \u0644\u0623\u0646\u0638\u0645\u0629 \u0627\u0644\u0625\u0646\u0630\u0627\u0631 \u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0639\u0644\u0649 SIA." } } - }, - "title": "\u0623\u0646\u0638\u0645\u0629 \u0625\u0646\u0630\u0627\u0631 SIA" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/ca.json b/homeassistant/components/sia/translations/ca.json index 904d1542c8f..ed51fec34cd 100644 --- a/homeassistant/components/sia/translations/ca.json +++ b/homeassistant/components/sia/translations/ca.json @@ -45,6 +45,5 @@ "title": "Opcions de configuraci\u00f3 de SIA." } } - }, - "title": "SIA Alarm Systems" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/de.json b/homeassistant/components/sia/translations/de.json index d2c9fc05040..385e19b9c64 100644 --- a/homeassistant/components/sia/translations/de.json +++ b/homeassistant/components/sia/translations/de.json @@ -45,6 +45,5 @@ "title": "Optionen f\u00fcr das SIA-Setup." } } - }, - "title": "SIA Alarmsysteme" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/el.json b/homeassistant/components/sia/translations/el.json index fe565e503a1..e6d2253301a 100644 --- a/homeassistant/components/sia/translations/el.json +++ b/homeassistant/components/sia/translations/el.json @@ -45,6 +45,5 @@ "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SIA." } } - }, - "title": "\u03a3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1 \u03a3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03bf\u03cd SIA" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/en.json b/homeassistant/components/sia/translations/en.json index 50a00a4bf23..9dea235b379 100644 --- a/homeassistant/components/sia/translations/en.json +++ b/homeassistant/components/sia/translations/en.json @@ -45,6 +45,5 @@ "title": "Options for the SIA Setup." } } - }, - "title": "SIA Alarm Systems" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/es.json b/homeassistant/components/sia/translations/es.json index 8e1bb05978d..8b8b7e97f1f 100644 --- a/homeassistant/components/sia/translations/es.json +++ b/homeassistant/components/sia/translations/es.json @@ -45,6 +45,5 @@ "title": "Opciones para la configuraci\u00f3n de SIA." } } - }, - "title": "Sistemas de alarma SIA" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/et.json b/homeassistant/components/sia/translations/et.json index 931718d78d8..ff1f73d217e 100644 --- a/homeassistant/components/sia/translations/et.json +++ b/homeassistant/components/sia/translations/et.json @@ -45,6 +45,5 @@ "title": "SIA seadistuse valikud." } } - }, - "title": "SIA Alarm Systems" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/fr.json b/homeassistant/components/sia/translations/fr.json index 843c707ce19..e3f36f8930c 100644 --- a/homeassistant/components/sia/translations/fr.json +++ b/homeassistant/components/sia/translations/fr.json @@ -45,6 +45,5 @@ "title": "Options pour la configuration SIA." } } - }, - "title": "Syst\u00e8mes d'alarme SIA" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/hu.json b/homeassistant/components/sia/translations/hu.json index 6a5c609e1f6..63a5208a44c 100644 --- a/homeassistant/components/sia/translations/hu.json +++ b/homeassistant/components/sia/translations/hu.json @@ -45,6 +45,5 @@ "title": "A SIA be\u00e1ll\u00edt\u00e1si lehet\u0151s\u00e9gek." } } - }, - "title": "SIA riaszt\u00f3rendszerek" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/id.json b/homeassistant/components/sia/translations/id.json index 4c80d089cbe..b9c927997f3 100644 --- a/homeassistant/components/sia/translations/id.json +++ b/homeassistant/components/sia/translations/id.json @@ -45,6 +45,5 @@ "title": "Opsi untuk Pengaturan SIA." } } - }, - "title": "Sistem Alarm SIA" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/it.json b/homeassistant/components/sia/translations/it.json index 977b316c9a5..f3e1b02c55a 100644 --- a/homeassistant/components/sia/translations/it.json +++ b/homeassistant/components/sia/translations/it.json @@ -45,6 +45,5 @@ "title": "Opzioni per l'impostazione SIA." } } - }, - "title": "Sistemi di allarme SIA" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/ja.json b/homeassistant/components/sia/translations/ja.json index 9286548ddb4..7e653b1e348 100644 --- a/homeassistant/components/sia/translations/ja.json +++ b/homeassistant/components/sia/translations/ja.json @@ -45,6 +45,5 @@ "title": "SIA\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3002" } } - }, - "title": "SIA\u30a2\u30e9\u30fc\u30e0\u30b7\u30b9\u30c6\u30e0" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/ko.json b/homeassistant/components/sia/translations/ko.json new file mode 100644 index 00000000000..cb0897c7873 --- /dev/null +++ b/homeassistant/components/sia/translations/ko.json @@ -0,0 +1,12 @@ +{ + "config": { + "error": { + "invalid_account_format": "\uacc4\uc815\uc774 16\uc9c4\uc218 \uac12\uc774 \uc544\ub2d9\ub2c8\ub2e4. 0-9 \ubc0f A-F\ub9cc \uc0ac\uc6a9\ud558\uc2ed\uc2dc\uc624.", + "invalid_account_length": "\uacc4\uc815\uc758 \uae38\uc774\uac00 \uc801\uc808\uce58 \uc54a\uc2b5\ub2c8\ub2e4. 3~16\uc790 \uc0ac\uc774\uc5ec\uc57c \ud569\ub2c8\ub2e4.", + "invalid_key_format": "\ud0a4\uac12\uc774 16\uc9c4\uc218 \uac12\uc774 \uc544\ub2d9\ub2c8\ub2e4. 0-9 \ubc0f A-F\ub9cc \uc0ac\uc6a9\ud558\uc2ed\uc2dc\uc624.", + "invalid_key_length": "\ud0a4\uac12\uc758 \uae38\uc774\uac00 \uc801\uc808\uce58 \uc54a\uc2b5\ub2c8\ub2e4. 16, 24 \ub610\ub294 32\uac1c\uc758 16\uc9c4\uc218\ubb38\uc790\uc5ec\uc57c \ub429\ub2c8\ub2e4.", + "invalid_ping": "\ud551 \uac04\uaca9\uc740 1\ubd84\uc5d0\uc11c 1440\ubd84 \uc0ac\uc774\uc5ec\uc57c \ud569\ub2c8\ub2e4.", + "invalid_zones": "\ucd5c\uc18c\ud55c 1\uac1c\uc758 \uc601\uc5ed\uc774 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sia/translations/nl.json b/homeassistant/components/sia/translations/nl.json index 8afc0b88651..0b3790d53e2 100644 --- a/homeassistant/components/sia/translations/nl.json +++ b/homeassistant/components/sia/translations/nl.json @@ -26,7 +26,7 @@ "additional_account": "Extra accounts", "encryption_key": "Encryptiesleutel", "ping_interval": "Ping Interval (min)", - "port": "Port", + "port": "Poort", "protocol": "Protocol", "zones": "Aantal zones voor het account" }, @@ -45,6 +45,5 @@ "title": "Opties voor de SIA Setup." } } - }, - "title": "SIA Alarm Systems" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/no.json b/homeassistant/components/sia/translations/no.json index 61ce61b7ee5..7f8cee998e5 100644 --- a/homeassistant/components/sia/translations/no.json +++ b/homeassistant/components/sia/translations/no.json @@ -45,6 +45,5 @@ "title": "Alternativer for SIA-oppsett." } } - }, - "title": "SIA Alarm Systems" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/pl.json b/homeassistant/components/sia/translations/pl.json index d41281519d4..8a7f5342e3f 100644 --- a/homeassistant/components/sia/translations/pl.json +++ b/homeassistant/components/sia/translations/pl.json @@ -45,6 +45,5 @@ "title": "Opcje dla konfiguracji SIA." } } - }, - "title": "Systemy alarmowe SIA" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/pt-BR.json b/homeassistant/components/sia/translations/pt-BR.json index a113717859f..55738b7728e 100644 --- a/homeassistant/components/sia/translations/pt-BR.json +++ b/homeassistant/components/sia/translations/pt-BR.json @@ -45,6 +45,5 @@ "title": "Op\u00e7\u00f5es para a configura\u00e7\u00e3o SIA." } } - }, - "title": "Sistemas de alarme SIA" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/ru.json b/homeassistant/components/sia/translations/ru.json index f7cfd4b5152..807a7a80877 100644 --- a/homeassistant/components/sia/translations/ru.json +++ b/homeassistant/components/sia/translations/ru.json @@ -45,6 +45,5 @@ "title": "SIA Alarm Systems" } } - }, - "title": "SIA Alarm Systems" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/sk.json b/homeassistant/components/sia/translations/sk.json index 892b8b2cd91..d703643c7de 100644 --- a/homeassistant/components/sia/translations/sk.json +++ b/homeassistant/components/sia/translations/sk.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "port": "Port" + "port": "Port", + "protocol": "Protokol" } } } diff --git a/homeassistant/components/sia/translations/tr.json b/homeassistant/components/sia/translations/tr.json index 8d5a1d9dbcb..2fa88f6fbc6 100644 --- a/homeassistant/components/sia/translations/tr.json +++ b/homeassistant/components/sia/translations/tr.json @@ -45,6 +45,5 @@ "title": "SIA Kurulumu i\u00e7in se\u00e7enekler." } } - }, - "title": "SIA Alarm Sistemleri" + } } \ No newline at end of file diff --git a/homeassistant/components/sia/translations/zh-Hant.json b/homeassistant/components/sia/translations/zh-Hant.json index 81a0ad0fab4..310099e4882 100644 --- a/homeassistant/components/sia/translations/zh-Hant.json +++ b/homeassistant/components/sia/translations/zh-Hant.json @@ -45,6 +45,5 @@ "title": "SIA \u8a2d\u5b9a\u9078\u9805" } } - }, - "title": "SIA \u8b66\u5831\u7cfb\u7d71" + } } \ No newline at end of file diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index 3afe8052e03..e87e1d37304 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.1.0", "simplehound==0.3"], + "requirements": ["pillow==9.1.1", "simplehound==0.3"], "codeowners": ["@robmarkcole"], "iot_class": "cloud_polling", "loggers": ["simplehound"] diff --git a/homeassistant/components/simplisafe/config_flow.py b/homeassistant/components/simplisafe/config_flow.py index 926f904a912..14afc743b23 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -20,7 +20,7 @@ from homeassistant.helpers import aiohttp_client, config_validation as cv from .const import DOMAIN, LOGGER DEFAULT_EMAIL_2FA_SLEEP = 3 -DEFAULT_EMAIL_2FA_TIMEOUT = 300 +DEFAULT_EMAIL_2FA_TIMEOUT = 600 STEP_REAUTH_SCHEMA = vol.Schema( { diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index cb1b02e37ae..f62da735f92 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.05.1"], + "requirements": ["simplisafe-python==2022.05.2"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "dhcp": [ diff --git a/homeassistant/components/simplisafe/translations/bg.json b/homeassistant/components/simplisafe/translations/bg.json index 9d32e18ae5c..da70697442c 100644 --- a/homeassistant/components/simplisafe/translations/bg.json +++ b/homeassistant/components/simplisafe/translations/bg.json @@ -4,10 +4,12 @@ "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": { - "identifier_exists": "\u041f\u0440\u043e\u0444\u0438\u043b\u044a\u0442 \u0435 \u0432\u0435\u0447\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0430\u043d", "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" }, + "progress": { + "email_2fa": "\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0430\u0442\u0430 \u0441\u0438 \u043f\u043e\u0449\u0430 \u0437\u0430 \u0432\u0440\u044a\u0437\u043a\u0430 \u0437\u0430 \u043f\u043e\u0442\u0432\u044a\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043e\u0442 Simplisafe." + }, "step": { "reauth_confirm": { "data": { @@ -15,13 +17,16 @@ }, "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" }, + "sms_2fa": { + "data": { + "code": "\u041a\u043e\u0434" + } + }, "user": { "data": { - "auth_code": "\u041a\u043e\u0434 \u0437\u0430 \u043e\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f", "password": "\u041f\u0430\u0440\u043e\u043b\u0430", "username": "E-mail \u0430\u0434\u0440\u0435\u0441" - }, - "title": "\u041f\u043e\u043f\u044a\u043b\u043d\u0435\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u0442\u0430 \u0441\u0438" + } } } } diff --git a/homeassistant/components/simplisafe/translations/ca.json b/homeassistant/components/simplisafe/translations/ca.json index 7c590a052b1..a2fd356932c 100644 --- a/homeassistant/components/simplisafe/translations/ca.json +++ b/homeassistant/components/simplisafe/translations/ca.json @@ -2,35 +2,36 @@ "config": { "abort": { "already_configured": "Aquest compte SimpliSafe ja est\u00e0 en \u00fas.", - "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament", - "wrong_account": "Les credencials d'usuari proporcionades no coincideixen amb les d'aquest compte SimpliSafe." + "email_2fa_timed_out": "S'ha esgotat el temps d'espera de l'autenticaci\u00f3 de dos factors a trav\u00e9s de correu electr\u00f2nic.", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { - "identifier_exists": "Aquest compte ja est\u00e0 registrat", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", - "still_awaiting_mfa": "Esperant clic de l'enlla\u00e7 del correu MFA", "unknown": "Error inesperat" }, + "progress": { + "email_2fa": "Mira el correu electr\u00f2nic on hauries de trobar l'enlla\u00e7 de verificaci\u00f3 de Simplisafe." + }, "step": { - "mfa": { - "title": "Autenticaci\u00f3 multi-factor SimpliSafe" - }, "reauth_confirm": { "data": { "password": "Contrasenya" }, - "description": "L'acc\u00e9s ha caducat o ha estat revocat. Introdueix la contrasenya per tornar a vincular el compte.", + "description": "Torna a introduir la contrasenya de {username}", "title": "Reautenticaci\u00f3 de la integraci\u00f3" }, + "sms_2fa": { + "data": { + "code": "Codi" + }, + "description": "Introdueix el codi d'autenticaci\u00f3 de dos factors que s'ha enviat per SMS." + }, "user": { "data": { - "auth_code": "Codi d'autoritzaci\u00f3", - "code": "Codi (utilitzat a la UI de Home Assistant)", "password": "Contrasenya", - "username": "Correu electr\u00f2nic" + "username": "Nom d'usuari" }, - "description": "SimpliSafe s'autentica amb Home Assistant a trav\u00e9s de la seva aplicaci\u00f3 web. A causa de les limitacions t\u00e8cniques, hi ha un pas manual al final d'aquest proc\u00e9s; assegura't de llegir la [documentaci\u00f3]({docs_url}) abans de comen\u00e7ar.\n\n1. Fes clic [aqu\u00ed]({url}) per obrir l'aplicaci\u00f3 web de SimpliSafe i introdueix les teves credencials.\n\n2. Quan el proc\u00e9s d'inici de sessi\u00f3 s'hagi completat, torna aqu\u00ed i introdueix a sota el codi d'autoritzaci\u00f3.", - "title": "Introdueix la teva informaci\u00f3" + "description": "Introdueix el teu nom d'usuari i contrasenya." } } }, diff --git a/homeassistant/components/simplisafe/translations/cs.json b/homeassistant/components/simplisafe/translations/cs.json index 152c0282216..520dcc2567e 100644 --- a/homeassistant/components/simplisafe/translations/cs.json +++ b/homeassistant/components/simplisafe/translations/cs.json @@ -5,14 +5,10 @@ "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { - "identifier_exists": "\u00da\u010det je ji\u017e zaregistrov\u00e1n", "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "step": { - "mfa": { - "title": "V\u00edcefaktorov\u00e9 ov\u011b\u0159ov\u00e1n\u00ed SimpliSafe" - }, "reauth_confirm": { "data": { "password": "Heslo" @@ -22,12 +18,9 @@ }, "user": { "data": { - "auth_code": "Autoriza\u010dn\u00ed k\u00f3d", - "code": "K\u00f3d (pou\u017eit\u00fd v u\u017eivatelsk\u00e9m rozhran\u00ed Home Assistant)", "password": "Heslo", "username": "E-mail" - }, - "title": "Vypl\u0148te sv\u00e9 \u00fadaje." + } } } }, diff --git a/homeassistant/components/simplisafe/translations/da.json b/homeassistant/components/simplisafe/translations/da.json index a5119762f90..8133eee3ec9 100644 --- a/homeassistant/components/simplisafe/translations/da.json +++ b/homeassistant/components/simplisafe/translations/da.json @@ -3,16 +3,12 @@ "abort": { "already_configured": "Denne SimpliSafe-konto er allerede i brug." }, - "error": { - "identifier_exists": "Konto er allerede registreret" - }, "step": { "user": { "data": { "password": "Adgangskode", "username": "Emailadresse" - }, - "title": "Udfyld dine oplysninger" + } } } } diff --git a/homeassistant/components/simplisafe/translations/de.json b/homeassistant/components/simplisafe/translations/de.json index 51083565770..d224f3c2441 100644 --- a/homeassistant/components/simplisafe/translations/de.json +++ b/homeassistant/components/simplisafe/translations/de.json @@ -2,20 +2,17 @@ "config": { "abort": { "already_configured": "Dieses SimpliSafe-Konto wird bereits verwendet.", - "reauth_successful": "Die erneute Authentifizierung war erfolgreich", - "wrong_account": "Die angegebenen Benutzeranmeldeinformationen stimmen nicht mit diesem SimpliSafe-Konto \u00fcberein." + "email_2fa_timed_out": "Zeit\u00fcberschreitung beim Warten auf E-Mail-basierte Zwei-Faktor-Authentifizierung.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "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" }, + "progress": { + "email_2fa": "\u00dcberpr\u00fcfe deine E-Mails auf einen Best\u00e4tigungslink von Simplisafe." + }, "step": { - "mfa": { - "title": "SimpliSafe Multi-Faktor-Authentifizierung" - }, "reauth_confirm": { "data": { "password": "Passwort" @@ -31,13 +28,10 @@ }, "user": { "data": { - "auth_code": "Autorisierungscode", - "code": "Code (wird in der Benutzeroberfl\u00e4che von Home Assistant verwendet)", "password": "Passwort", "username": "Benutzername" }, - "description": "Gib deinen Benutzernamen und Passwort ein.", - "title": "Gib deine Informationen ein" + "description": "Gib deinen Benutzernamen und Passwort ein." } } }, diff --git a/homeassistant/components/simplisafe/translations/el.json b/homeassistant/components/simplisafe/translations/el.json index 880097c6cef..d852664ce38 100644 --- a/homeassistant/components/simplisafe/translations/el.json +++ b/homeassistant/components/simplisafe/translations/el.json @@ -2,20 +2,17 @@ "config": { "abort": { "already_configured": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 SimpliSafe \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7.", - "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", - "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." + "email_2fa_timed_out": "\u03a4\u03bf \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf \u03ad\u03bb\u03b7\u03be\u03b5 \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 \u03c0\u03bf\u03c5 \u03b2\u03b1\u03c3\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 email.", + "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": { - "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" }, + "progress": { + "email_2fa": "\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\n\u03c0\u03bf\u03c5 \u03c3\u03b1\u03c2 \u03b5\u03c3\u03c4\u03ac\u03bb\u03b7 \u03bc\u03ad\u03c3\u03c9 email." + }, "step": { - "mfa": { - "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": { "data": { "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" @@ -31,13 +28,10 @@ }, "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", - "code": "\u039a\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2 (\u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf UI \u03c4\u03bf\u03c5 Home Assistant)", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "Email" }, - "description": "\u03a4\u03bf SimpliSafe \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 SimpliSafe web. \u039b\u03cc\u03b3\u03c9 \u03c4\u03b5\u03c7\u03bd\u03b9\u03ba\u03ce\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd, \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ad\u03bd\u03b1 \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf \u03b2\u03ae\u03bc\u03b1 \u03c3\u03c4\u03bf \u03c4\u03ad\u03bb\u03bf\u03c2 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1\u03c2- \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b4\u03b9\u03b1\u03b2\u03ac\u03c3\u03b5\u03b9 \u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03c0\u03c1\u03b9\u03bd \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5.\n\n1. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf [\u03b5\u03b4\u03ce]({url}) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae SimpliSafe web \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2.\n\n2. \u038c\u03c4\u03b1\u03bd \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2, \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03ba\u03b1\u03b9 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", - "title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2" + "description": "\u03a4\u03bf SimpliSafe \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf Home Assistant \u03bc\u03ad\u03c3\u03c9 \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 SimpliSafe web. \u039b\u03cc\u03b3\u03c9 \u03c4\u03b5\u03c7\u03bd\u03b9\u03ba\u03ce\u03bd \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd, \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ad\u03bd\u03b1 \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03bf \u03b2\u03ae\u03bc\u03b1 \u03c3\u03c4\u03bf \u03c4\u03ad\u03bb\u03bf\u03c2 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1\u03c2- \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b4\u03b9\u03b1\u03b2\u03ac\u03c3\u03b5\u03b9 \u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7]({docs_url}) \u03c0\u03c1\u03b9\u03bd \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5.\n\n1. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf [\u03b5\u03b4\u03ce]({url}) \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae SimpliSafe web \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2.\n\n2. \u038c\u03c4\u03b1\u03bd \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af \u03b7 \u03b4\u03b9\u03b1\u03b4\u03b9\u03ba\u03b1\u03c3\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2, \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03ba\u03b1\u03b9 \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2." } } }, diff --git a/homeassistant/components/simplisafe/translations/es-419.json b/homeassistant/components/simplisafe/translations/es-419.json index 7cdd07029eb..868d4d4f53d 100644 --- a/homeassistant/components/simplisafe/translations/es-419.json +++ b/homeassistant/components/simplisafe/translations/es-419.json @@ -3,17 +3,12 @@ "abort": { "already_configured": "Esta cuenta SimpliSafe ya est\u00e1 en uso." }, - "error": { - "identifier_exists": "Cuenta ya registrada" - }, "step": { "user": { "data": { - "code": "C\u00f3digo (utilizado en la interfaz de usuario de Home Assistant)", "password": "Contrase\u00f1a", "username": "Direcci\u00f3n de correo electr\u00f3nico" - }, - "title": "Completa tu informaci\u00f3n" + } } } }, diff --git a/homeassistant/components/simplisafe/translations/es.json b/homeassistant/components/simplisafe/translations/es.json index d4b066890f1..44716b87cec 100644 --- a/homeassistant/components/simplisafe/translations/es.json +++ b/homeassistant/components/simplisafe/translations/es.json @@ -2,35 +2,35 @@ "config": { "abort": { "already_configured": "Esta cuenta SimpliSafe ya est\u00e1 en uso.", - "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente", - "wrong_account": "Las credenciales de usuario proporcionadas no coinciden con esta cuenta de SimpliSafe." + "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { - "identifier_exists": "Cuenta ya registrada", "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", - "still_awaiting_mfa": "Esperando todav\u00eda el clic en el correo electr\u00f3nico de MFA", "unknown": "Error inesperado" }, + "progress": { + "email_2fa": "Mira el correo electr\u00f3nico donde deber\u00edas encontrar el enlace de verificaci\u00f3n de Simplisafe." + }, "step": { - "mfa": { - "title": "Autenticaci\u00f3n Multi-Factor SimpliSafe" - }, "reauth_confirm": { "data": { "password": "Contrase\u00f1a" }, - "description": "Tu token de acceso ha expirado o ha sido revocado. Introduce tu contrase\u00f1a para volver a vincular tu cuenta.", - "title": "Volver a vincular la Cuenta SimpliSafe" + "description": "Vuelve a introducir la contrase\u00f1a de {username}", + "title": "Reautenticaci\u00f3n de la integraci\u00f3n" + }, + "sms_2fa": { + "data": { + "code": "C\u00f3digo" + }, + "description": "Introduce el c\u00f3digo de autenticaci\u00f3n de dos factores enviado por SMS." }, "user": { "data": { - "auth_code": "C\u00f3digo de Autorizaci\u00f3n", - "code": "C\u00f3digo (utilizado en el interfaz de usuario de Home Assistant)", "password": "Contrase\u00f1a", "username": "Correo electr\u00f3nico" }, - "description": "SimpliSafe se autentifica con Home Assistant a trav\u00e9s de la aplicaci\u00f3n web de SimpliSafe. Debido a limitaciones t\u00e9cnicas, hay un paso manual al final de este proceso; aseg\u00farese de leer la [documentaci\u00f3n]({docs_url}) antes de empezar.\n\n1. Haga clic en [aqu\u00ed]({url}) para abrir la aplicaci\u00f3n web de SimpliSafe e introduzca sus credenciales.\n\n2. Cuando el proceso de inicio de sesi\u00f3n haya finalizado, vuelva aqu\u00ed e introduzca el c\u00f3digo de autorizaci\u00f3n que aparece a continuaci\u00f3n.", - "title": "Introduce tu informaci\u00f3n" + "description": "SimpliSafe se autentifica con Home Assistant a trav\u00e9s de la aplicaci\u00f3n web de SimpliSafe. Debido a limitaciones t\u00e9cnicas, hay un paso manual al final de este proceso; aseg\u00farese de leer la [documentaci\u00f3n]({docs_url}) antes de empezar.\n\n1. Haga clic en [aqu\u00ed]({url}) para abrir la aplicaci\u00f3n web de SimpliSafe e introduzca sus credenciales.\n\n2. Cuando el proceso de inicio de sesi\u00f3n haya finalizado, vuelva aqu\u00ed e introduzca el c\u00f3digo de autorizaci\u00f3n que aparece a continuaci\u00f3n." } } }, diff --git a/homeassistant/components/simplisafe/translations/et.json b/homeassistant/components/simplisafe/translations/et.json index 83c441de9f9..0f01f4d9b9c 100644 --- a/homeassistant/components/simplisafe/translations/et.json +++ b/homeassistant/components/simplisafe/translations/et.json @@ -2,20 +2,17 @@ "config": { "abort": { "already_configured": "See SimpliSafe'i konto on juba kasutusel.", - "reauth_successful": "Taastuvastamine \u00f5nnestus", - "wrong_account": "Esitatud kasutaja mandaadid ei \u00fchti selle SimpliSafe kontoga." + "email_2fa_timed_out": "Meilip\u00f5hise kahefaktorilise autentimise ajal\u00f5pp.", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "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" }, + "progress": { + "email_2fa": "Kontrolli oma meili Simplisafe'i kinnituslingi saamiseks." + }, "step": { - "mfa": { - "title": "SimpliSafe mitmeastmeline autentimine" - }, "reauth_confirm": { "data": { "password": "Salas\u00f5na" @@ -31,13 +28,10 @@ }, "user": { "data": { - "auth_code": "Tuvastuskood", - "code": "Kood (kasutatakse Home Assistant'i kasutajaliideses)", "password": "Salas\u00f5na", "username": "Kasutajanimi" }, - "description": "Sisesta kasutajatunnus ja salas\u00f5na", - "title": "Sisesta oma teave." + "description": "Sisesta kasutajatunnus ja salas\u00f5na" } } }, diff --git a/homeassistant/components/simplisafe/translations/fi.json b/homeassistant/components/simplisafe/translations/fi.json index d3d18987b44..47a52e9a0c6 100644 --- a/homeassistant/components/simplisafe/translations/fi.json +++ b/homeassistant/components/simplisafe/translations/fi.json @@ -1,15 +1,11 @@ { "config": { - "error": { - "identifier_exists": "Tili on jo rekister\u00f6ity" - }, "step": { "user": { "data": { "password": "Salasana", "username": "S\u00e4hk\u00f6postiosoite" - }, - "title": "T\u00e4yt\u00e4 tietosi." + } } } } diff --git a/homeassistant/components/simplisafe/translations/fr.json b/homeassistant/components/simplisafe/translations/fr.json index 5ed837d405a..4de2af95885 100644 --- a/homeassistant/components/simplisafe/translations/fr.json +++ b/homeassistant/components/simplisafe/translations/fr.json @@ -2,20 +2,17 @@ "config": { "abort": { "already_configured": "Ce compte SimpliSafe est d\u00e9j\u00e0 utilis\u00e9.", - "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", - "wrong_account": "Les informations d'identification d'utilisateur fournies ne correspondent pas \u00e0 ce compte SimpliSafe." + "email_2fa_timed_out": "D\u00e9lai d'attente de l'authentification \u00e0 deux facteurs par courriel expir\u00e9.", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "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" }, + "progress": { + "email_2fa": "Vous devriez recevoir un lien de v\u00e9rification par courriel envoy\u00e9 par Simplisafe." + }, "step": { - "mfa": { - "title": "Authentification multi facteur SimpliSafe" - }, "reauth_confirm": { "data": { "password": "Mot de passe" @@ -31,13 +28,10 @@ }, "user": { "data": { - "auth_code": "Code d'autorisation", - "code": "Code (utilis\u00e9 dans l'interface Home Assistant)", "password": "Mot de passe", "username": "Nom d'utilisateur" }, - "description": "Saisissez votre nom d'utilisateur et votre mot de passe.", - "title": "Veuillez saisir vos informations" + "description": "Saisissez votre nom d'utilisateur et votre mot de passe." } } }, diff --git a/homeassistant/components/simplisafe/translations/hu.json b/homeassistant/components/simplisafe/translations/hu.json index e1b8769f4b6..12f745b3b69 100644 --- a/homeassistant/components/simplisafe/translations/hu.json +++ b/homeassistant/components/simplisafe/translations/hu.json @@ -2,20 +2,17 @@ "config": { "abort": { "already_configured": "Ez a SimpliSafe-fi\u00f3k m\u00e1r haszn\u00e1latban van.", - "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.", - "wrong_account": "A megadott felhaszn\u00e1l\u00f3i hiteles\u00edt\u0151 adatok nem j\u00f3k ehhez a SimpliSafe fi\u00f3khoz." + "email_2fa_timed_out": "Az e-mail alap\u00fa k\u00e9tfaktoros hiteles\u00edt\u00e9sre val\u00f3 v\u00e1rakoz\u00e1s ideje lej\u00e1rt", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." }, "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" }, + "progress": { + "email_2fa": "Ellen\u0151rizze az e-mailj\u00e9t a Simplisafe \u00e1ltal k\u00fcld\u00f6tt ellen\u0151rz\u0151 link\u00e9rt." + }, "step": { - "mfa": { - "title": "SimpliSafe t\u00f6bbt\u00e9nyez\u0151s hiteles\u00edt\u00e9s" - }, "reauth_confirm": { "data": { "password": "Jelsz\u00f3" @@ -31,13 +28,10 @@ }, "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": "Felhaszn\u00e1l\u00f3n\u00e9v" }, - "description": "Adja meg felhaszn\u00e1l\u00f3nev\u00e9t \u00e9s jelszav\u00e1t.", - "title": "T\u00f6ltse ki az adatait" + "description": "Adja meg felhaszn\u00e1l\u00f3nev\u00e9t \u00e9s jelszav\u00e1t." } } }, diff --git a/homeassistant/components/simplisafe/translations/id.json b/homeassistant/components/simplisafe/translations/id.json index ad0ad01be63..68da3b7689b 100644 --- a/homeassistant/components/simplisafe/translations/id.json +++ b/homeassistant/components/simplisafe/translations/id.json @@ -2,20 +2,17 @@ "config": { "abort": { "already_configured": "Akun SimpliSafe ini sudah digunakan.", - "reauth_successful": "Autentikasi ulang berhasil", - "wrong_account": "Kredensial pengguna yang diberikan tidak cocok dengan akun SimpliSafe ini." + "email_2fa_timed_out": "Tenggang waktu habis ketika menunggu autentikasi dua faktor berbasis email.", + "reauth_successful": "Autentikasi ulang berhasil" }, "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" }, + "progress": { + "email_2fa": "Periksa email Anda untuk tautan verifikasi dari Simplisafe." + }, "step": { - "mfa": { - "title": "Autentikasi Multi-Faktor SimpliSafe" - }, "reauth_confirm": { "data": { "password": "Kata Sandi" @@ -31,13 +28,10 @@ }, "user": { "data": { - "auth_code": "Kode Otorisasi", - "code": "Kode (digunakan di antarmuka Home Assistant)", "password": "Kata Sandi", "username": "Nama Pengguna" }, - "description": "Masukkan nama pengguna dan kata sandi Anda.", - "title": "Isi informasi Anda." + "description": "Masukkan nama pengguna dan kata sandi Anda." } } }, diff --git a/homeassistant/components/simplisafe/translations/it.json b/homeassistant/components/simplisafe/translations/it.json index ad9e491ab9a..b9caae3f0a4 100644 --- a/homeassistant/components/simplisafe/translations/it.json +++ b/homeassistant/components/simplisafe/translations/it.json @@ -2,20 +2,17 @@ "config": { "abort": { "already_configured": "Questo account SimpliSafe \u00e8 gi\u00e0 in uso.", - "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", - "wrong_account": "Le credenziali utente fornite non corrispondono a questo account SimpliSafe." + "email_2fa_timed_out": "Timeout durante l'attesa dell'autenticazione a due fattori basata su email.", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "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" }, + "progress": { + "email_2fa": "Verifica la presenza nella tua email di un collegamento di verifica da Simplisafe." + }, "step": { - "mfa": { - "title": "Autenticazione a pi\u00f9 fattori (MFA) SimpliSafe " - }, "reauth_confirm": { "data": { "password": "Password" @@ -31,13 +28,10 @@ }, "user": { "data": { - "auth_code": "Codice di autorizzazione", - "code": "Codice (utilizzato nell'Interfaccia Utente di Home Assistant)", "password": "Password", "username": "Nome utente" }, - "description": "Digita il tuo nome utente e password.", - "title": "Inserisci le tue informazioni." + "description": "Digita il tuo nome utente e password." } } }, diff --git a/homeassistant/components/simplisafe/translations/ja.json b/homeassistant/components/simplisafe/translations/ja.json index 74b0b20c65e..57909d30f69 100644 --- a/homeassistant/components/simplisafe/translations/ja.json +++ b/homeassistant/components/simplisafe/translations/ja.json @@ -2,19 +2,17 @@ "config": { "abort": { "already_configured": "\u3053\u306eSimpliSafe account\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002", - "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f", - "wrong_account": "\u63d0\u4f9b\u3055\u308c\u305f\u30e6\u30fc\u30b6\u30fc\u8a8d\u8a3c\u60c5\u5831\u304c\u3001\u3053\u306eSimpliSafe\u30a2\u30ab\u30a6\u30f3\u30c8\u3068\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002" + "email_2fa_timed_out": "\u96fb\u5b50\u30e1\u30fc\u30eb\u306b\u3088\u308b2\u8981\u7d20\u8a8d\u8a3c\u306e\u5f85\u6a5f\u4e2d\u306b\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u3057\u307e\u3057\u305f\u3002", + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" }, "error": { - "identifier_exists": "\u30a2\u30ab\u30a6\u30f3\u30c8\u767b\u9332\u6e08\u307f", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", - "still_awaiting_mfa": "MFA email click\u3092\u307e\u3060\u5f85\u3063\u3066\u3044\u307e\u3059", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, + "progress": { + "email_2fa": "Simplisafe\u304b\u3089\u306e\u78ba\u8a8d\u30ea\u30f3\u30af\u304c\u306a\u3044\u304b\u30e1\u30fc\u30eb\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + }, "step": { - "mfa": { - "title": "SimpliSafe\u591a\u8981\u7d20\u8a8d\u8a3c" - }, "reauth_confirm": { "data": { "password": "\u30d1\u30b9\u30ef\u30fc\u30c9" @@ -22,15 +20,18 @@ "description": "\u30a2\u30af\u30bb\u30b9\u306e\u6709\u52b9\u671f\u9650\u304c\u5207\u308c\u3066\u3044\u308b\u304b\u3001\u53d6\u308a\u6d88\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u30ea\u30f3\u30af\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" }, + "sms_2fa": { + "data": { + "code": "\u30b3\u30fc\u30c9" + }, + "description": "SMS\u3067\u9001\u3089\u308c\u3066\u304d\u305f2\u8981\u7d20\u8a8d\u8a3c\u30b3\u30fc\u30c9\u3092\u5165\u529b\u3057\u307e\u3059\u3002" + }, "user": { "data": { - "auth_code": "\u8a8d\u8a3c\u30b3\u30fc\u30c9", - "code": "\u30b3\u30fc\u30c9(Home Assistant UI\u3067\u4f7f\u7528)", "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", "username": "E\u30e1\u30fc\u30eb" }, - "description": "2021\u5e74\u3088\u308a\u3001SimpliSafe\u306fWeb\u30a2\u30d7\u30ea\u306b\u3088\u308b\u65b0\u3057\u3044\u8a8d\u8a3c\u6a5f\u69cb\u306b\u79fb\u884c\u3057\u307e\u3057\u305f\u3002\u6280\u8853\u7684\u306a\u5236\u9650\u306e\u305f\u3081\u3001\u3053\u306e\u30d7\u30ed\u30bb\u30b9\u306e\u6700\u5f8c\u306b\u624b\u52d5\u3067\u306e\u624b\u9806\u304c\u3042\u308a\u307e\u3059\u3002\u958b\u59cb\u3059\u308b\u524d\u306b\u3001[\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8](http://home-assistant.io/integrations/simplisafe#getting-an-authorization-code)\u3092\u5fc5\u305a\u304a\u8aad\u307f\u304f\u3060\u3055\u3044\u3002\n\n\u6e96\u5099\u304c\u3067\u304d\u305f\u3089\u3001[\u3053\u3053]({url}) \u3092\u30af\u30ea\u30c3\u30af\u3057\u3066SimpliSafe\u306eWeb\u30a2\u30d7\u30ea\u3092\u958b\u304d\u3001\u8a8d\u8a3c\u60c5\u5831\u3092\u5165\u529b\u3057\u307e\u3059\u3002\u51e6\u7406\u304c\u5b8c\u4e86\u3057\u305f\u3089\u3001\u3053\u3053\u306b\u623b\u3063\u3066\u304d\u3066\u9001\u4fe1(submit) \u3092\u30af\u30ea\u30c3\u30af\u3057\u307e\u3059\u3002", - "title": "\u3042\u306a\u305f\u306e\u60c5\u5831\u3092\u8a18\u5165\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + "description": "2021\u5e74\u3088\u308a\u3001SimpliSafe\u306fWeb\u30a2\u30d7\u30ea\u306b\u3088\u308b\u65b0\u3057\u3044\u8a8d\u8a3c\u6a5f\u69cb\u306b\u79fb\u884c\u3057\u307e\u3057\u305f\u3002\u6280\u8853\u7684\u306a\u5236\u9650\u306e\u305f\u3081\u3001\u3053\u306e\u30d7\u30ed\u30bb\u30b9\u306e\u6700\u5f8c\u306b\u624b\u52d5\u3067\u306e\u624b\u9806\u304c\u3042\u308a\u307e\u3059\u3002\u958b\u59cb\u3059\u308b\u524d\u306b\u3001[\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8](http://home-assistant.io/integrations/simplisafe#getting-an-authorization-code)\u3092\u5fc5\u305a\u304a\u8aad\u307f\u304f\u3060\u3055\u3044\u3002\n\n\u6e96\u5099\u304c\u3067\u304d\u305f\u3089\u3001[\u3053\u3053]({url}) \u3092\u30af\u30ea\u30c3\u30af\u3057\u3066SimpliSafe\u306eWeb\u30a2\u30d7\u30ea\u3092\u958b\u304d\u3001\u8a8d\u8a3c\u60c5\u5831\u3092\u5165\u529b\u3057\u307e\u3059\u3002\u51e6\u7406\u304c\u5b8c\u4e86\u3057\u305f\u3089\u3001\u3053\u3053\u306b\u623b\u3063\u3066\u304d\u3066\u9001\u4fe1(submit) \u3092\u30af\u30ea\u30c3\u30af\u3057\u307e\u3059\u3002" } } }, diff --git a/homeassistant/components/simplisafe/translations/ko.json b/homeassistant/components/simplisafe/translations/ko.json index 5c98e8abaa6..4cbc24533e0 100644 --- a/homeassistant/components/simplisafe/translations/ko.json +++ b/homeassistant/components/simplisafe/translations/ko.json @@ -5,15 +5,13 @@ "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "identifier_exists": "\uacc4\uc815\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "still_awaiting_mfa": "\uc544\uc9c1 \ub2e4\ub2e8\uacc4 \uc778\uc99d(MFA) \uc774\uba54\uc77c\uc758 \ub9c1\ud06c \ud074\ub9ad\uc744 \uae30\ub2e4\ub9ac\uace0\uc788\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "progress": { + "email_2fa": "\uc774\uba54\uc77c\uc5d0\uc11c Simplisafe\uc758 \uc778\uc99d \ub9c1\ud06c\ub97c \ud655\uc778\ud558\uc2ed\uc2dc\uc624." + }, "step": { - "mfa": { - "title": "SimpliSafe \ub2e4\ub2e8\uacc4 \uc778\uc99d" - }, "reauth_confirm": { "data": { "password": "\ube44\ubc00\ubc88\ud638" @@ -23,11 +21,9 @@ }, "user": { "data": { - "code": "\ucf54\ub4dc (Home Assistant UI \uc5d0\uc11c \uc0ac\uc6a9\ub428)", "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc774\uba54\uc77c" - }, - "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." + } } } }, diff --git a/homeassistant/components/simplisafe/translations/lb.json b/homeassistant/components/simplisafe/translations/lb.json index 080d8afd394..e9034565e93 100644 --- a/homeassistant/components/simplisafe/translations/lb.json +++ b/homeassistant/components/simplisafe/translations/lb.json @@ -5,15 +5,10 @@ "reauth_successful": "Erfollegr\u00e4ich re-authentifiz\u00e9iert." }, "error": { - "identifier_exists": "Konto ass scho registr\u00e9iert", "invalid_auth": "Ong\u00eblteg Authentifikatioun", - "still_awaiting_mfa": "Waart nach den MFA E-Mail Klick.", "unknown": "Onerwaarte Feeler" }, "step": { - "mfa": { - "title": "SimpliSafe Multi-Faktor Authentifikatioun" - }, "reauth_confirm": { "data": { "password": "Passwuert" @@ -23,11 +18,9 @@ }, "user": { "data": { - "code": "Code (benotzt am Home Assistant Benotzer Interface)", "password": "Passwuert", "username": "E-Mail" - }, - "title": "F\u00ebllt \u00e4r Informatiounen aus" + } } } }, diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index 33bc10ac85d..8c356ed9e12 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -2,26 +2,23 @@ "config": { "abort": { "already_configured": "Dit SimpliSafe-account is al in gebruik.", - "reauth_successful": "Herauthenticatie was succesvol", - "wrong_account": "De opgegeven gebruikersgegevens komen niet overeen met deze SimpliSafe-account." + "email_2fa_timed_out": "Timed out tijdens het wachten op email-gebaseerde twee-factor authenticatie.", + "reauth_successful": "Herauthenticatie geslaagd" }, "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" }, + "progress": { + "email_2fa": "Controleer uw e-mail voor een verificatielink van Simplisafe." + }, "step": { - "mfa": { - "title": "SimpliSafe Multi-Factor Authenticatie" - }, "reauth_confirm": { "data": { "password": "Wachtwoord" }, "description": "Voer het wachtwoord voor {username} opnieuw in.", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "sms_2fa": { "data": { @@ -31,13 +28,10 @@ }, "user": { "data": { - "auth_code": "Autorisatie Code", - "code": "Code (gebruikt in Home Assistant)", "password": "Wachtwoord", "username": "Gebruikersnaam" }, - "description": "Voer uw gebruikersnaam en wachtwoord in.", - "title": "Vul uw gegevens in" + "description": "Voer uw gebruikersnaam en wachtwoord in." } } }, diff --git a/homeassistant/components/simplisafe/translations/no.json b/homeassistant/components/simplisafe/translations/no.json index 6527f2e4ae2..7e6875a2590 100644 --- a/homeassistant/components/simplisafe/translations/no.json +++ b/homeassistant/components/simplisafe/translations/no.json @@ -2,20 +2,17 @@ "config": { "abort": { "already_configured": "Denne SimpliSafe-kontoen er allerede i bruk.", - "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket", - "wrong_account": "Brukerlegitimasjonen som er oppgitt, samsvarer ikke med denne SimpliSafe -kontoen." + "email_2fa_timed_out": "Tidsavbrudd mens du ventet p\u00e5 e-postbasert tofaktorautentisering.", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "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" }, + "progress": { + "email_2fa": "Sjekk e-posten din for en bekreftelseslenke fra Simplisafe." + }, "step": { - "mfa": { - "title": "SimpliSafe flertrinnsbekreftelse" - }, "reauth_confirm": { "data": { "password": "Passord" @@ -31,13 +28,10 @@ }, "user": { "data": { - "auth_code": "Autorisasjonskode", - "code": "Kode (brukt i Home Assistant brukergrensesnittet)", "password": "Passord", "username": "Brukernavn" }, - "description": "Skriv inn brukernavn og passord.", - "title": "Fyll ut informasjonen din." + "description": "Skriv inn brukernavn og passord." } } }, diff --git a/homeassistant/components/simplisafe/translations/pl.json b/homeassistant/components/simplisafe/translations/pl.json index 21f38d5bff5..22c4739b7dd 100644 --- a/homeassistant/components/simplisafe/translations/pl.json +++ b/homeassistant/components/simplisafe/translations/pl.json @@ -2,20 +2,17 @@ "config": { "abort": { "already_configured": "To konto SimpliSafe jest ju\u017c w u\u017cyciu", - "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119", - "wrong_account": "Podane dane uwierzytelniaj\u0105ce u\u017cytkownika nie pasuj\u0105 do tego konta SimpliSafe." + "email_2fa_timed_out": "Przekroczono limit czasu oczekiwania na e-mailowe uwierzytelnianie dwusk\u0142adnikowe.", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "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" }, + "progress": { + "email_2fa": "Sprawd\u017a e-mail z linkiem weryfikacyjnym od Simplisafe." + }, "step": { - "mfa": { - "title": "Uwierzytelnianie wielosk\u0142adnikowe SimpliSafe" - }, "reauth_confirm": { "data": { "password": "Has\u0142o" @@ -31,13 +28,10 @@ }, "user": { "data": { - "auth_code": "Kod autoryzacji", - "code": "Kod (u\u017cywany w interfejsie Home Assistant)", "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" }, - "description": "Wprowad\u017a swoj\u0105 nazw\u0119 u\u017cytkownika i has\u0142o.", - "title": "Wprowad\u017a dane" + "description": "Wprowad\u017a swoj\u0105 nazw\u0119 u\u017cytkownika i has\u0142o." } } }, diff --git a/homeassistant/components/simplisafe/translations/pt-BR.json b/homeassistant/components/simplisafe/translations/pt-BR.json index 1dec82ec74d..ccfb13b6cc1 100644 --- a/homeassistant/components/simplisafe/translations/pt-BR.json +++ b/homeassistant/components/simplisafe/translations/pt-BR.json @@ -2,25 +2,22 @@ "config": { "abort": { "already_configured": "A conta j\u00e1 foi configurada", - "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", - "wrong_account": "As credenciais de usu\u00e1rio fornecidas n\u00e3o correspondem a esta conta SimpliSafe." + "email_2fa_timed_out": "Expirou enquanto aguardava a autentica\u00e7\u00e3o de dois fatores enviada por e-mail.", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "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" }, + "progress": { + "email_2fa": "Verifique seu e-mail para obter um link de verifica\u00e7\u00e3o do Simplisafe." + }, "step": { - "mfa": { - "title": "Autentica\u00e7\u00e3o SimpliSafe multifator" - }, "reauth_confirm": { "data": { "password": "Senha" }, - "description": "Por favor, digite novamente a senha para {username} .", + "description": "Por favor, digite novamente a senha para {username}.", "title": "Reautenticar Integra\u00e7\u00e3o" }, "sms_2fa": { @@ -31,13 +28,10 @@ }, "user": { "data": { - "auth_code": "C\u00f3digo de autoriza\u00e7\u00e3o", - "code": "C\u00f3digo (usado na IU do Home Assistant)", "password": "Senha", "username": "Usu\u00e1rio" }, - "description": "Insira seu nome de usu\u00e1rio e senha.", - "title": "Preencha suas informa\u00e7\u00f5es." + "description": "Insira seu nome de usu\u00e1rio e senha." } } }, diff --git a/homeassistant/components/simplisafe/translations/pt.json b/homeassistant/components/simplisafe/translations/pt.json index 9b5df6cf934..ba84255bc9d 100644 --- a/homeassistant/components/simplisafe/translations/pt.json +++ b/homeassistant/components/simplisafe/translations/pt.json @@ -4,7 +4,6 @@ "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" }, "error": { - "identifier_exists": "Conta j\u00e1 registada", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, @@ -19,8 +18,7 @@ "data": { "password": "Palavra-passe", "username": "Email" - }, - "title": "Preencha as suas informa\u00e7\u00f5es" + } } } } diff --git a/homeassistant/components/simplisafe/translations/ro.json b/homeassistant/components/simplisafe/translations/ro.json index 7531bef0a8e..efbbe49f38a 100644 --- a/homeassistant/components/simplisafe/translations/ro.json +++ b/homeassistant/components/simplisafe/translations/ro.json @@ -1,15 +1,11 @@ { "config": { - "error": { - "identifier_exists": "Contul este deja \u00eenregistrat" - }, "step": { "user": { "data": { "password": "Parola", "username": "Adresa de email" - }, - "title": "Completa\u021bi informa\u021biile dvs." + } } } } diff --git a/homeassistant/components/simplisafe/translations/ru.json b/homeassistant/components/simplisafe/translations/ru.json index f2907bd6c47..198d5225983 100644 --- a/homeassistant/components/simplisafe/translations/ru.json +++ b/homeassistant/components/simplisafe/translations/ru.json @@ -2,35 +2,36 @@ "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.", - "wrong_account": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u044d\u0442\u043e\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 SimpliSafe." + "email_2fa_timed_out": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "error": { - "identifier_exists": "\u0423\u0447\u0435\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", - "still_awaiting_mfa": "\u041e\u0436\u0438\u0434\u0430\u043d\u0438\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u043e \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u0435.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, + "progress": { + "email_2fa": "\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 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043e\u0442 Simplisafe." + }, "step": { - "mfa": { - "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": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c" }, - "description": "\u0421\u0440\u043e\u043a \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438\u0441\u0442\u0435\u043a \u0438\u043b\u0438 \u0431\u044b\u043b \u0430\u043d\u043d\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u043d. \u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u043d\u043e\u0432\u043e \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c.", + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f {username}.", "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" }, + "sms_2fa": { + "data": { + "code": "\u041a\u043e\u0434" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0412\u0430\u043c \u0432 SMS." + }, "user": { "data": { - "auth_code": "\u041a\u043e\u0434 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438", - "code": "\u041a\u043e\u0434 (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 Home Assistant)", "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" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, - "description": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 SimpliSafe. \u0418\u0437-\u0437\u0430 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439, \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c \u0432\u0440\u0443\u0447\u043d\u0443\u044e. \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]({docs_url}) \u043f\u0435\u0440\u0435\u0434 \u043d\u0430\u0447\u0430\u043b\u043e\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({url}), \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 SimpliSafe \u0438 \u0432\u0432\u0435\u0441\u0442\u0438 \u0441\u0432\u043e\u0438 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435. \n\n2. \u041a\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0432\u0445\u043e\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d, \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", - "title": "SimpliSafe" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c." } } }, diff --git a/homeassistant/components/simplisafe/translations/sl.json b/homeassistant/components/simplisafe/translations/sl.json index 6f326bc5b68..d98ad9c20c7 100644 --- a/homeassistant/components/simplisafe/translations/sl.json +++ b/homeassistant/components/simplisafe/translations/sl.json @@ -1,20 +1,14 @@ { "config": { "abort": { - "already_configured": "Ta ra\u010dun SimpliSafe je \u017ee v uporabi.", - "wrong_account": "Navedene uporabni\u0161ke poverilnice se ne ujemajo s tem ra\u010dunom SimpliSafe." - }, - "error": { - "identifier_exists": "Ra\u010dun je \u017ee registriran" + "already_configured": "Ta ra\u010dun SimpliSafe je \u017ee v uporabi." }, "step": { "user": { "data": { - "code": "Koda (uporablja se v uporabni\u0161kem vmesniku Home Assistant)", "password": "Geslo", "username": "E-po\u0161tni naslov" - }, - "title": "Izpolnite svoje podatke" + } } } }, diff --git a/homeassistant/components/simplisafe/translations/sv.json b/homeassistant/components/simplisafe/translations/sv.json index a4e8e052073..2b2d675f2b5 100644 --- a/homeassistant/components/simplisafe/translations/sv.json +++ b/homeassistant/components/simplisafe/translations/sv.json @@ -3,16 +3,21 @@ "abort": { "already_configured": "Det h\u00e4r SimpliSafe-kontot har redan konfigurerats." }, - "error": { - "identifier_exists": "Kontot \u00e4r redan registrerat" + "progress": { + "email_2fa": "Kontrollera din e-post f\u00f6r en verifieringsl\u00e4nk fr\u00e5n Simplisafe." }, "step": { + "sms_2fa": { + "data": { + "code": "Kod" + }, + "description": "Ange tv\u00e5faktorsautentiseringskoden som skickats till dig via SMS." + }, "user": { "data": { "password": "L\u00f6senord", "username": "E-postadress" - }, - "title": "Fyll i din information" + } } } } diff --git a/homeassistant/components/simplisafe/translations/tr.json b/homeassistant/components/simplisafe/translations/tr.json index 255207833c7..74c260e2d3d 100644 --- a/homeassistant/components/simplisafe/translations/tr.json +++ b/homeassistant/components/simplisafe/translations/tr.json @@ -2,35 +2,36 @@ "config": { "abort": { "already_configured": "Bu SimpliSafe hesab\u0131 zaten kullan\u0131mda.", - "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", - "wrong_account": "Sa\u011flanan kullan\u0131c\u0131 kimlik bilgileri bu SimpliSafe hesab\u0131yla e\u015fle\u015fmiyor." + "email_2fa_timed_out": "E-posta tabanl\u0131 iki fakt\u00f6rl\u00fc kimlik do\u011frulama i\u00e7in beklerken zaman a\u015f\u0131m\u0131na u\u011frad\u0131.", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" }, "error": { - "identifier_exists": "Hesap zaten kay\u0131tl\u0131", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", - "still_awaiting_mfa": "Hala MFA e-posta t\u0131klamas\u0131 bekleniyor", "unknown": "Beklenmeyen hata" }, + "progress": { + "email_2fa": "Simplisafe'den bir do\u011frulama ba\u011flant\u0131s\u0131 i\u00e7in e-postan\u0131z\u0131 kontrol edin." + }, "step": { - "mfa": { - "title": "SimpliSafe \u00c7ok Fakt\u00f6rl\u00fc Kimlik Do\u011frulama" - }, "reauth_confirm": { "data": { "password": "Parola" }, - "description": "Eri\u015fim kodunuzun s\u00fcresi doldu veya iptal edildi. Hesab\u0131n\u0131z\u0131 yeniden ba\u011flamak i\u00e7in parolan\u0131z\u0131 girin.", + "description": "L\u00fctfen {username} \u015fifresini tekrar girin.", "title": "Entegrasyonu Yeniden Do\u011frula" }, + "sms_2fa": { + "data": { + "code": "Kod" + }, + "description": "Size SMS ile g\u00f6nderilen iki fakt\u00f6rl\u00fc do\u011frulama kodunu girin." + }, "user": { "data": { - "auth_code": "Yetkilendirme Kodu", - "code": "Kod (Home Assistant kullan\u0131c\u0131 aray\u00fcz\u00fcnde kullan\u0131l\u0131r)", "password": "Parola", - "username": "E-posta" + "username": "Kullan\u0131c\u0131 Ad\u0131" }, - "description": "SimpliSafe, SimpliSafe web uygulamas\u0131 arac\u0131l\u0131\u011f\u0131yla Home Assistant ile kimlik do\u011frulamas\u0131 yapar. Teknik s\u0131n\u0131rlamalar nedeniyle bu i\u015flemin sonunda manuel bir ad\u0131m vard\u0131r; l\u00fctfen ba\u015flamadan \u00f6nce [belgeleri]( {docs_url} \n\n 1. SimpliSafe web uygulamas\u0131n\u0131 a\u00e7mak ve kimlik bilgilerinizi girmek i\u00e7in [buraya]( {url} \n\n 2. Oturum a\u00e7ma i\u015flemi tamamland\u0131\u011f\u0131nda buraya d\u00f6n\u00fcn ve yetkilendirme kodunu a\u015fa\u011f\u0131ya girin.", - "title": "Bilgilerinizi doldurun." + "description": "Kullan\u0131c\u0131 ad\u0131n\u0131z\u0131 ve \u015fifrenizi girin." } } }, diff --git a/homeassistant/components/simplisafe/translations/uk.json b/homeassistant/components/simplisafe/translations/uk.json index 8ca29743c5b..76e0fb397cb 100644 --- a/homeassistant/components/simplisafe/translations/uk.json +++ b/homeassistant/components/simplisafe/translations/uk.json @@ -5,15 +5,10 @@ "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043f\u0440\u043e\u0439\u0448\u043b\u0430 \u0443\u0441\u043f\u0456\u0448\u043d\u043e" }, "error": { - "identifier_exists": "\u041e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u043e.", "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", - "still_awaiting_mfa": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f, \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e\u0433\u043e \u043f\u043e \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0456\u0439 \u043f\u043e\u0448\u0442\u0456.", "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" }, "step": { - "mfa": { - "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": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c" @@ -23,11 +18,9 @@ }, "user": { "data": { - "code": "\u041a\u043e\u0434 (\u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 Home Assistant)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" - }, - "title": "\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u0432\u0430\u0448\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e" + } } } }, diff --git a/homeassistant/components/simplisafe/translations/zh-Hans.json b/homeassistant/components/simplisafe/translations/zh-Hans.json index 134eee33bfe..d28951425ec 100644 --- a/homeassistant/components/simplisafe/translations/zh-Hans.json +++ b/homeassistant/components/simplisafe/translations/zh-Hans.json @@ -1,17 +1,14 @@ { "config": { "error": { - "identifier_exists": "\u8d26\u6237\u5df2\u6ce8\u518c", "invalid_auth": "\u65e0\u6548\u7684\u8eab\u4efd\u9a8c\u8bc1" }, "step": { "user": { "data": { - "auth_code": "\u6388\u6743\u7801", "password": "\u5bc6\u7801", "username": "\u7535\u5b50\u90ae\u4ef6\u5730\u5740" - }, - "title": "\u586b\u5199\u60a8\u7684\u4fe1\u606f" + } } } } diff --git a/homeassistant/components/simplisafe/translations/zh-Hant.json b/homeassistant/components/simplisafe/translations/zh-Hant.json index 1b10153ab05..ce763da538e 100644 --- a/homeassistant/components/simplisafe/translations/zh-Hant.json +++ b/homeassistant/components/simplisafe/translations/zh-Hant.json @@ -2,20 +2,17 @@ "config": { "abort": { "already_configured": "\u6b64 SimpliSafe \u5e33\u865f\u5df2\u88ab\u4f7f\u7528\u3002", - "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", - "wrong_account": "\u6240\u4ee5\u63d0\u4f9b\u7684\u6191\u8b49\u8207 Simplisafe \u5e33\u865f\u4e0d\u7b26\u3002" + "email_2fa_timed_out": "\u7b49\u5f85\u5169\u6b65\u9a5f\u9a57\u8b49\u78bc\u90f5\u4ef6\u903e\u6642\u3002", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "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" }, + "progress": { + "email_2fa": "\u8f38\u5165\u90f5\u4ef6\u6240\u6536\u5230 \u7684Simplisafe \u9a57\u8b49\u9023\u7d50\u3002" + }, "step": { - "mfa": { - "title": "SimpliSafe \u591a\u6b65\u9a5f\u9a57\u8b49" - }, "reauth_confirm": { "data": { "password": "\u5bc6\u78bc" @@ -31,13 +28,10 @@ }, "user": { "data": { - "auth_code": "\u8a8d\u8b49\u78bc", - "code": "\u9a57\u8b49\u78bc\uff08\u4f7f\u7528\u65bc Home Assistant UI\uff09", "password": "\u5bc6\u78bc", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, - "description": "\u8f38\u5165\u4f7f\u7528\u8005\u540d\u7a31\u8207\u5bc6\u78bc\u3002", - "title": "\u586b\u5beb\u8cc7\u8a0a\u3002" + "description": "\u8f38\u5165\u4f7f\u7528\u8005\u540d\u7a31\u8207\u5bc6\u78bc\u3002" } } }, diff --git a/homeassistant/components/siren/strings.json b/homeassistant/components/siren/strings.json new file mode 100644 index 00000000000..c8e60e91ce0 --- /dev/null +++ b/homeassistant/components/siren/strings.json @@ -0,0 +1,3 @@ +{ + "title": "Siren" +} diff --git a/homeassistant/components/siren/translations/bg.json b/homeassistant/components/siren/translations/bg.json new file mode 100644 index 00000000000..bd7725464a7 --- /dev/null +++ b/homeassistant/components/siren/translations/bg.json @@ -0,0 +1,3 @@ +{ + "title": "\u0421\u0438\u0440\u0435\u043d\u0430" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/ca.json b/homeassistant/components/siren/translations/ca.json new file mode 100644 index 00000000000..f3c1468be31 --- /dev/null +++ b/homeassistant/components/siren/translations/ca.json @@ -0,0 +1,3 @@ +{ + "title": "Sirena" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/cs.json b/homeassistant/components/siren/translations/cs.json new file mode 100644 index 00000000000..50303faf105 --- /dev/null +++ b/homeassistant/components/siren/translations/cs.json @@ -0,0 +1,3 @@ +{ + "title": "Sir\u00e9na" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/de.json b/homeassistant/components/siren/translations/de.json new file mode 100644 index 00000000000..bcea16c56f1 --- /dev/null +++ b/homeassistant/components/siren/translations/de.json @@ -0,0 +1,3 @@ +{ + "title": "Sirene" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/el.json b/homeassistant/components/siren/translations/el.json new file mode 100644 index 00000000000..c5a968c2fde --- /dev/null +++ b/homeassistant/components/siren/translations/el.json @@ -0,0 +1,3 @@ +{ + "title": "\u03a3\u03b5\u03b9\u03c1\u03ae\u03bd\u03b1" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/en.json b/homeassistant/components/siren/translations/en.json new file mode 100644 index 00000000000..549bad8914b --- /dev/null +++ b/homeassistant/components/siren/translations/en.json @@ -0,0 +1,3 @@ +{ + "title": "Siren" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/et.json b/homeassistant/components/siren/translations/et.json new file mode 100644 index 00000000000..c0bd3d03ec7 --- /dev/null +++ b/homeassistant/components/siren/translations/et.json @@ -0,0 +1,3 @@ +{ + "title": "Sireen" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/fr.json b/homeassistant/components/siren/translations/fr.json new file mode 100644 index 00000000000..2dad0e1b3b8 --- /dev/null +++ b/homeassistant/components/siren/translations/fr.json @@ -0,0 +1,3 @@ +{ + "title": "Sir\u00e8ne" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/hu.json b/homeassistant/components/siren/translations/hu.json new file mode 100644 index 00000000000..64642eb828d --- /dev/null +++ b/homeassistant/components/siren/translations/hu.json @@ -0,0 +1,3 @@ +{ + "title": "Szir\u00e9na" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/id.json b/homeassistant/components/siren/translations/id.json new file mode 100644 index 00000000000..bcea16c56f1 --- /dev/null +++ b/homeassistant/components/siren/translations/id.json @@ -0,0 +1,3 @@ +{ + "title": "Sirene" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/it.json b/homeassistant/components/siren/translations/it.json new file mode 100644 index 00000000000..f3c1468be31 --- /dev/null +++ b/homeassistant/components/siren/translations/it.json @@ -0,0 +1,3 @@ +{ + "title": "Sirena" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/ja.json b/homeassistant/components/siren/translations/ja.json new file mode 100644 index 00000000000..c83a8107246 --- /dev/null +++ b/homeassistant/components/siren/translations/ja.json @@ -0,0 +1,3 @@ +{ + "title": "\u30b5\u30a4\u30ec\u30f3" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/ko.json b/homeassistant/components/siren/translations/ko.json new file mode 100644 index 00000000000..4fe08e488ee --- /dev/null +++ b/homeassistant/components/siren/translations/ko.json @@ -0,0 +1,3 @@ +{ + "title": "\uc0ac\uc774\ub80c" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/nl.json b/homeassistant/components/siren/translations/nl.json new file mode 100644 index 00000000000..bcea16c56f1 --- /dev/null +++ b/homeassistant/components/siren/translations/nl.json @@ -0,0 +1,3 @@ +{ + "title": "Sirene" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/no.json b/homeassistant/components/siren/translations/no.json new file mode 100644 index 00000000000..bcea16c56f1 --- /dev/null +++ b/homeassistant/components/siren/translations/no.json @@ -0,0 +1,3 @@ +{ + "title": "Sirene" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/pl.json b/homeassistant/components/siren/translations/pl.json new file mode 100644 index 00000000000..965b368e83b --- /dev/null +++ b/homeassistant/components/siren/translations/pl.json @@ -0,0 +1,3 @@ +{ + "title": "Syrena" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/pt-BR.json b/homeassistant/components/siren/translations/pt-BR.json new file mode 100644 index 00000000000..bcea16c56f1 --- /dev/null +++ b/homeassistant/components/siren/translations/pt-BR.json @@ -0,0 +1,3 @@ +{ + "title": "Sirene" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/ru.json b/homeassistant/components/siren/translations/ru.json new file mode 100644 index 00000000000..bd7725464a7 --- /dev/null +++ b/homeassistant/components/siren/translations/ru.json @@ -0,0 +1,3 @@ +{ + "title": "\u0421\u0438\u0440\u0435\u043d\u0430" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/tr.json b/homeassistant/components/siren/translations/tr.json new file mode 100644 index 00000000000..549bad8914b --- /dev/null +++ b/homeassistant/components/siren/translations/tr.json @@ -0,0 +1,3 @@ +{ + "title": "Siren" +} \ No newline at end of file diff --git a/homeassistant/components/siren/translations/zh-Hant.json b/homeassistant/components/siren/translations/zh-Hant.json new file mode 100644 index 00000000000..549bad8914b --- /dev/null +++ b/homeassistant/components/siren/translations/zh-Hant.json @@ -0,0 +1,3 @@ +{ + "title": "Siren" +} \ No newline at end of file diff --git a/homeassistant/components/slack/__init__.py b/homeassistant/components/slack/__init__.py index e3ae111f2ea..ae52013621f 100644 --- a/homeassistant/components/slack/__init__.py +++ b/homeassistant/components/slack/__init__.py @@ -1,2 +1,62 @@ -"""The slack component.""" -DOMAIN = "slack" +"""The slack integration.""" +import logging + +from aiohttp.client_exceptions import ClientError +from slack import WebClient +from slack.errors import SlackApiError + +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_PLATFORM, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client, discovery +from homeassistant.helpers.typing import ConfigType + +from .const import DATA_CLIENT, DOMAIN + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = [Platform.NOTIFY] + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the Slack component.""" + # Iterate all entries for notify to only get Slack + if Platform.NOTIFY in config: + for entry in config[Platform.NOTIFY]: + if entry[CONF_PLATFORM] == DOMAIN: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=entry + ) + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Slack from a config entry.""" + session = aiohttp_client.async_get_clientsession(hass) + slack = WebClient(token=entry.data[CONF_API_KEY], run_async=True, session=session) + + try: + await slack.auth_test() + except (SlackApiError, ClientError) as ex: + if isinstance(ex, SlackApiError) and ex.response["error"] == "invalid_auth": + _LOGGER.error("Invalid API key") + return False + raise ConfigEntryNotReady("Error while setting up integration") from ex + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = entry.data | {DATA_CLIENT: slack} + + hass.async_create_task( + discovery.async_load_platform( + hass, + Platform.NOTIFY, + DOMAIN, + hass.data[DOMAIN][entry.entry_id], + hass.data[DOMAIN], + ) + ) + + return True diff --git a/homeassistant/components/slack/config_flow.py b/homeassistant/components/slack/config_flow.py new file mode 100644 index 00000000000..253750a2310 --- /dev/null +++ b/homeassistant/components/slack/config_flow.py @@ -0,0 +1,87 @@ +"""Config flow for Slack integration.""" +from __future__ import annotations + +import logging + +from slack import WebClient +from slack.errors import SlackApiError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_API_KEY, CONF_ICON, CONF_NAME, CONF_USERNAME +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers import aiohttp_client + +from .const import CONF_DEFAULT_CHANNEL, DOMAIN + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema( + { + vol.Required(CONF_API_KEY): str, + vol.Required(CONF_DEFAULT_CHANNEL): str, + vol.Optional(CONF_ICON): str, + vol.Optional(CONF_USERNAME): str, + } +) + + +class SlackFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Slack.""" + + async def async_step_user( + self, user_input: dict[str, str] | None = None + ) -> FlowResult: + """Handle a flow initiated by the user.""" + errors = {} + + if user_input is not None: + error, info = await self._async_try_connect(user_input[CONF_API_KEY]) + if error is not None: + errors["base"] = error + elif info is not None: + await self.async_set_unique_id(info["team_id"].lower()) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=user_input.get(CONF_NAME, info["team"]), + data={CONF_NAME: user_input.get(CONF_NAME, info["team"])} + | user_input, + ) + + user_input = user_input or {} + return self.async_show_form( + step_id="user", + data_schema=CONFIG_SCHEMA, + errors=errors, + ) + + async def async_step_import(self, import_config: dict[str, str]) -> FlowResult: + """Import a config entry from configuration.yaml.""" + _LOGGER.warning( + "Configuration of the Slack integration in YAML is deprecated and " + "will be removed in a future release; Your existing configuration " + "has been imported into the UI automatically and can be safely removed " + "from your configuration.yaml file" + ) + entries = self._async_current_entries() + if any(x.data[CONF_API_KEY] == import_config[CONF_API_KEY] for x in entries): + return self.async_abort(reason="already_configured") + return await self.async_step_user(import_config) + + async def _async_try_connect( + self, token: str + ) -> tuple[str, None] | tuple[None, dict[str, str]]: + """Try connecting to Slack.""" + session = aiohttp_client.async_get_clientsession(self.hass) + client = WebClient(token=token, run_async=True, session=session) + + try: + info = await client.auth_test() + except SlackApiError as ex: + if ex.response["error"] == "invalid_auth": + return "invalid_auth", None + return "cannot_connect", None + except Exception as ex: # pylint:disable=broad-except + _LOGGER.exception("Unexpected exception: %s", ex) + return "unknown", None + return None, info diff --git a/homeassistant/components/slack/const.py b/homeassistant/components/slack/const.py new file mode 100644 index 00000000000..b7b5707aeeb --- /dev/null +++ b/homeassistant/components/slack/const.py @@ -0,0 +1,16 @@ +"""Constants for the Slack integration.""" +from typing import Final + +ATTR_BLOCKS = "blocks" +ATTR_BLOCKS_TEMPLATE = "blocks_template" +ATTR_FILE = "file" +ATTR_PASSWORD = "password" +ATTR_PATH = "path" +ATTR_URL = "url" +ATTR_USERNAME = "username" + +CONF_DEFAULT_CHANNEL = "default_channel" + +DATA_CLIENT = "client" +DEFAULT_TIMEOUT_SECONDS = 15 +DOMAIN: Final = "slack" diff --git a/homeassistant/components/slack/manifest.json b/homeassistant/components/slack/manifest.json index d54bb9e0ec6..57c1690e647 100644 --- a/homeassistant/components/slack/manifest.json +++ b/homeassistant/components/slack/manifest.json @@ -1,9 +1,10 @@ { "domain": "slack", "name": "Slack", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/slack", "requirements": ["slackclient==2.5.0"], - "codeowners": ["@bachya"], + "codeowners": ["@bachya", "@tkdrob"], "iot_class": "cloud_push", "loggers": ["slack"] } diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py index 4dfacda266c..bfcf79ef676 100644 --- a/homeassistant/components/slack/notify.py +++ b/homeassistant/components/slack/notify.py @@ -20,26 +20,32 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) -from homeassistant.const import ATTR_ICON, CONF_API_KEY, CONF_ICON, CONF_USERNAME +from homeassistant.const import ( + ATTR_ICON, + CONF_API_KEY, + CONF_ICON, + CONF_PATH, + CONF_USERNAME, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import aiohttp_client, config_validation as cv, template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from .const import ( + ATTR_BLOCKS, + ATTR_BLOCKS_TEMPLATE, + ATTR_FILE, + ATTR_PASSWORD, + ATTR_PATH, + ATTR_URL, + ATTR_USERNAME, + CONF_DEFAULT_CHANNEL, + DATA_CLIENT, +) + _LOGGER = logging.getLogger(__name__) -ATTR_BLOCKS = "blocks" -ATTR_BLOCKS_TEMPLATE = "blocks_template" -ATTR_FILE = "file" -ATTR_PASSWORD = "password" -ATTR_PATH = "path" -ATTR_URL = "url" -ATTR_USERNAME = "username" - -CONF_DEFAULT_CHANNEL = "default_channel" - -DEFAULT_TIMEOUT_SECONDS = 15 - -FILE_PATH_SCHEMA = vol.Schema({vol.Required(ATTR_PATH): cv.isfile}) +FILE_PATH_SCHEMA = vol.Schema({vol.Required(CONF_PATH): cv.isfile}) FILE_URL_SCHEMA = vol.Schema( { @@ -66,6 +72,7 @@ DATA_SCHEMA = vol.All( cv.ensure_list, [vol.Any(DATA_FILE_SCHEMA, DATA_TEXT_ONLY_SCHEMA)] ) +# Deprecated in Home Assistant 2022.5 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_API_KEY): cv.string, @@ -109,27 +116,13 @@ async def async_get_service( discovery_info: DiscoveryInfoType | None = None, ) -> SlackNotificationService | None: """Set up the Slack notification service.""" - session = aiohttp_client.async_get_clientsession(hass) - client = WebClient(token=config[CONF_API_KEY], run_async=True, session=session) - - try: - await client.auth_test() - except SlackApiError as err: - _LOGGER.error("Error while setting up integration: %r", err) + if discovery_info is None: return None - except ClientError as err: - _LOGGER.warning( - "Error testing connection to slack: %r " - "Continuing setup anyway, but notify service might not work", - err, - ) return SlackNotificationService( hass, - client, - config[CONF_DEFAULT_CHANNEL], - username=config.get(CONF_USERNAME), - icon=config.get(CONF_ICON), + discovery_info.pop(DATA_CLIENT), + discovery_info, ) @@ -153,16 +146,12 @@ class SlackNotificationService(BaseNotificationService): self, hass: HomeAssistant, client: WebClient, - default_channel: str, - username: str | None, - icon: str | None, + config: dict[str, str], ) -> None: """Initialize.""" - self._client = client - self._default_channel = default_channel self._hass = hass - self._icon = icon - self._username = username + self._client = client + self._config = config async def _async_send_local_file_message( self, @@ -294,7 +283,7 @@ class SlackNotificationService(BaseNotificationService): title = kwargs.get(ATTR_TITLE) targets = _async_sanitize_channel_names( - kwargs.get(ATTR_TARGET, [self._default_channel]) + kwargs.get(ATTR_TARGET, [self._config[CONF_DEFAULT_CHANNEL]]) ) # Message Type 1: A text-only message @@ -312,8 +301,8 @@ class SlackNotificationService(BaseNotificationService): targets, message, title, - username=data.get(ATTR_USERNAME, self._username), - icon=data.get(ATTR_ICON, self._icon), + username=data.get(ATTR_USERNAME, self._config.get(ATTR_USERNAME)), + icon=data.get(ATTR_ICON, self._config.get(ATTR_ICON)), blocks=blocks, ) diff --git a/homeassistant/components/slack/strings.json b/homeassistant/components/slack/strings.json new file mode 100644 index 00000000000..f14129cf156 --- /dev/null +++ b/homeassistant/components/slack/strings.json @@ -0,0 +1,29 @@ +{ + "config": { + "step": { + "user": { + "description": "Refer to the documentation on getting your Slack API key.", + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]", + "default_channel": "Default Channel", + "icon": "Icon", + "username": "[%key:common::config_flow::data::username%]" + }, + "data_description": { + "api_key": "The Slack API token to use for sending Slack messages.", + "default_channel": "The channel to post to if no channel is specified when sending a message.", + "icon": "Use one of the Slack emojis as an Icon for the supplied username.", + "username": "Home Assistant will post to Slack using the username specified." + } + } + }, + "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": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + } + } +} diff --git a/homeassistant/components/slack/translations/bg.json b/homeassistant/components/slack/translations/bg.json new file mode 100644 index 00000000000..35a5c7dd36d --- /dev/null +++ b/homeassistant/components/slack/translations/bg.json @@ -0,0 +1,22 @@ +{ + "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", + "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", + "default_channel": "\u041a\u0430\u043d\u0430\u043b \u043f\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043d\u0435", + "icon": "\u0418\u043a\u043e\u043d\u0430", + "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/slack/translations/ca.json b/homeassistant/components/slack/translations/ca.json new file mode 100644 index 00000000000..ca107cede26 --- /dev/null +++ b/homeassistant/components/slack/translations/ca.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "El servei ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "default_channel": "Canal predeterminat", + "icon": "Icona", + "username": "Nom d'usuari" + }, + "data_description": { + "api_key": "'Token' API de Slack que s'utilitza per enviar els missatges de Slack.", + "default_channel": "Canal al qual publicar en cas que no s'especifiqui cap canal en enviar un missatge.", + "icon": "Utilitza un dels emojis de Slack com a icona per al nom d'usuari proporcionat.", + "username": "Home Assistant publicar\u00e0 a Slack amb el nom d'usuari especificat." + }, + "description": "Consulta la documentaci\u00f3 per obtenir la teva clau API de Slack." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/cs.json b/homeassistant/components/slack/translations/cs.json new file mode 100644 index 00000000000..a4b5fb71cbe --- /dev/null +++ b/homeassistant/components/slack/translations/cs.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba je ji\u017e nastavena" + }, + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "api_key": "Kl\u00ed\u010d API", + "icon": "Ikona", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/de.json b/homeassistant/components/slack/translations/de.json new file mode 100644 index 00000000000..d87383ffac7 --- /dev/null +++ b/homeassistant/components/slack/translations/de.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Der Dienst ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "default_channel": "Standardkanal", + "icon": "Symbol", + "username": "Benutzername" + }, + "data_description": { + "api_key": "Der Slack-API-Token, das zum Senden von Slack-Nachrichten verwendet werden soll.", + "default_channel": "Der Kanal, an den gepostet werden soll, wenn beim Senden einer Nachricht kein Kanal angegeben wird.", + "icon": "Verwende eines der Slack-Emojis als Symbol f\u00fcr den bereitgestellten Benutzernamen.", + "username": "Home Assistant postet unter Verwendung des angegebenen Benutzernamens in Slack." + }, + "description": "Lies in der Dokumentation nach, wie du deinen Slack-API-Schl\u00fcssel erh\u00e4ltst." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/el.json b/homeassistant/components/slack/translations/el.json new file mode 100644 index 00000000000..8628dde5821 --- /dev/null +++ b/homeassistant/components/slack/translations/el.json @@ -0,0 +1,29 @@ +{ + "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", + "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", + "default_channel": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03ba\u03b1\u03bd\u03ac\u03bb\u03b9", + "icon": "\u0395\u03b9\u03ba\u03bf\u03bd\u03af\u03b4\u03b9\u03bf", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "data_description": { + "api_key": "\u03a4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc Slack API \u03b3\u03b9\u03b1 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03bc\u03b7\u03bd\u03c5\u03bc\u03ac\u03c4\u03c9\u03bd Slack.", + "default_channel": "\u03a4\u03bf \u03ba\u03b1\u03bd\u03ac\u03bb\u03b9 \u03c3\u03c4\u03bf \u03bf\u03c0\u03bf\u03af\u03bf \u03b8\u03b1 \u03b4\u03b7\u03bc\u03bf\u03c3\u03b9\u03b5\u03cd\u03c3\u03b5\u03c4\u03b5 \u03b1\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03ba\u03b1\u03bd\u03ac\u03bb\u03b9 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03c3\u03c4\u03bf\u03bb\u03ae \u03b5\u03bd\u03cc\u03c2 \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2.", + "icon": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 Slack emoji \u03c9\u03c2 \u03b5\u03b9\u03ba\u03bf\u03bd\u03af\u03b4\u03b9\u03bf \u03b3\u03b9\u03b1 \u03c4\u03bf \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7.", + "username": "\u03a4\u03bf Home Assistant \u03b8\u03b1 \u03b4\u03b7\u03bc\u03bf\u03c3\u03b9\u03b5\u03cd\u03b5\u03b9 \u03c3\u03c4\u03bf Slack \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7." + }, + "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 Slack API." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/en.json b/homeassistant/components/slack/translations/en.json new file mode 100644 index 00000000000..23e5830764e --- /dev/null +++ b/homeassistant/components/slack/translations/en.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Service is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "default_channel": "Default Channel", + "icon": "Icon", + "username": "Username" + }, + "data_description": { + "api_key": "The Slack API token to use for sending Slack messages.", + "default_channel": "The channel to post to if no channel is specified when sending a message.", + "icon": "Use one of the Slack emojis as an Icon for the supplied username.", + "username": "Home Assistant will post to Slack using the username specified." + }, + "description": "Refer to the documentation on getting your Slack API key." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/es.json b/homeassistant/components/slack/translations/es.json new file mode 100644 index 00000000000..52aee38b4f9 --- /dev/null +++ b/homeassistant/components/slack/translations/es.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "El servicio ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Error al conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Clave API", + "default_channel": "Canal por defecto", + "icon": "Icono", + "username": "Nombre de usuario" + }, + "data_description": { + "api_key": "El token de la API de Slack que se usar\u00e1 para enviar mensajes de Slack.", + "default_channel": "Canal al que publicar en caso de que no se especifique ning\u00fan canal al enviar un mensaje.", + "icon": "Utilice uno de los emojis de Slack como icono para el nombre de usuario proporcionado.", + "username": "Home Assistant publicar\u00e1 en Slack con el nombre de usuario especificado." + }, + "description": "Consulte la documentaci\u00f3n sobre c\u00f3mo obtener su clave API de Slack." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/et.json b/homeassistant/components/slack/translations/et.json new file mode 100644 index 00000000000..bc76c6d848a --- /dev/null +++ b/homeassistant/components/slack/translations/et.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Teenus on juba seadistatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "default_channel": "Vaikimisi kanal", + "icon": "Ikoon", + "username": "Kasutajanimi" + }, + "data_description": { + "api_key": "Slacki API v\u00f5ti mida kasutatakse Slacki s\u00f5numite saatmiseks.", + "default_channel": "Kanal, kuhu postitada kui s\u00f5numi saatmisel ei ole kanalit m\u00e4\u00e4ratud.", + "icon": "Kasuta \u00fchte Slacki emotikoni esitatud kasutajanime ikoonina.", + "username": "Home Assistant postitab Slacki kasutades m\u00e4\u00e4ratud kasutajanime." + }, + "description": "Vaata oma Slack API v\u00f5tme hankimise dokumentatsiooni." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/fr.json b/homeassistant/components/slack/translations/fr.json new file mode 100644 index 00000000000..0573e8c81c2 --- /dev/null +++ b/homeassistant/components/slack/translations/fr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification non valide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "default_channel": "Canal par d\u00e9faut", + "icon": "Ic\u00f4ne", + "username": "Nom d'utilisateur" + }, + "data_description": { + "api_key": "Le jeton d'API Slack \u00e0 utiliser pour l'envoi des messages sur Slack.", + "default_channel": "Le canal sur lequel poster si aucun canal n'est sp\u00e9cifi\u00e9 lors de l'envoi d'un message.", + "icon": "Utilisez l'un des \u00e9mojis de Slack en tant qu'ic\u00f4ne pour le nom d'utilisateur fourni.", + "username": "Home Assistant postera sur Slack en utilisant le nom d'utilisateur sp\u00e9cifi\u00e9." + }, + "description": "Consultez la documentation pour obtenir votre cl\u00e9 d'API Slack." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/he.json b/homeassistant/components/slack/translations/he.json similarity index 52% rename from homeassistant/components/climacell/translations/he.json rename to homeassistant/components/slack/translations/he.json index b663a5e0a0f..b0201cf2445 100644 --- a/homeassistant/components/climacell/translations/he.json +++ b/homeassistant/components/slack/translations/he.json @@ -1,18 +1,19 @@ { "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_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", + "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": { "api_key": "\u05de\u05e4\u05ea\u05d7 API", - "api_version": "\u05d2\u05e8\u05e1\u05ea API", - "latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1", - "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", - "name": "\u05e9\u05dd" + "icon": "\u05e1\u05de\u05dc\u05d9\u05dc", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" } } } diff --git a/homeassistant/components/slack/translations/hu.json b/homeassistant/components/slack/translations/hu.json new file mode 100644 index 00000000000..c2737dd6e01 --- /dev/null +++ b/homeassistant/components/slack/translations/hu.json @@ -0,0 +1,29 @@ +{ + "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "default_channel": "Alap\u00e9rtelmezett csatorna", + "icon": "Ikon", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "data_description": { + "api_key": "A Slack API token a Slack \u00fczenetek k\u00fcld\u00e9s\u00e9re.", + "default_channel": "Az a csatorna, amelyre k\u00f6zz\u00e9tehet, ha nincs megadva csatorna az \u00fczenet k\u00fcld\u00e9sekor.", + "icon": "Haszn\u00e1lja a Slack hangulatjelek egyik\u00e9t ikonk\u00e9nt a megadott felhaszn\u00e1l\u00f3n\u00e9vhez.", + "username": "A Home Assistant a megadott felhaszn\u00e1l\u00f3n\u00e9vvel fog \u00fczenetet k\u00fcldeni a Slackre." + }, + "description": "L\u00e1sd a Slack API-kulcs megszerz\u00e9s\u00e9nek dokument\u00e1ci\u00f3j\u00e1t." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/id.json b/homeassistant/components/slack/translations/id.json new file mode 100644 index 00000000000..3897704b540 --- /dev/null +++ b/homeassistant/components/slack/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "default_channel": "Saluran Baku", + "icon": "Ikon", + "username": "Nama Pengguna" + }, + "data_description": { + "api_key": "Token API Slack yang digunakan untuk mengirim pesan Slack.", + "default_channel": "Saluran tujuan posting jika tidak ada saluran yang ditentukan saat mengirim pesan.", + "icon": "Gunakan salah satu emoji Slack sebagai Ikon untuk nama pengguna yang disediakan.", + "username": "Home Assistant akan memposting ke Slack menggunakan nama pengguna yang ditentukan." + }, + "description": "Lihat dokumentasi tentang mendapatkan kunci API Slack Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/it.json b/homeassistant/components/slack/translations/it.json new file mode 100644 index 00000000000..dbd6a44de23 --- /dev/null +++ b/homeassistant/components/slack/translations/it.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API", + "default_channel": "Canale predefinito", + "icon": "Icona", + "username": "Nome utente" + }, + "data_description": { + "api_key": "Il token API Slack da utilizzare per inviare messaggi Slack.", + "default_channel": "Il canale su cui inviare se non viene specificato alcun canale durante l'invio di un messaggio.", + "icon": "Usa uno degli emoji Slack come icona per il nome utente fornito.", + "username": "Home Assistant pubblicher\u00e0 su Slack utilizzando il nome utente specificato." + }, + "description": "Fai riferimento alla documentazione su come ottenere la chiave API Slack." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/ja.json b/homeassistant/components/slack/translations/ja.json new file mode 100644 index 00000000000..c6a7f478db6 --- /dev/null +++ b/homeassistant/components/slack/translations/ja.json @@ -0,0 +1,29 @@ +{ + "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", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "user": { + "data": { + "api_key": "API\u30ad\u30fc", + "default_channel": "\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u30c1\u30e3\u30f3\u30cd\u30eb", + "icon": "\u30a2\u30a4\u30b3\u30f3", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + }, + "data_description": { + "api_key": "Slack\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u9001\u4fe1\u306b\u4f7f\u7528\u3059\u308b\u3001Slack API\u30c8\u30fc\u30af\u30f3\u3002", + "default_channel": "\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u9001\u4fe1\u6642\u306b\u3001\u30c1\u30e3\u30f3\u30cd\u30eb\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u306a\u304b\u3063\u305f\u5834\u5408\u306b\u6295\u7a3f\u3059\u308b\u30c1\u30e3\u30f3\u30cd\u30eb\u3002", + "icon": "Slack\u306e\u7d75\u6587\u5b57\u3092\u3001\u6307\u5b9a\u3055\u308c\u305f\u30e6\u30fc\u30b6\u30fc\u540d\u306e\u30a2\u30a4\u30b3\u30f3\u3068\u3057\u3066\u4f7f\u7528\u3057\u307e\u3059\u3002", + "username": "Home Assistant\u306f\u3001\u6307\u5b9a\u3055\u308c\u305f\u30e6\u30fc\u30b6\u30fc\u540d\u3092\u4f7f\u7528\u3057\u3066Slack\u306b\u6295\u7a3f\u3057\u307e\u3059\u3002" + }, + "description": "Slack API\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" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/ko.json b/homeassistant/components/slack/translations/ko.json new file mode 100644 index 00000000000..3e0a8596029 --- /dev/null +++ b/homeassistant/components/slack/translations/ko.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4", + "default_channel": "\uae30\ubcf8 \ucc44\ub110", + "icon": "\uc544\uc774\ucf58", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "data_description": { + "api_key": "Slack \uba54\uc2dc\uc9c0\ub97c \ubcf4\ub0b4\ub294 \ub370 \uc0ac\uc6a9\ud560 Slack API \ud1a0\ud070\uc785\ub2c8\ub2e4.", + "default_channel": "\uba54\uc2dc\uc9c0\ub97c \ubcf4\ub0bc \ub54c \ucc44\ub110\uc774 \uc9c0\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc0ac\uc6a9\ud560 \ucc44\ub110\uc785\ub2c8\ub2e4.", + "icon": "Slack \uc774\ubaa8\ud2f0\ucf58\uc744 \uc0ac\uc6a9\uc790 \uc774\ub984\uc758 \uc544\uc774\ucf58\uc73c\ub85c \uc0ac\uc6a9\ud569\ub2c8\ub2e4.", + "username": "Home Assistant\ub294 \uc0ac\uc6a9\uc790 \uc774\ub984\uc73c\ub85c Slack\uc5d0 \uac8c\uc2dc\ud569\ub2c8\ub2e4." + }, + "description": "Slack API \ud0a4\ub97c \uac00\uc838\uc624\ub294 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud558\uc2ed\uc2dc\uc624." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/nl.json b/homeassistant/components/slack/translations/nl.json new file mode 100644 index 00000000000..bb04a7dbf18 --- /dev/null +++ b/homeassistant/components/slack/translations/nl.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Dienst is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "api_key": "API-sleutel", + "default_channel": "Standaard kanaal", + "icon": "Icoon", + "username": "Gebruikersnaam" + }, + "data_description": { + "api_key": "Het Slack API-token dat moet worden gebruikt voor het verzenden van Slack-berichten.", + "default_channel": "Het kanaal waarnaar moet worden gepost als er geen kanaal is opgegeven bij het verzenden van een bericht.", + "icon": "Gebruik een van de Slack emoji's als icoon voor de opgegeven gebruikersnaam.", + "username": "Home Assistant plaatst berichten op Slack met de opgegeven gebruikersnaam." + }, + "description": "Raadpleeg de documentatie over het verkrijgen van uw Slack API-sleutel." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/no.json b/homeassistant/components/slack/translations/no.json new file mode 100644 index 00000000000..850d18ff5e0 --- /dev/null +++ b/homeassistant/components/slack/translations/no.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Tjenesten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "default_channel": "Standard kanal", + "icon": "Ikon", + "username": "Brukernavn" + }, + "data_description": { + "api_key": "Slack API-tokenet som skal brukes til \u00e5 sende Slack-meldinger.", + "default_channel": "Kanalen det skal legges inn p\u00e5 hvis ingen kanal er angitt n\u00e5r du sender en melding.", + "icon": "Bruk en av Slack-emojiene som et ikon for det oppgitte brukernavnet.", + "username": "Home Assistant vil legge ut p\u00e5 Slack ved \u00e5 bruke det spesifiserte brukernavnet." + }, + "description": "Se dokumentasjonen for \u00e5 f\u00e5 Slack API-n\u00f8kkelen din." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/pl.json b/homeassistant/components/slack/translations/pl.json new file mode 100644 index 00000000000..33e8155a7f2 --- /dev/null +++ b/homeassistant/components/slack/translations/pl.json @@ -0,0 +1,29 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "default_channel": "Domy\u015blny kana\u0142", + "icon": "Ikona", + "username": "Nazwa u\u017cytkownika" + }, + "data_description": { + "api_key": "Token API Slack u\u017cywany do wysy\u0142ania wiadomo\u015bci Slack.", + "default_channel": "Kana\u0142, do kt\u00f3rego zostanie wys\u0142ana wiadomo\u015b\u0107, je\u015bli podczas wysy\u0142ania wiadomo\u015bci nie zosta\u0142 okre\u015blony \u017caden kana\u0142.", + "icon": "U\u017cyj jednej z emotikonek Slack jako ikony dla podanej nazwy u\u017cytkownika.", + "username": "Home Assistant wy\u015ble wiadomo\u015bci, u\u017cywaj\u0105c podanej nazwy u\u017cytkownika." + }, + "description": "Zapoznaj si\u0119 z dokumentacj\u0105 dotycz\u0105c\u0105 uzyskiwania klucza API Slack." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/pt-BR.json b/homeassistant/components/slack/translations/pt-BR.json new file mode 100644 index 00000000000..a6299848bfc --- /dev/null +++ b/homeassistant/components/slack/translations/pt-BR.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falhou ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Chave de API", + "default_channel": "Canal padr\u00e3o", + "icon": "\u00cdcone", + "username": "Nome de usu\u00e1rio" + }, + "data_description": { + "api_key": "O token da API do Slack a ser usado para enviar mensagens do Slack.", + "default_channel": "O canal para postar se nenhum canal for especificado ao enviar uma mensagem.", + "icon": "Use um dos emojis do Slack como \u00edcone para o nome de usu\u00e1rio fornecido.", + "username": "O Home Assistant postar\u00e1 no Slack usando o nome de usu\u00e1rio especificado." + }, + "description": "Consulte a documenta\u00e7\u00e3o sobre como obter sua chave de API do Slack." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/ru.json b/homeassistant/components/slack/translations/ru.json new file mode 100644 index 00000000000..dd25d00e367 --- /dev/null +++ b/homeassistant/components/slack/translations/ru.json @@ -0,0 +1,29 @@ +{ + "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.", + "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", + "default_channel": "\u041a\u0430\u043d\u0430\u043b \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e", + "icon": "\u0418\u043a\u043e\u043d\u043a\u0430", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + "data_description": { + "api_key": "\u0422\u043e\u043a\u0435\u043d API Slack, \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 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439.", + "default_channel": "\u041a\u0430\u043d\u0430\u043b, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0435\u0441\u043b\u0438 \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043a\u0430\u043d\u0430\u043b \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d.", + "icon": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043e\u0434\u0438\u043d \u0438\u0437 \u0441\u043c\u0430\u0439\u043b\u0438\u043a\u043e\u0432 Slack \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0437\u043d\u0430\u0447\u043a\u0430 \u0434\u043b\u044f \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.", + "username": "Home Assistant \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 Slack, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f." + }, + "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 API Slack." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/sk.json b/homeassistant/components/slack/translations/sk.json new file mode 100644 index 00000000000..4e2a5fa0ed7 --- /dev/null +++ b/homeassistant/components/slack/translations/sk.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepodarilo sa pripoji\u0165", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "api_key": "API k\u013e\u00fa\u010d", + "icon": "Ikona", + "username": "U\u017e\u00edvate\u013esk\u00e9 meno" + }, + "data_description": { + "username": "Home Assistant odo\u0161le pr\u00edspevok na Slack pomocou zadan\u00e9ho pou\u017e\u00edvate\u013esk\u00e9ho mena." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/tr.json b/homeassistant/components/slack/translations/tr.json new file mode 100644 index 00000000000..4fd5a28f790 --- /dev/null +++ b/homeassistant/components/slack/translations/tr.json @@ -0,0 +1,29 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "default_channel": "Varsay\u0131lan Kanal", + "icon": "Simge", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "data_description": { + "api_key": "Slack mesajlar\u0131 g\u00f6ndermek i\u00e7in kullan\u0131lacak Slack API belirteci.", + "default_channel": "Mesaj g\u00f6nderirken kanal belirtilmemi\u015fse g\u00f6nderi yap\u0131lacak kanal.", + "icon": "Sa\u011flanan kullan\u0131c\u0131 ad\u0131 i\u00e7in Slack emojilerinden birini simge olarak kullan\u0131n.", + "username": "Home Assistant, belirtilen kullan\u0131c\u0131 ad\u0131n\u0131 kullanarak Slack'e g\u00f6nderecektir." + }, + "description": "Slack API anahtar\u0131n\u0131z\u0131 almayla ilgili belgelere bak\u0131n." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/zh-Hans.json b/homeassistant/components/slack/translations/zh-Hans.json new file mode 100644 index 00000000000..0f9177e8a5c --- /dev/null +++ b/homeassistant/components/slack/translations/zh-Hans.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u670d\u52a1\u5df2\u914d\u7f6e" + }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "invalid_auth": "\u8eab\u4efd\u9a8c\u8bc1\u65e0\u6548", + "unknown": "\u610f\u5916\u7684\u9519\u8bef" + }, + "step": { + "user": { + "data": { + "api_key": "API \u5bc6\u94a5", + "default_channel": "\u9ed8\u8ba4\u901a\u9053", + "icon": "\u56fe\u6807", + "username": "\u7528\u6237\u540d" + }, + "data_description": { + "api_key": "\u7528\u4e8e\u53d1\u9001 Slack \u6d88\u606f\u7684 Slack API token\u3002", + "default_channel": "\u5728\u53d1\u9001\u6d88\u606f\u672a\u6307\u5b9a\u901a\u9053\u65f6\u8981\u53d1\u5e03\u5230\u7684\u901a\u9053\u3002", + "icon": "\u4f7f\u7528\u5176\u4e2d\u4e00\u4e2a Slack \u8868\u60c5\u7b26\u53f7\u4f5c\u4e3a\u63d0\u4f9b\u7684\u7528\u6237\u540d\u7684\u56fe\u6807\u3002", + "username": "Home Assistant \u5c06\u4f7f\u7528\u6307\u5b9a\u7684\u7528\u6237\u540d\u53d1\u5e03\u5230 Slack\u3002" + }, + "description": "\u8bf7\u53c2\u9605\u6709\u5173\u83b7\u53d6 Slack API \u5bc6\u94a5\u7684\u6587\u6863\u3002" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slack/translations/zh-Hant.json b/homeassistant/components/slack/translations/zh-Hant.json new file mode 100644 index 00000000000..083f5a9ffeb --- /dev/null +++ b/homeassistant/components/slack/translations/zh-Hant.json @@ -0,0 +1,29 @@ +{ + "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", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "api_key": "API \u91d1\u9470", + "default_channel": "\u9810\u8a2d\u983b\u9053", + "icon": "\u5716\u793a", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "data_description": { + "api_key": "\u4f7f\u7528\u65bc\u50b3\u9001 Slack \u8a0a\u606f\u7684 Slack API \u6b0a\u6756\u3002", + "default_channel": "\u767c\u9001\u8a0a\u606f\u6642\u3001\u82e5\u7121\u6307\u5b9a\u983b\u9053\u6642\uff0c\u6240\u8981\u5f35\u8cbc\u7684\u983b\u9053\u3002", + "icon": "\u4f7f\u7528 Slack \u8868\u60c5\u7b26\u865f\u4f5c\u70ba\u63d0\u4f9b\u4f7f\u7528\u8005\u540d\u7a31\u4e4b\u5716\u793a\u3002", + "username": "Home Assistant \u5c07\u6703\u4f7f\u7528\u6307\u5b9a\u4f7f\u7528\u8005\u540d\u7a31\u5f35\u8cbc\u81f3 Slack\u3002" + }, + "description": "\u8acb\u53c3\u8003\u6587\u4ef6\u4ee5\u53d6\u5f97 API \u91d1\u9470\u3002" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sleepiq/translations/es.json b/homeassistant/components/sleepiq/translations/es.json index ae6059aa227..57b8f421844 100644 --- a/homeassistant/components/sleepiq/translations/es.json +++ b/homeassistant/components/sleepiq/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "La cuenta ya est\u00e1 configurada" + "already_configured": "La cuenta ya est\u00e1 configurada", + "reauth_successful": "Re-autenticaci\u00f3n realizada correctamente" }, "error": { "cannot_connect": "Error al conectar", @@ -11,7 +12,8 @@ "reauth_confirm": { "data": { "password": "Contrase\u00f1a" - } + }, + "title": "Reautenticaci\u00f3n de la integraci\u00f3n" }, "user": { "data": { diff --git a/homeassistant/components/sleepiq/translations/nl.json b/homeassistant/components/sleepiq/translations/nl.json index 09029be118e..4b32ac233b0 100644 --- a/homeassistant/components/sleepiq/translations/nl.json +++ b/homeassistant/components/sleepiq/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -14,7 +14,7 @@ "password": "Wachtwoord" }, "description": "De SleepIQ-integratie moet uw account {username} opnieuw verifi\u00ebren.", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { diff --git a/homeassistant/components/slimproto/media_player.py b/homeassistant/components/slimproto/media_player.py index 6b1989830e2..2f85aa4b9df 100644 --- a/homeassistant/components/slimproto/media_player.py +++ b/homeassistant/components/slimproto/media_player.py @@ -180,7 +180,9 @@ class SlimProtoPlayer(MediaPlayerEntity): 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) + sourced_media = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = sourced_media.url to_send_media_type = sourced_media.mime_type diff --git a/homeassistant/components/slimproto/translations/bg.json b/homeassistant/components/slimproto/translations/bg.json new file mode 100644 index 00000000000..f49a3911eab --- /dev/null +++ b/homeassistant/components/slimproto/translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u0412\u0435\u0447\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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/ca.json b/homeassistant/components/slimproto/translations/ca.json new file mode 100644 index 00000000000..cc92e3ec9f1 --- /dev/null +++ b/homeassistant/components/slimproto/translations/ca.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/es.json b/homeassistant/components/slimproto/translations/es.json new file mode 100644 index 00000000000..352f82f605f --- /dev/null +++ b/homeassistant/components/slimproto/translations/es.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ya configurado. S\u00f3lo es posible una sola configuraci\u00f3n." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/he.json b/homeassistant/components/slimproto/translations/he.json new file mode 100644 index 00000000000..d0c3523da94 --- /dev/null +++ b/homeassistant/components/slimproto/translations/he.json @@ -0,0 +1,7 @@ +{ + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/ja.json b/homeassistant/components/slimproto/translations/ja.json new file mode 100644 index 00000000000..4b8d0691b68 --- /dev/null +++ b/homeassistant/components/slimproto/translations/ja.json @@ -0,0 +1,7 @@ +{ + "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" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/nl.json b/homeassistant/components/slimproto/translations/nl.json index 79aaec23123..703ac8614c4 100644 --- a/homeassistant/components/slimproto/translations/nl.json +++ b/homeassistant/components/slimproto/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." } } } \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/ru.json b/homeassistant/components/slimproto/translations/ru.json new file mode 100644 index 00000000000..55894412230 --- /dev/null +++ b/homeassistant/components/slimproto/translations/ru.json @@ -0,0 +1,7 @@ +{ + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/sv.json b/homeassistant/components/slimproto/translations/sv.json new file mode 100644 index 00000000000..8ca6afbf92f --- /dev/null +++ b/homeassistant/components/slimproto/translations/sv.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Redan konfigurerad. Endast en konfiguration m\u00f6jlig." + }, + "step": { + "user": { + "one": "Tom", + "other": "Tom" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/tr.json b/homeassistant/components/slimproto/translations/tr.json new file mode 100644 index 00000000000..b04fdc4468d --- /dev/null +++ b/homeassistant/components/slimproto/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": { + "one": "Bo\u015f", + "other": "Bo\u015f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sma/translations/ko.json b/homeassistant/components/sma/translations/ko.json index 5e5aa899d96..bb627d5c622 100644 --- a/homeassistant/components/sma/translations/ko.json +++ b/homeassistant/components/sma/translations/ko.json @@ -6,17 +6,21 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "cannot_retrieve_device_info": "\uc131\uacf5\uc801\uc73c\ub85c \uc5f0\uacb0\ub418\uc5c8\uc9c0\ub9cc \uc7a5\uce58 \uc815\ubcf4\ub97c \uac80\uc0c9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { + "group": "\uadf8\ub8f9", "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638", "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" - } + }, + "description": "SMA \uc7a5\uce58 \uc815\ubcf4\ub97c \uc785\ub825\ud569\ub2c8\ub2e4.", + "title": "SMA Solar \uc124\uc815" } } } diff --git a/homeassistant/components/sma/translations/nl.json b/homeassistant/components/sma/translations/nl.json index d860518a18c..24298fe4bb3 100644 --- a/homeassistant/components/sma/translations/nl.json +++ b/homeassistant/components/sma/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang" + "already_in_progress": "De configuratie is momenteel al bezig" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -16,7 +16,7 @@ "group": "Groep", "host": "Host", "password": "Wachtwoord", - "ssl": "Gebruik een SSL-certificaat", + "ssl": "Maakt gebruik van een SSL-certificaat", "verify_ssl": "SSL-certificaat verifi\u00ebren" }, "description": "Voer uw SMA-apparaatgegevens in.", diff --git a/homeassistant/components/smappee/translations/de.json b/homeassistant/components/smappee/translations/de.json index 127c0f4d725..121b74e9627 100644 --- a/homeassistant/components/smappee/translations/de.json +++ b/homeassistant/components/smappee/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured_device": "Ger\u00e4t ist bereits konfiguriert", - "already_configured_local_device": "Lokale(s) Ger\u00e4t(e) ist / sind bereits konfiguriert. Bitte entferne diese zuerst, bevor du ein Cloud-Ger\u00e4t konfigurierst.", + "already_configured_local_device": "Lokale(s) Ger\u00e4t(e) ist/sind bereits konfiguriert. Bitte entferne diese zuerst, bevor du ein Cloud-Ger\u00e4t konfigurierst.", "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_mdns": "Nicht unterst\u00fctztes Ger\u00e4t f\u00fcr die Smappee-Integration.", diff --git a/homeassistant/components/smappee/translations/es.json b/homeassistant/components/smappee/translations/es.json index a1124557876..891e9642d53 100644 --- a/homeassistant/components/smappee/translations/es.json +++ b/homeassistant/components/smappee/translations/es.json @@ -3,7 +3,7 @@ "abort": { "already_configured_device": "El dispositivo ya est\u00e1 configurado", "already_configured_local_device": "Los dispositivos locales ya est\u00e1n configurados. Elim\u00ednelos primero antes de configurar un dispositivo en la nube.", - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "cannot_connect": "No se pudo conectar", "invalid_mdns": "Dispositivo no compatible para la integraci\u00f3n de Smappee.", "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n.", diff --git a/homeassistant/components/smappee/translations/nl.json b/homeassistant/components/smappee/translations/nl.json index ed9ef6e5e9f..8881aa1f047 100644 --- a/homeassistant/components/smappee/translations/nl.json +++ b/homeassistant/components/smappee/translations/nl.json @@ -3,11 +3,11 @@ "abort": { "already_configured_device": "Apparaat is al geconfigureerd", "already_configured_local_device": "Lokale apparaten zijn al geconfigureerd. Verwijder deze eerst voordat u een cloudapparaat configureert.", - "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", "cannot_connect": "Kan geen verbinding maken", "invalid_mdns": "Niet-ondersteund apparaat voor de Smappee-integratie.", - "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})" + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})" }, "flow_title": "{name}", "step": { diff --git a/homeassistant/components/smart_meter_texas/translations/nl.json b/homeassistant/components/smart_meter_texas/translations/nl.json index d25cf575b44..50b4c3f2fe6 100644 --- a/homeassistant/components/smart_meter_texas/translations/nl.json +++ b/homeassistant/components/smart_meter_texas/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, @@ -12,7 +12,7 @@ "user": { "data": { "password": "Wachtwoord", - "username": "Benutzername" + "username": "Gebruikersnaam" } } } diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py index 794a902e941..1b1738a94d4 100644 --- a/homeassistant/components/smartthings/light.py +++ b/homeassistant/components/smartthings/light.py @@ -14,8 +14,8 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - SUPPORT_TRANSITION, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -86,7 +86,7 @@ class SmartThingsLight(SmartThingsEntity, LightEntity): features = 0 # Brightness and transition if Capability.switch_level in self._device.capabilities: - features |= SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + features |= SUPPORT_BRIGHTNESS | LightEntityFeature.TRANSITION # Color Temperature if Capability.color_temperature in self._device.capabilities: features |= SUPPORT_COLOR_TEMP @@ -124,7 +124,10 @@ class SmartThingsLight(SmartThingsEntity, LightEntity): async def async_turn_off(self, **kwargs) -> None: """Turn the light off.""" # Switch/transition - if self._supported_features & SUPPORT_TRANSITION and ATTR_TRANSITION in kwargs: + if ( + self._supported_features & LightEntityFeature.TRANSITION + and ATTR_TRANSITION in kwargs + ): await self.async_set_level(0, int(kwargs[ATTR_TRANSITION])) else: await self._device.switch_off(set_status=True) diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index a6d35c40335..fbd63d41373 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -32,6 +32,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_send, ) from homeassistant.helpers.network import NoURLAvailableError, get_url +from homeassistant.helpers.storage import Store from .const import ( APP_NAME_PREFIX, @@ -210,8 +211,8 @@ async def setup_smartapp_endpoint(hass: HomeAssistant): return # Get/create config to store a unique id for this hass instance. - store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) - if not (config := await store.async_load()): + store = Store(hass, STORAGE_VERSION, STORAGE_KEY) + if not (config := await store.async_load()) or not isinstance(config, dict): # Create config config = { CONF_INSTANCE_ID: str(uuid4()), @@ -282,7 +283,7 @@ async def unload_smartapp_endpoint(hass: HomeAssistant): if cloudhook_url and cloud.async_is_logged_in(hass): await cloud.async_delete_cloudhook(hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]) # Remove cloudhook from storage - store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + store = Store(hass, STORAGE_VERSION, STORAGE_KEY) await store.async_save( { CONF_INSTANCE_ID: hass.data[DOMAIN][CONF_INSTANCE_ID], @@ -404,7 +405,7 @@ async def _continue_flow( ( flow for flow in hass.config_entries.flow.async_progress_by_handler(DOMAIN) - if flow["context"]["unique_id"] == unique_id + if flow["context"].get("unique_id") == unique_id ), None, ) diff --git a/homeassistant/components/smarttub/translations/es.json b/homeassistant/components/smarttub/translations/es.json index 20a57210448..8f2eb153cb7 100644 --- a/homeassistant/components/smarttub/translations/es.json +++ b/homeassistant/components/smarttub/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { diff --git a/homeassistant/components/smarttub/translations/ja.json b/homeassistant/components/smarttub/translations/ja.json index dfe2ef1ab26..44d753fa22e 100644 --- a/homeassistant/components/smarttub/translations/ja.json +++ b/homeassistant/components/smarttub/translations/ja.json @@ -9,7 +9,7 @@ }, "step": { "reauth_confirm": { - "description": "SmartTub\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", + "description": "SmartTub\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", "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" }, "user": { diff --git a/homeassistant/components/smarttub/translations/ko.json b/homeassistant/components/smarttub/translations/ko.json index ff50dadc63e..6ba95c931bd 100644 --- a/homeassistant/components/smarttub/translations/ko.json +++ b/homeassistant/components/smarttub/translations/ko.json @@ -9,6 +9,7 @@ }, "step": { "reauth_confirm": { + "description": "SmartTub \ud1b5\ud569\uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" }, "user": { diff --git a/homeassistant/components/smarttub/translations/nl.json b/homeassistant/components/smarttub/translations/nl.json index 6d1e605d315..ad351ba42e3 100644 --- a/homeassistant/components/smarttub/translations/nl.json +++ b/homeassistant/components/smarttub/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie" @@ -10,7 +10,7 @@ "step": { "reauth_confirm": { "description": "De SmartTub-integratie moet uw account opnieuw verifi\u00ebren", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { diff --git a/homeassistant/components/smhi/translations/bg.json b/homeassistant/components/smhi/translations/bg.json index bcd30370ad4..d1dfdde7a32 100644 --- a/homeassistant/components/smhi/translations/bg.json +++ b/homeassistant/components/smhi/translations/bg.json @@ -4,15 +4,13 @@ "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" }, "error": { - "name_exists": "\u0418\u043c\u0435\u0442\u043e \u0432\u0435\u0447\u0435 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430", "wrong_location": "\u041f\u043e\u0434\u0434\u044a\u0440\u0436\u0430\u0442 \u0441\u0435 \u0441\u0430\u043c\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0428\u0432\u0435\u0446\u0438\u044f" }, "step": { "user": { "data": { "latitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0448\u0438\u0440\u0438\u043d\u0430", - "longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430", - "name": "\u0418\u043c\u0435" + "longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430" }, "title": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0428\u0432\u0435\u0446\u0438\u044f" } diff --git a/homeassistant/components/smhi/translations/ca.json b/homeassistant/components/smhi/translations/ca.json index 4755e35f2ba..c3f53542274 100644 --- a/homeassistant/components/smhi/translations/ca.json +++ b/homeassistant/components/smhi/translations/ca.json @@ -4,15 +4,13 @@ "already_configured": "El compte ja est\u00e0 configurat" }, "error": { - "name_exists": "El nom ja existeix", "wrong_location": "La ubicaci\u00f3 ha d'estar a Su\u00e8cia" }, "step": { "user": { "data": { "latitude": "Latitud", - "longitude": "Longitud", - "name": "Nom" + "longitude": "Longitud" }, "title": "Ubicaci\u00f3 a Su\u00e8cia" } diff --git a/homeassistant/components/smhi/translations/cs.json b/homeassistant/components/smhi/translations/cs.json index 5721edc084b..6d65b6411c9 100644 --- a/homeassistant/components/smhi/translations/cs.json +++ b/homeassistant/components/smhi/translations/cs.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "N\u00e1zev ji\u017e existuje", "wrong_location": "Lokalita pouze pro \u0160v\u00e9dsko" }, "step": { "user": { "data": { "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", - "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka", - "name": "Jm\u00e9no" + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka" }, "title": "Lokalita ve \u0160v\u00e9dsku" } diff --git a/homeassistant/components/smhi/translations/da.json b/homeassistant/components/smhi/translations/da.json index 979f4b0f7b6..9b07283456b 100644 --- a/homeassistant/components/smhi/translations/da.json +++ b/homeassistant/components/smhi/translations/da.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "Navnet findes allerede", "wrong_location": "Lokalitet kun i Sverige" }, "step": { "user": { "data": { "latitude": "Breddegrad", - "longitude": "L\u00e6ngdegrad", - "name": "Navn" + "longitude": "L\u00e6ngdegrad" }, "title": "Lokalitet i Sverige" } diff --git a/homeassistant/components/smhi/translations/de.json b/homeassistant/components/smhi/translations/de.json index 716f9693e96..7434d9d5800 100644 --- a/homeassistant/components/smhi/translations/de.json +++ b/homeassistant/components/smhi/translations/de.json @@ -4,15 +4,13 @@ "already_configured": "Konto wurde bereits konfiguriert" }, "error": { - "name_exists": "Name existiert bereits", "wrong_location": "Standort nur in Schweden" }, "step": { "user": { "data": { "latitude": "Breitengrad", - "longitude": "L\u00e4ngengrad", - "name": "Name" + "longitude": "L\u00e4ngengrad" }, "title": "Standort in Schweden" } diff --git a/homeassistant/components/smhi/translations/el.json b/homeassistant/components/smhi/translations/el.json index c852bd3a08e..736dc0c026a 100644 --- a/homeassistant/components/smhi/translations/el.json +++ b/homeassistant/components/smhi/translations/el.json @@ -4,15 +4,13 @@ "already_configured": "\u039f \u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2" }, "error": { - "name_exists": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7", "wrong_location": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u039c\u03cc\u03bd\u03bf \u03a3\u03bf\u03c5\u03b7\u03b4\u03af\u03b1" }, "step": { "user": { "data": { "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", - "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2" }, "title": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03c3\u03c4\u03b7 \u03a3\u03bf\u03c5\u03b7\u03b4\u03af\u03b1" } diff --git a/homeassistant/components/smhi/translations/en.json b/homeassistant/components/smhi/translations/en.json index 5400122be8a..5c8f4f6ae24 100644 --- a/homeassistant/components/smhi/translations/en.json +++ b/homeassistant/components/smhi/translations/en.json @@ -4,15 +4,13 @@ "already_configured": "Account is already configured" }, "error": { - "name_exists": "Name already exists", "wrong_location": "Location Sweden only" }, "step": { "user": { "data": { "latitude": "Latitude", - "longitude": "Longitude", - "name": "Name" + "longitude": "Longitude" }, "title": "Location in Sweden" } diff --git a/homeassistant/components/smhi/translations/es-419.json b/homeassistant/components/smhi/translations/es-419.json index 3db65a34ad9..c73d574c5be 100644 --- a/homeassistant/components/smhi/translations/es-419.json +++ b/homeassistant/components/smhi/translations/es-419.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "El nombre ya existe", "wrong_location": "Ubicaci\u00f3n Suecia solamente" }, "step": { "user": { "data": { "latitude": "Latitud", - "longitude": "Longitud", - "name": "Nombre" + "longitude": "Longitud" }, "title": "Ubicaci\u00f3n en Suecia" } diff --git a/homeassistant/components/smhi/translations/es.json b/homeassistant/components/smhi/translations/es.json index d9902ba7d08..430f04d59fc 100644 --- a/homeassistant/components/smhi/translations/es.json +++ b/homeassistant/components/smhi/translations/es.json @@ -1,15 +1,16 @@ { "config": { + "abort": { + "already_configured": "La cuenta ya est\u00e1 configurada" + }, "error": { - "name_exists": "Nombre ya existe", "wrong_location": "Ubicaci\u00f3n Suecia solamente" }, "step": { "user": { "data": { "latitude": "Latitud", - "longitude": "Longitud", - "name": "Nombre" + "longitude": "Longitud" }, "title": "Ubicaci\u00f3n en Suecia" } diff --git a/homeassistant/components/smhi/translations/et.json b/homeassistant/components/smhi/translations/et.json index a32f38e28ca..d07ec7d3391 100644 --- a/homeassistant/components/smhi/translations/et.json +++ b/homeassistant/components/smhi/translations/et.json @@ -4,15 +4,13 @@ "already_configured": "Konto on juba h\u00e4\u00e4lestatud" }, "error": { - "name_exists": "Nimi on juba olemas", "wrong_location": "Asukoht saab olla ainult Rootsis" }, "step": { "user": { "data": { "latitude": "Laiuskraad", - "longitude": "Pikkuskraad", - "name": "Nimi" + "longitude": "Pikkuskraad" }, "title": "Asukoht Rootsis" } diff --git a/homeassistant/components/smhi/translations/fi.json b/homeassistant/components/smhi/translations/fi.json index 2a05c47dcbc..cc5377e3654 100644 --- a/homeassistant/components/smhi/translations/fi.json +++ b/homeassistant/components/smhi/translations/fi.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "Nimi on jo olemassa", "wrong_location": "Sijainti vain Ruotsi" }, "step": { "user": { "data": { "latitude": "Leveysaste", - "longitude": "Pituusaste", - "name": "Nimi" + "longitude": "Pituusaste" }, "title": "Sijainti Ruotsissa" } diff --git a/homeassistant/components/smhi/translations/fr.json b/homeassistant/components/smhi/translations/fr.json index 6bf27915f97..84d5c6cfa7c 100644 --- a/homeassistant/components/smhi/translations/fr.json +++ b/homeassistant/components/smhi/translations/fr.json @@ -4,15 +4,13 @@ "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" }, "error": { - "name_exists": "Ce nom est d\u00e9j\u00e0 utilis\u00e9", "wrong_location": "En Su\u00e8de uniquement" }, "step": { "user": { "data": { "latitude": "Latitude", - "longitude": "Longitude", - "name": "Nom" + "longitude": "Longitude" }, "title": "Localisation en Su\u00e8de" } diff --git a/homeassistant/components/smhi/translations/he.json b/homeassistant/components/smhi/translations/he.json index cb8ff44626c..622d75d6c6c 100644 --- a/homeassistant/components/smhi/translations/he.json +++ b/homeassistant/components/smhi/translations/he.json @@ -7,8 +7,7 @@ "user": { "data": { "latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1", - "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", - "name": "\u05e9\u05dd" + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" } } } diff --git a/homeassistant/components/smhi/translations/hu.json b/homeassistant/components/smhi/translations/hu.json index e83ea03a193..22307044da2 100644 --- a/homeassistant/components/smhi/translations/hu.json +++ b/homeassistant/components/smhi/translations/hu.json @@ -4,15 +4,13 @@ "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" }, "step": { "user": { "data": { "latitude": "Sz\u00e9less\u00e9g", - "longitude": "Hossz\u00fas\u00e1g", - "name": "Elnevez\u00e9s" + "longitude": "Hossz\u00fas\u00e1g" }, "title": "Helysz\u00edn Sv\u00e9dorsz\u00e1gban" } diff --git a/homeassistant/components/smhi/translations/id.json b/homeassistant/components/smhi/translations/id.json index 842580dac79..b1b9450dc05 100644 --- a/homeassistant/components/smhi/translations/id.json +++ b/homeassistant/components/smhi/translations/id.json @@ -4,15 +4,13 @@ "already_configured": "Akun sudah dikonfigurasi" }, "error": { - "name_exists": "Nama sudah ada", "wrong_location": "Hanya untuk lokasi di Swedia" }, "step": { "user": { "data": { "latitude": "Lintang", - "longitude": "Bujur", - "name": "Nama" + "longitude": "Bujur" }, "title": "Lokasi di Swedia" } diff --git a/homeassistant/components/smhi/translations/it.json b/homeassistant/components/smhi/translations/it.json index 3df53d13ec1..830107967dd 100644 --- a/homeassistant/components/smhi/translations/it.json +++ b/homeassistant/components/smhi/translations/it.json @@ -4,15 +4,13 @@ "already_configured": "L'account \u00e8 gi\u00e0 configurato" }, "error": { - "name_exists": "Il nome \u00e8 gi\u00e0 esistente", "wrong_location": "Localit\u00e0 solamente della Svezia" }, "step": { "user": { "data": { "latitude": "Latitudine", - "longitude": "Logitudine", - "name": "Nome" + "longitude": "Logitudine" }, "title": "Localit\u00e0 in Svezia" } diff --git a/homeassistant/components/smhi/translations/ja.json b/homeassistant/components/smhi/translations/ja.json index 38e4ccad389..390732d7db4 100644 --- a/homeassistant/components/smhi/translations/ja.json +++ b/homeassistant/components/smhi/translations/ja.json @@ -4,15 +4,13 @@ "already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" }, "error": { - "name_exists": "\u540d\u524d\u306f\u3059\u3067\u306b\u5b58\u5728\u3057\u307e\u3059", "wrong_location": "\u6240\u5728\u5730 \u30b9\u30a6\u30a7\u30fc\u30c7\u30f3\u306e\u307f" }, "step": { "user": { "data": { "latitude": "\u7def\u5ea6", - "longitude": "\u7d4c\u5ea6", - "name": "\u540d\u524d" + "longitude": "\u7d4c\u5ea6" }, "title": "\u30b9\u30a6\u30a7\u30fc\u30c7\u30f3\u3067\u306e\u4f4d\u7f6e" } diff --git a/homeassistant/components/smhi/translations/ko.json b/homeassistant/components/smhi/translations/ko.json index dac6c9018b2..6f1cbeda859 100644 --- a/homeassistant/components/smhi/translations/ko.json +++ b/homeassistant/components/smhi/translations/ko.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4", "wrong_location": "\uc2a4\uc6e8\ub374 \uc9c0\uc5ed \uc804\uc6a9\uc785\ub2c8\ub2e4" }, "step": { "user": { "data": { "latitude": "\uc704\ub3c4", - "longitude": "\uacbd\ub3c4", - "name": "\uc774\ub984" + "longitude": "\uacbd\ub3c4" }, "title": "\uc2a4\uc6e8\ub374 \uc9c0\uc5ed \uc704\uce58" } diff --git a/homeassistant/components/smhi/translations/lb.json b/homeassistant/components/smhi/translations/lb.json index 9444569c4a1..9abb503923d 100644 --- a/homeassistant/components/smhi/translations/lb.json +++ b/homeassistant/components/smhi/translations/lb.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "Numm g\u00ebtt et schonn", "wrong_location": "N\u00ebmmen Uertschaften an Schweden" }, "step": { "user": { "data": { "latitude": "Breedegrad", - "longitude": "L\u00e4ngegrad", - "name": "Numm" + "longitude": "L\u00e4ngegrad" }, "title": "Uertschaft an Schweden" } diff --git a/homeassistant/components/smhi/translations/nl.json b/homeassistant/components/smhi/translations/nl.json index 597e106ab7e..abf19b47ef9 100644 --- a/homeassistant/components/smhi/translations/nl.json +++ b/homeassistant/components/smhi/translations/nl.json @@ -4,15 +4,13 @@ "already_configured": "Account is al geconfigureerd" }, "error": { - "name_exists": "Naam bestaat al", "wrong_location": "Locatie alleen Zweden" }, "step": { "user": { "data": { "latitude": "Breedtegraad", - "longitude": "Lengtegraad", - "name": "Naam" + "longitude": "Lengtegraad" }, "title": "Locatie in Zweden" } diff --git a/homeassistant/components/smhi/translations/no.json b/homeassistant/components/smhi/translations/no.json index 914baaca9ce..b2ec9e8732a 100644 --- a/homeassistant/components/smhi/translations/no.json +++ b/homeassistant/components/smhi/translations/no.json @@ -4,15 +4,13 @@ "already_configured": "Kontoen er allerede konfigurert" }, "error": { - "name_exists": "Navnet eksisterer allerede", "wrong_location": "Bare plassering i Sverige" }, "step": { "user": { "data": { "latitude": "Breddegrad", - "longitude": "Lengdegrad", - "name": "Navn" + "longitude": "Lengdegrad" }, "title": "Plassering i Sverige" } diff --git a/homeassistant/components/smhi/translations/pl.json b/homeassistant/components/smhi/translations/pl.json index 18e9156a936..65fc02e0c7c 100644 --- a/homeassistant/components/smhi/translations/pl.json +++ b/homeassistant/components/smhi/translations/pl.json @@ -4,15 +4,13 @@ "already_configured": "Konto jest ju\u017c skonfigurowane" }, "error": { - "name_exists": "Nazwa ju\u017c istnieje", "wrong_location": "Lokalizacja w Szwecji" }, "step": { "user": { "data": { "latitude": "Szeroko\u015b\u0107 geograficzna", - "longitude": "D\u0142ugo\u015b\u0107 geograficzna", - "name": "Nazwa" + "longitude": "D\u0142ugo\u015b\u0107 geograficzna" }, "title": "Lokalizacja w Szwecji" } diff --git a/homeassistant/components/smhi/translations/pt-BR.json b/homeassistant/components/smhi/translations/pt-BR.json index 235008c7c31..2f335a58aa6 100644 --- a/homeassistant/components/smhi/translations/pt-BR.json +++ b/homeassistant/components/smhi/translations/pt-BR.json @@ -4,15 +4,13 @@ "already_configured": "A conta j\u00e1 foi configurada" }, "error": { - "name_exists": "O nome j\u00e1 existe", "wrong_location": "Localiza\u00e7\u00e3o apenas na Su\u00e9cia" }, "step": { "user": { "data": { "latitude": "Latitude", - "longitude": "Longitude", - "name": "Nome" + "longitude": "Longitude" }, "title": "Localiza\u00e7\u00e3o na Su\u00e9cia" } diff --git a/homeassistant/components/smhi/translations/pt.json b/homeassistant/components/smhi/translations/pt.json index c23a33b6e19..d5cd5e83a13 100644 --- a/homeassistant/components/smhi/translations/pt.json +++ b/homeassistant/components/smhi/translations/pt.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "Nome j\u00e1 existe", "wrong_location": "Localiza\u00e7\u00e3o apenas na Su\u00e9cia" }, "step": { "user": { "data": { "latitude": "Latitude", - "longitude": "Longitude", - "name": "Nome" + "longitude": "Longitude" }, "title": "Localiza\u00e7\u00e3o na Su\u00e9cia" } diff --git a/homeassistant/components/smhi/translations/ro.json b/homeassistant/components/smhi/translations/ro.json index 0261289fd31..f1ad756f798 100644 --- a/homeassistant/components/smhi/translations/ro.json +++ b/homeassistant/components/smhi/translations/ro.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "Numele exist\u0103 deja", "wrong_location": "Loca\u021bia numai \u00een Suedia" }, "step": { "user": { "data": { "latitude": "Latitudine", - "longitude": "Longitudine", - "name": "Nume" + "longitude": "Longitudine" }, "title": "Loca\u021bie \u00een Suedia" } diff --git a/homeassistant/components/smhi/translations/ru.json b/homeassistant/components/smhi/translations/ru.json index b771dfcf961..41e009b85d3 100644 --- a/homeassistant/components/smhi/translations/ru.json +++ b/homeassistant/components/smhi/translations/ru.json @@ -4,15 +4,13 @@ "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": { - "name_exists": "\u042d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.", "wrong_location": "\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0428\u0432\u0435\u0446\u0438\u0438." }, "step": { "user": { "data": { "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", - "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430" }, "title": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0428\u0432\u0435\u0446\u0438\u0438" } diff --git a/homeassistant/components/smhi/translations/sk.json b/homeassistant/components/smhi/translations/sk.json index 81532ef4801..e6945904d90 100644 --- a/homeassistant/components/smhi/translations/sk.json +++ b/homeassistant/components/smhi/translations/sk.json @@ -4,8 +4,7 @@ "user": { "data": { "latitude": "Zemepisn\u00e1 \u0161\u00edrka", - "longitude": "Zemepisn\u00e1 d\u013a\u017eka", - "name": "N\u00e1zov" + "longitude": "Zemepisn\u00e1 d\u013a\u017eka" } } } diff --git a/homeassistant/components/smhi/translations/sl.json b/homeassistant/components/smhi/translations/sl.json index 91fe7d8375f..07e249a2245 100644 --- a/homeassistant/components/smhi/translations/sl.json +++ b/homeassistant/components/smhi/translations/sl.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "Ime \u017ee obstaja", "wrong_location": "Lokacija le na \u0160vedskem" }, "step": { "user": { "data": { "latitude": "Zemljepisna \u0161irina", - "longitude": "Zemljepisna dol\u017eina", - "name": "Ime" + "longitude": "Zemljepisna dol\u017eina" }, "title": "Lokacija na \u0160vedskem" } diff --git a/homeassistant/components/smhi/translations/sv.json b/homeassistant/components/smhi/translations/sv.json index b6afc479b70..0bb597e393b 100644 --- a/homeassistant/components/smhi/translations/sv.json +++ b/homeassistant/components/smhi/translations/sv.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "Namnet finns redan", "wrong_location": "Plats i Sverige endast" }, "step": { "user": { "data": { "latitude": "Latitud", - "longitude": "Longitud", - "name": "Namn" + "longitude": "Longitud" }, "title": "Plats i Sverige" } diff --git a/homeassistant/components/smhi/translations/th.json b/homeassistant/components/smhi/translations/th.json index 0c08363fca6..85494a9dc6b 100644 --- a/homeassistant/components/smhi/translations/th.json +++ b/homeassistant/components/smhi/translations/th.json @@ -4,8 +4,7 @@ "user": { "data": { "latitude": "\u0e25\u0e30\u0e15\u0e34\u0e08\u0e39\u0e14", - "longitude": "\u0e25\u0e2d\u0e07\u0e08\u0e34\u0e08\u0e39\u0e14", - "name": "\u0e0a\u0e37\u0e48\u0e2d" + "longitude": "\u0e25\u0e2d\u0e07\u0e08\u0e34\u0e08\u0e39\u0e14" } } } diff --git a/homeassistant/components/smhi/translations/tr.json b/homeassistant/components/smhi/translations/tr.json index 206573ae69b..613ac6e691e 100644 --- a/homeassistant/components/smhi/translations/tr.json +++ b/homeassistant/components/smhi/translations/tr.json @@ -4,15 +4,13 @@ "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" }, "error": { - "name_exists": "Bu ad zaten var", "wrong_location": "Konum sadece \u0130sve\u00e7" }, "step": { "user": { "data": { "latitude": "Enlem", - "longitude": "Boylam", - "name": "Ad" + "longitude": "Boylam" }, "title": "\u0130sve\u00e7'teki konum" } diff --git a/homeassistant/components/smhi/translations/uk.json b/homeassistant/components/smhi/translations/uk.json index 24af32172ba..e7de5db5d1b 100644 --- a/homeassistant/components/smhi/translations/uk.json +++ b/homeassistant/components/smhi/translations/uk.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "\u0426\u044f \u043d\u0430\u0437\u0432\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f.", "wrong_location": "\u0422\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u0428\u0432\u0435\u0446\u0456\u0457." }, "step": { "user": { "data": { "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", - "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", - "name": "\u041d\u0430\u0437\u0432\u0430" + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430" }, "title": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432 \u0428\u0432\u0435\u0446\u0456\u0457" } diff --git a/homeassistant/components/smhi/translations/zh-Hans.json b/homeassistant/components/smhi/translations/zh-Hans.json index b2ddc3aa1d7..64421e462d3 100644 --- a/homeassistant/components/smhi/translations/zh-Hans.json +++ b/homeassistant/components/smhi/translations/zh-Hans.json @@ -1,15 +1,13 @@ { "config": { "error": { - "name_exists": "\u540d\u79f0\u5df2\u5b58\u5728", "wrong_location": "\u4ec5\u9650\u745e\u5178\u7684\u4f4d\u7f6e" }, "step": { "user": { "data": { "latitude": "\u7eac\u5ea6", - "longitude": "\u7ecf\u5ea6", - "name": "\u540d\u79f0" + "longitude": "\u7ecf\u5ea6" }, "title": "\u5728\u745e\u5178\u7684\u4f4d\u7f6e" } diff --git a/homeassistant/components/smhi/translations/zh-Hant.json b/homeassistant/components/smhi/translations/zh-Hant.json index f5d993ab6c9..8b68a30a4b2 100644 --- a/homeassistant/components/smhi/translations/zh-Hant.json +++ b/homeassistant/components/smhi/translations/zh-Hant.json @@ -4,15 +4,13 @@ "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728", "wrong_location": "\u50c5\u9650\u745e\u5178\u5ea7\u6a19" }, "step": { "user": { "data": { "latitude": "\u7def\u5ea6", - "longitude": "\u7d93\u5ea6", - "name": "\u540d\u7a31" + "longitude": "\u7d93\u5ea6" }, "title": "\u745e\u5178\u5ea7\u6a19" } diff --git a/homeassistant/components/sms/__init__.py b/homeassistant/components/sms/__init__.py index 9b091942556..b1c2703409c 100644 --- a/homeassistant/components/sms/__init__.py +++ b/homeassistant/components/sms/__init__.py @@ -1,4 +1,6 @@ """The sms component.""" +import logging + import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -7,13 +9,24 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import ConfigType -from .const import DOMAIN, SMS_GATEWAY +from .const import CONF_BAUD_SPEED, DEFAULT_BAUD_SPEED, DOMAIN, SMS_GATEWAY from .gateway import create_sms_gateway +_LOGGER = logging.getLogger(__name__) + PLATFORMS = [Platform.SENSOR] +SMS_CONFIG_SCHEMA = {vol.Required(CONF_DEVICE): cv.isdevice} + CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.Schema({vol.Required(CONF_DEVICE): cv.isdevice})}, + { + DOMAIN: vol.Schema( + vol.All( + cv.deprecated(CONF_DEVICE), + SMS_CONFIG_SCHEMA, + ), + ) + }, extra=vol.ALLOW_EXTRA, ) @@ -39,7 +52,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Configure Gammu state machine.""" device = entry.data[CONF_DEVICE] - config = {"Device": device, "Connection": "at"} + connection_mode = "at" + baud_speed = entry.data.get(CONF_BAUD_SPEED, DEFAULT_BAUD_SPEED) + if baud_speed != DEFAULT_BAUD_SPEED: + connection_mode += baud_speed + config = {"Device": device, "Connection": connection_mode} + _LOGGER.debug("Connecting mode:%s", connection_mode) gateway = await create_sms_gateway(config, hass) if not gateway: return False diff --git a/homeassistant/components/sms/config_flow.py b/homeassistant/components/sms/config_flow.py index 37b78ee3ea3..9128b6187c1 100644 --- a/homeassistant/components/sms/config_flow.py +++ b/homeassistant/components/sms/config_flow.py @@ -6,13 +6,21 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions from homeassistant.const import CONF_DEVICE +from homeassistant.helpers import selector -from .const import DOMAIN +from .const import CONF_BAUD_SPEED, DEFAULT_BAUD_SPEED, DEFAULT_BAUD_SPEEDS, DOMAIN from .gateway import create_sms_gateway _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema({vol.Required(CONF_DEVICE): str}) +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_DEVICE): str, + vol.Optional(CONF_BAUD_SPEED, default=DEFAULT_BAUD_SPEED): selector.selector( + {"select": {"options": DEFAULT_BAUD_SPEEDS}} + ), + } +) async def get_imei_from_config(hass: core.HomeAssistant, data): @@ -21,7 +29,11 @@ async def get_imei_from_config(hass: core.HomeAssistant, data): Data has the keys from DATA_SCHEMA with values provided by the user. """ device = data[CONF_DEVICE] - config = {"Device": device, "Connection": "at"} + connection_mode = "at" + baud_speed = data.get(CONF_BAUD_SPEED, DEFAULT_BAUD_SPEED) + if baud_speed != DEFAULT_BAUD_SPEED: + connection_mode += baud_speed + config = {"Device": device, "Connection": connection_mode} gateway = await create_sms_gateway(config, hass) if not gateway: raise CannotConnect diff --git a/homeassistant/components/sms/const.py b/homeassistant/components/sms/const.py index ab2c15a0c49..7c40a04073c 100644 --- a/homeassistant/components/sms/const.py +++ b/homeassistant/components/sms/const.py @@ -3,3 +3,27 @@ DOMAIN = "sms" SMS_GATEWAY = "SMS_GATEWAY" SMS_STATE_UNREAD = "UnRead" +CONF_BAUD_SPEED = "baud_speed" +DEFAULT_BAUD_SPEED = "0" +DEFAULT_BAUD_SPEEDS = [ + {"value": DEFAULT_BAUD_SPEED, "label": "Auto"}, + {"value": "50", "label": "50"}, + {"value": "75", "label": "75"}, + {"value": "110", "label": "110"}, + {"value": "134", "label": "134"}, + {"value": "150", "label": "150"}, + {"value": "200", "label": "200"}, + {"value": "300", "label": "300"}, + {"value": "600", "label": "600"}, + {"value": "1200", "label": "1200"}, + {"value": "1800", "label": "1800"}, + {"value": "2400", "label": "2400"}, + {"value": "4800", "label": "4800"}, + {"value": "9600", "label": "9600"}, + {"value": "19200", "label": "19200"}, + {"value": "28800", "label": "28800"}, + {"value": "38400", "label": "38400"}, + {"value": "57600", "label": "57600"}, + {"value": "76800", "label": "76800"}, + {"value": "115200", "label": "115200"}, +] diff --git a/homeassistant/components/sms/gateway.py b/homeassistant/components/sms/gateway.py index bd8d2f365c9..09992600943 100644 --- a/homeassistant/components/sms/gateway.py +++ b/homeassistant/components/sms/gateway.py @@ -16,6 +16,7 @@ class Gateway: def __init__(self, config, hass): """Initialize the sms gateway.""" + _LOGGER.debug("Init with connection mode:%s", config["Connection"]) self._worker = GammuAsyncWorker(self.sms_pull) self._worker.configure(config) self._hass = hass diff --git a/homeassistant/components/sms/strings.json b/homeassistant/components/sms/strings.json index 872cb17cbea..b4a9279845d 100644 --- a/homeassistant/components/sms/strings.json +++ b/homeassistant/components/sms/strings.json @@ -3,7 +3,10 @@ "step": { "user": { "title": "Connect to the modem", - "data": { "device": "Device" } + "data": { + "device": "Device", + "baud_speed": "Baud Speed" + } } }, "error": { diff --git a/homeassistant/components/sms/translations/ca.json b/homeassistant/components/sms/translations/ca.json index f7640befed4..f6491d0dd46 100644 --- a/homeassistant/components/sms/translations/ca.json +++ b/homeassistant/components/sms/translations/ca.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Velocitat en Baudis", "device": "Dispositiu" }, "title": "Connexi\u00f3 al m\u00f2dem" diff --git a/homeassistant/components/sms/translations/de.json b/homeassistant/components/sms/translations/de.json index 3c5cc3c0490..f17e30789c2 100644 --- a/homeassistant/components/sms/translations/de.json +++ b/homeassistant/components/sms/translations/de.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Baud-Geschwindigkeit", "device": "Ger\u00e4t" }, "title": "Verbinden mit dem Modem" diff --git a/homeassistant/components/sms/translations/el.json b/homeassistant/components/sms/translations/el.json index 372cdfed20f..6484f847523 100644 --- a/homeassistant/components/sms/translations/el.json +++ b/homeassistant/components/sms/translations/el.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "\u03a4\u03b1\u03c7\u03cd\u03c4\u03b7\u03c4\u03b1 Baud", "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" }, "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf \u03bc\u03cc\u03bd\u03c4\u03b5\u03bc" diff --git a/homeassistant/components/sms/translations/en.json b/homeassistant/components/sms/translations/en.json index dbbac1871c7..12d4e82c648 100644 --- a/homeassistant/components/sms/translations/en.json +++ b/homeassistant/components/sms/translations/en.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Baud Speed", "device": "Device" }, "title": "Connect to the modem" diff --git a/homeassistant/components/sms/translations/es.json b/homeassistant/components/sms/translations/es.json index f2f36d426f9..27669a2b52f 100644 --- a/homeassistant/components/sms/translations/es.json +++ b/homeassistant/components/sms/translations/es.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Velocidad en Baudis", "device": "Dispositivo" }, "title": "Conectar con el m\u00f3dem" diff --git a/homeassistant/components/sms/translations/et.json b/homeassistant/components/sms/translations/et.json index 70a378d591e..e3f67f1ac1a 100644 --- a/homeassistant/components/sms/translations/et.json +++ b/homeassistant/components/sms/translations/et.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Pordi kiirus", "device": "Seade" }, "title": "Modemiga \u00fchenduse loomine" diff --git a/homeassistant/components/sms/translations/fr.json b/homeassistant/components/sms/translations/fr.json index ebfa3c1da08..3caec67daaa 100644 --- a/homeassistant/components/sms/translations/fr.json +++ b/homeassistant/components/sms/translations/fr.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Vitesse en bauds", "device": "Appareil" }, "title": "Se connecter au modem" diff --git a/homeassistant/components/sms/translations/hu.json b/homeassistant/components/sms/translations/hu.json index 6fa524b18ab..3ebf7fbd948 100644 --- a/homeassistant/components/sms/translations/hu.json +++ b/homeassistant/components/sms/translations/hu.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Baud sebess\u00e9g", "device": "Eszk\u00f6z" }, "title": "Csatlakoz\u00e1s a modemhez" diff --git a/homeassistant/components/sms/translations/id.json b/homeassistant/components/sms/translations/id.json index 63ebb088521..d2b190eb3d0 100644 --- a/homeassistant/components/sms/translations/id.json +++ b/homeassistant/components/sms/translations/id.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Kecepatan Baud", "device": "Perangkat" }, "title": "Hubungkan ke modem" diff --git a/homeassistant/components/sms/translations/it.json b/homeassistant/components/sms/translations/it.json index 9d2ac87d833..48524b72f24 100644 --- a/homeassistant/components/sms/translations/it.json +++ b/homeassistant/components/sms/translations/it.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Velocit\u00e0 Baud", "device": "Dispositivo" }, "title": "Connettiti al modem" diff --git a/homeassistant/components/sms/translations/ja.json b/homeassistant/components/sms/translations/ja.json index 248427ad9bf..ddfb644d90f 100644 --- a/homeassistant/components/sms/translations/ja.json +++ b/homeassistant/components/sms/translations/ja.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "\u30dc\u30fc\u30ec\u30fc\u30c8", "device": "\u30c7\u30d0\u30a4\u30b9" }, "title": "\u30e2\u30c7\u30e0\u306b\u63a5\u7d9a" diff --git a/homeassistant/components/sms/translations/ko.json b/homeassistant/components/sms/translations/ko.json index 5ead95c1a27..aa954a52e19 100644 --- a/homeassistant/components/sms/translations/ko.json +++ b/homeassistant/components/sms/translations/ko.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "\uc804\uc1a1 \uc18d\ub3c4", "device": "\uae30\uae30" }, "title": "\ubaa8\ub380\uc5d0 \uc5f0\uacb0\ud558\uae30" diff --git a/homeassistant/components/sms/translations/nl.json b/homeassistant/components/sms/translations/nl.json index ddcc54d239f..7e05de163cf 100644 --- a/homeassistant/components/sms/translations/nl.json +++ b/homeassistant/components/sms/translations/nl.json @@ -2,15 +2,16 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { + "baud_speed": "Baud snelheid", "device": "Apparaat" }, "title": "Maak verbinding met de modem" diff --git a/homeassistant/components/sms/translations/no.json b/homeassistant/components/sms/translations/no.json index ab692ff8fa6..e8abd1ab220 100644 --- a/homeassistant/components/sms/translations/no.json +++ b/homeassistant/components/sms/translations/no.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Overf\u00f8ringshastighet", "device": "Enhet" }, "title": "Koble til modemet" diff --git a/homeassistant/components/sms/translations/pl.json b/homeassistant/components/sms/translations/pl.json index 615f08e3e06..c3aae889b7e 100644 --- a/homeassistant/components/sms/translations/pl.json +++ b/homeassistant/components/sms/translations/pl.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Szybko\u015b\u0107 transmisji (Baud)", "device": "Urz\u0105dzenie" }, "title": "Po\u0142\u0105czenie z modemem" diff --git a/homeassistant/components/sms/translations/pt-BR.json b/homeassistant/components/sms/translations/pt-BR.json index 05e211760f2..708d64e33de 100644 --- a/homeassistant/components/sms/translations/pt-BR.json +++ b/homeassistant/components/sms/translations/pt-BR.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Velocidade de transmiss\u00e3o", "device": "Dispositivo" }, "title": "Conectado ao modem" diff --git a/homeassistant/components/sms/translations/ru.json b/homeassistant/components/sms/translations/ru.json index 2200b582123..e5daa2005c0 100644 --- a/homeassistant/components/sms/translations/ru.json +++ b/homeassistant/components/sms/translations/ru.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0434\u0430\u043d\u043d\u044b\u0445", "device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" diff --git a/homeassistant/components/sms/translations/tr.json b/homeassistant/components/sms/translations/tr.json index 0488390beed..1558fe5a58e 100644 --- a/homeassistant/components/sms/translations/tr.json +++ b/homeassistant/components/sms/translations/tr.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "Baud H\u0131z\u0131", "device": "Cihaz" }, "title": "Modeme ba\u011flan\u0131n" diff --git a/homeassistant/components/sms/translations/zh-Hant.json b/homeassistant/components/sms/translations/zh-Hant.json index b6e08ffa7ec..0b84e7910f7 100644 --- a/homeassistant/components/sms/translations/zh-Hant.json +++ b/homeassistant/components/sms/translations/zh-Hant.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "baud_speed": "\u9b91\u7387", "device": "\u88dd\u7f6e" }, "title": "\u9023\u7dda\u81f3\u6578\u64da\u6a5f" diff --git a/homeassistant/components/smtp/notify.py b/homeassistant/components/smtp/notify.py index ef743912bc9..866d7980d08 100644 --- a/homeassistant/components/smtp/notify.py +++ b/homeassistant/components/smtp/notify.py @@ -25,10 +25,12 @@ from homeassistant.const import ( CONF_SENDER, CONF_TIMEOUT, CONF_USERNAME, + CONF_VERIFY_SSL, ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.reload import setup_reload_service import homeassistant.util.dt as dt_util +from homeassistant.util.ssl import client_context from . import DOMAIN, PLATFORMS @@ -65,6 +67,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_PASSWORD): cv.string, vol.Optional(CONF_SENDER_NAME): cv.string, vol.Optional(CONF_DEBUG, default=DEFAULT_DEBUG): cv.boolean, + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, } ) @@ -73,16 +76,17 @@ def get_service(hass, config, discovery_info=None): """Get the mail notification service.""" setup_reload_service(hass, DOMAIN, PLATFORMS) mail_service = MailNotificationService( - config.get(CONF_SERVER), - config.get(CONF_PORT), - config.get(CONF_TIMEOUT), - config.get(CONF_SENDER), - config.get(CONF_ENCRYPTION), + config[CONF_SERVER], + config[CONF_PORT], + config[CONF_TIMEOUT], + config[CONF_SENDER], + config[CONF_ENCRYPTION], config.get(CONF_USERNAME), config.get(CONF_PASSWORD), - config.get(CONF_RECIPIENT), + config[CONF_RECIPIENT], config.get(CONF_SENDER_NAME), - config.get(CONF_DEBUG), + config[CONF_DEBUG], + config[CONF_VERIFY_SSL], ) if mail_service.connection_is_valid(): @@ -106,6 +110,7 @@ class MailNotificationService(BaseNotificationService): recipients, sender_name, debug, + verify_ssl, ): """Initialize the SMTP service.""" self._server = server @@ -118,18 +123,25 @@ class MailNotificationService(BaseNotificationService): self.recipients = recipients self._sender_name = sender_name self.debug = debug + self._verify_ssl = verify_ssl self.tries = 2 def connect(self): """Connect/authenticate to SMTP Server.""" + ssl_context = client_context() if self._verify_ssl else None if self.encryption == "tls": - mail = smtplib.SMTP_SSL(self._server, self._port, timeout=self._timeout) + mail = smtplib.SMTP_SSL( + self._server, + self._port, + timeout=self._timeout, + context=ssl_context, + ) else: mail = smtplib.SMTP(self._server, self._port, timeout=self._timeout) mail.set_debuglevel(self.debug) mail.ehlo_or_helo_if_needed() if self.encryption == "starttls": - mail.starttls() + mail.starttls(context=ssl_context) mail.ehlo() if self.username and self.password: mail.login(self.username, self.password) @@ -235,7 +247,7 @@ def _attach_file(atch_name, content_id): attachment = MIMEImage(file_bytes) except TypeError: _LOGGER.warning( - "Attachment %s has an unknown MIME type. " "Falling back to file", + "Attachment %s has an unknown MIME type. Falling back to file", atch_name, ) attachment = MIMEApplication(file_bytes, Name=atch_name) diff --git a/homeassistant/components/solax/translations/es.json b/homeassistant/components/solax/translations/es.json index 4728aed6395..f6658c63353 100644 --- a/homeassistant/components/solax/translations/es.json +++ b/homeassistant/components/solax/translations/es.json @@ -1,7 +1,15 @@ { "config": { "error": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "port": "Puerto" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/soma/translations/es.json b/homeassistant/components/soma/translations/es.json index 6f6b47275dd..0ae2d26adec 100644 --- a/homeassistant/components/soma/translations/es.json +++ b/homeassistant/components/soma/translations/es.json @@ -2,13 +2,13 @@ "config": { "abort": { "already_setup": "S\u00f3lo puede configurar una cuenta de Soma.", - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "connection_error": "No se ha podido conectar a SOMA Connect.", "missing_configuration": "El componente Soma no est\u00e1 configurado. Por favor, leer la documentaci\u00f3n.", "result_error": "SOMA Connect respondi\u00f3 con un error." }, "create_entry": { - "default": "Autenticado con \u00e9xito con Soma." + "default": "Autenticaci\u00f3n exitosa" }, "step": { "user": { diff --git a/homeassistant/components/soma/translations/nl.json b/homeassistant/components/soma/translations/nl.json index 32db40348ba..33d9452950b 100644 --- a/homeassistant/components/soma/translations/nl.json +++ b/homeassistant/components/soma/translations/nl.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "already_setup": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", - "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "already_setup": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", "connection_error": "Kan geen verbinding maken", "missing_configuration": "De Soma-component is niet geconfigureerd. Gelieve de documentatie te volgen.", "result_error": "SOMA Connect reageerde met een foutstatus." }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "step": { "user": { diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index ae12c4ea266..ed6c58bc0a0 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -77,6 +77,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Somfy from a config entry.""" + + _LOGGER.warning( + "The Somfy integration is deprecated and will be removed " + "in Home Assistant Core 2022.7; due to the Somfy Open API deprecation." + "The Somfy Open API will shutdown June 21st 2022, migrate to the " + "Overkiz integration to control your Somfy devices" + ) + # Backwards compat if "auth_implementation" not in entry.data: hass.config_entries.async_update_entry( diff --git a/homeassistant/components/somfy/translations/es.json b/homeassistant/components/somfy/translations/es.json index 1fd22ebda3f..2f8b35f5af8 100644 --- a/homeassistant/components/somfy/translations/es.json +++ b/homeassistant/components/somfy/translations/es.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n", - "missing_configuration": "El componente Somfy no est\u00e1 configurado. Por favor, sigue la documentaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", + "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n.", "no_url_available": "No hay URL disponible. Para obtener informaci\u00f3n sobre este error, [consulta la secci\u00f3n de ayuda]({docs_url})", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, diff --git a/homeassistant/components/somfy/translations/nl.json b/homeassistant/components/somfy/translations/nl.json index 11b3de442dd..efd07952467 100644 --- a/homeassistant/components/somfy/translations/nl.json +++ b/homeassistant/components/somfy/translations/nl.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "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})", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/somfy_mylink/translations/ca.json b/homeassistant/components/somfy_mylink/translations/ca.json index a9181aa4605..d25ba195027 100644 --- a/homeassistant/components/somfy_mylink/translations/ca.json +++ b/homeassistant/components/somfy_mylink/translations/ca.json @@ -25,17 +25,8 @@ "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { - "entity_config": { - "data": { - "reverse": "La coberta est\u00e0 invertida" - }, - "description": "Opcions de configuraci\u00f3 de `{entity_id}`", - "title": "Configura l'entitat" - }, "init": { "data": { - "default_reverse": "Estat d'inversi\u00f3 predeterminat per a cobertes sense configurar", - "entity_id": "Configura una entitat espec\u00edfica.", "target_id": "Opcions de configuraci\u00f3 de la coberta." }, "title": "Configura opcions de MyLink" @@ -48,6 +39,5 @@ "title": "Configura coberta MyLink" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/de.json b/homeassistant/components/somfy_mylink/translations/de.json index 9fc1af6d92c..303337542f0 100644 --- a/homeassistant/components/somfy_mylink/translations/de.json +++ b/homeassistant/components/somfy_mylink/translations/de.json @@ -25,17 +25,8 @@ "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { - "entity_config": { - "data": { - "reverse": "Jalousie ist invertiert" - }, - "description": "Optionen f\u00fcr `{entity_id}` konfigurieren", - "title": "Entit\u00e4t konfigurieren" - }, "init": { "data": { - "default_reverse": "Standardinvertierungsstatus f\u00fcr nicht konfigurierte Abdeckungen", - "entity_id": "Konfiguriere eine bestimmte Entit\u00e4t.", "target_id": "Konfigurieren der Optionen f\u00fcr eine Jalousie." }, "title": "MyLink-Optionen konfigurieren" @@ -48,6 +39,5 @@ "title": "MyLink-Cover konfigurieren" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/el.json b/homeassistant/components/somfy_mylink/translations/el.json index 59ffb563687..565f8459a3f 100644 --- a/homeassistant/components/somfy_mylink/translations/el.json +++ b/homeassistant/components/somfy_mylink/translations/el.json @@ -25,17 +25,8 @@ "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "step": { - "entity_config": { - "data": { - "reverse": "\u03a4\u03bf \u03ba\u03ac\u03bb\u03c5\u03bc\u03bc\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03bd\u03c4\u03b5\u03c3\u03c4\u03c1\u03b1\u03bc\u03bc\u03ad\u03bd\u03bf" - }, - "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 `{entity_id}`", - "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" - }, "init": { "data": { - "default_reverse": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03bc\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03b1 \u03ba\u03b1\u03bb\u03cd\u03bc\u03bc\u03b1\u03c4\u03b1", - "entity_id": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bc\u03b9\u03b1\u03c2 \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b7\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2.", "target_id": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03ba\u03ac\u03bb\u03c5\u03bc\u03bc\u03b1." }, "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd MyLink" @@ -48,6 +39,5 @@ "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 MyLink Cover" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/en.json b/homeassistant/components/somfy_mylink/translations/en.json index cb373008778..e646bf9abcf 100644 --- a/homeassistant/components/somfy_mylink/translations/en.json +++ b/homeassistant/components/somfy_mylink/translations/en.json @@ -25,17 +25,8 @@ "cannot_connect": "Failed to connect" }, "step": { - "entity_config": { - "data": { - "reverse": "Cover is reversed" - }, - "description": "Configure options for `{entity_id}`", - "title": "Configure Entity" - }, "init": { "data": { - "default_reverse": "Default reversal status for unconfigured covers", - "entity_id": "Configure a specific entity.", "target_id": "Configure options for a cover." }, "title": "Configure MyLink Options" @@ -48,6 +39,5 @@ "title": "Configure MyLink Cover" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/es.json b/homeassistant/components/somfy_mylink/translations/es.json index 40d82a4522a..8647f7bb8fd 100644 --- a/homeassistant/components/somfy_mylink/translations/es.json +++ b/homeassistant/components/somfy_mylink/translations/es.json @@ -25,17 +25,8 @@ "cannot_connect": "No se pudo conectar" }, "step": { - "entity_config": { - "data": { - "reverse": "La cubierta est\u00e1 invertida" - }, - "description": "Configurar opciones para `{entity_id}`", - "title": "Configurar entidad" - }, "init": { "data": { - "default_reverse": "Estado de inversi\u00f3n predeterminado para cubiertas no configuradas", - "entity_id": "Configurar una entidad espec\u00edfica.", "target_id": "Configurar opciones para una cubierta." }, "title": "Configurar opciones de MyLink" @@ -48,6 +39,5 @@ "title": "Configurar la cubierta MyLink" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/et.json b/homeassistant/components/somfy_mylink/translations/et.json index 17122eb449e..f3d2e4c4a9a 100644 --- a/homeassistant/components/somfy_mylink/translations/et.json +++ b/homeassistant/components/somfy_mylink/translations/et.json @@ -25,17 +25,8 @@ "cannot_connect": "\u00dchendamine nurjus" }, "step": { - "entity_config": { - "data": { - "reverse": "(Akna)kate t\u00f6\u00f6tab vastupidi" - }, - "description": "Olemi {entity_id} suvandite seadmine", - "title": "Seadista olem" - }, "init": { "data": { - "default_reverse": "Seadistamata (akna)katete vaikep\u00f6\u00f6rduse olek", - "entity_id": "Seadista konkreetne olem.", "target_id": "Seadista (akna)katte suvandid" }, "title": "Seadista MyLinki suvandid" @@ -48,6 +39,5 @@ "title": "Seadista MyLink Cover" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/fr.json b/homeassistant/components/somfy_mylink/translations/fr.json index cc257e16451..8dc822976fe 100644 --- a/homeassistant/components/somfy_mylink/translations/fr.json +++ b/homeassistant/components/somfy_mylink/translations/fr.json @@ -25,17 +25,8 @@ "cannot_connect": "\u00c9chec de connexion" }, "step": { - "entity_config": { - "data": { - "reverse": "La couverture est invers\u00e9e" - }, - "description": "Configurer les options pour \u00ab {entity_id} \u00bb", - "title": "Configurez une entit\u00e9 sp\u00e9cifique" - }, "init": { "data": { - "default_reverse": "Statut d'inversion par d\u00e9faut pour les couvertures non configur\u00e9es", - "entity_id": "Configurez une entit\u00e9 sp\u00e9cifique.", "target_id": "Configurez les options pour la couverture." }, "title": "Configurer les options MyLink" @@ -48,6 +39,5 @@ "title": "Configurer la couverture MyLink" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/hu.json b/homeassistant/components/somfy_mylink/translations/hu.json index 2b91ccd8676..c3348b1f628 100644 --- a/homeassistant/components/somfy_mylink/translations/hu.json +++ b/homeassistant/components/somfy_mylink/translations/hu.json @@ -25,17 +25,8 @@ "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { - "entity_config": { - "data": { - "reverse": "A bor\u00edt\u00f3 megfordult" - }, - "description": "Konfigur\u00e1lja az \u201e {entity_id} \u201d be\u00e1ll\u00edt\u00e1sait", - "title": "Entit\u00e1s konfigur\u00e1l\u00e1sa" - }, "init": { "data": { - "default_reverse": "A konfigur\u00e1latlan bor\u00edt\u00f3k alap\u00e9rtelmezett megford\u00edt\u00e1si \u00e1llapota", - "entity_id": "Konfigur\u00e1ljon egy adott entit\u00e1st.", "target_id": "Az \u00e1rny\u00e9kol\u00f3 be\u00e1ll\u00edt\u00e1sainak konfigur\u00e1l\u00e1sa." }, "title": "Mylink be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" @@ -48,6 +39,5 @@ "title": "MyLink \u00e1rny\u00e9kol\u00f3 konfigur\u00e1l\u00e1sa" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/id.json b/homeassistant/components/somfy_mylink/translations/id.json index c4b2269ef2c..38d0d550bf4 100644 --- a/homeassistant/components/somfy_mylink/translations/id.json +++ b/homeassistant/components/somfy_mylink/translations/id.json @@ -25,17 +25,8 @@ "cannot_connect": "Gagal terhubung" }, "step": { - "entity_config": { - "data": { - "reverse": "Penutup dibalik" - }, - "description": "Konfigurasikan opsi untuk `{entity_id}`", - "title": "Konfigurasikan Entitas" - }, "init": { "data": { - "default_reverse": "Status pembalikan baku untuk penutup yang belum dikonfigurasi", - "entity_id": "Konfigurasikan entitas tertentu.", "target_id": "Konfigurasikan opsi untuk penutup." }, "title": "Konfigurasikan Opsi MyLink" @@ -48,6 +39,5 @@ "title": "Konfigurasikan Cover MyLink" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/it.json b/homeassistant/components/somfy_mylink/translations/it.json index cfd3041d248..d4fcb7513c4 100644 --- a/homeassistant/components/somfy_mylink/translations/it.json +++ b/homeassistant/components/somfy_mylink/translations/it.json @@ -25,17 +25,8 @@ "cannot_connect": "Impossibile connettersi" }, "step": { - "entity_config": { - "data": { - "reverse": "La serranda \u00e8 invertita" - }, - "description": "Configura le opzioni per `{entity_id}`", - "title": "Configura entit\u00e0" - }, "init": { "data": { - "default_reverse": "Stato d'inversione predefinito per le serrande non configurate", - "entity_id": "Configura un'entit\u00e0 specifica.", "target_id": "Configura opzioni per una tapparella" }, "title": "Configura le opzioni MyLink" @@ -48,6 +39,5 @@ "title": "Configura serranda MyLink" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/ja.json b/homeassistant/components/somfy_mylink/translations/ja.json index 4482dd37db2..49f819659b4 100644 --- a/homeassistant/components/somfy_mylink/translations/ja.json +++ b/homeassistant/components/somfy_mylink/translations/ja.json @@ -25,17 +25,8 @@ "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" }, "step": { - "entity_config": { - "data": { - "reverse": "\u30ab\u30d0\u30fc\u304c\u9006\u306b\u306a\u3063\u3066\u3044\u307e\u3059" - }, - "description": "{entity_id}} \u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a", - "title": "\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u306e\u8a2d\u5b9a" - }, "init": { "data": { - "default_reverse": "\u672a\u8a2d\u5b9a\u306e\u30ab\u30d0\u30fc\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u53cd\u8ee2\u72b6\u614b", - "entity_id": "\u7279\u5b9a\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u3002", "target_id": "\u30ab\u30d0\u30fc\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u3002" }, "title": "MyLink\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u8a2d\u5b9a" @@ -48,6 +39,5 @@ "title": "MyLink\u30ab\u30d0\u30fc\u306e\u8a2d\u5b9a" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/ko.json b/homeassistant/components/somfy_mylink/translations/ko.json index b099d3d6ed8..3ec70c148b3 100644 --- a/homeassistant/components/somfy_mylink/translations/ko.json +++ b/homeassistant/components/somfy_mylink/translations/ko.json @@ -25,17 +25,8 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { - "entity_config": { - "data": { - "reverse": "\uc5ec\ub2eb\uc774\uac00 \ubc18\uc804\ub418\uc5c8\uc2b5\ub2c8\ub2e4" - }, - "description": "`{entity_id}`\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30", - "title": "\uad6c\uc131\uc694\uc18c \uad6c\uc131\ud558\uae30" - }, "init": { "data": { - "default_reverse": "\uad6c\uc131\ub418\uc9c0 \uc54a\uc740 \uc5ec\ub2eb\uc774\uc5d0 \ub300\ud55c \uae30\ubcf8 \ubc18\uc804 \uc0c1\ud0dc", - "entity_id": "\ud2b9\uc815 \uad6c\uc131\uc694\uc18c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", "target_id": "\uc5ec\ub2eb\uc774\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30" }, "title": "MyLink \uc635\uc158 \uad6c\uc131\ud558\uae30" @@ -48,6 +39,5 @@ "title": "MyLink \uc5ec\ub2eb\uc774 \uad6c\uc131\ud558\uae30" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/lb.json b/homeassistant/components/somfy_mylink/translations/lb.json index efaba3ab497..0fe190e43ee 100644 --- a/homeassistant/components/somfy_mylink/translations/lb.json +++ b/homeassistant/components/somfy_mylink/translations/lb.json @@ -22,6 +22,5 @@ "abort": { "cannot_connect": "Feeler beim verbannen" } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/nl.json b/homeassistant/components/somfy_mylink/translations/nl.json index 279010485b6..6fae9b7404a 100644 --- a/homeassistant/components/somfy_mylink/translations/nl.json +++ b/homeassistant/components/somfy_mylink/translations/nl.json @@ -25,29 +25,19 @@ "cannot_connect": "Kan geen verbinding maken" }, "step": { - "entity_config": { - "data": { - "reverse": "Rolluik is omgekeerd" - }, - "description": "Configureer opties voor `{entity_id}`", - "title": "Entiteit configureren" - }, "init": { "data": { - "default_reverse": "Standaard omkeerstatus voor niet-geconfigureerde rolluiken", - "entity_id": "Configureer een specifieke entiteit.", - "target_id": "Configureer opties voor een rolluik." + "target_id": "Configureer opties voor een afdekking." }, "title": "Configureer MyLink-opties" }, "target_config": { "data": { - "reverse": "Rolluik is omgekeerd" + "reverse": "Afdekking is omgekeerd" }, "description": "Configureer opties voor ' {target_name} '", "title": "Configureer MyLink Cover" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/no.json b/homeassistant/components/somfy_mylink/translations/no.json index 65c89866f65..b826eabf45d 100644 --- a/homeassistant/components/somfy_mylink/translations/no.json +++ b/homeassistant/components/somfy_mylink/translations/no.json @@ -25,17 +25,8 @@ "cannot_connect": "Tilkobling mislyktes" }, "step": { - "entity_config": { - "data": { - "reverse": "Rullegardinet reverseres" - }, - "description": "Konfigurer alternativer for \"{entity_id}\"", - "title": "Konfigurer entitet" - }, "init": { "data": { - "default_reverse": "Standard tilbakef\u00f8ringsstatus for ukonfigurerte rullegardiner", - "entity_id": "Konfigurer en bestemt entitet.", "target_id": "Konfigurer alternativer for et rullgardin" }, "title": "Konfigurere MyLink-alternativer" @@ -48,6 +39,5 @@ "title": "Konfigurer MyLink-deksel" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/pl.json b/homeassistant/components/somfy_mylink/translations/pl.json index c4c10de4f9d..8c713ffd31a 100644 --- a/homeassistant/components/somfy_mylink/translations/pl.json +++ b/homeassistant/components/somfy_mylink/translations/pl.json @@ -25,17 +25,8 @@ "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { - "entity_config": { - "data": { - "reverse": "Roleta/pokrywa jest odwr\u00f3cona" - }, - "description": "Konfiguracja opcji dla \"{entity_id}\"", - "title": "Konfiguracja encji" - }, "init": { "data": { - "default_reverse": "Domy\u015blny stan odwr\u00f3cenia nieskonfigurowanych rolet/pokryw", - "entity_id": "Skonfiguruj okre\u015blon\u0105 encj\u0119.", "target_id": "Konfiguracja opcji rolety" }, "title": "Konfiguracja opcji MyLink" @@ -48,6 +39,5 @@ "title": "Konfiguracja rolety MyLink" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/pt-BR.json b/homeassistant/components/somfy_mylink/translations/pt-BR.json index 12efd378984..7cbe3e77f6c 100644 --- a/homeassistant/components/somfy_mylink/translations/pt-BR.json +++ b/homeassistant/components/somfy_mylink/translations/pt-BR.json @@ -25,17 +25,8 @@ "cannot_connect": "Falha ao conectar" }, "step": { - "entity_config": { - "data": { - "reverse": "A cobertura est\u00e1 invertida" - }, - "description": "Configurar op\u00e7\u00f5es para ` {entity_id} `", - "title": "Configurar entidade" - }, "init": { "data": { - "default_reverse": "Status de revers\u00e3o padr\u00e3o para coberturas n\u00e3o configuradas", - "entity_id": "Configure uma entidade espec\u00edfica.", "target_id": "Configure as op\u00e7\u00f5es para uma cobertura." }, "title": "Configurar op\u00e7\u00f5es do MyLink" @@ -48,6 +39,5 @@ "title": "Configurar a MyLink Cover" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/ru.json b/homeassistant/components/somfy_mylink/translations/ru.json index d1ab5624ecf..0385609cb21 100644 --- a/homeassistant/components/somfy_mylink/translations/ru.json +++ b/homeassistant/components/somfy_mylink/translations/ru.json @@ -25,17 +25,8 @@ "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { - "entity_config": { - "data": { - "reverse": "\u0418\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0448\u0442\u043e\u0440 \u0438 \u0436\u0430\u043b\u044e\u0437\u0438" - }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0434\u043b\u044f `{entity_id}`", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430" - }, "init": { "data": { - "default_reverse": "\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0448\u0442\u043e\u0440 \u0438 \u0436\u0430\u043b\u044e\u0437\u0438", - "entity_id": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430", "target_id": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0448\u0442\u043e\u0440 \u0438\u043b\u0438 \u0436\u0430\u043b\u044e\u0437\u0438." }, "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 MyLink" @@ -48,6 +39,5 @@ "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 MyLink Cover" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/tr.json b/homeassistant/components/somfy_mylink/translations/tr.json index fb402493e3b..8893b7362d9 100644 --- a/homeassistant/components/somfy_mylink/translations/tr.json +++ b/homeassistant/components/somfy_mylink/translations/tr.json @@ -25,17 +25,8 @@ "cannot_connect": "Ba\u011flanma hatas\u0131" }, "step": { - "entity_config": { - "data": { - "reverse": "Kapak ters \u00e7evrildi" - }, - "description": "'{entity_id}' i\u00e7in se\u00e7enekleri yap\u0131land\u0131r\u0131n", - "title": "Varl\u0131\u011f\u0131 Yap\u0131land\u0131r" - }, "init": { "data": { - "default_reverse": "Yap\u0131land\u0131r\u0131lmam\u0131\u015f kapaklar i\u00e7in varsay\u0131lan geri alma durumu", - "entity_id": "Belirli bir varl\u0131\u011f\u0131 yap\u0131land\u0131r\u0131n.", "target_id": "Kapak i\u00e7in se\u00e7enekleri yap\u0131land\u0131r\u0131n." }, "title": "MyLink Se\u00e7eneklerini Yap\u0131land\u0131r\u0131n" @@ -48,6 +39,5 @@ "title": "MyLink Kapa\u011f\u0131n\u0131 Yap\u0131land\u0131r\u0131n" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/uk.json b/homeassistant/components/somfy_mylink/translations/uk.json index 2d251531340..f458c21bcf9 100644 --- a/homeassistant/components/somfy_mylink/translations/uk.json +++ b/homeassistant/components/somfy_mylink/translations/uk.json @@ -24,17 +24,9 @@ "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" }, "step": { - "entity_config": { - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u0434\u043b\u044f \"{entity_id}\"", - "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c" - }, "init": { - "data": { - "entity_id": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u043f\u0435\u0446\u0438\u0444\u0456\u0447\u043d\u043e\u0457 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456." - }, "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0435\u0439 MyLink" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/zh-Hant.json b/homeassistant/components/somfy_mylink/translations/zh-Hant.json index 0aa72fb0c61..15a3927877d 100644 --- a/homeassistant/components/somfy_mylink/translations/zh-Hant.json +++ b/homeassistant/components/somfy_mylink/translations/zh-Hant.json @@ -25,17 +25,8 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { - "entity_config": { - "data": { - "reverse": "\u7a97\u7c3e\u53cd\u5411" - }, - "description": "`{entity_id}` \u8a2d\u5b9a\u9078\u9805", - "title": "\u8a2d\u5b9a\u5be6\u9ad4" - }, "init": { "data": { - "default_reverse": "\u672a\u8a2d\u5b9a\u7a97\u7c3e\u9810\u8a2d\u70ba\u53cd\u5411", - "entity_id": "\u8a2d\u5b9a\u7279\u5b9a\u5be6\u9ad4\u3002", "target_id": "\u7a97\u7c3e\u8a2d\u5b9a\u9078\u9805\u3002" }, "title": "MyLink \u8a2d\u5b9a\u9078\u9805" @@ -48,6 +39,5 @@ "title": "\u8a2d\u5b9a MyLink \u7a97\u7c3e" } } - }, - "title": "Somfy MyLink" + } } \ No newline at end of file diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index c182bb2bbeb..adc588f6951 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -76,7 +76,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( ), ) -_T = TypeVar("_T", bound="SonarrSensor") +_SonarrSensorT = TypeVar("_SonarrSensorT", bound="SonarrSensor") _P = ParamSpec("_P") @@ -109,8 +109,8 @@ async def async_setup_entry( def sonarr_exception_handler( - func: Callable[Concatenate[_T, _P], Awaitable[None]] # type: ignore[misc] -) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]: # type: ignore[misc] + func: Callable[Concatenate[_SonarrSensorT, _P], Awaitable[None]] +) -> Callable[Concatenate[_SonarrSensorT, _P], Coroutine[Any, Any, None]]: """Decorate Sonarr calls to handle Sonarr exceptions. A decorator that wraps the passed in function, catches Sonarr errors, @@ -118,7 +118,9 @@ def sonarr_exception_handler( """ @wraps(func) - async def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: + async def wrapper( + self: _SonarrSensorT, *args: _P.args, **kwargs: _P.kwargs + ) -> None: try: await func(self, *args, **kwargs) self.last_update_success = True @@ -235,7 +237,7 @@ class SonarrSensor(SonarrEntity, SensorEntity): stats = item.statistics attrs[ item.title - ] = f"{stats.episodeFileCount}/{stats.episodeCount} Episodes" + ] = f"{getattr(stats,'episodeFileCount', 0)}/{getattr(stats, 'episodeCount', 0)} Episodes" elif key == "upcoming" and self.data.get(key) is not None: for episode in self.data[key]: identifier = f"S{episode.seasonNumber:02d}E{episode.episodeNumber:02d}" diff --git a/homeassistant/components/sonarr/translations/bg.json b/homeassistant/components/sonarr/translations/bg.json index c6ab1b205e9..05d96cf621c 100644 --- a/homeassistant/components/sonarr/translations/bg.json +++ b/homeassistant/components/sonarr/translations/bg.json @@ -17,8 +17,6 @@ "user": { "data": { "api_key": "API \u043a\u043b\u044e\u0447", - "host": "\u0425\u043e\u0441\u0442", - "port": "\u041f\u043e\u0440\u0442", "url": "URL" } } diff --git a/homeassistant/components/sonarr/translations/ca.json b/homeassistant/components/sonarr/translations/ca.json index d3cb5720875..49170885242 100644 --- a/homeassistant/components/sonarr/translations/ca.json +++ b/homeassistant/components/sonarr/translations/ca.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "Clau API", - "base_path": "Ruta a l'API", - "host": "Amfitri\u00f3", - "port": "Port", - "ssl": "Utilitza un certificat SSL", "url": "URL", "verify_ssl": "Verifica el certificat SSL" } diff --git a/homeassistant/components/sonarr/translations/cs.json b/homeassistant/components/sonarr/translations/cs.json index 9be2106b4e2..27c3a92bc2e 100644 --- a/homeassistant/components/sonarr/translations/cs.json +++ b/homeassistant/components/sonarr/translations/cs.json @@ -18,9 +18,6 @@ "user": { "data": { "api_key": "Kl\u00ed\u010d API", - "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/de.json b/homeassistant/components/sonarr/translations/de.json index eb521c1c237..4559b75c655 100644 --- a/homeassistant/components/sonarr/translations/de.json +++ b/homeassistant/components/sonarr/translations/de.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "API-Schl\u00fcssel", - "base_path": "Pfad zur API", - "host": "Host", - "port": "Port", - "ssl": "Verwendet ein SSL-Zertifikat", "url": "URL", "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" } diff --git a/homeassistant/components/sonarr/translations/el.json b/homeassistant/components/sonarr/translations/el.json index 22895b3a38a..542225c0960 100644 --- a/homeassistant/components/sonarr/translations/el.json +++ b/homeassistant/components/sonarr/translations/el.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", - "base_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf API", - "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", - "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", "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" } diff --git a/homeassistant/components/sonarr/translations/en.json b/homeassistant/components/sonarr/translations/en.json index f676005dcfe..61b1158d9a3 100644 --- a/homeassistant/components/sonarr/translations/en.json +++ b/homeassistant/components/sonarr/translations/en.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "API Key", - "base_path": "Path to API", - "host": "Host", - "port": "Port", - "ssl": "Uses an SSL certificate", "url": "URL", "verify_ssl": "Verify SSL certificate" } diff --git a/homeassistant/components/sonarr/translations/es.json b/homeassistant/components/sonarr/translations/es.json index b2f073297d9..3c1b7c0fa9a 100644 --- a/homeassistant/components/sonarr/translations/es.json +++ b/homeassistant/components/sonarr/translations/es.json @@ -13,15 +13,11 @@ "step": { "reauth_confirm": { "description": "La integraci\u00f3n de Sonarr necesita volver a autenticarse manualmente con la API de Sonarr alojada en: {host}", - "title": "Volver a autenticarse con Sonarr" + "title": "Reautenticaci\u00f3n de la integraci\u00f3n" }, "user": { "data": { "api_key": "Clave API", - "base_path": "Ruta a la API", - "host": "Host", - "port": "Puerto", - "ssl": "Utiliza un certificado SSL", "url": "URL", "verify_ssl": "Verificar certificado SSL" } diff --git a/homeassistant/components/sonarr/translations/et.json b/homeassistant/components/sonarr/translations/et.json index 4629e59e68d..8fa1d128455 100644 --- a/homeassistant/components/sonarr/translations/et.json +++ b/homeassistant/components/sonarr/translations/et.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "API v\u00f5ti", - "base_path": "API asukoht", - "host": "", - "port": "", - "ssl": "Kasutab SSL serti", "url": "URL", "verify_ssl": "Kontrolli SSL sertifikaati" } diff --git a/homeassistant/components/sonarr/translations/fr.json b/homeassistant/components/sonarr/translations/fr.json index 0793adc1b9f..dc88f6770bc 100644 --- a/homeassistant/components/sonarr/translations/fr.json +++ b/homeassistant/components/sonarr/translations/fr.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "Cl\u00e9 d'API", - "base_path": "Chemin vers l'API", - "host": "H\u00f4te", - "port": "Port", - "ssl": "Utilise un certificat SSL", "url": "URL", "verify_ssl": "V\u00e9rifier le certificat SSL" } diff --git a/homeassistant/components/sonarr/translations/he.json b/homeassistant/components/sonarr/translations/he.json index 53b98ae4139..3fbaf2d2684 100644 --- a/homeassistant/components/sonarr/translations/he.json +++ b/homeassistant/components/sonarr/translations/he.json @@ -17,9 +17,6 @@ "user": { "data": { "api_key": "\u05de\u05e4\u05ea\u05d7 API", - "host": "\u05de\u05d0\u05e8\u05d7", - "port": "\u05e4\u05ea\u05d7\u05d4", - "ssl": "\u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05d0\u05d9\u05e9\u05d5\u05e8 SSL", "url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8", "verify_ssl": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8 SSL" } diff --git a/homeassistant/components/sonarr/translations/hu.json b/homeassistant/components/sonarr/translations/hu.json index 5aabd38a974..9e77eecfb6f 100644 --- a/homeassistant/components/sonarr/translations/hu.json +++ b/homeassistant/components/sonarr/translations/hu.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "API kulcs", - "base_path": "El\u00e9r\u00e9si \u00fat az API-hoz", - "host": "C\u00edm", - "port": "Port", - "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", "url": "URL", "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } diff --git a/homeassistant/components/sonarr/translations/id.json b/homeassistant/components/sonarr/translations/id.json index ec76bf44491..a84276eab01 100644 --- a/homeassistant/components/sonarr/translations/id.json +++ b/homeassistant/components/sonarr/translations/id.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "Kunci API", - "base_path": "Jalur ke API", - "host": "Host", - "port": "Port", - "ssl": "Menggunakan sertifikat SSL", "url": "URL", "verify_ssl": "Verifikasi sertifikat SSL" } diff --git a/homeassistant/components/sonarr/translations/it.json b/homeassistant/components/sonarr/translations/it.json index ba78810d928..727e556bb3c 100644 --- a/homeassistant/components/sonarr/translations/it.json +++ b/homeassistant/components/sonarr/translations/it.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "Chiave API", - "base_path": "Percorso dell'API", - "host": "Host", - "port": "Porta", - "ssl": "Utilizza un certificato SSL", "url": "URL", "verify_ssl": "Verifica il certificato SSL" } diff --git a/homeassistant/components/sonarr/translations/ja.json b/homeassistant/components/sonarr/translations/ja.json index 53a659d9bde..cbe230bbc94 100644 --- a/homeassistant/components/sonarr/translations/ja.json +++ b/homeassistant/components/sonarr/translations/ja.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "API\u30ad\u30fc", - "base_path": "API\u3078\u306e\u30d1\u30b9", - "host": "\u30db\u30b9\u30c8", - "port": "\u30dd\u30fc\u30c8", - "ssl": "SSL\u8a3c\u660e\u66f8\u3092\u4f7f\u7528\u3059\u308b", "url": "URL", "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" } diff --git a/homeassistant/components/sonarr/translations/ko.json b/homeassistant/components/sonarr/translations/ko.json index fbff72e46f2..b7389a03043 100644 --- a/homeassistant/components/sonarr/translations/ko.json +++ b/homeassistant/components/sonarr/translations/ko.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "API \ud0a4", - "base_path": "API \uacbd\ub85c", - "host": "\ud638\uc2a4\ud2b8", - "port": "\ud3ec\ud2b8", - "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" } } diff --git a/homeassistant/components/sonarr/translations/lb.json b/homeassistant/components/sonarr/translations/lb.json index 392b3f7d4e2..dfde55e9924 100644 --- a/homeassistant/components/sonarr/translations/lb.json +++ b/homeassistant/components/sonarr/translations/lb.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "API Schl\u00ebssel", - "base_path": "Pad zur API", - "host": "Host", - "port": "Port", - "ssl": "Benotzt ee SSL Zertifikat", "verify_ssl": "SSL Zertifikat iwwerpr\u00e9iwen" } } diff --git a/homeassistant/components/sonarr/translations/nl.json b/homeassistant/components/sonarr/translations/nl.json index 58bf04e283d..bb310896bed 100644 --- a/homeassistant/components/sonarr/translations/nl.json +++ b/homeassistant/components/sonarr/translations/nl.json @@ -1,27 +1,23 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol", + "already_configured": "Dienst is al geconfigureerd", + "reauth_successful": "Herauthenticatie geslaagd", "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie" }, "flow_title": "{name}", "step": { "reauth_confirm": { "description": "De Sonarr-integratie moet handmatig opnieuw worden geverifieerd met de Sonarr-API die wordt gehost op: {url}", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { "api_key": "API-sleutel", - "base_path": "Pad naar API", - "host": "Host", - "port": "Poort", - "ssl": "Maakt gebruik van een SSL-certificaat", "url": "URL", "verify_ssl": "SSL-certificaat verifi\u00ebren" } diff --git a/homeassistant/components/sonarr/translations/no.json b/homeassistant/components/sonarr/translations/no.json index 5ee028b8f02..3d64a9199a4 100644 --- a/homeassistant/components/sonarr/translations/no.json +++ b/homeassistant/components/sonarr/translations/no.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "API-n\u00f8kkel", - "base_path": "Bane til API", - "host": "Vert", - "port": "Port", - "ssl": "Bruker et SSL-sertifikat", "url": "URL", "verify_ssl": "Verifisere SSL-sertifikat" } diff --git a/homeassistant/components/sonarr/translations/pl.json b/homeassistant/components/sonarr/translations/pl.json index e5be700a752..3f93623af76 100644 --- a/homeassistant/components/sonarr/translations/pl.json +++ b/homeassistant/components/sonarr/translations/pl.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "Klucz API", - "base_path": "\u015acie\u017cka do API", - "host": "Nazwa hosta lub adres IP", - "port": "Port", - "ssl": "Certyfikat SSL", "url": "URL", "verify_ssl": "Weryfikacja certyfikatu SSL" } diff --git a/homeassistant/components/sonarr/translations/pt-BR.json b/homeassistant/components/sonarr/translations/pt-BR.json index 4c474ef2349..1aa3de5b209 100644 --- a/homeassistant/components/sonarr/translations/pt-BR.json +++ b/homeassistant/components/sonarr/translations/pt-BR.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "Chave da API", - "base_path": "Caminho para a API", - "host": "Nome do host", - "port": "Porta", - "ssl": "Usar um certificado SSL", "url": "URL", "verify_ssl": "Verifique o certificado SSL" } diff --git a/homeassistant/components/sonarr/translations/pt.json b/homeassistant/components/sonarr/translations/pt.json index bb36cb63dfc..9001b9c2c3f 100644 --- a/homeassistant/components/sonarr/translations/pt.json +++ b/homeassistant/components/sonarr/translations/pt.json @@ -17,9 +17,6 @@ "user": { "data": { "api_key": "Chave da API", - "host": "Servidor", - "port": "Porta", - "ssl": "Utiliza um certificado SSL", "verify_ssl": "Verificar o certificado SSL" } } diff --git a/homeassistant/components/sonarr/translations/ru.json b/homeassistant/components/sonarr/translations/ru.json index 531058f3fff..47843b028db 100644 --- a/homeassistant/components/sonarr/translations/ru.json +++ b/homeassistant/components/sonarr/translations/ru.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API", - "base_path": "\u041f\u0443\u0442\u044c \u043a API", - "host": "\u0425\u043e\u0441\u0442", - "port": "\u041f\u043e\u0440\u0442", - "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", "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" } diff --git a/homeassistant/components/sonarr/translations/sk.json b/homeassistant/components/sonarr/translations/sk.json index 6e1cd075aa9..64731388e98 100644 --- a/homeassistant/components/sonarr/translations/sk.json +++ b/homeassistant/components/sonarr/translations/sk.json @@ -9,8 +9,7 @@ "step": { "user": { "data": { - "api_key": "API k\u013e\u00fa\u010d", - "port": "Port" + "api_key": "API k\u013e\u00fa\u010d" } } } diff --git a/homeassistant/components/sonarr/translations/sl.json b/homeassistant/components/sonarr/translations/sl.json index b8c5332be9c..fd6fbd9cd2a 100644 --- a/homeassistant/components/sonarr/translations/sl.json +++ b/homeassistant/components/sonarr/translations/sl.json @@ -3,7 +3,6 @@ "step": { "user": { "data": { - "ssl": "Uporablja SSL certifikat", "verify_ssl": "Preverite SSL certifikat" } } diff --git a/homeassistant/components/sonarr/translations/tr.json b/homeassistant/components/sonarr/translations/tr.json index c064e4947c3..c72d84767d1 100644 --- a/homeassistant/components/sonarr/translations/tr.json +++ b/homeassistant/components/sonarr/translations/tr.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "API Anahtar\u0131", - "base_path": "API yolu", - "host": "Ana Bilgisayar", - "port": "Port", - "ssl": "SSL sertifikas\u0131 kullan\u0131r", "url": "URL", "verify_ssl": "SSL sertifikas\u0131n\u0131 do\u011frulay\u0131n" } diff --git a/homeassistant/components/sonarr/translations/uk.json b/homeassistant/components/sonarr/translations/uk.json index 0b6b7acf26d..85f1c42cb42 100644 --- a/homeassistant/components/sonarr/translations/uk.json +++ b/homeassistant/components/sonarr/translations/uk.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API", - "base_path": "\u0428\u043b\u044f\u0445 \u0434\u043e API", - "host": "\u0425\u043e\u0441\u0442", - "port": "\u041f\u043e\u0440\u0442", - "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" } } diff --git a/homeassistant/components/sonarr/translations/zh-Hans.json b/homeassistant/components/sonarr/translations/zh-Hans.json index 265928213f5..045eb27df30 100644 --- a/homeassistant/components/sonarr/translations/zh-Hans.json +++ b/homeassistant/components/sonarr/translations/zh-Hans.json @@ -17,9 +17,6 @@ "user": { "data": { "api_key": "API \u5bc6\u94a5", - "host": "\u4e3b\u673a\u5730\u5740", - "port": "\u7aef\u53e3", - "ssl": "\u4f7f\u7528 SSL \u8bc1\u4e66", "verify_ssl": "\u9a8c\u8bc1 SSL \u8bc1\u4e66" } } diff --git a/homeassistant/components/sonarr/translations/zh-Hant.json b/homeassistant/components/sonarr/translations/zh-Hant.json index 4688ec2e438..6d9be9dd294 100644 --- a/homeassistant/components/sonarr/translations/zh-Hant.json +++ b/homeassistant/components/sonarr/translations/zh-Hant.json @@ -18,10 +18,6 @@ "user": { "data": { "api_key": "API \u91d1\u9470", - "base_path": "API \u8def\u5f91", - "host": "\u4e3b\u6a5f\u7aef", - "port": "\u901a\u8a0a\u57e0", - "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "url": "\u7db2\u5740", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" } diff --git a/homeassistant/components/songpal/translations/nl.json b/homeassistant/components/songpal/translations/nl.json index 0550b154ca4..aa4be57dda2 100644 --- a/homeassistant/components/songpal/translations/nl.json +++ b/homeassistant/components/songpal/translations/nl.json @@ -5,7 +5,7 @@ "not_songpal_device": "Geen Songpal-apparaat" }, "error": { - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "flow_title": "{name} ({host})", "step": { diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 114c2815a56..1d7acd8d8dc 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -40,6 +40,7 @@ from .const import ( SONOS_VANISHED, UPNP_ST, ) +from .exception import SonosUpdateError from .favorites import SonosFavorites from .speaker import SonosSpeaker @@ -203,7 +204,7 @@ class SonosDiscoveryManager: def _add_speaker(self, soco: SoCo) -> None: """Create and set up a new SonosSpeaker instance.""" try: - speaker_info = soco.get_speaker_info(True) + speaker_info = soco.get_speaker_info(True, timeout=7) 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) @@ -264,19 +265,11 @@ class SonosDiscoveryManager: 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: + known_speaker.ping() + except SonosUpdateError: _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._poll_manual_hosts diff --git a/homeassistant/components/sonos/exception.py b/homeassistant/components/sonos/exception.py index bce1e3233c1..dd2d30796cc 100644 --- a/homeassistant/components/sonos/exception.py +++ b/homeassistant/components/sonos/exception.py @@ -9,3 +9,7 @@ class UnknownMediaType(BrowseError): class SonosUpdateError(HomeAssistantError): """Update failed.""" + + +class S1BatteryMissing(SonosUpdateError): + """Battery update failed on S1 firmware.""" diff --git a/homeassistant/components/sonos/helpers.py b/homeassistant/components/sonos/helpers.py index 3edf23f0c3c..9e8fcbd6a12 100644 --- a/homeassistant/components/sonos/helpers.py +++ b/homeassistant/components/sonos/helpers.py @@ -35,16 +35,14 @@ _P = ParamSpec("_P") @overload def soco_error( errorcodes: None = ..., -) -> Callable[ # type: ignore[misc] - [Callable[Concatenate[_T, _P], _R]], Callable[Concatenate[_T, _P], _R] -]: +) -> Callable[[Callable[Concatenate[_T, _P], _R]], Callable[Concatenate[_T, _P], _R]]: ... @overload def soco_error( errorcodes: list[str], -) -> Callable[ # type: ignore[misc] +) -> Callable[ [Callable[Concatenate[_T, _P], _R]], Callable[Concatenate[_T, _P], _R | None] ]: ... @@ -52,19 +50,19 @@ def soco_error( def soco_error( errorcodes: list[str] | None = None, -) -> Callable[ # type: ignore[misc] +) -> Callable[ [Callable[Concatenate[_T, _P], _R]], Callable[Concatenate[_T, _P], _R | None] ]: """Filter out specified UPnP errors and raise exceptions for service calls.""" def decorator( - funct: Callable[Concatenate[_T, _P], _R] # type: ignore[misc] - ) -> Callable[Concatenate[_T, _P], _R | None]: # type: ignore[misc] + funct: Callable[Concatenate[_T, _P], _R] + ) -> Callable[Concatenate[_T, _P], _R | None]: """Decorate functions.""" def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R | None: """Wrap for all soco UPnP exception.""" - args_soco = next((arg for arg in args if isinstance(arg, SoCo)), None) # type: ignore[attr-defined] + args_soco = next((arg for arg in args if isinstance(arg, SoCo)), None) try: result = funct(self, *args, **kwargs) except (OSError, SoCoException, SoCoUPnPException) as err: diff --git a/homeassistant/components/sonos/media.py b/homeassistant/components/sonos/media.py index 1b4dbd00d59..e3d8f043d4b 100644 --- a/homeassistant/components/sonos/media.py +++ b/homeassistant/components/sonos/media.py @@ -120,6 +120,8 @@ class SonosMedia: self.clear() track_info = self.poll_track_info() + if not track_info["uri"]: + return self.uri = track_info["uri"] audio_source = self.soco.music_source_from_uri(self.uri) @@ -135,7 +137,7 @@ class SonosMedia: self.title = track_info.get("title") self.image_url = track_info.get("album_art") - playlist_position = int(track_info.get("playlist_position")) + playlist_position = int(track_info.get("playlist_position", -1)) if playlist_position > 0: self.queue_position = playlist_position diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index f7f5e2722ff..cd129d82843 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -18,12 +18,14 @@ import voluptuous as vol from homeassistant.components import media_source, spotify from homeassistant.components.media_player import ( + MediaPlayerEnqueue, MediaPlayerEntity, MediaPlayerEntityFeature, async_process_play_media_url, ) from homeassistant.components.media_player.const import ( ATTR_INPUT_SOURCE, + ATTR_MEDIA_ANNOUNCE, ATTR_MEDIA_ENQUEUE, MEDIA_TYPE_ALBUM, MEDIA_TYPE_ARTIST, @@ -127,6 +129,9 @@ async def async_setup_entry( speakers.append(entity.speaker) if service_call.service == SERVICE_JOIN: + _LOGGER.warning( + "Service 'sonos.join' is deprecated and will be removed in 2022.8, please use 'media_player.join'" + ) master = platform.entities.get(service_call.data[ATTR_MASTER]) if master: await SonosSpeaker.join_multi(hass, master.speaker, speakers) # type: ignore[arg-type] @@ -136,6 +141,9 @@ async def async_setup_entry( service_call.data[ATTR_MASTER], ) elif service_call.service == SERVICE_UNJOIN: + _LOGGER.warning( + "Service 'sonos.unjoin' is deprecated and will be removed in 2022.8, please use 'media_player.unjoin'" + ) await SonosSpeaker.unjoin_multi(hass, speakers) # type: ignore[arg-type] elif service_call.service == SERVICE_SNAPSHOT: await SonosSpeaker.snapshot_multi( @@ -520,7 +528,9 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): self.coordinator.soco.clear_queue() @soco_error() - def play_media(self, media_type: str, media_id: str, **kwargs: Any) -> None: + def play_media( # noqa: C901 + self, media_type: str, media_id: str, **kwargs: Any + ) -> None: """ Send the play_media command to the media player. @@ -531,9 +541,13 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): If media_type is "playlist", media_id should be a Sonos Playlist name. Otherwise, media_id should be a URI. - - If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the queue. """ + # Use 'replace' as the default enqueue option + enqueue = kwargs.get(ATTR_MEDIA_ENQUEUE, MediaPlayerEnqueue.REPLACE) + if kwargs.get(ATTR_MEDIA_ANNOUNCE): + # Temporary workaround until announce support is added + enqueue = MediaPlayerEnqueue.PLAY + if spotify.is_spotify_media_type(media_type): media_type = spotify.resolve_spotify_media_type(media_type) media_id = spotify.spotify_uri_from_media_browser_url(media_id) @@ -545,7 +559,9 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): media_type = MEDIA_TYPE_MUSIC media_id = ( run_coroutine_threadsafe( - media_source.async_resolve_media(self.hass, media_id), + media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ), self.hass.loop, ) .result() @@ -567,9 +583,17 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): ) if result.shuffle: self.set_shuffle(True) - if kwargs.get(ATTR_MEDIA_ENQUEUE): + if enqueue == MediaPlayerEnqueue.ADD: plex_plugin.add_to_queue(result.media) - else: + elif enqueue in ( + MediaPlayerEnqueue.NEXT, + MediaPlayerEnqueue.PLAY, + ): + pos = (self.media.queue_position or 0) + 1 + new_pos = plex_plugin.add_to_queue(result.media, position=pos) + if enqueue == MediaPlayerEnqueue.PLAY: + soco.play_from_queue(new_pos - 1) + elif enqueue == MediaPlayerEnqueue.REPLACE: soco.clear_queue() plex_plugin.add_to_queue(result.media) soco.play_from_queue(0) @@ -577,9 +601,17 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): share_link = self.coordinator.share_link if share_link.is_share_link(media_id): - if kwargs.get(ATTR_MEDIA_ENQUEUE): + if enqueue == MediaPlayerEnqueue.ADD: share_link.add_share_link_to_queue(media_id) - else: + elif enqueue in ( + MediaPlayerEnqueue.NEXT, + MediaPlayerEnqueue.PLAY, + ): + pos = (self.media.queue_position or 0) + 1 + new_pos = share_link.add_share_link_to_queue(media_id, position=pos) + if enqueue == MediaPlayerEnqueue.PLAY: + soco.play_from_queue(new_pos - 1) + elif enqueue == MediaPlayerEnqueue.REPLACE: soco.clear_queue() share_link.add_share_link_to_queue(media_id) soco.play_from_queue(0) @@ -587,9 +619,17 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): # If media ID is a relative URL, we serve it from HA. media_id = async_process_play_media_url(self.hass, media_id) - if kwargs.get(ATTR_MEDIA_ENQUEUE): + if enqueue == MediaPlayerEnqueue.ADD: soco.add_uri_to_queue(media_id) - else: + elif enqueue in ( + MediaPlayerEnqueue.NEXT, + MediaPlayerEnqueue.PLAY, + ): + pos = (self.media.queue_position or 0) + 1 + new_pos = soco.add_uri_to_queue(media_id, position=pos) + if enqueue == MediaPlayerEnqueue.PLAY: + soco.play_from_queue(new_pos - 1) + elif enqueue == MediaPlayerEnqueue.REPLACE: soco.play_uri(media_id, force_radio=is_radio) elif media_type == MEDIA_TYPE_PLAYLIST: if media_id.startswith("S:"): diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 4e4661b389b..5d4199ec905 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -57,6 +57,7 @@ from .const import ( SONOS_VANISHED, SUBSCRIPTION_TIMEOUT, ) +from .exception import S1BatteryMissing, SonosUpdateError from .favorites import SonosFavorites from .helpers import soco_error from .media import SonosMedia @@ -83,16 +84,6 @@ UNUSED_DEVICE_KEYS = ["SPID", "TargetRoomName"] _LOGGER = logging.getLogger(__name__) -def fetch_battery_info_or_none(soco: SoCo) -> dict[str, Any] | None: - """Fetch battery_info from the given SoCo object. - - Returns None if the device doesn't support battery info - or if the device is offline. - """ - with contextlib.suppress(ConnectionError, TimeoutError, SoCoException): - return soco.get_battery_info() - - class SonosSpeaker: """Representation of a Sonos speaker.""" @@ -207,8 +198,11 @@ class SonosSpeaker: self.hass, SONOS_CREATE_AUDIO_FORMAT_SENSOR, self, audio_format ) - if battery_info := fetch_battery_info_or_none(self.soco): - self.battery_info = battery_info + try: + self.battery_info = self.fetch_battery_info() + except SonosUpdateError: + _LOGGER.debug("No battery available for %s", self.zone_name) + else: # Battery events can be infrequent, polling is still necessary self._battery_poll_timer = track_time_interval( self.hass, self.async_poll_battery, BATTERY_SCAN_INTERVAL @@ -530,6 +524,13 @@ class SonosSpeaker: # # Speaker availability methods # + @soco_error() + def ping(self) -> None: + """Test device availability. Failure will raise SonosUpdateError.""" + self.soco.renderingControl.GetVolume( + [("InstanceID", 0), ("Channel", "Master")], timeout=1 + ) + @callback def speaker_activity(self, source): """Track the last activity on this speaker, set availability and resubscribe.""" @@ -560,23 +561,13 @@ class SonosSpeaker: return try: - # Make a short-timeout call as a final check - # before marking this speaker as unavailable - await self.hass.async_add_executor_job( - partial( - self.soco.renderingControl.GetVolume, - [("InstanceID", 0), ("Channel", "Master")], - timeout=1, - ) - ) - except OSError: + await self.hass.async_add_executor_job(self.ping) + except SonosUpdateError: _LOGGER.warning( "No recent activity and cannot reach %s, marking unavailable", self.zone_name, ) await self.async_offline() - else: - self.speaker_activity("timeout poll") async def async_offline(self) -> None: """Handle removal of speaker when unavailable.""" @@ -619,6 +610,15 @@ class SonosSpeaker: # # Battery management # + @soco_error() + def fetch_battery_info(self) -> dict[str, Any]: + """Fetch battery_info for the speaker.""" + battery_info = self.soco.get_battery_info() + if not battery_info: + # S1 firmware returns an empty payload + raise S1BatteryMissing + return battery_info + async def async_update_battery_info(self, more_info: str) -> None: """Update battery info using a SonosEvent payload value.""" battery_dict = dict(x.split(":") for x in more_info.split(",")) @@ -658,11 +658,17 @@ class SonosSpeaker: if is_charging == self.charging: self.battery_info.update({"Level": int(battery_dict["BattPct"])}) + elif not is_charging: + # Avoid polling the speaker if possible + self.battery_info["PowerSource"] = "BATTERY" else: - if battery_info := await self.hass.async_add_executor_job( - fetch_battery_info_or_none, self.soco - ): - self.battery_info = battery_info + # Poll to obtain current power source not provided by event + try: + self.battery_info = await self.hass.async_add_executor_job( + self.fetch_battery_info + ) + except SonosUpdateError as err: + _LOGGER.debug("Could not request current power source: %s", err) @property def power_source(self) -> str | None: @@ -692,10 +698,13 @@ class SonosSpeaker: ): return - if battery_info := await self.hass.async_add_executor_job( - fetch_battery_info_or_none, self.soco - ): - self.battery_info = battery_info + try: + self.battery_info = await self.hass.async_add_executor_job( + self.fetch_battery_info + ) + except SonosUpdateError as err: + _LOGGER.debug("Could not poll battery info: %s", err) + else: self.async_write_entity_states() # @@ -749,18 +758,18 @@ class SonosSpeaker: def _get_soco_group() -> list[str]: """Ask SoCo cache for existing topology.""" coordinator_uid = self.soco.uid - slave_uids = [] + joined_uids = [] with contextlib.suppress(OSError, SoCoException): if self.soco.group and self.soco.group.coordinator: coordinator_uid = self.soco.group.coordinator.uid - slave_uids = [ + joined_uids = [ p.uid for p in self.soco.group.members if p.uid != coordinator_uid and p.is_visible ] - return [coordinator_uid] + slave_uids + return [coordinator_uid] + joined_uids async def _async_extract_group(event: SonosEvent | None) -> list[str]: """Extract group layout from a topology event.""" @@ -814,13 +823,13 @@ class SonosSpeaker: self.sonos_group_entities = sonos_group_entities self.async_write_entity_states() - for slave_uid in group[1:]: - slave = self.hass.data[DATA_SONOS].discovered.get(slave_uid) - if slave: - slave.coordinator = self - slave.sonos_group = sonos_group - slave.sonos_group_entities = sonos_group_entities - slave.async_write_entity_states() + for joined_uid in group[1:]: + joined_speaker = self.hass.data[DATA_SONOS].discovered.get(joined_uid) + if joined_speaker: + joined_speaker.coordinator = self + joined_speaker.sonos_group = sonos_group + joined_speaker.sonos_group_entities = sonos_group_entities + joined_speaker.async_write_entity_states() _LOGGER.debug("Regrouped %s: %s", self.zone_name, self.sonos_group_entities) @@ -838,7 +847,7 @@ class SonosSpeaker: return _async_handle_group_event(event) @soco_error() - def join(self, slaves: list[SonosSpeaker]) -> list[SonosSpeaker]: + def join(self, speakers: list[SonosSpeaker]) -> list[SonosSpeaker]: """Form a group with other players.""" if self.coordinator: self.unjoin() @@ -846,12 +855,12 @@ class SonosSpeaker: else: group = self.sonos_group.copy() - for slave in slaves: - if slave.soco.uid != self.soco.uid: - slave.soco.join(self.soco) - slave.coordinator = self - if slave not in group: - group.append(slave) + for speaker in speakers: + if speaker.soco.uid != self.soco.uid: + speaker.soco.join(self.soco) + speaker.coordinator = self + if speaker not in group: + group.append(speaker) return group @@ -880,11 +889,11 @@ class SonosSpeaker: def _unjoin_all(speakers: list[SonosSpeaker]) -> None: """Sync helper.""" - # Unjoin slaves first to prevent inheritance of queues + # Detach all joined speakers first to prevent inheritance of queues coordinators = [s for s in speakers if s.is_coordinator] - slaves = [s for s in speakers if not s.is_coordinator] + joined_speakers = [s for s in speakers if not s.is_coordinator] - for speaker in slaves + coordinators: + for speaker in joined_speakers + coordinators: speaker.unjoin() async with hass.data[DATA_SONOS].topology_condition: @@ -928,7 +937,7 @@ class SonosSpeaker: assert self.soco_snapshot is not None self.soco_snapshot.restore() except (TypeError, AssertionError, AttributeError, SoCoException) as ex: - # Can happen if restoring a coordinator onto a current slave + # Can happen if restoring a coordinator onto a current group member _LOGGER.warning("Error on restore %s: %s", self.zone_name, ex) self.soco_snapshot = None @@ -1036,7 +1045,7 @@ class SonosSpeaker: if coordinator != current_group[0]: return False - # Test that slaves match + # Test that joined members match if set(group[1:]) != set(current_group[1:]): return False diff --git a/homeassistant/components/sonos/translations/es.json b/homeassistant/components/sonos/translations/es.json index 3560280c90e..e6af9ac0e44 100644 --- a/homeassistant/components/sonos/translations/es.json +++ b/homeassistant/components/sonos/translations/es.json @@ -3,7 +3,7 @@ "abort": { "no_devices_found": "No se encontraron dispositivos en la red", "not_sonos_device": "El dispositivo descubierto no es un dispositivo Sonos", - "single_instance_allowed": "S\u00f3lo se necesita una \u00fanica configuraci\u00f3n de Sonos." + "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/ko.json b/homeassistant/components/sonos/translations/ko.json index f85f3c5cab4..95a2c1ccb5a 100644 --- a/homeassistant/components/sonos/translations/ko.json +++ b/homeassistant/components/sonos/translations/ko.json @@ -2,6 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "not_sonos_device": "\ubc1c\uacac\ub41c \uc7a5\uce58\uac00 Sonos \uc7a5\uce58\uac00 \uc544\ub2d9\ub2c8\ub2e4", "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { diff --git a/homeassistant/components/sonos/translations/nl.json b/homeassistant/components/sonos/translations/nl.json index 0147c5c5382..615654c98ad 100644 --- a/homeassistant/components/sonos/translations/nl.json +++ b/homeassistant/components/sonos/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", "not_sonos_device": "Gevonden apparaat is geen Sonos-apparaat", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/soundtouch/media_player.py b/homeassistant/components/soundtouch/media_player.py index 3172eb4aed6..7c9ade3bee1 100644 --- a/homeassistant/components/soundtouch/media_player.py +++ b/homeassistant/components/soundtouch/media_player.py @@ -357,7 +357,9 @@ class SoundTouchDevice(MediaPlayerEntity): 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) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = async_process_play_media_url(self.hass, play_item.url) await self.hass.async_add_executor_job( diff --git a/homeassistant/components/speedtestdotnet/translations/ar.json b/homeassistant/components/speedtestdotnet/translations/ar.json index 527ad2ecd96..943182c47a4 100644 --- a/homeassistant/components/speedtestdotnet/translations/ar.json +++ b/homeassistant/components/speedtestdotnet/translations/ar.json @@ -1,8 +1,5 @@ { "config": { - "abort": { - "wrong_server_id": "\u0645\u0639\u0631\u0641 \u0627\u0644\u062e\u0627\u062f\u0645 \u063a\u064a\u0631 \u0635\u0627\u0644\u062d" - }, "step": { "user": { "description": "\u0647\u0644 \u0623\u0646\u062a \u0645\u062a\u0623\u0643\u062f \u0645\u0646 \u0623\u0646\u0643 \u062a\u0631\u064a\u062f \u0625\u0639\u062f\u0627\u062f SpeedTest\u061f" diff --git a/homeassistant/components/speedtestdotnet/translations/ca.json b/homeassistant/components/speedtestdotnet/translations/ca.json index 57caf0ed69b..c1c5dda71cf 100644 --- a/homeassistant/components/speedtestdotnet/translations/ca.json +++ b/homeassistant/components/speedtestdotnet/translations/ca.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", - "wrong_server_id": "L'identificador del servidor no \u00e9s v\u00e0lid" + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/cs.json b/homeassistant/components/speedtestdotnet/translations/cs.json index 7564e3ab4f7..22ad2f23322 100644 --- a/homeassistant/components/speedtestdotnet/translations/cs.json +++ b/homeassistant/components/speedtestdotnet/translations/cs.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace.", - "wrong_server_id": "ID serveru nen\u00ed platn\u00e9." + "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/de.json b/homeassistant/components/speedtestdotnet/translations/de.json index eff51fab0b4..81910cb9c70 100644 --- a/homeassistant/components/speedtestdotnet/translations/de.json +++ b/homeassistant/components/speedtestdotnet/translations/de.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", - "wrong_server_id": "Server-ID ist ung\u00fcltig" + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/el.json b/homeassistant/components/speedtestdotnet/translations/el.json index 096e339732c..25b5e23ab69 100644 --- a/homeassistant/components/speedtestdotnet/translations/el.json +++ b/homeassistant/components/speedtestdotnet/translations/el.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", - "wrong_server_id": "\u03a4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf" + "single_instance_allowed": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ae\u03b4\u03b7. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/en.json b/homeassistant/components/speedtestdotnet/translations/en.json index b56ff193e33..eab480073bc 100644 --- a/homeassistant/components/speedtestdotnet/translations/en.json +++ b/homeassistant/components/speedtestdotnet/translations/en.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Already configured. Only a single configuration possible.", - "wrong_server_id": "Server ID is not valid" + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/es.json b/homeassistant/components/speedtestdotnet/translations/es.json index 1ccca471c7e..42f263971ba 100644 --- a/homeassistant/components/speedtestdotnet/translations/es.json +++ b/homeassistant/components/speedtestdotnet/translations/es.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n.", - "wrong_server_id": "Id del servidor no v\u00e1lido" + "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/et.json b/homeassistant/components/speedtestdotnet/translations/et.json index 834581a9941..ac1915e760a 100644 --- a/homeassistant/components/speedtestdotnet/translations/et.json +++ b/homeassistant/components/speedtestdotnet/translations/et.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", - "wrong_server_id": "Serveri ID ei sobi" + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/fr.json b/homeassistant/components/speedtestdotnet/translations/fr.json index 4ad47bb6a3e..d2efebd0eb1 100644 --- a/homeassistant/components/speedtestdotnet/translations/fr.json +++ b/homeassistant/components/speedtestdotnet/translations/fr.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", - "wrong_server_id": "L'ID du serveur n'est pas valide" + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/he.json b/homeassistant/components/speedtestdotnet/translations/he.json index ea3dd80aa94..08506bf3437 100644 --- a/homeassistant/components/speedtestdotnet/translations/he.json +++ b/homeassistant/components/speedtestdotnet/translations/he.json @@ -1,8 +1,7 @@ { "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.", - "wrong_server_id": "\u05de\u05d6\u05d4\u05d4 \u05d4\u05e9\u05e8\u05ea \u05d0\u05d9\u05e0\u05d5 \u05d7\u05d5\u05e7\u05d9" + "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": { diff --git a/homeassistant/components/speedtestdotnet/translations/hu.json b/homeassistant/components/speedtestdotnet/translations/hu.json index 9e602652e5b..c223e8b9376 100644 --- a/homeassistant/components/speedtestdotnet/translations/hu.json +++ b/homeassistant/components/speedtestdotnet/translations/hu.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", - "wrong_server_id": "A szerver azonos\u00edt\u00f3 \u00e9rv\u00e9nytelen" + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/id.json b/homeassistant/components/speedtestdotnet/translations/id.json index 24e78609380..f609c3d384a 100644 --- a/homeassistant/components/speedtestdotnet/translations/id.json +++ b/homeassistant/components/speedtestdotnet/translations/id.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", - "wrong_server_id": "ID server tidak valid" + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/it.json b/homeassistant/components/speedtestdotnet/translations/it.json index d0c44329313..07615fe093c 100644 --- a/homeassistant/components/speedtestdotnet/translations/it.json +++ b/homeassistant/components/speedtestdotnet/translations/it.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", - "wrong_server_id": "L'ID Server non \u00e8 valido" + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/ja.json b/homeassistant/components/speedtestdotnet/translations/ja.json index 4e254367593..5712139b6b9 100644 --- a/homeassistant/components/speedtestdotnet/translations/ja.json +++ b/homeassistant/components/speedtestdotnet/translations/ja.json @@ -1,8 +1,7 @@ { "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", - "wrong_server_id": "\u30b5\u30fc\u30d0\u30fcID\u304c\u7121\u52b9\u3067\u3059" + "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": { diff --git a/homeassistant/components/speedtestdotnet/translations/ko.json b/homeassistant/components/speedtestdotnet/translations/ko.json index e3b65208317..dbef7c3b7d3 100644 --- a/homeassistant/components/speedtestdotnet/translations/ko.json +++ b/homeassistant/components/speedtestdotnet/translations/ko.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "wrong_server_id": "\uc11c\ubc84 ID\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/lb.json b/homeassistant/components/speedtestdotnet/translations/lb.json index 3081080d1fb..4007faf71e7 100644 --- a/homeassistant/components/speedtestdotnet/translations/lb.json +++ b/homeassistant/components/speedtestdotnet/translations/lb.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech.", - "wrong_server_id": "Server ID ass ong\u00eblteg" + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/nl.json b/homeassistant/components/speedtestdotnet/translations/nl.json index 3a112d48e9d..b5af5dbc78d 100644 --- a/homeassistant/components/speedtestdotnet/translations/nl.json +++ b/homeassistant/components/speedtestdotnet/translations/nl.json @@ -1,12 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "wrong_server_id": "Server-ID is niet geldig" + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "user": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } }, diff --git a/homeassistant/components/speedtestdotnet/translations/no.json b/homeassistant/components/speedtestdotnet/translations/no.json index a079bc1fa7b..01909d39f06 100644 --- a/homeassistant/components/speedtestdotnet/translations/no.json +++ b/homeassistant/components/speedtestdotnet/translations/no.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", - "wrong_server_id": "Server ID er ikke gyldig" + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/pl.json b/homeassistant/components/speedtestdotnet/translations/pl.json index d64a7920bea..b06d7cdd285 100644 --- a/homeassistant/components/speedtestdotnet/translations/pl.json +++ b/homeassistant/components/speedtestdotnet/translations/pl.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", - "wrong_server_id": "Identyfikator serwera jest nieprawid\u0142owy" + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/pt-BR.json b/homeassistant/components/speedtestdotnet/translations/pt-BR.json index 0498a2fad6d..739b3b41875 100644 --- a/homeassistant/components/speedtestdotnet/translations/pt-BR.json +++ b/homeassistant/components/speedtestdotnet/translations/pt-BR.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", - "wrong_server_id": "O ID do servidor n\u00e3o \u00e9 v\u00e1lido" + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/ru.json b/homeassistant/components/speedtestdotnet/translations/ru.json index c26c63d8511..3ffcd10bf31 100644 --- a/homeassistant/components/speedtestdotnet/translations/ru.json +++ b/homeassistant/components/speedtestdotnet/translations/ru.json @@ -1,8 +1,7 @@ { "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.", - "wrong_server_id": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d." + "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": { diff --git a/homeassistant/components/speedtestdotnet/translations/tr.json b/homeassistant/components/speedtestdotnet/translations/tr.json index b13be7c5e0c..5becafdf153 100644 --- a/homeassistant/components/speedtestdotnet/translations/tr.json +++ b/homeassistant/components/speedtestdotnet/translations/tr.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", - "wrong_server_id": "Sunucu kimli\u011fi ge\u00e7erli de\u011fil" + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/uk.json b/homeassistant/components/speedtestdotnet/translations/uk.json index e54ed1da92e..0456d600e59 100644 --- a/homeassistant/components/speedtestdotnet/translations/uk.json +++ b/homeassistant/components/speedtestdotnet/translations/uk.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f.", - "wrong_server_id": "\u041d\u0435\u043f\u0440\u0438\u043f\u0443\u0441\u0442\u0438\u043c\u0438\u0439 \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0441\u0435\u0440\u0432\u0435\u0440\u0430." + "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/zh-Hant.json b/homeassistant/components/speedtestdotnet/translations/zh-Hant.json index 49b8b0cfb20..43d30d4aeb8 100644 --- a/homeassistant/components/speedtestdotnet/translations/zh-Hant.json +++ b/homeassistant/components/speedtestdotnet/translations/zh-Hant.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", - "wrong_server_id": "\u4f3a\u670d\u5668 ID \u7121\u6548" + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "step": { "user": { diff --git a/homeassistant/components/spider/translations/nl.json b/homeassistant/components/spider/translations/nl.json index 373d203aed7..7540b821163 100644 --- a/homeassistant/components/spider/translations/nl.json +++ b/homeassistant/components/spider/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index c057ea240c0..bf4e9d8deae 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import timedelta +import logging from typing import Any import aiohttp @@ -10,6 +11,10 @@ import requests from spotipy import Spotify, SpotifyException import voluptuous as vol +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_CREDENTIALS, @@ -19,7 +24,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.config_entry_oauth2_flow import ( OAuth2Session, async_get_config_entry_implementation, @@ -27,7 +32,6 @@ from homeassistant.helpers.config_entry_oauth2_flow import ( from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from . import config_flow from .browse_media import async_browse_media from .const import DOMAIN, LOGGER, SPOTIFY_SCOPES from .util import ( @@ -36,15 +40,20 @@ from .util import ( spotify_uri_from_media_browser_url, ) +_LOGGER = logging.getLogger(__name__) + CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Inclusive(CONF_CLIENT_ID, ATTR_CREDENTIALS): cv.string, - vol.Inclusive(CONF_CLIENT_SECRET, ATTR_CREDENTIALS): cv.string, - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Inclusive(CONF_CLIENT_ID, ATTR_CREDENTIALS): cv.string, + vol.Inclusive(CONF_CLIENT_SECRET, ATTR_CREDENTIALS): cv.string, + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) @@ -76,17 +85,21 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True if CONF_CLIENT_ID in config[DOMAIN]: - config_flow.SpotifyFlowHandler.async_register_implementation( + await async_import_client_credential( hass, - config_entry_oauth2_flow.LocalOAuth2Implementation( - hass, - DOMAIN, + DOMAIN, + ClientCredential( config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET], - "https://accounts.spotify.com/authorize", - "https://accounts.spotify.com/api/token", ), ) + _LOGGER.warning( + "Configuration of Spotify integration in YAML is deprecated and " + "will be removed in a future release; Your existing OAuth " + "Application Credentials have been imported into the UI " + "automatically and can be safely removed from your " + "configuration.yaml file" + ) return True diff --git a/homeassistant/components/spotify/application_credentials.py b/homeassistant/components/spotify/application_credentials.py new file mode 100644 index 00000000000..203028ba7d2 --- /dev/null +++ b/homeassistant/components/spotify/application_credentials.py @@ -0,0 +1,12 @@ +"""Application credentials platform for spotify.""" + +from homeassistant.components.application_credentials import AuthorizationServer +from homeassistant.core import HomeAssistant + + +async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer: + """Return authorization server.""" + return AuthorizationServer( + authorize_url="https://accounts.spotify.com/authorize", + token_url="https://accounts.spotify.com/api/token", + ) diff --git a/homeassistant/components/spotify/manifest.json b/homeassistant/components/spotify/manifest.json index dd18a05bdc7..979f262e54a 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": ["auth"], + "dependencies": ["application_credentials"], "codeowners": ["@frenck"], "config_flow": true, "quality_scale": "silver", diff --git a/homeassistant/components/spotify/translations/nl.json b/homeassistant/components/spotify/translations/nl.json index 0d1e63fde5d..2e478de73ab 100644 --- a/homeassistant/components/spotify/translations/nl.json +++ b/homeassistant/components/spotify/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "missing_configuration": "De Spotify integratie is niet geconfigureerd. Gelieve de documentatie te volgen.", - "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout [check the help section] ({docs_url})", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", "reauth_account_mismatch": "Het Spotify account waarmee er is geverifieerd, komt niet overeen met het account dat opnieuw moet worden geverifieerd." }, "create_entry": { @@ -15,7 +15,7 @@ }, "reauth_confirm": { "description": "De Spotify integratie moet opnieuw worden geverifieerd met Spotify voor account: {account}", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" } } }, diff --git a/homeassistant/components/sql/config_flow.py b/homeassistant/components/sql/config_flow.py index ba9a5f7e4dd..dc3a839ef1d 100644 --- a/homeassistant/components/sql/config_flow.py +++ b/homeassistant/components/sql/config_flow.py @@ -13,7 +13,7 @@ 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.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector @@ -44,24 +44,23 @@ def validate_sql_select(value: str) -> str | None: 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 + engine = sqlalchemy.create_engine(db_url, future=True) + sessmaker = scoped_session(sessionmaker(bind=engine, future=True)) 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) + result: Result = sess.execute(sqlalchemy.text(query)) except SQLAlchemyError as error: + _LOGGER.debug("Execution error %s", error) if sess: sess.close() raise ValueError(error) from error + for res in result.mappings(): + data = res[column] + _LOGGER.debug("Return value from query: %s", data) + if sess: sess.close() @@ -73,9 +72,6 @@ class SQLConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - entry: config_entries.ConfigEntry - hass: HomeAssistant - @staticmethod @callback def async_get_options_flow( diff --git a/homeassistant/components/sql/manifest.json b/homeassistant/components/sql/manifest.json index c779e4567cd..4562b945008 100644 --- a/homeassistant/components/sql/manifest.json +++ b/homeassistant/components/sql/manifest.json @@ -2,7 +2,7 @@ "domain": "sql", "name": "SQL", "documentation": "https://www.home-assistant.io/integrations/sql", - "requirements": ["sqlalchemy==1.4.36"], + "requirements": ["sqlalchemy==1.4.37"], "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 5e748bed55e..33ddafe2c0a 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -6,6 +6,7 @@ import decimal import logging import sqlalchemy +from sqlalchemy.engine import Result from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import scoped_session, sessionmaker import voluptuous as vol @@ -73,11 +74,11 @@ async def async_setup_platform( for query in config[CONF_QUERIES]: 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_NAME: query[CONF_NAME], + CONF_QUERY: query[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), + CONF_COLUMN_NAME: query[CONF_COLUMN_NAME], } hass.async_create_task( hass.config_entries.flow.async_init( @@ -111,19 +112,18 @@ async def async_setup_entry( value_template.hass = hass try: - engine = sqlalchemy.create_engine(db_url) - sessmaker = scoped_session(sessionmaker(bind=engine)) + engine = sqlalchemy.create_engine(db_url, future=True) + sessmaker = scoped_session(sessionmaker(bind=engine, future=True)) except SQLAlchemyError as err: _LOGGER.error("Can not open database %s", {redact_credentials(str(err))}) return # 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;") - ) + if "mssql" in db_url: + query_str = query_str.upper().replace("SELECT", "SELECT TOP 1") + else: + query_str = query_str.replace(";", "") + " LIMIT 1;" async_add_entities( [ @@ -179,7 +179,7 @@ class SQLSensor(SensorEntity): self._attr_extra_state_attributes = {} sess: scoped_session = self.sessionmaker() try: - result = sess.execute(self._query) + result: Result = sess.execute(sqlalchemy.text(self._query)) except SQLAlchemyError as err: _LOGGER.error( "Error executing query %s: %s", @@ -188,10 +188,8 @@ class SQLSensor(SensorEntity): ) return - _LOGGER.debug("Result %s, ResultMapping %s", result, result.mappings()) - for res in result.mappings(): - _LOGGER.debug("result = %s", res.items()) + _LOGGER.debug("Query %s result in %s", self._query, res.items()) data = res[self._column_name] for key, value in res.items(): if isinstance(value, decimal.Decimal): diff --git a/homeassistant/components/sql/strings.json b/homeassistant/components/sql/strings.json index 8d3a194ac3e..2a300f75b3e 100644 --- a/homeassistant/components/sql/strings.json +++ b/homeassistant/components/sql/strings.json @@ -5,8 +5,7 @@ }, "error": { "db_url_invalid": "Database URL invalid", - "query_invalid": "SQL Query invalid", - "value_template_invalid": "Value Template invalid" + "query_invalid": "SQL Query invalid" }, "step": { "user": { @@ -52,8 +51,7 @@ }, "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%]" + "query_invalid": "[%key:component::sql::config::error::query_invalid%]" } } } diff --git a/homeassistant/components/sql/translations/bg.json b/homeassistant/components/sql/translations/bg.json new file mode 100644 index 00000000000..5d2b776b0da --- /dev/null +++ b/homeassistant/components/sql/translations/bg.json @@ -0,0 +1,33 @@ +{ + "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" + }, + "step": { + "user": { + "data": { + "column": "\u041a\u043e\u043b\u043e\u043d\u0430", + "name": "\u0418\u043c\u0435", + "unit_of_measurement": "\u041c\u0435\u0440\u043d\u0430 \u0435\u0434\u0438\u043d\u0438\u0446\u0430" + }, + "data_description": { + "unit_of_measurement": "\u041c\u0435\u0440\u043d\u0430 \u0435\u0434\u0438\u043d\u0438\u0446\u0430 (\u043f\u043e \u0438\u0437\u0431\u043e\u0440)" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "column": "\u041a\u043e\u043b\u043e\u043d\u0430", + "name": "\u0418\u043c\u0435", + "unit_of_measurement": "\u041c\u0435\u0440\u043d\u0430 \u0435\u0434\u0438\u043d\u0438\u0446\u0430" + }, + "data_description": { + "unit_of_measurement": "\u041c\u0435\u0440\u043d\u0430 \u0435\u0434\u0438\u043d\u0438\u0446\u0430 (\u043f\u043e \u0438\u0437\u0431\u043e\u0440)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/ca.json b/homeassistant/components/sql/translations/ca.json index d69f22ca855..48498362c89 100644 --- a/homeassistant/components/sql/translations/ca.json +++ b/homeassistant/components/sql/translations/ca.json @@ -5,14 +5,14 @@ }, "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" + "query_invalid": "Consulta SQL inv\u00e0lida" }, "step": { "user": { "data": { "column": "Columna", "db_url": "URL de la base de dades", + "name": "Nom", "query": "Selecciona la consulta", "unit_of_measurement": "Unitat de mesura", "value_template": "Plantilla de valor" @@ -20,6 +20,7 @@ "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", + "name": "Nom que s'utilitzar\u00e0 per a l'entrada de configuraci\u00f3 i tamb\u00e9 pel sensor", "query": "Consulta a executar, ha de comen\u00e7ar per 'SELECT'", "unit_of_measurement": "Unitat de mesura (opcional)", "value_template": "Plantilla de valor (opcional)" @@ -30,14 +31,14 @@ "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" + "query_invalid": "Consulta SQL inv\u00e0lida" }, "step": { "init": { "data": { "column": "Columna", "db_url": "URL de la base de dades", + "name": "Nom", "query": "Selecciona la consulta", "unit_of_measurement": "Unitat de mesura", "value_template": "Plantilla de valor" @@ -45,6 +46,7 @@ "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", + "name": "Nom que s'utilitzar\u00e0 per a l'entrada de configuraci\u00f3 i tamb\u00e9 pel sensor", "query": "Consulta a executar, ha de comen\u00e7ar per 'SELECT'", "unit_of_measurement": "Unitat de mesura (opcional)", "value_template": "Plantilla de valor (opcional)" diff --git a/homeassistant/components/sql/translations/de.json b/homeassistant/components/sql/translations/de.json index 2207997133d..258bd06f876 100644 --- a/homeassistant/components/sql/translations/de.json +++ b/homeassistant/components/sql/translations/de.json @@ -5,14 +5,14 @@ }, "error": { "db_url_invalid": "Datenbank-URL ung\u00fcltig", - "query_invalid": "SQL-Abfrage ung\u00fcltig", - "value_template_invalid": "Wertvorlage ung\u00fcltig" + "query_invalid": "SQL-Abfrage ung\u00fcltig" }, "step": { "user": { "data": { "column": "Spalte", "db_url": "Datenbank-URL", + "name": "Name", "query": "Abfrage ausw\u00e4hlen", "unit_of_measurement": "Ma\u00dfeinheit", "value_template": "Wertvorlage" @@ -20,6 +20,7 @@ "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", + "name": "Name, der f\u00fcr den Konfigurationseintrag und auch f\u00fcr den Sensor verwendet wird", "query": "Auszuf\u00fchrende Abfrage, muss mit 'SELECT' beginnen", "unit_of_measurement": "Ma\u00dfeinheit (optional)", "value_template": "Wertvorlage (optional)" @@ -30,14 +31,14 @@ "options": { "error": { "db_url_invalid": "Datenbank-URL ung\u00fcltig", - "query_invalid": "SQL-Abfrage ung\u00fcltig", - "value_template_invalid": "Wertvorlage ung\u00fcltig" + "query_invalid": "SQL-Abfrage ung\u00fcltig" }, "step": { "init": { "data": { "column": "Spalte", "db_url": "Datenbank-URL", + "name": "Name", "query": "Abfrage ausw\u00e4hlen", "unit_of_measurement": "Ma\u00dfeinheit", "value_template": "Wertvorlage" @@ -45,6 +46,7 @@ "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", + "name": "Name, der f\u00fcr den Konfigurationseintrag und auch f\u00fcr den Sensor verwendet wird", "query": "Auszuf\u00fchrende Abfrage, muss mit 'SELECT' beginnen", "unit_of_measurement": "Ma\u00dfeinheit (optional)", "value_template": "Wertvorlage (optional)" diff --git a/homeassistant/components/sql/translations/el.json b/homeassistant/components/sql/translations/el.json index 4f19401a8f3..9f568eb0b03 100644 --- a/homeassistant/components/sql/translations/el.json +++ b/homeassistant/components/sql/translations/el.json @@ -5,14 +5,14 @@ }, "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" + "query_invalid": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 SQL" }, "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", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "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" @@ -20,6 +20,7 @@ "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", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b9\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03c4\u03bf\u03bd \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1", "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)" @@ -30,14 +31,14 @@ "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" + "query_invalid": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 SQL" }, "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", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "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" @@ -45,6 +46,7 @@ "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", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b9\u03c3\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03c4\u03bf\u03bd \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1", "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)" diff --git a/homeassistant/components/sql/translations/en.json b/homeassistant/components/sql/translations/en.json index 4b72d024df4..9d05f771853 100644 --- a/homeassistant/components/sql/translations/en.json +++ b/homeassistant/components/sql/translations/en.json @@ -5,8 +5,7 @@ }, "error": { "db_url_invalid": "Database URL invalid", - "query_invalid": "SQL Query invalid", - "value_template_invalid": "Value Template invalid" + "query_invalid": "SQL Query invalid" }, "step": { "user": { @@ -32,8 +31,7 @@ "options": { "error": { "db_url_invalid": "Database URL invalid", - "query_invalid": "SQL Query invalid", - "value_template_invalid": "Value Template invalid" + "query_invalid": "SQL Query invalid" }, "step": { "init": { diff --git a/homeassistant/components/sql/translations/es.json b/homeassistant/components/sql/translations/es.json new file mode 100644 index 00000000000..7117115efc7 --- /dev/null +++ b/homeassistant/components/sql/translations/es.json @@ -0,0 +1,54 @@ +{ + "config": { + "abort": { + "already_configured": "La cuenta ya est\u00e1 configurada" + }, + "error": { + "db_url_invalid": "URL de la base de datos inv\u00e1lido" + }, + "step": { + "user": { + "data": { + "name": "Nombre", + "query": "Selecciona la consulta", + "unit_of_measurement": "Unidad de medida", + "value_template": "Plantilla de valor" + }, + "data_description": { + "column": "Columna de respuesta de la consulta para presentar como estado", + "db_url": "URL de la base de datos, d\u00e9jalo en blanco para utilizar la predeterminada de HA", + "name": "Nombre que se utilizar\u00e1 para la entrada de configuraci\u00f3n y tambi\u00e9n para el sensor", + "query": "Consulta a ejecutar, debe empezar por 'SELECT'", + "unit_of_measurement": "Unidad de medida (opcional)", + "value_template": "Plantilla de valor (opcional)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "URL de la base de datos inv\u00e1lido", + "query_invalid": "Consulta SQL inv\u00e1lida" + }, + "step": { + "init": { + "data": { + "column": "Columna", + "db_url": "URL de la base de datos", + "name": "Nombre", + "query": "Selecciona la consulta", + "unit_of_measurement": "Unidad de medida", + "value_template": "Plantilla de valor" + }, + "data_description": { + "column": "Columna de respuesta de la consulta para presentar como estado", + "db_url": "URL de la base de datos, d\u00e9jalo en blanco para utilizar la predeterminada de HA", + "name": "Nombre que se utilizar\u00e1 para la entrada de configuraci\u00f3n y tambi\u00e9n para el sensor", + "query": "Consulta a ejecutar, debe empezar por 'SELECT'", + "unit_of_measurement": "Unidad de medida (opcional)", + "value_template": "Plantilla de valor (opcional)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/et.json b/homeassistant/components/sql/translations/et.json index b1f7339aba2..701c0e6d490 100644 --- a/homeassistant/components/sql/translations/et.json +++ b/homeassistant/components/sql/translations/et.json @@ -5,14 +5,14 @@ }, "error": { "db_url_invalid": "Andmebaasi URL on vigane", - "query_invalid": "SQL-p\u00e4ring on kehtetu", - "value_template_invalid": "V\u00e4\u00e4rtuse mall on kehtetu" + "query_invalid": "SQL-p\u00e4ring on kehtetu" }, "step": { "user": { "data": { "column": "Veerg", "db_url": "Andmebaasi URL", + "name": "Nimi", "query": "Vali p\u00e4ring", "unit_of_measurement": "M\u00f5\u00f5t\u00fchik", "value_template": "V\u00e4\u00e4rtuse mall" @@ -20,6 +20,7 @@ "data_description": { "column": "Tagastatud p\u00e4ringu veerg olekuna esitamiseks", "db_url": "Andmebaasi URL, j\u00e4ta t\u00fchjaks, et kasutada HA vaikeandmebaasi", + "name": "Nimi, mida kasutatakse Config Entry'i ja ka anduri jaoks", "query": "K\u00e4ivitatav p\u00e4ring peab algama 'SELECT'-iga.", "unit_of_measurement": "M\u00f5\u00f5t\u00fchik (valikuline)", "value_template": "V\u00e4\u00e4rtuse mall (valikuline)" @@ -30,14 +31,14 @@ "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" + "query_invalid": "V\u00e4\u00e4rtuse mall on kehtetu" }, "step": { "init": { "data": { "column": "Veerg", "db_url": "Andmebaasi URL", + "name": "Nimi", "query": "Vali p\u00e4ring", "unit_of_measurement": "M\u00f5\u00f5t\u00fchik", "value_template": "Tagastatud p\u00e4ringu veerg olekuna esitamiseks" @@ -45,6 +46,7 @@ "data_description": { "column": "Tagastatud p\u00e4ringu veerg olekuna esitamiseks", "db_url": "Andmebaasi URL, j\u00e4ta t\u00fchjaks, et kasutada HA vaikeandmebaasi", + "name": "Nimi, mida kasutatakse Config Entry'i ja ka anduri jaoks", "query": "K\u00e4ivitatav p\u00e4ring peab algama 'SELECT'-iga.", "unit_of_measurement": "M\u00f5\u00f5t\u00fchik (valikuline)", "value_template": "V\u00e4\u00e4rtuse mall (valikuline)" diff --git a/homeassistant/components/sql/translations/fr.json b/homeassistant/components/sql/translations/fr.json index 078fdc44d24..9d791062798 100644 --- a/homeassistant/components/sql/translations/fr.json +++ b/homeassistant/components/sql/translations/fr.json @@ -5,14 +5,14 @@ }, "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" + "query_invalid": "Requ\u00eate SQL non valide" }, "step": { "user": { "data": { "column": "Colonne", "db_url": "URL de la base de donn\u00e9es", + "name": "Nom", "query": "Requ\u00eate de s\u00e9lection", "unit_of_measurement": "Unit\u00e9 de mesure", "value_template": "Mod\u00e8le de valeur" @@ -20,6 +20,7 @@ "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", + "name": "Le nom qui sera utilis\u00e9 pour l'entr\u00e9e de configuration ainsi que pour le capteur", "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)" @@ -30,14 +31,14 @@ "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" + "query_invalid": "Requ\u00eate SQL non valide" }, "step": { "init": { "data": { "column": "Colonne", "db_url": "URL de la base de donn\u00e9es", + "name": "Nom", "query": "Requ\u00eate de s\u00e9lection", "unit_of_measurement": "Unit\u00e9 de mesure", "value_template": "Mod\u00e8le de valeur" @@ -45,6 +46,7 @@ "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", + "name": "Le nom qui sera utilis\u00e9 pour l'entr\u00e9e de configuration ainsi que pour le capteur", "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)" diff --git a/homeassistant/components/sql/translations/he.json b/homeassistant/components/sql/translations/he.json new file mode 100644 index 00000000000..9b9bda1ed3f --- /dev/null +++ b/homeassistant/components/sql/translations/he.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + }, + "step": { + "user": { + "data": { + "name": "\u05e9\u05dd" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "\u05e9\u05dd" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/hu.json b/homeassistant/components/sql/translations/hu.json index cf125215ae3..2e7f74c8fae 100644 --- a/homeassistant/components/sql/translations/hu.json +++ b/homeassistant/components/sql/translations/hu.json @@ -5,14 +5,14 @@ }, "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" + "query_invalid": "\u00c9rv\u00e9nytelen SQL-lek\u00e9rdez\u00e9s" }, "step": { "user": { "data": { "column": "Oszlop", "db_url": "Adatb\u00e1zis URL", + "name": "Elnevez\u00e9s", "query": "Lek\u00e9rdez\u00e9s kijel\u00f6l\u00e9se", "unit_of_measurement": "M\u00e9rt\u00e9kegys\u00e9g", "value_template": "\u00c9rt\u00e9ksablon" @@ -20,6 +20,7 @@ "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", + "name": "A konfigur\u00e1ci\u00f3s bejegyz\u00e9shez \u00e9s az \u00e9rz\u00e9kel\u0151h\u00f6z haszn\u00e1lt n\u00e9v", "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)" @@ -30,14 +31,14 @@ "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" + "query_invalid": "\u00c9rv\u00e9nytelen SQL-lek\u00e9rdez\u00e9s" }, "step": { "init": { "data": { "column": "Oszlop", "db_url": "Adatb\u00e1zis URL", + "name": "Elnevez\u00e9s", "query": "Lek\u00e9rdez\u00e9s kijel\u00f6l\u00e9se", "unit_of_measurement": "M\u00e9rt\u00e9kegys\u00e9g", "value_template": "\u00c9rt\u00e9ksablon" @@ -45,6 +46,7 @@ "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", + "name": "A konfigur\u00e1ci\u00f3s bejegyz\u00e9shez \u00e9s az \u00e9rz\u00e9kel\u0151h\u00f6z haszn\u00e1lt n\u00e9v", "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)" diff --git a/homeassistant/components/sql/translations/id.json b/homeassistant/components/sql/translations/id.json index 9cd3574c6c9..08810e24b2c 100644 --- a/homeassistant/components/sql/translations/id.json +++ b/homeassistant/components/sql/translations/id.json @@ -5,14 +5,14 @@ }, "error": { "db_url_invalid": "URL database tidak valid", - "query_invalid": "Kueri SQL tidak valid", - "value_template_invalid": "Templat Nilai tidak valid" + "query_invalid": "Kueri SQL tidak valid" }, "step": { "user": { "data": { "column": "Kolom", "db_url": "URL Database", + "name": "Nama", "query": "Kueri Select", "unit_of_measurement": "Satuan Ukuran", "value_template": "Templat Nilai" @@ -20,6 +20,7 @@ "data_description": { "column": "Kolom pada kueri yang dikembalikan untuk ditampilkan sebagai status", "db_url": "URL database, kosongkan untuk menggunakan database HA default", + "name": "Nama yang akan digunakan untuk Entri Konfigurasi dan juga Sensor", "query": "Kueri untuk dijalankan, perlu dimulai dengan 'SELECT'", "unit_of_measurement": "Satuan Ukuran (opsional)", "value_template": "Template Nilai (opsional)" @@ -30,14 +31,14 @@ "options": { "error": { "db_url_invalid": "URL database tidak valid", - "query_invalid": "Kueri SQL tidak valid", - "value_template_invalid": "Templat Nilai tidak valid" + "query_invalid": "Kueri SQL tidak valid" }, "step": { "init": { "data": { "column": "Kolom", "db_url": "URL Database", + "name": "Nama", "query": "Kueri Select", "unit_of_measurement": "Satuan Ukuran", "value_template": "Templat Nilai" @@ -45,6 +46,7 @@ "data_description": { "column": "Kolom pada kueri yang dikembalikan untuk ditampilkan sebagai status", "db_url": "URL database, kosongkan untuk menggunakan database HA default", + "name": "Nama yang akan digunakan untuk Entri Konfigurasi dan juga Sensor", "query": "Kueri untuk dijalankan, perlu dimulai dengan 'SELECT'", "unit_of_measurement": "Satuan Ukuran (opsional)", "value_template": "Template Nilai (opsional)" diff --git a/homeassistant/components/sql/translations/it.json b/homeassistant/components/sql/translations/it.json index 01672eb5a51..04cfb5744bf 100644 --- a/homeassistant/components/sql/translations/it.json +++ b/homeassistant/components/sql/translations/it.json @@ -5,14 +5,14 @@ }, "error": { "db_url_invalid": "URL database non valido", - "query_invalid": "Query SQL non valida", - "value_template_invalid": "Modello di valore non valido" + "query_invalid": "Query SQL non valida" }, "step": { "user": { "data": { "column": "Colonna", "db_url": "URL del database", + "name": "Nome", "query": "Query Select", "unit_of_measurement": "Unit\u00e0 di misura", "value_template": "Modello di valore" @@ -20,6 +20,7 @@ "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", + "name": "Nome che sar\u00e0 utilizzato per la voce di configurazione e anche per il sensore", "query": "Query da eseguire, deve iniziare con 'SELECT'", "unit_of_measurement": "Unit\u00e0 di misura (opzionale)", "value_template": "Modello di valore (opzionale)" @@ -30,14 +31,14 @@ "options": { "error": { "db_url_invalid": "URL database non valido", - "query_invalid": "Query SQL non valida", - "value_template_invalid": "Modello di valore non valido" + "query_invalid": "Query SQL non valida" }, "step": { "init": { "data": { "column": "Colonna", "db_url": "URL del database", + "name": "Nome", "query": "Query Select", "unit_of_measurement": "Unit\u00e0 di misura", "value_template": "Modello di valore" @@ -45,6 +46,7 @@ "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", + "name": "Nome che sar\u00e0 utilizzato per la voce di configurazione e anche per il sensore", "query": "Query da eseguire, deve iniziare con 'SELECT'", "unit_of_measurement": "Unit\u00e0 di misura (opzionale)", "value_template": "Modello di valore (opzionale)" diff --git a/homeassistant/components/sql/translations/ja.json b/homeassistant/components/sql/translations/ja.json new file mode 100644 index 00000000000..4b6e6fec7bb --- /dev/null +++ b/homeassistant/components/sql/translations/ja.json @@ -0,0 +1,57 @@ +{ + "config": { + "abort": { + "already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "error": { + "db_url_invalid": "\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306eURL\u304c\u7121\u52b9\u3067\u3059", + "query_invalid": "SQL\u30af\u30a8\u30ea\u304c\u7121\u52b9\u3067\u3059" + }, + "step": { + "user": { + "data": { + "column": "\u30b3\u30e9\u30e0", + "db_url": "\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306eURL", + "name": "\u540d\u524d", + "query": "\u30af\u30a8\u30ea\u3092\u9078\u629e", + "unit_of_measurement": "\u6e2c\u5b9a\u5358\u4f4d", + "value_template": "\u5024\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8" + }, + "data_description": { + "column": "\u8fd4\u3055\u308c\u305f\u30af\u30a8\u30ea\u3092\u72b6\u614b\u3068\u3057\u3066\u8868\u793a\u3059\u308b\u305f\u3081\u306e\u30ab\u30e9\u30e0", + "db_url": "\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9URL\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u306eHA\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u4f7f\u7528\u3059\u308b\u306b\u306f\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u307e\u3059", + "name": "\u69cb\u6210\u30a8\u30f3\u30c8\u30ea\u3068\u30bb\u30f3\u30b5\u30fc\u306b\u4f7f\u7528\u3055\u308c\u308b\u540d\u524d", + "query": "\u5b9f\u884c\u3059\u308b\u30af\u30a8\u30ea\u306f\u3001 'SELECT' \u3067\u59cb\u3081\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", + "unit_of_measurement": "\u6e2c\u5b9a\u5358\u4f4d(\u30aa\u30d7\u30b7\u30e7\u30f3)", + "value_template": "\u5024\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8(\u30aa\u30d7\u30b7\u30e7\u30f3)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306eURL\u304c\u7121\u52b9\u3067\u3059", + "query_invalid": "SQL\u30af\u30a8\u30ea\u304c\u7121\u52b9\u3067\u3059" + }, + "step": { + "init": { + "data": { + "column": "\u30b3\u30e9\u30e0", + "db_url": "\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306eURL", + "name": "\u540d\u524d", + "query": "\u30af\u30a8\u30ea\u3092\u9078\u629e", + "unit_of_measurement": "\u6e2c\u5b9a\u5358\u4f4d", + "value_template": "\u5024\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8" + }, + "data_description": { + "column": "\u8fd4\u3055\u308c\u305f\u30af\u30a8\u30ea\u3092\u72b6\u614b\u3068\u3057\u3066\u8868\u793a\u3059\u308b\u305f\u3081\u306e\u30ab\u30e9\u30e0", + "db_url": "\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9URL\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u306eHA\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u4f7f\u7528\u3059\u308b\u306b\u306f\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u307e\u3059", + "name": "\u69cb\u6210\u30a8\u30f3\u30c8\u30ea\u3068\u30bb\u30f3\u30b5\u30fc\u306b\u4f7f\u7528\u3055\u308c\u308b\u540d\u524d", + "query": "\u5b9f\u884c\u3059\u308b\u30af\u30a8\u30ea\u306f\u3001 'SELECT' \u3067\u59cb\u3081\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", + "unit_of_measurement": "\u6e2c\u5b9a\u5358\u4f4d(\u30aa\u30d7\u30b7\u30e7\u30f3)", + "value_template": "\u5024\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8(\u30aa\u30d7\u30b7\u30e7\u30f3)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/ko.json b/homeassistant/components/sql/translations/ko.json new file mode 100644 index 00000000000..3d4baeb6e35 --- /dev/null +++ b/homeassistant/components/sql/translations/ko.json @@ -0,0 +1,51 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "db_url_invalid": "\ub370\uc774\ud130\ubca0\uc774\uc2a4 URL\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "query_invalid": "SQL \ucffc\ub9ac\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "column": "\uc5f4", + "db_url": "\ub370\uc774\ud130\ubca0\uc774\uc2a4 URL", + "name": "\uc774\ub984", + "query": "\ucffc\ub9ac \uc120\ud0dd", + "unit_of_measurement": "\uce21\uc815 \ub2e8\uc704", + "value_template": "\uac12 \ud15c\ud50c\ub9bf" + }, + "data_description": { + "column": "\ubc18\ud658\ub41c \ucffc\ub9ac\uac00 \uc0c1\ud0dc\ub85c \ud45c\uc2dc\ub418\ub3c4\ub85d \ud558\ub294 \uc5f4", + "db_url": "\ub370\uc774\ud130\ubca0\uc774\uc2a4 URL, \uae30\ubcf8 HA \ub370\uc774\ud130\ubca0\uc774\uc2a4\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 \ube44\uc6cc \ub461\ub2c8\ub2e4.", + "name": "\ud1b5\ud569\uad6c\uc131\uc694\uc18c \ubc0f \uc13c\uc11c\uc5d0 \uc0ac\uc6a9\ub420 \uc774\ub984", + "query": "\uc2e4\ud589\ud560 \ucffc\ub9ac\ub294 'SELECT'\ub85c \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4.", + "unit_of_measurement": "\uce21\uc815 \ub2e8\uc704(\uc120\ud0dd \uc0ac\ud56d)", + "value_template": "\uac12 \ud15c\ud50c\ub9bf(\uc120\ud0dd \uc0ac\ud56d)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "\ub370\uc774\ud130\ubca0\uc774\uc2a4 URL\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "step": { + "init": { + "data": { + "name": "\uc774\ub984" + }, + "data_description": { + "column": "\ubc18\ud658\ub41c \ucffc\ub9ac\uac00 \uc0c1\ud0dc\ub85c \ud45c\uc2dc\ub418\ub3c4\ub85d \ud558\ub294 \uc5f4", + "db_url": "\ub370\uc774\ud130\ubca0\uc774\uc2a4 URL, \uae30\ubcf8 HA \ub370\uc774\ud130\ubca0\uc774\uc2a4\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 \ube44\uc6cc \ub461\ub2c8\ub2e4.", + "name": "\ud1b5\ud569\uad6c\uc131\uc694\uc18c \ubc0f \uc13c\uc11c\uc5d0 \uc0ac\uc6a9\ub420 \uc774\ub984", + "query": "\uc2e4\ud589\ud560 \ucffc\ub9ac\ub294 'SELECT'\ub85c \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4.", + "unit_of_measurement": "\uce21\uc815 \ub2e8\uc704(\uc120\ud0dd \uc0ac\ud56d)", + "value_template": "\uac12 \ud15c\ud50c\ub9bf(\uc120\ud0dd \uc0ac\ud56d)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/nl.json b/homeassistant/components/sql/translations/nl.json index ee6ad3503bb..594f589018d 100644 --- a/homeassistant/components/sql/translations/nl.json +++ b/homeassistant/components/sql/translations/nl.json @@ -5,23 +5,25 @@ }, "error": { "db_url_invalid": "Database URL ongeldig", - "query_invalid": "SQL Query ongeldig", - "value_template_invalid": "Waarde Template ongeldig" + "query_invalid": "SQL Query ongeldig" }, "step": { "user": { "data": { "column": "Kolom", "db_url": "Database URL", + "name": "Naam", "query": "Selecteer Query", "unit_of_measurement": "Meeteenheid", - "value_template": "Waarde Template" + "value_template": "Waardesjabloon" }, "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", + "name": "Naam die zal worden gebruikt voor Config Entry en ook de Sensor", + "query": "Query die moet worden uitgevoerd, moet beginnen met 'SELECT'", "unit_of_measurement": "Meeteenheid (optioneel)", - "value_template": "Waarde Template (optioneel)" + "value_template": "Waardesjabloon (optioneel)" } } } @@ -29,23 +31,25 @@ "options": { "error": { "db_url_invalid": "Database URL ongeldig", - "query_invalid": "SQL Query ongeldig", - "value_template_invalid": "Waarde Template ongeldig" + "query_invalid": "SQL Query ongeldig" }, "step": { "init": { "data": { "column": "Kolom", "db_url": "Database URL", + "name": "Naam", "query": "Selecteer Query", "unit_of_measurement": "Meeteenheid", - "value_template": "Waarde Template" + "value_template": "Waardesjabloon" }, "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", + "name": "Naam die zal worden gebruikt voor Config Entry en ook de Sensor", + "query": "Query die moet worden uitgevoerd, moet beginnen met 'SELECT'", "unit_of_measurement": "Meeteenheid (optioneel)", - "value_template": "Waarde Template (optioneel)" + "value_template": "Waardesjabloon (optioneel)" } } } diff --git a/homeassistant/components/sql/translations/no.json b/homeassistant/components/sql/translations/no.json index 292486d3c0f..907a618da75 100644 --- a/homeassistant/components/sql/translations/no.json +++ b/homeassistant/components/sql/translations/no.json @@ -5,14 +5,14 @@ }, "error": { "db_url_invalid": "Database-URL er ugyldig", - "query_invalid": "SQL-sp\u00f8rringen er ugyldig", - "value_template_invalid": "Verdimalen er ugyldig" + "query_invalid": "SQL-sp\u00f8rringen er ugyldig" }, "step": { "user": { "data": { "column": "Kolonne", "db_url": "Database URL", + "name": "Navn", "query": "Velg Sp\u00f8rring", "unit_of_measurement": "M\u00e5leenhet", "value_template": "Verdimal" @@ -20,6 +20,7 @@ "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", + "name": "Navn som vil bli brukt for Config Entry og ogs\u00e5 sensoren", "query": "Sp\u00f8rsm\u00e5let skal kj\u00f8res, m\u00e5 starte med \"SELECT\"", "unit_of_measurement": "M\u00e5leenhet (valgfritt)", "value_template": "Verdimal (valgfritt)" @@ -30,14 +31,14 @@ "options": { "error": { "db_url_invalid": "Database-URL er ugyldig", - "query_invalid": "SQL-sp\u00f8rringen er ugyldig", - "value_template_invalid": "Verdimalen er ugyldig" + "query_invalid": "SQL-sp\u00f8rringen er ugyldig" }, "step": { "init": { "data": { "column": "Kolonne", "db_url": "Database URL", + "name": "Navn", "query": "Velg Sp\u00f8rring", "unit_of_measurement": "M\u00e5leenhet", "value_template": "Verdimal" @@ -45,6 +46,7 @@ "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", + "name": "Navn som vil bli brukt for Config Entry og ogs\u00e5 sensoren", "query": "Sp\u00f8rsm\u00e5let skal kj\u00f8res, m\u00e5 starte med \"SELECT\"", "unit_of_measurement": "M\u00e5leenhet (valgfritt)", "value_template": "Verdimal (valgfritt)" diff --git a/homeassistant/components/sql/translations/pl.json b/homeassistant/components/sql/translations/pl.json index 0e8cb47b148..3f4211c72a5 100644 --- a/homeassistant/components/sql/translations/pl.json +++ b/homeassistant/components/sql/translations/pl.json @@ -5,14 +5,14 @@ }, "error": { "db_url_invalid": "Nieprawid\u0142owy adres URL bazy danych", - "query_invalid": "Nieprawid\u0142owe zapytanie SQL", - "value_template_invalid": "Nieprawid\u0142owy szablon warto\u015bci" + "query_invalid": "Nieprawid\u0142owe zapytanie SQL" }, "step": { "user": { "data": { "column": "Kolumna", "db_url": "Adres URL bazy danych", + "name": "Nazwa", "query": "Wybierz zapytanie", "unit_of_measurement": "Jednostka miary", "value_template": "Szablon warto\u015bci" @@ -20,6 +20,7 @@ "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", + "name": "Nazwa, kt\u00f3ra b\u0119dzie u\u017cywana do wprowadzania konfiguracji, a tak\u017ce do sensora", "query": "Zapytanie do uruchomienia, musi zaczyna\u0107 si\u0119 od \u201eSELECT\u201d", "unit_of_measurement": "Jednostka miary (opcjonalnie)", "value_template": "Szablon warto\u015bci (opcjonalnie)" @@ -30,14 +31,14 @@ "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" + "query_invalid": "Nieprawid\u0142owe zapytanie SQL" }, "step": { "init": { "data": { "column": "Kolumna", "db_url": "Adres URL bazy danych", + "name": "Nazwa", "query": "Wybierz zapytanie", "unit_of_measurement": "Jednostka miary", "value_template": "Szablon warto\u015bci" @@ -45,6 +46,7 @@ "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", + "name": "Nazwa, kt\u00f3ra b\u0119dzie u\u017cywana do wprowadzania konfiguracji, a tak\u017ce do sensora", "query": "Zapytanie do uruchomienia, musi zaczyna\u0107 si\u0119 od \u201eSELECT\u201d", "unit_of_measurement": "Jednostka miary (opcjonalnie)", "value_template": "Szablon warto\u015bci (opcjonalnie)" diff --git a/homeassistant/components/sql/translations/pt-BR.json b/homeassistant/components/sql/translations/pt-BR.json index 10df43c7e1f..89231f88f2f 100644 --- a/homeassistant/components/sql/translations/pt-BR.json +++ b/homeassistant/components/sql/translations/pt-BR.json @@ -5,14 +5,14 @@ }, "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" + "query_invalid": "Consulta SQL inv\u00e1lida" }, "step": { "user": { "data": { "column": "Coluna", "db_url": "URL do banco de dados", + "name": "Nome", "query": "Selecionar consulta", "unit_of_measurement": "Unidade de medida", "value_template": "Modelo do valor" @@ -20,6 +20,7 @@ "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", + "name": "Nome que ser\u00e1 usado para entrada de configura\u00e7\u00e3o e tamb\u00e9m para o Sensor", "query": "Consulte para ser executada, precisa come\u00e7ar com 'SELECT'", "unit_of_measurement": "Unidade de medida (opcional)", "value_template": "Modelo do valor (opcional)" @@ -30,14 +31,14 @@ "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" + "query_invalid": "Consulta SQL inv\u00e1lida" }, "step": { "init": { "data": { "column": "Coluna", "db_url": "URL do banco de dados", + "name": "Nome", "query": "Selecionar consulta", "unit_of_measurement": "Unidade de medida", "value_template": "Modelo do valor" @@ -45,6 +46,7 @@ "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", + "name": "Nome que ser\u00e1 usado para entrada de configura\u00e7\u00e3o e tamb\u00e9m para o Sensor", "query": "Consulte para ser executada, precisa come\u00e7ar com 'SELECT'", "unit_of_measurement": "Unidade de medida (opcional)", "value_template": "Modelo do valor (opcional)" diff --git a/homeassistant/components/sql/translations/ru.json b/homeassistant/components/sql/translations/ru.json index 8f8e0741583..fbc3c00874a 100644 --- a/homeassistant/components/sql/translations/ru.json +++ b/homeassistant/components/sql/translations/ru.json @@ -5,14 +5,14 @@ }, "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." + "query_invalid": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 SQL-\u0437\u0430\u043f\u0440\u043e\u0441." }, "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", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "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" @@ -20,6 +20,7 @@ "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", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0437\u0430\u043f\u0438\u0441\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0430.", "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)" @@ -30,14 +31,14 @@ "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." + "query_invalid": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 SQL-\u0437\u0430\u043f\u0440\u043e\u0441." }, "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", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "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" @@ -45,6 +46,7 @@ "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", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0437\u0430\u043f\u0438\u0441\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0430.", "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)" diff --git a/homeassistant/components/sql/translations/sv.json b/homeassistant/components/sql/translations/sv.json new file mode 100644 index 00000000000..25bd828223f --- /dev/null +++ b/homeassistant/components/sql/translations/sv.json @@ -0,0 +1,23 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Namn" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Namn" + }, + "data_description": { + "name": "Namn som kommer att anv\u00e4ndas f\u00f6r Config Entry och \u00e4ven sensorn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/tr.json b/homeassistant/components/sql/translations/tr.json index edbce58d65a..e37b603162d 100644 --- a/homeassistant/components/sql/translations/tr.json +++ b/homeassistant/components/sql/translations/tr.json @@ -5,14 +5,14 @@ }, "error": { "db_url_invalid": "Veritaban\u0131 URL'si ge\u00e7ersiz", - "query_invalid": "SQL Sorgusu ge\u00e7ersiz", - "value_template_invalid": "De\u011fer \u015eablonu ge\u00e7ersiz" + "query_invalid": "SQL Sorgusu ge\u00e7ersiz" }, "step": { "user": { "data": { "column": "S\u00fctun", "db_url": "Veritaban\u0131 URL'si", + "name": "Ad", "query": "Sorgu Se\u00e7", "unit_of_measurement": "\u00d6l\u00e7\u00fc Birimi", "value_template": "De\u011fer \u015eablonu" @@ -20,6 +20,7 @@ "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", + "name": "Yap\u0131land\u0131rma Giri\u015fi ve ayr\u0131ca Sens\u00f6r i\u00e7in kullan\u0131lacak ad", "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)" @@ -30,14 +31,14 @@ "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" + "query_invalid": "SQL Sorgusu ge\u00e7ersiz" }, "step": { "init": { "data": { "column": "S\u00fctun", "db_url": "Veritaban\u0131 URL'si", + "name": "Ad", "query": "Sorgu Se\u00e7", "unit_of_measurement": "\u00d6l\u00e7\u00fc Birimi", "value_template": "De\u011fer \u015eablonu" @@ -45,6 +46,7 @@ "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", + "name": "Yap\u0131land\u0131rma Giri\u015fi ve ayr\u0131ca Sens\u00f6r i\u00e7in kullan\u0131lacak ad", "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)" diff --git a/homeassistant/components/sql/translations/zh-Hant.json b/homeassistant/components/sql/translations/zh-Hant.json index 801d973049d..3e746b78fff 100644 --- a/homeassistant/components/sql/translations/zh-Hant.json +++ b/homeassistant/components/sql/translations/zh-Hant.json @@ -5,14 +5,14 @@ }, "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" + "query_invalid": "SQL \u67e5\u8a62\u7121\u6548" }, "step": { "user": { "data": { "column": "\u6b04\u4f4d", "db_url": "\u8cc7\u6599\u5eab URL", + "name": "\u540d\u7a31", "query": "\u9078\u64c7\u67e5\u8a62", "unit_of_measurement": "\u6e2c\u91cf\u55ae\u4f4d", "value_template": "\u6578\u503c\u6a21\u677f" @@ -20,6 +20,7 @@ "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", + "name": "\u540d\u7a31\u5c07\u6703\u7528\u65bc\u8a2d\u5b9a\u5be6\u9ad4\u8207\u611f\u6e2c\u5668", "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" @@ -30,14 +31,14 @@ "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" + "query_invalid": "SQL \u67e5\u8a62\u7121\u6548" }, "step": { "init": { "data": { "column": "\u6b04\u4f4d", "db_url": "\u8cc7\u6599\u5eab URL", + "name": "\u540d\u7a31", "query": "\u9078\u64c7\u67e5\u8a62", "unit_of_measurement": "\u6e2c\u91cf\u55ae\u4f4d", "value_template": "\u6578\u503c\u6a21\u677f" @@ -45,6 +46,7 @@ "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", + "name": "\u540d\u7a31\u5c07\u6703\u7528\u65bc\u8a2d\u5b9a\u5be6\u9ad4\u8207\u611f\u6e2c\u5668", "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" diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index e302a75e0c2..cd628a639c5 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -10,6 +10,7 @@ import voluptuous as vol from homeassistant.components import media_source from homeassistant.components.media_player import ( + MediaPlayerEnqueue, MediaPlayerEntity, MediaPlayerEntityFeature, ) @@ -20,6 +21,9 @@ from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, + REPEAT_MODE_ALL, + REPEAT_MODE_OFF, + REPEAT_MODE_ONE, ) from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry from homeassistant.const import ( @@ -231,9 +235,11 @@ class SqueezeBoxEntity(MediaPlayerEntity): | MediaPlayerEntityFeature.TURN_OFF | MediaPlayerEntityFeature.PLAY_MEDIA | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.REPEAT_SET | MediaPlayerEntityFeature.SHUFFLE_SET | MediaPlayerEntityFeature.CLEAR_PLAYLIST | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.GROUPING ) def __init__(self, player): @@ -374,12 +380,22 @@ class SqueezeBoxEntity(MediaPlayerEntity): return self._player.album @property - def shuffle(self): - """Boolean if shuffle is enabled.""" - return self._player.shuffle + def repeat(self): + """Repeat setting.""" + if self._player.repeat == "song": + return REPEAT_MODE_ONE + if self._player.repeat == "playlist": + return REPEAT_MODE_ALL + return REPEAT_MODE_OFF @property - def sync_group(self): + def shuffle(self): + """Boolean if shuffle is enabled.""" + # Squeezebox has a third shuffle mode (album) not recognized by Home Assistant + return self._player.shuffle == "song" + + @property + def group_members(self): """List players we are synced with.""" player_ids = { p.unique_id: p.entity_id for p in self.hass.data[DOMAIN][KNOWN_PLAYERS] @@ -390,6 +406,11 @@ class SqueezeBoxEntity(MediaPlayerEntity): sync_group.append(player_ids[player]) return sync_group + @property + def sync_group(self): + """List players we are synced with. Deprecated.""" + return self.group_members + @property def query_result(self): """Return the result from the call_query service.""" @@ -449,20 +470,23 @@ class SqueezeBoxEntity(MediaPlayerEntity): await self._player.async_set_power(True) async def async_play_media(self, media_type, media_id, **kwargs): - """ - Send the play_media command to the media player. - - If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the current playlist. - """ - cmd = "play" + """Send the play_media command to the media player.""" index = None - if kwargs.get(ATTR_MEDIA_ENQUEUE): + enqueue: MediaPlayerEnqueue | None = kwargs.get(ATTR_MEDIA_ENQUEUE) + + if enqueue == MediaPlayerEnqueue.ADD: cmd = "add" + elif enqueue == MediaPlayerEnqueue.NEXT: + cmd = "insert" + else: + cmd = "play" if media_source.is_media_source_id(media_id): media_type = MEDIA_TYPE_MUSIC - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = play_item.url if media_type in MEDIA_TYPE_MUSIC: @@ -499,6 +523,17 @@ class SqueezeBoxEntity(MediaPlayerEntity): if index is not None: await self._player.async_index(index) + async def async_set_repeat(self, repeat): + """Set the repeat mode.""" + if repeat == REPEAT_MODE_ALL: + repeat_mode = "playlist" + elif repeat == REPEAT_MODE_ONE: + repeat_mode = "song" + else: + repeat_mode = "none" + + await self._player.async_set_repeat(repeat_mode) + async def async_set_shuffle(self, shuffle): """Enable/disable shuffle mode.""" shuffle_mode = "song" if shuffle else "none" @@ -535,9 +570,9 @@ class SqueezeBoxEntity(MediaPlayerEntity): self._query_result = await self._player.async_query(*all_params) _LOGGER.debug("call_query got result %s", self._query_result) - async def async_sync(self, other_player): + async def async_join_players(self, group_members): """ - Add another Squeezebox player to this player's sync group. + Add other Squeezebox players to this player's sync group. If the other player is a member of a sync group, it will leave the current sync group without asking. @@ -545,15 +580,33 @@ class SqueezeBoxEntity(MediaPlayerEntity): player_ids = { p.entity_id: p.unique_id for p in self.hass.data[DOMAIN][KNOWN_PLAYERS] } - if other_player_id := player_ids.get(other_player): - await self._player.async_sync(other_player_id) - else: - _LOGGER.info("Could not find player_id for %s. Not syncing", other_player) - async def async_unsync(self): + for other_player in group_members: + if other_player_id := player_ids.get(other_player): + await self._player.async_sync(other_player_id) + else: + _LOGGER.info( + "Could not find player_id for %s. Not syncing", other_player + ) + + async def async_sync(self, other_player): + """Sync this Squeezebox player to another. Deprecated.""" + _LOGGER.warning( + "Service squeezebox.sync is deprecated; use media_player.join_players instead" + ) + await self.async_join_players([other_player]) + + async def async_unjoin_player(self): """Unsync this Squeezebox player.""" await self._player.async_unsync() + async def async_unsync(self): + """Unsync this Squeezebox player. Deprecated.""" + _LOGGER.warning( + "Service squeezebox.unsync is deprecated; use media_player.unjoin_player instead" + ) + await self.async_unjoin_player() + async def async_browse_media(self, media_content_type=None, media_content_id=None): """Implement the websocket media browsing helper.""" _LOGGER.debug( diff --git a/homeassistant/components/squeezebox/translations/nl.json b/homeassistant/components/squeezebox/translations/nl.json index fc05f6d4807..ad18c117bb1 100644 --- a/homeassistant/components/squeezebox/translations/nl.json +++ b/homeassistant/components/squeezebox/translations/nl.json @@ -5,7 +5,7 @@ "no_server_found": "Geen LMS server gevonden." }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "no_server_found": "Kan server niet automatisch vinden.", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/srp_energy/translations/nl.json b/homeassistant/components/srp_energy/translations/nl.json index ce4ac90c223..b52bda50d98 100644 --- a/homeassistant/components/srp_energy/translations/nl.json +++ b/homeassistant/components/srp_energy/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index b59dc0ce1ee..e854472f21f 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -25,7 +25,6 @@ from homeassistant.data_entry_flow import BaseServiceInfo from homeassistant.helpers import discovery_flow from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.frame import report from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_ssdp, bind_hass @@ -45,6 +44,8 @@ ATTR_SSDP_SERVER = "ssdp_server" ATTR_SSDP_BOOTID = "BOOTID.UPNP.ORG" ATTR_SSDP_NEXTBOOTID = "NEXTBOOTID.UPNP.ORG" # Attributes for accessing info from retrieved UPnP device description +ATTR_ST = "st" +ATTR_NT = "nt" ATTR_UPNP_DEVICE_TYPE = "deviceType" ATTR_UPNP_FRIENDLY_NAME = "friendlyName" ATTR_UPNP_MANUFACTURER = "manufacturer" @@ -61,7 +62,13 @@ ATTR_UPNP_PRESENTATION_URL = "presentationURL" # Attributes for accessing info added by Home Assistant ATTR_HA_MATCHING_DOMAINS = "x_homeassistant_matching_domains" -PRIMARY_MATCH_KEYS = [ATTR_UPNP_MANUFACTURER, "st", ATTR_UPNP_DEVICE_TYPE, "nt"] +PRIMARY_MATCH_KEYS = [ + ATTR_UPNP_MANUFACTURER, + ATTR_ST, + ATTR_UPNP_DEVICE_TYPE, + ATTR_NT, + ATTR_UPNP_MANUFACTURER_URL, +] _LOGGER = logging.getLogger(__name__) @@ -103,63 +110,6 @@ class SsdpServiceInfo( ): """Prepared info from ssdp/upnp entries.""" - def __getitem__(self, name: str) -> Any: - """ - Allow property access by name for compatibility reason. - - Deprecated, and will be removed in version 2022.6. - """ - report( - f"accessed discovery_info['{name}'] instead of discovery_info.{name}, " - f"discovery_info.upnp['{name}'] " - f"or discovery_info.ssdp_headers['{name}']; " - "this will fail in version 2022.6", - exclude_integrations={DOMAIN}, - error_if_core=False, - ) - # Use a property if it is available, fallback to upnp data - if hasattr(self, name): - return getattr(self, name) - if name in self.ssdp_headers and name not in self.upnp: - return self.ssdp_headers.get(name) - return self.upnp[name] - - def get(self, name: str, default: Any = None) -> Any: - """ - Enable method for compatibility reason. - - Deprecated, and will be removed in version 2022.6. - """ - report( - f"accessed discovery_info.get('{name}') instead of discovery_info.{name}, " - f"discovery_info.upnp.get('{name}') " - f"or discovery_info.ssdp_headers.get('{name}'); " - "this will fail in version 2022.6", - exclude_integrations={DOMAIN}, - error_if_core=False, - ) - if hasattr(self, name): - return getattr(self, name) - return self.upnp.get(name, self.ssdp_headers.get(name, default)) - - def __contains__(self, name: str) -> bool: - """ - Enable method for compatibility reason. - - Deprecated, and will be removed in version 2022.6. - """ - report( - f"accessed discovery_info.__contains__('{name}') " - f"instead of discovery_info.upnp.__contains__('{name}') " - f"or discovery_info.ssdp_headers.__contains__('{name}'); " - "this will fail in version 2022.6", - exclude_integrations={DOMAIN}, - error_if_core=False, - ) - if hasattr(self, name): - return getattr(self, name) is not None - return name in self.upnp or name in self.ssdp_headers - SsdpChange = Enum("SsdpChange", "ALIVE BYEBYE UPDATE") SsdpCallback = Callable[[SsdpServiceInfo, SsdpChange], Awaitable] diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index 993a0033b1b..5cbd3d0d10e 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.29.0"], + "requirements": ["async-upnp-client==0.30.1"], "dependencies": ["network"], "after_dependencies": ["zeroconf"], "codeowners": [], diff --git a/homeassistant/components/starline/__init__.py b/homeassistant/components/starline/__init__.py index 7d05c0d47ee..886a583ae93 100644 --- a/homeassistant/components/starline/__init__.py +++ b/homeassistant/components/starline/__init__.py @@ -7,6 +7,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_SCAN_INTERVAL from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr from .account import StarlineAccount from .const import ( @@ -33,7 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN] = {} hass.data[DOMAIN][entry.entry_id] = account - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) for device in account.api.devices.values(): device_registry.async_get_or_create( config_entry_id=entry.entry_id, **account.device_info(device) diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index bc6acb2732a..3f33fa015b9 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -64,8 +64,12 @@ STAT_CHANGE = "change" STAT_CHANGE_SAMPLE = "change_sample" STAT_CHANGE_SECOND = "change_second" STAT_COUNT = "count" +STAT_COUNT_BINARY_ON = "count_on" +STAT_COUNT_BINARY_OFF = "count_off" STAT_DATETIME_NEWEST = "datetime_newest" STAT_DATETIME_OLDEST = "datetime_oldest" +STAT_DATETIME_VALUE_MAX = "datetime_value_max" +STAT_DATETIME_VALUE_MIN = "datetime_value_min" STAT_DISTANCE_95P = "distance_95_percent_of_values" STAT_DISTANCE_99P = "distance_99_percent_of_values" STAT_DISTANCE_ABSOLUTE = "distance_absolute" @@ -99,6 +103,8 @@ STATS_NUMERIC_SUPPORT = { STAT_COUNT, STAT_DATETIME_NEWEST, STAT_DATETIME_OLDEST, + STAT_DATETIME_VALUE_MAX, + STAT_DATETIME_VALUE_MIN, STAT_DISTANCE_95P, STAT_DISTANCE_99P, STAT_DISTANCE_ABSOLUTE, @@ -118,18 +124,26 @@ STATS_BINARY_SUPPORT = { STAT_AVERAGE_STEP, STAT_AVERAGE_TIMELESS, STAT_COUNT, + STAT_COUNT_BINARY_ON, + STAT_COUNT_BINARY_OFF, + STAT_DATETIME_NEWEST, + STAT_DATETIME_OLDEST, STAT_MEAN, } STATS_NOT_A_NUMBER = { STAT_DATETIME_NEWEST, STAT_DATETIME_OLDEST, + STAT_DATETIME_VALUE_MAX, + STAT_DATETIME_VALUE_MIN, STAT_QUANTILES, } STATS_DATETIME = { STAT_DATETIME_NEWEST, STAT_DATETIME_OLDEST, + STAT_DATETIME_VALUE_MAX, + STAT_DATETIME_VALUE_MIN, } # Statistics which retain the unit of the source entity @@ -336,7 +350,7 @@ class StatisticsSensor(SensorEntity): if new_state.state == STATE_UNAVAILABLE: self.attributes[STAT_SOURCE_VALUE_VALID] = None return - if new_state.state in (STATE_UNKNOWN, None): + if new_state.state in (STATE_UNKNOWN, None, ""): self.attributes[STAT_SOURCE_VALUE_VALID] = False return @@ -351,7 +365,7 @@ class StatisticsSensor(SensorEntity): except ValueError: self.attributes[STAT_SOURCE_VALUE_VALID] = False _LOGGER.error( - "%s: parsing error, expected number and received %s", + "%s: parsing error. Expected number or binary state, but received '%s'", self.entity_id, new_state.state, ) @@ -370,7 +384,11 @@ class StatisticsSensor(SensorEntity): unit = base_unit elif self._state_characteristic in STATS_NOT_A_NUMBER: unit = None - elif self._state_characteristic == STAT_COUNT: + elif self._state_characteristic in ( + STAT_COUNT, + STAT_COUNT_BINARY_ON, + STAT_COUNT_BINARY_OFF, + ): unit = None elif self._state_characteristic == STAT_VARIANCE: unit = base_unit + "²" @@ -614,6 +632,16 @@ class StatisticsSensor(SensorEntity): return self.ages[0] return None + def _stat_datetime_value_max(self) -> datetime | None: + if len(self.states) > 0: + return self.ages[self.states.index(max(self.states))] + return None + + def _stat_datetime_value_min(self) -> datetime | None: + if len(self.states) > 0: + return self.ages[self.states.index(min(self.states))] + return None + def _stat_distance_95_percent_of_values(self) -> StateType: if len(self.states) >= 2: return 2 * 1.96 * cast(float, self._stat_standard_deviation()) @@ -704,6 +732,18 @@ class StatisticsSensor(SensorEntity): def _stat_binary_count(self) -> StateType: return len(self.states) + def _stat_binary_count_on(self) -> StateType: + return self.states.count(True) + + def _stat_binary_count_off(self) -> StateType: + return self.states.count(False) + + def _stat_binary_datetime_newest(self) -> datetime | None: + return self._stat_datetime_newest() + + def _stat_binary_datetime_oldest(self) -> datetime | None: + return self._stat_datetime_oldest() + def _stat_binary_mean(self) -> StateType: if len(self.states) > 0: return 100.0 / len(self.states) * self.states.count(True) diff --git a/homeassistant/components/steam_online/config_flow.py b/homeassistant/components/steam_online/config_flow.py index 246d54c0bff..338b0a80fb6 100644 --- a/homeassistant/components/steam_online/config_flow.py +++ b/homeassistant/components/steam_online/config_flow.py @@ -23,11 +23,16 @@ from .const import ( ) -def validate_input(user_input: dict[str, str | int]) -> list[dict[str, str | int]]: +def validate_input( + user_input: dict[str, str | int], multi: bool = False +) -> 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]) + if multi: + names = interface.GetPlayerSummaries(steamids=user_input[CONF_ACCOUNTS]) + else: + names = interface.GetPlayerSummaries(steamids=user_input[CONF_ACCOUNT]) return names["response"]["players"]["player"] @@ -75,6 +80,9 @@ class SteamFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="reauth_successful") self._abort_if_unique_id_configured() if self.source == config_entries.SOURCE_IMPORT: + res = await self.hass.async_add_executor_job( + validate_input, user_input, True + ) accounts_data = { CONF_ACCOUNTS: { acc["steamid"]: acc["personaname"] for acc in res @@ -166,11 +174,14 @@ class SteamOptionsFlowHandler(config_entries.OptionsFlow): } await self.hass.config_entries.async_reload(self.entry.entry_id) return self.async_create_entry(title="", data=channel_data) + error = None try: users = { name["steamid"]: name["personaname"] for name in await self.hass.async_add_executor_job(self.get_accounts) } + if not users: + error = {"base": "unauthorized"} except steam.api.HTTPTimeoutError: users = self.options[CONF_ACCOUNTS] @@ -183,12 +194,17 @@ class SteamOptionsFlowHandler(config_entries.OptionsFlow): } self.options[CONF_ACCOUNTS] = users | self.options[CONF_ACCOUNTS] - return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) + return self.async_show_form( + step_id="init", data_schema=vol.Schema(options), errors=error + ) 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"]] + try: + friends = interface.GetFriendList(steamid=self.entry.data[CONF_ACCOUNT]) + _users_str = [user["steamid"] for user in friends["friendslist"]["friends"]] + except steam.api.HTTPError: + return [] names = interface.GetPlayerSummaries(steamids=_users_str) return names["response"]["players"]["player"] diff --git a/homeassistant/components/steam_online/coordinator.py b/homeassistant/components/steam_online/coordinator.py index 78c850b0ac9..f210c449fa6 100644 --- a/homeassistant/components/steam_online/coordinator.py +++ b/homeassistant/components/steam_online/coordinator.py @@ -56,7 +56,7 @@ class SteamDataUpdateCoordinator(DataUpdateCoordinator): } for k in players: data = self.player_interface.GetSteamLevel(steamid=players[k]["steamid"]) - players[k]["level"] = data["response"]["player_level"] + players[k]["level"] = data["response"].get("player_level") return players async def _async_update_data(self) -> dict[str, dict[str, str | int]]: diff --git a/homeassistant/components/steam_online/strings.json b/homeassistant/components/steam_online/strings.json index 6d80bb77f1b..1b431795ea4 100644 --- a/homeassistant/components/steam_online/strings.json +++ b/homeassistant/components/steam_online/strings.json @@ -31,6 +31,9 @@ "accounts": "Names of accounts to be monitored" } } + }, + "error": { + "unauthorized": "Friends list restricted: Please refer to the documentation on how to see all other friends" } } } diff --git a/homeassistant/components/steam_online/translations/bg.json b/homeassistant/components/steam_online/translations/bg.json new file mode 100644 index 00000000000..66ae6cc081f --- /dev/null +++ b/homeassistant/components/steam_online/translations/bg.json @@ -0,0 +1,23 @@ +{ + "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" + }, + "step": { + "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" + }, + "user": { + "data": { + "api_key": "API \u043a\u043b\u044e\u0447" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/ca.json b/homeassistant/components/steam_online/translations/ca.json new file mode 100644 index 00000000000..a9491e2e502 --- /dev/null +++ b/homeassistant/components/steam_online/translations/ca.json @@ -0,0 +1,39 @@ +{ + "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_account": "ID del compte inv\u00e0lid", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "reauth_confirm": { + "description": "La integraci\u00f3 Steam ha de tornar a autenticar-se manualment\n\nPots trobar la teva clau a: {api_key_url}", + "title": "Reautenticaci\u00f3 de la integraci\u00f3" + }, + "user": { + "data": { + "account": "ID del compte de Steam", + "api_key": "Clau API" + }, + "description": "Utilitza {account_id_url} per trobar l'ID del teu compte de Steam" + } + } + }, + "options": { + "error": { + "unauthorized": "Llista d'amics restringida: consulta la documentaci\u00f3 sobre com veure tots els amics" + }, + "step": { + "init": { + "data": { + "accounts": "Noms dels comptes a controlar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/de.json b/homeassistant/components/steam_online/translations/de.json index 17950e14e24..44fb7f8b08b 100644 --- a/homeassistant/components/steam_online/translations/de.json +++ b/homeassistant/components/steam_online/translations/de.json @@ -12,7 +12,7 @@ }, "step": { "reauth_confirm": { - "description": "Die Steam-Integration muss manuell erneut authentifiziert werden \n\nDeinen Schl\u00fcssel findest du hier: https://steamcommunity.com/dev/apikey", + "description": "Die Steam-Integration muss manuell erneut authentifiziert werden \nHier findest du deinen Schl\u00fcssel: {api_key_url}", "title": "Integration erneut authentifizieren" }, "user": { @@ -20,11 +20,14 @@ "account": "Steam-Konto-ID", "api_key": "API-Schl\u00fcssel" }, - "description": "Verwende https://steamid.io, um deine Steam-Konto-ID zu finden" + "description": "Verwende {account_id_url}, um deine Steam-Konto-ID zu finden" } } }, "options": { + "error": { + "unauthorized": "Freundesliste eingeschr\u00e4nkt: Bitte lies in der Dokumentation nach, wie du alle anderen Freunde sehen kannst" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/en.json b/homeassistant/components/steam_online/translations/en.json index 990a33dbeff..7226c5ee177 100644 --- a/homeassistant/components/steam_online/translations/en.json +++ b/homeassistant/components/steam_online/translations/en.json @@ -25,6 +25,9 @@ } }, "options": { + "error": { + "unauthorized": "Friends list restricted: Please refer to the documentation on how to see all other friends" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/es.json b/homeassistant/components/steam_online/translations/es.json new file mode 100644 index 00000000000..9636fc04a54 --- /dev/null +++ b/homeassistant/components/steam_online/translations/es.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "El servicio ya est\u00e1 configurado", + "reauth_successful": "Re-autenticaci\u00f3n realizada correctamente" + }, + "error": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", + "invalid_account": "ID de la cuenta inv\u00e1lida", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "title": "Volver a autenticar la integraci\u00f3n" + }, + "user": { + "data": { + "account": "ID de la cuenta de Steam", + "api_key": "Clave API" + }, + "description": "Utiliza {account_id_url} para encontrar el ID de tu cuenta de Steam" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Nombres de las cuentas a controlar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/et.json b/homeassistant/components/steam_online/translations/et.json index 8d501ccf50d..62e38eb4088 100644 --- a/homeassistant/components/steam_online/translations/et.json +++ b/homeassistant/components/steam_online/translations/et.json @@ -12,7 +12,7 @@ }, "step": { "reauth_confirm": { - "description": "Steami sidumine tuleb uuesti autentida\n\nV\u00f5tme leiad siit: https://steamcommunity.com/dev/apikey", + "description": "Steami sidumine tuleb k\u00e4sitsi uuesti autentida \n\n Oma v\u00f5tme leiad siit: {api_key_url}", "title": "Taastuvasta sidumine" }, "user": { @@ -20,11 +20,14 @@ "account": "Steami konto ID", "api_key": "API v\u00f5ti" }, - "description": "Kasuta https://steamid.io, et leida oma Steam'i konto ID" + "description": "Steami konto ID leidmiseks kasuta {account_id_url}" } } }, "options": { + "error": { + "unauthorized": "S\u00f5prade nimekiri on piiratud: vaata dokumentatsiooni kuidas n\u00e4ha k\u00f5iki teisi s\u00f5pru" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/fr.json b/homeassistant/components/steam_online/translations/fr.json index 5ee6892eef7..416927493d1 100644 --- a/homeassistant/components/steam_online/translations/fr.json +++ b/homeassistant/components/steam_online/translations/fr.json @@ -12,7 +12,7 @@ }, "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", + "description": "L'int\u00e9gration Steam doit \u00eatre r\u00e9-authentifi\u00e9e manuellement\n\nVous pouvez trouver votre cl\u00e9 ici\u00a0: {api_key_url}", "title": "R\u00e9-authentifier l'int\u00e9gration" }, "user": { @@ -20,11 +20,14 @@ "account": "ID de compte Steam", "api_key": "Cl\u00e9 d'API" }, - "description": "Utilisez https://steamid.io pour trouver l'ID de votre compte Steam" + "description": "Utilisez {account_id_url} pour trouver l'ID de votre compte Steam" } } }, "options": { + "error": { + "unauthorized": "Liste d'amis restreinte\u00a0: Veuillez consulter la documentation afin d'afficher tous les autres amis" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/he.json b/homeassistant/components/steam_online/translations/he.json new file mode 100644 index 00000000000..85d9e59a96c --- /dev/null +++ b/homeassistant/components/steam_online/translations/he.json @@ -0,0 +1,23 @@ +{ + "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": { + "title": "\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05e9\u05dc \u05e9\u05d9\u05dc\u05d5\u05d1" + }, + "user": { + "data": { + "api_key": "\u05de\u05e4\u05ea\u05d7 API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/hu.json b/homeassistant/components/steam_online/translations/hu.json index 9bd0976345e..a7d6c6a7de4 100644 --- a/homeassistant/components/steam_online/translations/hu.json +++ b/homeassistant/components/steam_online/translations/hu.json @@ -12,7 +12,7 @@ }, "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", + "description": "A Steam integr\u00e1ci\u00f3t manu\u00e1lisan \u00fajra kell hiteles\u00edteni .\n\nA kulcs itt tal\u00e1lhat\u00f3: {api_key_url}", "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" }, "user": { @@ -20,11 +20,14 @@ "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" + "description": "A {account_id_url} haszn\u00e1lat\u00e1val kaphat\u00f3 meg a Steam fi\u00f3kazonos\u00edt\u00f3" } } }, "options": { + "error": { + "unauthorized": "Bar\u00e1ti lista korl\u00e1tozott: K\u00e9rj\u00fck, olvassa el a dokument\u00e1ci\u00f3t arr\u00f3l, hogyan l\u00e1thatja az \u00f6sszes t\u00f6bbi bar\u00e1tot." + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/id.json b/homeassistant/components/steam_online/translations/id.json index 07d708067fc..e944662fee1 100644 --- a/homeassistant/components/steam_online/translations/id.json +++ b/homeassistant/components/steam_online/translations/id.json @@ -12,7 +12,7 @@ }, "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", + "description": "Integrasi Steam perlu diautentikasi ulang secara manual \n\nAnda dapat menemukan kunci Anda di sini: {api_key_url}", "title": "Autentikasi Ulang Integrasi" }, "user": { @@ -20,11 +20,14 @@ "account": "ID akun Steam", "api_key": "Kunci API" }, - "description": "Gunakan https://steamid.io untuk menemukan ID akun Steam Anda" + "description": "Gunakan {account_id_url} untuk menemukan ID akun Steam Anda" } } }, "options": { + "error": { + "unauthorized": "Daftar teman dibatasi: Rujuk ke dokumentasi tentang cara melihat semua teman lain" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/it.json b/homeassistant/components/steam_online/translations/it.json index 71036870961..3f2e9481301 100644 --- a/homeassistant/components/steam_online/translations/it.json +++ b/homeassistant/components/steam_online/translations/it.json @@ -12,7 +12,7 @@ }, "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", + "description": "L'integrazione Steam richiede una nuova autenticazione manuale \n\nPuoi trovare la tua chiave qui: {api_key_url}", "title": "Autentica nuovamente l'integrazione" }, "user": { @@ -20,11 +20,14 @@ "account": "ID dell'account Steam", "api_key": "Chiave API" }, - "description": "Usa https://steamid.io per trovare l'ID del tuo account Steam" + "description": "Usa {account_id_url} per trovare l'ID del tuo account Steam" } } }, "options": { + "error": { + "unauthorized": "Elenco amici limitato: consultare la documentazione su come vedere tutti gli altri amici." + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/ja.json b/homeassistant/components/steam_online/translations/ja.json new file mode 100644 index 00000000000..e138d46319b --- /dev/null +++ b/homeassistant/components/steam_online/translations/ja.json @@ -0,0 +1,39 @@ +{ + "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_account": "\u30a2\u30ab\u30a6\u30f3\u30c8ID\u304c\u7121\u52b9", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "reauth_confirm": { + "description": "Steam\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306f\u624b\u52d5\u3067\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\n\n\u3053\u3053\u3067\u3042\u306a\u305f\u306e\u30ad\u30fc\u3092\u898b\u3064\u3051\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059: {api_key_url}", + "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" + }, + "user": { + "data": { + "account": "Steam\u30a2\u30ab\u30a6\u30f3\u30c8ID", + "api_key": "API\u30ad\u30fc" + }, + "description": "{account_id_url} \u3092\u4f7f\u7528\u3057\u3066\u3001\u3042\u306a\u305f\u306eSteam\u306e\u30a2\u30ab\u30a6\u30f3\u30c8ID\u3092\u898b\u3064\u3051\u307e\u3059" + } + } + }, + "options": { + "error": { + "unauthorized": "\u30d5\u30ec\u30f3\u30c9\u30ea\u30b9\u30c8\u306e\u5236\u9650: \u4ed6\u306e\u3059\u3079\u3066\u306e\u30d5\u30ec\u30f3\u30c9\u3092\u8868\u793a\u3059\u308b\u65b9\u6cd5\u306b\u3064\u3044\u3066\u306f\u3001\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044" + }, + "step": { + "init": { + "data": { + "accounts": "\u76e3\u8996\u5bfe\u8c61\u306e\u30a2\u30ab\u30a6\u30f3\u30c8\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/ko.json b/homeassistant/components/steam_online/translations/ko.json new file mode 100644 index 00000000000..d106b141eb2 --- /dev/null +++ b/homeassistant/components/steam_online/translations/ko.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_account": "\uc798\ubabb\ub41c \uacc4\uc815 ID", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "reauth_confirm": { + "description": "Steam \ud1b5\ud569\uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c\ud569\ub2c8\ub2e4.\n\n\uc5ec\uae30\uc5d0\uc11c \ud0a4\ub97c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4: {api_key_url}", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" + }, + "user": { + "data": { + "account": "Steam \uacc4\uc815 ID", + "api_key": "API \ud0a4" + }, + "description": "{account_id_url} \uc5d0\uc11c Steam \uacc4\uc815 ID\ub97c \ucc3e\uc73c\uc138\uc694." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \uacc4\uc815 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/nl.json b/homeassistant/components/steam_online/translations/nl.json index 20600463f6a..5512c18ee2b 100644 --- a/homeassistant/components/steam_online/translations/nl.json +++ b/homeassistant/components/steam_online/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "already_configured": "Dienst is al geconfigureerd", + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -12,19 +12,22 @@ }, "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" + "description": "De Steam integratie moet handmatig opnieuw geauthenticeerd worden\n\nU kunt uw sleutel hier vinden: {api_key_url}", + "title": "Integratie herauthenticeren" }, "user": { "data": { "account": "Steam Account-ID", "api_key": "API-sleutel" }, - "description": "Gebruik https://steamid.io om je Steam Account-ID te vinden." + "description": "Gebruik {account_id_url} om uw Steam account ID te vinden" } } }, "options": { + "error": { + "unauthorized": "Vriendenlijst beperkt: raadpleeg de documentatie over hoe je alle andere vrienden kunt zien" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/no.json b/homeassistant/components/steam_online/translations/no.json index 7e0122dfe18..1b30669fad4 100644 --- a/homeassistant/components/steam_online/translations/no.json +++ b/homeassistant/components/steam_online/translations/no.json @@ -12,7 +12,7 @@ }, "step": { "reauth_confirm": { - "description": "Steam-integrasjonen m\u00e5 re-autentiseres manuelt \n\n Du finner n\u00f8kkelen din her: https://steamcommunity.com/dev/apikey", + "description": "Steam-integrasjonen m\u00e5 re-autentiseres manuelt \n\n Du finner n\u00f8kkelen din her: {api_key_url}", "title": "Godkjenne integrering p\u00e5 nytt" }, "user": { @@ -20,11 +20,14 @@ "account": "Steam-konto-ID", "api_key": "API-n\u00f8kkel" }, - "description": "Bruk https://steamid.io for \u00e5 finne din Steam-konto-ID" + "description": "Bruk {account_id_url} for \u00e5 finne Steam-konto-ID-en din" } } }, "options": { + "error": { + "unauthorized": "Begrenset venneliste: Se dokumentasjonen for hvordan du ser alle andre venner" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/pl.json b/homeassistant/components/steam_online/translations/pl.json index 09c95bca9c4..3ceff9e442f 100644 --- a/homeassistant/components/steam_online/translations/pl.json +++ b/homeassistant/components/steam_online/translations/pl.json @@ -12,7 +12,7 @@ }, "step": { "reauth_confirm": { - "description": "Integracja Steam musi zosta\u0107 ponownie uwierzytelniona r\u0119cznie\n\nSw\u00f3j klucz znajdziesz tutaj: https://steamcommunity.com/dev/apikey", + "description": "Integracja Steam musi zosta\u0107 ponownie uwierzytelniona r\u0119cznie\n\nSw\u00f3j klucz znajdziesz tutaj: {api_key_url}", "title": "Ponownie uwierzytelnij integracj\u0119" }, "user": { @@ -20,11 +20,14 @@ "account": "Identyfikator konta Steam", "api_key": "Klucz API" }, - "description": "U\u017cyj https://steamid.io, aby znale\u017a\u0107 sw\u00f3j identyfikator konta Steam" + "description": "U\u017cyj {account_id_url}, aby znale\u017a\u0107 sw\u00f3j identyfikator konta Steam" } } }, "options": { + "error": { + "unauthorized": "Lista znajomych ograniczona: zapoznaj si\u0119 z dokumentacj\u0105, aby dowiedzie\u0107 si\u0119, jak zobaczy\u0107 wszystkich innych znajomych" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/pt-BR.json b/homeassistant/components/steam_online/translations/pt-BR.json index 93ff3e00dc5..5aa9d0d2520 100644 --- a/homeassistant/components/steam_online/translations/pt-BR.json +++ b/homeassistant/components/steam_online/translations/pt-BR.json @@ -12,7 +12,7 @@ }, "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", + "description": "A integra\u00e7\u00e3o da Steam precisa ser autenticada manualmente \n\n Voc\u00ea pode encontrar sua chave aqui: {api_key_url}", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { @@ -20,11 +20,14 @@ "account": "ID da conta Steam", "api_key": "Chave da API" }, - "description": "Use https://steamid.io para encontrar o ID da sua conta Steam" + "description": "Use {account_id_url} para encontrar o ID da sua conta Steam" } } }, "options": { + "error": { + "unauthorized": "Lista restrita de amigos: consulte a documenta\u00e7\u00e3o sobre como ver todos os outros amigos" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steam_online/translations/ru.json b/homeassistant/components/steam_online/translations/ru.json index 990226036d9..0da65eda473 100644 --- a/homeassistant/components/steam_online/translations/ru.json +++ b/homeassistant/components/steam_online/translations/ru.json @@ -12,12 +12,23 @@ }, "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", + "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. \n\n\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: {api_key_url}", "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" + "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", + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "description": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 {account_id_url}, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0432\u043e\u0435\u0433\u043e \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 Steam." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "\u0418\u043c\u0435\u043d\u0430 \u0443\u0447\u0435\u0442\u043d\u044b\u0445 \u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430" } } } diff --git a/homeassistant/components/steam_online/translations/sv.json b/homeassistant/components/steam_online/translations/sv.json new file mode 100644 index 00000000000..51c39c12b35 --- /dev/null +++ b/homeassistant/components/steam_online/translations/sv.json @@ -0,0 +1,24 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "description": "Steam-integrationen m\u00e5ste autentiseras p\u00e5 nytt manuellt\n\nDu hittar din nyckel h\u00e4r: {api_key_url}" + }, + "user": { + "data": { + "api_key": "API Nyckel" + }, + "description": "Anv\u00e4nd {account_id_url} f\u00f6r att hitta ditt Steam-konto-ID" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Namn p\u00e5 konton som ska \u00f6vervakas" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/tr.json b/homeassistant/components/steam_online/translations/tr.json new file mode 100644 index 00000000000..a1717857080 --- /dev/null +++ b/homeassistant/components/steam_online/translations/tr.json @@ -0,0 +1,39 @@ +{ + "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_account": "Ge\u00e7ersiz hesap kimli\u011fi", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "reauth_confirm": { + "description": "Steam entegrasyonunun manuel olarak yeniden do\u011frulanmas\u0131 gerekiyor \n\n Anahtar\u0131n\u0131z\u0131 burada bulabilirsiniz: {api_key_url}", + "title": "Entegrasyonu Yeniden Do\u011frula" + }, + "user": { + "data": { + "account": "Steam hesap kimli\u011fi", + "api_key": "API Anahtar\u0131" + }, + "description": "Steam hesap kimli\u011finizi bulmak i\u00e7in {account_id_url} kullan\u0131n" + } + } + }, + "options": { + "error": { + "unauthorized": "Arkada\u015f listesi k\u0131s\u0131tland\u0131: L\u00fctfen di\u011fer t\u00fcm arkada\u015flar\u0131 nas\u0131l g\u00f6rece\u011finizle ilgili belgelere bak\u0131n" + }, + "step": { + "init": { + "data": { + "accounts": "\u0130zlenecek hesaplar\u0131n adlar\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/zh-Hans.json b/homeassistant/components/steam_online/translations/zh-Hans.json new file mode 100644 index 00000000000..bf7b93740a0 --- /dev/null +++ b/homeassistant/components/steam_online/translations/zh-Hans.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u670d\u52a1\u5df2\u88ab\u914d\u7f6e", + "reauth_successful": "\u91cd\u9a8c\u8bc1\u6210\u529f" + }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "invalid_account": "\u5e10\u6237 ID \u65e0\u6548", + "invalid_auth": "\u51ed\u8bc1\u65e0\u6548", + "unknown": "\u672a\u77e5\u9519\u8bef" + }, + "step": { + "reauth_confirm": { + "description": "Steam \u96c6\u6210\u9700\u8981\u624b\u52a8\u91cd\u65b0\u8ba4\u8bc1\uff1a\n\n\u60a8\u53ef\u4ee5\u5728\u6b64\u5904\u627e\u5230\u60a8\u7684\u5bc6\u94a5\uff1a{api_key_url}", + "title": "\u91cd\u9a8c\u8bc1\u96c6\u6210" + }, + "user": { + "data": { + "account": "Steam \u5e10\u6237 ID", + "api_key": "API \u5bc6\u94a5" + }, + "description": "\u4f7f\u7528 {account_id_url} \u4ee5\u67e5\u627e\u60a8\u7684 Steam \u5e10\u6237 ID" + } + } + } +} \ 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 index 775c9710592..7de1d6f1a3c 100644 --- a/homeassistant/components/steam_online/translations/zh-Hant.json +++ b/homeassistant/components/steam_online/translations/zh-Hant.json @@ -12,7 +12,7 @@ }, "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", + "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\uff1a{api_key_url}", "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" }, "user": { @@ -20,11 +20,14 @@ "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" + "description": "\u4f7f\u7528 {account_id_url} \u4ee5\u78ba\u8a8d\u60a8\u7684 Steam \u5e33\u865f ID" } } }, "options": { + "error": { + "unauthorized": "\u597d\u53cb\u5217\u8868\u53d7\u9650\uff1a\u8acb\u53c3\u8003\u6587\u4ef6\u8cc7\u6599\u4ee5\u4e86\u89e3\u5982\u4f55\u986f\u793a\u6240\u6709\u597d\u53cb\u3002" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/steamist/translations/ja.json b/homeassistant/components/steamist/translations/ja.json index 36658f87577..b37135e9c44 100644 --- a/homeassistant/components/steamist/translations/ja.json +++ b/homeassistant/components/steamist/translations/ja.json @@ -25,7 +25,7 @@ "data": { "host": "\u30db\u30b9\u30c8" }, - "description": "\u30db\u30b9\u30c8\u3092\u7a7a\u306b\u3057\u3066\u304a\u304f\u3068\u3001\u30c7\u30a3\u30b9\u30ab\u30d0\u30ea\u30fc(discovery)\u3092\u4f7f\u3063\u3066\u30c7\u30d0\u30a4\u30b9\u3092\u691c\u7d22\u3057\u307e\u3059\u3002" + "description": "\u30db\u30b9\u30c8\u3092\u7a7a\u767d\u306b\u3057\u3066\u304a\u304f\u3068\u3001\u30c7\u30a3\u30b9\u30ab\u30d0\u30ea\u30fc(discovery)\u3092\u4f7f\u3063\u3066\u30c7\u30d0\u30a4\u30b9\u3092\u691c\u7d22\u3057\u307e\u3059\u3002" } } } diff --git a/homeassistant/components/steamist/translations/nl.json b/homeassistant/components/steamist/translations/nl.json index 345ec01dce3..62c492f5e02 100644 --- a/homeassistant/components/steamist/translations/nl.json +++ b/homeassistant/components/steamist/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken", "no_devices_found": "Geen apparaten gevonden op het netwerk", "not_steamist_device": "Geen steamist-apparaat" diff --git a/homeassistant/components/stookalert/translations/nl.json b/homeassistant/components/stookalert/translations/nl.json index b6bd443c31b..fe8f84469a5 100644 --- a/homeassistant/components/stookalert/translations/nl.json +++ b/homeassistant/components/stookalert/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index abaf367486d..895bdaf3201 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -23,7 +23,7 @@ import secrets import threading import time from types import MappingProxyType -from typing import Any, cast +from typing import Any, Final, cast import voluptuous as vol @@ -39,13 +39,19 @@ from .const import ( ATTR_STREAMS, CONF_LL_HLS, CONF_PART_DURATION, + CONF_RTSP_TRANSPORT, CONF_SEGMENT_DURATION, + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, DOMAIN, + FORMAT_CONTENT_TYPE, HLS_PROVIDER, MAX_SEGMENTS, + OUTPUT_FORMATS, OUTPUT_IDLE_TIMEOUT, RECORDER_PROVIDER, + RTSP_TRANSPORTS, SEGMENT_DURATION_ADJUSTER, + SOURCE_TIMEOUT, STREAM_RESTART_INCREMENT, STREAM_RESTART_RESET_TIME, TARGET_SEGMENT_DURATION_NON_LL_HLS, @@ -54,6 +60,18 @@ from .core import PROVIDERS, IdleTimer, KeyFrameConverter, StreamOutput, StreamS from .diagnostics import Diagnostics from .hls import HlsStreamOutput, async_setup_hls +__all__ = [ + "CONF_RTSP_TRANSPORT", + "CONF_USE_WALLCLOCK_AS_TIMESTAMPS", + "FORMAT_CONTENT_TYPE", + "HLS_PROVIDER", + "OUTPUT_FORMATS", + "RTSP_TRANSPORTS", + "SOURCE_TIMEOUT", + "Stream", + "create_stream", +] + _LOGGER = logging.getLogger(__name__) STREAM_SOURCE_REDACT_PATTERN = [ @@ -72,28 +90,32 @@ def redact_credentials(data: str) -> str: def create_stream( hass: HomeAssistant, stream_source: str, - options: dict[str, str], + options: dict[str, str | bool], stream_label: str | None = None, ) -> Stream: """Create a stream with the specified identfier based on the source url. The stream_source is typically an rtsp url (though any url accepted by ffmpeg is fine) and - options are passed into pyav / ffmpeg as options. + options (see STREAM_OPTIONS_SCHEMA) are converted and passed into pyav / ffmpeg. The stream_label is a string used as an additional message in logging. """ if DOMAIN not in hass.config.components: raise HomeAssistantError("Stream integration is not set up.") + # Convert extra stream options into PyAV options + pyav_options = convert_stream_options(options) # For RTSP streams, prefer TCP if isinstance(stream_source, str) and stream_source[:7] == "rtsp://": - options = { + pyav_options = { "rtsp_flags": "prefer_tcp", "stimeout": "5000000", - **options, + **pyav_options, } - stream = Stream(hass, stream_source, options=options, stream_label=stream_label) + stream = Stream( + hass, stream_source, options=pyav_options, stream_label=stream_label + ) hass.data[DOMAIN][ATTR_STREAMS].append(stream) return stream @@ -464,3 +486,28 @@ class Stream: def _should_retry() -> bool: """Return true if worker failures should be retried, for disabling during tests.""" return True + + +STREAM_OPTIONS_SCHEMA: Final = vol.Schema( + { + vol.Optional(CONF_RTSP_TRANSPORT): vol.In(RTSP_TRANSPORTS), + vol.Optional(CONF_USE_WALLCLOCK_AS_TIMESTAMPS): bool, + } +) + + +def convert_stream_options(stream_options: dict[str, str | bool]) -> dict[str, str]: + """Convert options from stream options into PyAV options.""" + pyav_options: dict[str, str] = {} + try: + STREAM_OPTIONS_SCHEMA(stream_options) + except vol.Invalid as exc: + raise HomeAssistantError("Invalid stream options") from exc + + if rtsp_transport := stream_options.get(CONF_RTSP_TRANSPORT): + assert isinstance(rtsp_transport, str) + pyav_options["rtsp_transport"] = rtsp_transport + if stream_options.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS): + pyav_options["use_wallclock_as_timestamps"] = "1" + + return pyav_options diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index 50ae43df0d0..f8c9ba85d59 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -42,3 +42,14 @@ STREAM_RESTART_RESET_TIME = 300 # Reset wait_timeout after this many seconds CONF_LL_HLS = "ll_hls" CONF_PART_DURATION = "part_duration" CONF_SEGMENT_DURATION = "segment_duration" + +CONF_PREFER_TCP = "prefer_tcp" +CONF_RTSP_TRANSPORT = "rtsp_transport" +# The first dict entry below may be used as the default when populating options +RTSP_TRANSPORTS = { + "tcp": "TCP", + "udp": "UDP", + "udp_multicast": "UDP Multicast", + "http": "HTTP", +} +CONF_USE_WALLCLOCK_AS_TIMESTAMPS = "use_wallclock_as_timestamps" diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 8db6a239818..8c0b867752e 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -395,6 +395,9 @@ class KeyFrameConverter: This is run by the worker thread and will only be called once per worker. """ + if self._codec_context: + return + # Keep import here so that we can import stream integration without installing reqs # pylint: disable=import-outside-toplevel from av import CodecContext diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index 44b19d2cc85..23584b59fb9 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -215,16 +215,6 @@ class HlsPlaylistView(StreamView): return web.Response( body=None, status=HTTPStatus.BAD_REQUEST, - # From Appendix B.1 of the RFC: - # Successful responses to blocking Playlist requests should be cached - # for six Target Durations. Unsuccessful responses (such as 404s) should - # be cached for four Target Durations. Successful responses to non-blocking - # Playlist requests should be cached for half the Target Duration. - # Unsuccessful responses to non-blocking Playlist requests should be - # cached for for one Target Duration. - headers={ - "Cache-Control": f"max-age={(4 if blocking else 1)*target_duration:.0f}" - }, ) @staticmethod @@ -233,9 +223,6 @@ class HlsPlaylistView(StreamView): return web.Response( body=None, status=HTTPStatus.NOT_FOUND, - headers={ - "Cache-Control": f"max-age={(4 if blocking else 1)*target_duration:.0f}" - }, ) async def handle( @@ -318,7 +305,6 @@ class HlsPlaylistView(StreamView): body=self.render(track).encode("utf-8"), headers={ "Content-Type": FORMAT_CONTENT_TYPE[HLS_PROVIDER], - "Cache-Control": f"max-age={(6 if blocking_request else 0.5)*track.target_duration:.0f}", }, ) response.enable_compression(web.ContentCoding.gzip) @@ -373,22 +359,16 @@ class HlsPartView(StreamView): return web.Response( body=None, status=HTTPStatus.NOT_FOUND, - headers={"Cache-Control": f"max-age={track.target_duration:.0f}"}, ) # If the part is ready or has been hinted, if int(part_num) == len(segment.parts): await track.part_recv(timeout=track.stream_settings.hls_part_timeout) if int(part_num) >= len(segment.parts): - return web.HTTPRequestRangeNotSatisfiable( - headers={ - "Cache-Control": f"max-age={track.target_duration:.0f}", - } - ) + return web.HTTPRequestRangeNotSatisfiable() return web.Response( body=segment.parts[int(part_num)].data, headers={ "Content-Type": "video/iso.segment", - "Cache-Control": f"max-age={6*track.target_duration:.0f}", }, ) @@ -421,12 +401,10 @@ class HlsSegmentView(StreamView): return web.Response( body=None, status=HTTPStatus.NOT_FOUND, - headers={"Cache-Control": f"max-age={track.target_duration:.0f}"}, ) return web.Response( body=segment.get_data(), headers={ "Content-Type": "video/iso.segment", - "Cache-Control": f"max-age={6*track.target_duration:.0f}", }, ) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index bde5ab0fb05..f8d12c1cb44 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -452,6 +452,10 @@ def stream_worker( ) -> None: """Handle consuming streams.""" + if av.library_versions["libavformat"][0] >= 59 and "stimeout" in options: + # the stimeout option was renamed to timeout as of ffmpeg 5.0 + options["timeout"] = options["stimeout"] + del options["stimeout"] try: container = av.open(source, options=options, timeout=SOURCE_TIMEOUT) except av.AVError as err: diff --git a/homeassistant/components/subaru/translations/es.json b/homeassistant/components/subaru/translations/es.json index 42a13a37a7a..170983e8df4 100644 --- a/homeassistant/components/subaru/translations/es.json +++ b/homeassistant/components/subaru/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "cannot_connect": "No se pudo conectar" }, "error": { @@ -18,11 +18,25 @@ "description": "Por favor, introduzca su PIN de MySubaru\nNOTA: Todos los veh\u00edculos de la cuenta deben tener el mismo PIN", "title": "Configuraci\u00f3n de Subaru Starlink" }, + "two_factor": { + "data": { + "contact_method": "Selecciona un m\u00e9todo de contacto:" + }, + "description": "Autenticaci\u00f3n de dos factores requerida", + "title": "Configuraci\u00f3n de Subaru Starlink" + }, + "two_factor_validate": { + "data": { + "validation_code": "C\u00f3digo de validaci\u00f3n" + }, + "description": "Introduce el c\u00f3digo de validaci\u00f3n recibido", + "title": "Configuraci\u00f3n de Subaru Starlink" + }, "user": { "data": { "country": "Seleccionar pa\u00eds", "password": "Contrase\u00f1a", - "username": "Nombre de usuario" + "username": "Usuario" }, "description": "Por favor, introduzca sus credenciales de MySubaru\nNOTA: La configuraci\u00f3n inicial puede tardar hasta 30 segundos", "title": "Configuraci\u00f3n de Subaru Starlink" diff --git a/homeassistant/components/subaru/translations/id.json b/homeassistant/components/subaru/translations/id.json index cf0d37a0c30..35395be56b5 100644 --- a/homeassistant/components/subaru/translations/id.json +++ b/homeassistant/components/subaru/translations/id.json @@ -11,7 +11,7 @@ "incorrect_pin": "PIN salah", "incorrect_validation_code": "Kode validasi salah", "invalid_auth": "Autentikasi tidak valid", - "two_factor_request_failed": "Permintaan kode 2FA gagal, silakan coba lagi" + "two_factor_request_failed": "Permintaan kode 2FA gagal, coba lagi" }, "step": { "pin": { diff --git a/homeassistant/components/sun/translations/ko.json b/homeassistant/components/sun/translations/ko.json index d9d6f6ff081..f918b879acf 100644 --- a/homeassistant/components/sun/translations/ko.json +++ b/homeassistant/components/sun/translations/ko.json @@ -1,4 +1,11 @@ { + "config": { + "step": { + "user": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + }, "state": { "_": { "above_horizon": "\uc8fc\uac04", diff --git a/homeassistant/components/sun/translations/nl.json b/homeassistant/components/sun/translations/nl.json index 8c284e4e43d..57d17476331 100644 --- a/homeassistant/components/sun/translations/nl.json +++ b/homeassistant/components/sun/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "user": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } }, diff --git a/homeassistant/components/sun/trigger.py b/homeassistant/components/sun/trigger.py index 266df1f6a3b..75f5b36f8f5 100644 --- a/homeassistant/components/sun/trigger.py +++ b/homeassistant/components/sun/trigger.py @@ -3,15 +3,20 @@ from datetime import timedelta import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import ( CONF_EVENT, CONF_OFFSET, CONF_PLATFORM, SUN_EVENT_SUNRISE, ) -from homeassistant.core import HassJob, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_sunrise, async_track_sunset +from homeassistant.helpers.typing import ConfigType # mypy: allow-untyped-defs, no-check-untyped-defs @@ -24,7 +29,12 @@ TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( ) -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for events based on configuration.""" trigger_data = automation_info["trigger_data"] event = config.get(CONF_EVENT) diff --git a/homeassistant/components/surepetcare/translations/es.json b/homeassistant/components/surepetcare/translations/es.json index 3d3945748cb..13f2eb38bef 100644 --- a/homeassistant/components/surepetcare/translations/es.json +++ b/homeassistant/components/surepetcare/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada" + "already_configured": "La cuenta ya est\u00e1 configurada" }, "error": { "cannot_connect": "No se pudo conectar", diff --git a/homeassistant/components/surepetcare/translations/sk.json b/homeassistant/components/surepetcare/translations/sk.json index 5ada995aa6e..1b1e671c054 100644 --- a/homeassistant/components/surepetcare/translations/sk.json +++ b/homeassistant/components/surepetcare/translations/sk.json @@ -2,6 +2,13 @@ "config": { "error": { "invalid_auth": "Neplatn\u00e9 overenie" + }, + "step": { + "user": { + "data": { + "password": "Heslo" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/switch/device_action.py b/homeassistant/components/switch/device_action.py index 6947656406b..1aed2fa2467 100644 --- a/homeassistant/components/switch/device_action.py +++ b/homeassistant/components/switch/device_action.py @@ -19,7 +19,7 @@ async def async_call_action_from_config( hass: HomeAssistant, config: ConfigType, variables: TemplateVarsType, - context: Context, + context: Context | None, ) -> None: """Change state based on configuration.""" await toggle_entity.async_call_action_from_config( diff --git a/homeassistant/components/switch/device_trigger.py b/homeassistant/components/switch/device_trigger.py index 533ee8bd54d..9f56d7a09d2 100644 --- a/homeassistant/components/switch/device_trigger.py +++ b/homeassistant/components/switch/device_trigger.py @@ -1,8 +1,6 @@ """Provides device triggers for switches.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -36,7 +34,7 @@ async def async_attach_trigger( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/switch/translations/ca.json b/homeassistant/components/switch/translations/ca.json index 49667b9f60a..a029be79d38 100644 --- a/homeassistant/components/switch/translations/ca.json +++ b/homeassistant/components/switch/translations/ca.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Entitat d'interruptor" - }, - "description": "Selecciona l'interruptor del llum." - } - } - }, "device_automation": { "action_type": { "toggle": "Commuta {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} s'enc\u00e9n o s'apaga", - "toggled": "{entity_name} s'activa o es desactiva", "turned_off": "{entity_name} desactivat", "turned_on": "{entity_name} activat" } diff --git a/homeassistant/components/switch/translations/cs.json b/homeassistant/components/switch/translations/cs.json index a7b7f35033e..b82eaab9e4f 100644 --- a/homeassistant/components/switch/translations/cs.json +++ b/homeassistant/components/switch/translations/cs.json @@ -1,14 +1,4 @@ { - "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}", @@ -20,7 +10,6 @@ "is_on": "{entity_name} je zapnuto" }, "trigger_type": { - "toggled": "{entity_name} zapnuto nebo vypnuto", "turned_off": "{entity_name} bylo vypnuto", "turned_on": "{entity_name} bylo zapnuto" } diff --git a/homeassistant/components/switch/translations/de.json b/homeassistant/components/switch/translations/de.json index 12784fab206..886426619dc 100644 --- a/homeassistant/components/switch/translations/de.json +++ b/homeassistant/components/switch/translations/de.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Switch-Entit\u00e4t" - }, - "description": "W\u00e4hle den Schalter f\u00fcr den Lichtschalter aus." - } - } - }, "device_automation": { "action_type": { "toggle": "{entity_name} umschalten", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} ein- oder ausgeschaltet", - "toggled": "{entity_name} ein- oder ausgeschaltet", "turned_off": "{entity_name} ausgeschaltet", "turned_on": "{entity_name} eingeschaltet" } diff --git a/homeassistant/components/switch/translations/el.json b/homeassistant/components/switch/translations/el.json index 986ee7cff92..280ddddcfed 100644 --- a/homeassistant/components/switch/translations/el.json +++ b/homeassistant/components/switch/translations/el.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7" - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7 \u03c6\u03ce\u03c4\u03c9\u03bd." - } - } - }, "device_automation": { "action_type": { "toggle": "\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "\u03a4\u03bf {entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", - "toggled": "To {entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_off": "{entity_name} \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5", "turned_on": "{entity_name} \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03b8\u03b7\u03ba\u03b5" } diff --git a/homeassistant/components/switch/translations/en.json b/homeassistant/components/switch/translations/en.json index ae04ed42520..1e4fe8e4ad8 100644 --- a/homeassistant/components/switch/translations/en.json +++ b/homeassistant/components/switch/translations/en.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Switch entity" - }, - "description": "Select the switch for the light switch." - } - } - }, "device_automation": { "action_type": { "toggle": "Toggle {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} turned on or off", - "toggled": "{entity_name} turned on or off", "turned_off": "{entity_name} turned off", "turned_on": "{entity_name} turned on" } diff --git a/homeassistant/components/switch/translations/es.json b/homeassistant/components/switch/translations/es.json index 95a60ab55ea..e6190f32c8b 100644 --- a/homeassistant/components/switch/translations/es.json +++ b/homeassistant/components/switch/translations/es.json @@ -11,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} activado o desactivado", - "toggled": "{entity_name} activado o desactivado", "turned_off": "{entity_name} apagado", "turned_on": "{entity_name} encendido" } diff --git a/homeassistant/components/switch/translations/et.json b/homeassistant/components/switch/translations/et.json index e9c9928c89d..aaf1da62b5c 100644 --- a/homeassistant/components/switch/translations/et.json +++ b/homeassistant/components/switch/translations/et.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "L\u00fcliti olem" - }, - "description": "Vali valgusti l\u00fcliti" - } - } - }, "device_automation": { "action_type": { "toggle": "Muuda {entity_name} olekut", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} l\u00fclitus sisse v\u00f5i v\u00e4lja", - "toggled": "{entity_name} l\u00fclitus sisse v\u00f5i v\u00e4lja", "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", "turned_on": "{entity_name} l\u00fclitus sisse" } diff --git a/homeassistant/components/switch/translations/fr.json b/homeassistant/components/switch/translations/fr.json index ec357e10c72..bbe40ea7732 100644 --- a/homeassistant/components/switch/translations/fr.json +++ b/homeassistant/components/switch/translations/fr.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Entit\u00e9 du commutateur" - }, - "description": "S\u00e9lectionnez le commutateur correspondant \u00e0 l'interrupteur d'\u00e9clairage." - } - } - }, "device_automation": { "action_type": { "toggle": "Basculer {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} a \u00e9t\u00e9 activ\u00e9 ou d\u00e9sactiv\u00e9", - "toggled": "{entity_name} a \u00e9t\u00e9 activ\u00e9 ou d\u00e9sactiv\u00e9", "turned_off": "{entity_name} a \u00e9t\u00e9 d\u00e9sactiv\u00e9", "turned_on": "{entity_name} a \u00e9t\u00e9 activ\u00e9" } diff --git a/homeassistant/components/switch/translations/he.json b/homeassistant/components/switch/translations/he.json index c3d76bb6f9f..991f45744ca 100644 --- a/homeassistant/components/switch/translations/he.json +++ b/homeassistant/components/switch/translations/he.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "\u05d9\u05e9\u05d5\u05ea \u05de\u05ea\u05d2" - }, - "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05de\u05ea\u05d2 \u05e2\u05d1\u05d5\u05e8 \u05de\u05ea\u05d2 \u05d4\u05d0\u05d5\u05e8." - } - } - }, "device_automation": { "action_type": { "toggle": "\u05d4\u05d7\u05dc\u05e4\u05ea \u05de\u05e6\u05d1 {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05db\u05d5\u05d1\u05d4", - "toggled": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05db\u05d5\u05d1\u05d4", "turned_off": "{entity_name} \u05db\u05d5\u05d1\u05d4", "turned_on": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc" } diff --git a/homeassistant/components/switch/translations/hu.json b/homeassistant/components/switch/translations/hu.json index 2dff00040ca..86d77afaa43 100644 --- a/homeassistant/components/switch/translations/hu.json +++ b/homeassistant/components/switch/translations/hu.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Kapcsol\u00f3 entit\u00e1s" - }, - "description": "V\u00e1lassza ki a kapcsol\u00f3t a vil\u00e1g\u00edt\u00e1shoz." - } - } - }, "device_automation": { "action_type": { "toggle": "{entity_name} kapcsol\u00e1sa", @@ -21,7 +11,6 @@ }, "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/id.json b/homeassistant/components/switch/translations/id.json index c49cab13a9c..589985509d0 100644 --- a/homeassistant/components/switch/translations/id.json +++ b/homeassistant/components/switch/translations/id.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Entitas saklar" - }, - "description": "Pilih saklar mana untuk saklar lampu." - } - } - }, "device_automation": { "action_type": { "toggle": "Nyala/matikan {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} diaktifkan atau dinonaktifkan", - "toggled": "{entity_name} diaktifkan atau dinonaktifkan", "turned_off": "{entity_name} dimatikan", "turned_on": "{entity_name} dinyalakan" } diff --git a/homeassistant/components/switch/translations/it.json b/homeassistant/components/switch/translations/it.json index b71a46e48be..6168b6bdf4e 100644 --- a/homeassistant/components/switch/translations/it.json +++ b/homeassistant/components/switch/translations/it.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Entit\u00e0 dell'interruttore" - }, - "description": "Seleziona l'interruttore per l'interruttore della luce." - } - } - }, "device_automation": { "action_type": { "toggle": "Attiva/Disattiva {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} attivata o disattivata", - "toggled": "{entity_name} attiva o disattiva", "turned_off": "{entity_name} disattivato", "turned_on": "{entity_name} attivato" } diff --git a/homeassistant/components/switch/translations/ja.json b/homeassistant/components/switch/translations/ja.json index d4e335257ad..071f47dbd58 100644 --- a/homeassistant/components/switch/translations/ja.json +++ b/homeassistant/components/switch/translations/ja.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "\u30b9\u30a4\u30c3\u30c1\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3" - }, - "description": "\u7167\u660e\u30b9\u30a4\u30c3\u30c1\u306e\u30b9\u30a4\u30c3\u30c1\u3092\u9078\u629e\u3057\u307e\u3059\u3002" - } - } - }, "device_automation": { "action_type": { "toggle": "\u30c8\u30b0\u30eb {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u304c\u30aa\u30f3\u307e\u305f\u306f\u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", - "toggled": "{entity_name} \u304c\u30aa\u30f3\u307e\u305f\u306f\u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "turned_off": "{entity_name} \u30aa\u30d5\u306b\u306a\u308a\u307e\u3057\u305f", "turned_on": "{entity_name} \u30aa\u30f3\u306b\u306a\u3063\u3066\u3044\u307e\u3059" } diff --git a/homeassistant/components/switch/translations/nl.json b/homeassistant/components/switch/translations/nl.json index 87636c8a1f1..28398d9d8a6 100644 --- a/homeassistant/components/switch/translations/nl.json +++ b/homeassistant/components/switch/translations/nl.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Entiteit wijzigen" - }, - "description": "Kies de schakelaar voor de lichtschakelaar." - } - } - }, "device_automation": { "action_type": { "toggle": "Omschakelen {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} in- of uitgeschakeld", - "toggled": "{entity_name} in-of uitgeschakeld", "turned_off": "{entity_name} uitgeschakeld", "turned_on": "{entity_name} ingeschakeld" } diff --git a/homeassistant/components/switch/translations/no.json b/homeassistant/components/switch/translations/no.json index 802df4e6a42..964723c68b6 100644 --- a/homeassistant/components/switch/translations/no.json +++ b/homeassistant/components/switch/translations/no.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Bytt enhet" - }, - "description": "Velg bryteren for lysbryteren." - } - } - }, "device_automation": { "action_type": { "toggle": "Veksle {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} sl\u00e5tt p\u00e5 eller av", - "toggled": "{entity_name} sl\u00e5tt p\u00e5 eller av", "turned_off": "{entity_name} sl\u00e5tt av", "turned_on": "{entity_name} sl\u00e5tt p\u00e5" } diff --git a/homeassistant/components/switch/translations/pl.json b/homeassistant/components/switch/translations/pl.json index 51ecfc44c5d..3d87d8572df 100644 --- a/homeassistant/components/switch/translations/pl.json +++ b/homeassistant/components/switch/translations/pl.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Encja prze\u0142\u0105cznika" - }, - "description": "Wybierz prze\u0142\u0105cznik do w\u0142\u0105cznika \u015bwiat\u0142a." - } - } - }, "device_automation": { "action_type": { "toggle": "prze\u0142\u0105cz {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", - "toggled": "{entity_name} zostanie w\u0142\u0105czony lub wy\u0142\u0105czony", "turned_off": "nast\u0105pi wy\u0142\u0105czenie {entity_name}", "turned_on": "nast\u0105pi w\u0142\u0105czenie {entity_name}" } diff --git a/homeassistant/components/switch/translations/pt-BR.json b/homeassistant/components/switch/translations/pt-BR.json index 0d24cd74bfe..d2c2c3be5a0 100644 --- a/homeassistant/components/switch/translations/pt-BR.json +++ b/homeassistant/components/switch/translations/pt-BR.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Entidade de interruptor" - }, - "description": "Selecione o interruptor para a l\u00e2mpada." - } - } - }, "device_automation": { "action_type": { "toggle": "Alternar {entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} ligado ou desligado", - "toggled": "{entity_name} ligado ou desligado", "turned_off": "{entity_name} desligado", "turned_on": "{entity_name} ligado" } diff --git a/homeassistant/components/switch/translations/ru.json b/homeassistant/components/switch/translations/ru.json index 2602a1ac20b..a1483e071e8 100644 --- a/homeassistant/components/switch/translations/ru.json +++ b/homeassistant/components/switch/translations/ru.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "\u0412\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c" - }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0435\u0433\u043e \u043a\u0430\u043a \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u044f." - } - } - }, "device_automation": { "action_type": { "toggle": "{entity_name}: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", - "toggled": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "turned_off": "{entity_name} \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", "turned_on": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f" } diff --git a/homeassistant/components/switch/translations/sv.json b/homeassistant/components/switch/translations/sv.json index 2db2a2718e0..a2cd74434eb 100644 --- a/homeassistant/components/switch/translations/sv.json +++ b/homeassistant/components/switch/translations/sv.json @@ -10,7 +10,6 @@ "is_on": "{entity_name} \u00e4r p\u00e5" }, "trigger_type": { - "toggled": "{entity_name} slogs p\u00e5 eller av", "turned_off": "{entity_name} st\u00e4ngdes av", "turned_on": "{entity_name} slogs p\u00e5" } diff --git a/homeassistant/components/switch/translations/tr.json b/homeassistant/components/switch/translations/tr.json index 2a98d2e972f..8387983017f 100644 --- a/homeassistant/components/switch/translations/tr.json +++ b/homeassistant/components/switch/translations/tr.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "Varl\u0131\u011f\u0131 de\u011fi\u015ftir" - }, - "description": "I\u015f\u0131k anahtar\u0131 i\u00e7in anahtar\u0131 se\u00e7in." - } - } - }, "device_automation": { "action_type": { "toggle": "{entity_name} de\u011fi\u015ftir", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name} a\u00e7\u0131ld\u0131 veya kapat\u0131ld\u0131", - "toggled": "{entity_name} a\u00e7\u0131ld\u0131 veya kapat\u0131ld\u0131", "turned_off": "{entity_name} kapat\u0131ld\u0131", "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" } diff --git a/homeassistant/components/switch/translations/zh-Hans.json b/homeassistant/components/switch/translations/zh-Hans.json index 822ca2795df..afe9f58db8f 100644 --- a/homeassistant/components/switch/translations/zh-Hans.json +++ b/homeassistant/components/switch/translations/zh-Hans.json @@ -10,7 +10,6 @@ "is_on": "{entity_name} \u5df2\u6253\u5f00" }, "trigger_type": { - "toggled": "{entity_name} \u88ab\u6253\u5f00\u6216\u5173\u95ed", "turned_off": "{entity_name} \u88ab\u5173\u95ed", "turned_on": "{entity_name} \u88ab\u6253\u5f00" } diff --git a/homeassistant/components/switch/translations/zh-Hant.json b/homeassistant/components/switch/translations/zh-Hant.json index 631db326ae0..a1f38544f67 100644 --- a/homeassistant/components/switch/translations/zh-Hant.json +++ b/homeassistant/components/switch/translations/zh-Hant.json @@ -1,14 +1,4 @@ { - "config": { - "step": { - "init": { - "data": { - "entity_id": "\u958b\u95dc\u5be6\u9ad4" - }, - "description": "\u9078\u64c7\u6307\u5b9a\u70ba\u71c8\u5149\u4e4b\u958b\u95dc\u3002" - } - } - }, "device_automation": { "action_type": { "toggle": "\u5207\u63db{entity_name}", @@ -21,7 +11,6 @@ }, "trigger_type": { "changed_states": "{entity_name}\u5df2\u958b\u555f\u6216\u95dc\u9589", - "toggled": "{entity_name}\u5df2\u958b\u555f\u6216\u95dc\u9589", "turned_off": "{entity_name}\u5df2\u95dc\u9589", "turned_on": "{entity_name}\u5df2\u958b\u555f" } diff --git a/homeassistant/components/switch_as_x/translations/ca.json b/homeassistant/components/switch_as_x/translations/ca.json index f48a744066c..ea568634dc1 100644 --- a/homeassistant/components/switch_as_x/translations/ca.json +++ b/homeassistant/components/switch_as_x/translations/ca.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Entitat d'interruptor" - } - }, "user": { "data": { "entity_id": "Commutador", "target_domain": "Nou tipus" }, - "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" + "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." } } }, diff --git a/homeassistant/components/switch_as_x/translations/cs.json b/homeassistant/components/switch_as_x/translations/cs.json index 11f8471a62a..c521a79e1d9 100644 --- a/homeassistant/components/switch_as_x/translations/cs.json +++ b/homeassistant/components/switch_as_x/translations/cs.json @@ -1,16 +1,10 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Entita vyp\u00edna\u010de" - } - }, "user": { "data": { "target_domain": "Typ" - }, - "title": "Ud\u011blejte vyp\u00edna\u010dem ..." + } } } }, diff --git a/homeassistant/components/switch_as_x/translations/de.json b/homeassistant/components/switch_as_x/translations/de.json index 1125f4f5a8f..6278ddc9988 100644 --- a/homeassistant/components/switch_as_x/translations/de.json +++ b/homeassistant/components/switch_as_x/translations/de.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Switch-Entit\u00e4t" - } - }, "user": { "data": { "entity_id": "Schalter", "target_domain": "Neuer Typ" }, - "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" + "description": "W\u00e4hle einen Schalter, der im Home Assistant als Licht, Abdeckung oder sonstiges angezeigt werden soll. Der urspr\u00fcngliche Schalter wird ausgeblendet." } } }, diff --git a/homeassistant/components/switch_as_x/translations/el.json b/homeassistant/components/switch_as_x/translations/el.json index ed10f241927..cf5925abc35 100644 --- a/homeassistant/components/switch_as_x/translations/el.json +++ b/homeassistant/components/switch_as_x/translations/el.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7" - } - }, "user": { "data": { "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 ..." + "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." } } }, diff --git a/homeassistant/components/switch_as_x/translations/en.json b/homeassistant/components/switch_as_x/translations/en.json index 4253f0506ef..7709a27cf35 100644 --- a/homeassistant/components/switch_as_x/translations/en.json +++ b/homeassistant/components/switch_as_x/translations/en.json @@ -1,18 +1,12 @@ { "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.", - "title": "Change switch device 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." } } }, diff --git a/homeassistant/components/switch_as_x/translations/es.json b/homeassistant/components/switch_as_x/translations/es.json new file mode 100644 index 00000000000..7e91d3217a4 --- /dev/null +++ b/homeassistant/components/switch_as_x/translations/es.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_id": "Conmutador", + "target_domain": "Nuevo tipo" + } + } + } + }, + "title": "Cambia el tipo de dispositivo de un conmutador" +} \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/et.json b/homeassistant/components/switch_as_x/translations/et.json index 9d5a5839fbf..bb3ee78e75c 100644 --- a/homeassistant/components/switch_as_x/translations/et.json +++ b/homeassistant/components/switch_as_x/translations/et.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "L\u00fcliti olem" - } - }, "user": { "data": { "entity_id": "L\u00fcliti", "target_domain": "Uus t\u00fc\u00fcp" }, - "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" + "description": "Vali l\u00fcliti mida soovid Home Assistantis valgustina, avakattena v\u00f5i millegi muuna kuvada. Algne l\u00fcliti peidetakse." } } }, diff --git a/homeassistant/components/switch_as_x/translations/fr.json b/homeassistant/components/switch_as_x/translations/fr.json index fa14bd80885..484881e222a 100644 --- a/homeassistant/components/switch_as_x/translations/fr.json +++ b/homeassistant/components/switch_as_x/translations/fr.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Entit\u00e9 du commutateur" - } - }, "user": { "data": { "entity_id": "Interrupteur", "target_domain": "Nouveau type" }, - "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" + "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." } } }, diff --git a/homeassistant/components/switch_as_x/translations/he.json b/homeassistant/components/switch_as_x/translations/he.json deleted file mode 100644 index 39889dab709..00000000000 --- a/homeassistant/components/switch_as_x/translations/he.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "config": { - "step": { - "config": { - "user": { - "entity_id": "\u05d9\u05e9\u05d5\u05ea \u05de\u05ea\u05d2" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/hu.json b/homeassistant/components/switch_as_x/translations/hu.json index 0d919f5ecd3..9f3d49ec348 100644 --- a/homeassistant/components/switch_as_x/translations/hu.json +++ b/homeassistant/components/switch_as_x/translations/hu.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Kapcsol\u00f3 entit\u00e1s" - } - }, "user": { "data": { "entity_id": "Kapcsol\u00f3", "target_domain": "\u00daj t\u00edpus" }, - "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" + "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." } } }, diff --git a/homeassistant/components/switch_as_x/translations/id.json b/homeassistant/components/switch_as_x/translations/id.json index 47d31e26c03..e8e86db884f 100644 --- a/homeassistant/components/switch_as_x/translations/id.json +++ b/homeassistant/components/switch_as_x/translations/id.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Entitas saklar" - } - }, "user": { "data": { "entity_id": "Sakelar", "target_domain": "Tipe Baru" }, - "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" + "description": "Pilih sakelar yang ingin Anda tampilkan di Home Assistant sebagai lampu, penutup, atau apa pun. Sakelar asli akan disembunyikan." } } }, diff --git a/homeassistant/components/switch_as_x/translations/it.json b/homeassistant/components/switch_as_x/translations/it.json index 4706b4d9ece..9d7f3c798bf 100644 --- a/homeassistant/components/switch_as_x/translations/it.json +++ b/homeassistant/components/switch_as_x/translations/it.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Cambia entit\u00e0" - } - }, "user": { "data": { "entity_id": "Interruttore", "target_domain": "Nuovo tipo" }, - "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" + "description": "Scegli un interruttore che vuoi mostrare in Home Assistant come luce, copertura o qualsiasi altra cosa. L'interruttore originale sar\u00e0 nascosto." } } }, diff --git a/homeassistant/components/switch_as_x/translations/ja.json b/homeassistant/components/switch_as_x/translations/ja.json index 434e1b588d5..41af80fa2b8 100644 --- a/homeassistant/components/switch_as_x/translations/ja.json +++ b/homeassistant/components/switch_as_x/translations/ja.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "\u30b9\u30a4\u30c3\u30c1\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3" - } - }, "user": { "data": { "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..." + "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" } } }, diff --git a/homeassistant/components/switch_as_x/translations/ko.json b/homeassistant/components/switch_as_x/translations/ko.json new file mode 100644 index 00000000000..bd0b909895d --- /dev/null +++ b/homeassistant/components/switch_as_x/translations/ko.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_id": "\uc2a4\uc704\uce58", + "target_domain": "\uc0c8\ub85c\uc6b4 \uc720\ud615" + }, + "description": "Home Assistant\uc5d0\uc11c \uc870\uba85, \ucee4\ubc84 \ub610\ub294 \ub2e4\ub978 \uac83\uc73c\ub85c \ud45c\uc2dc\ud558\ub824\ub294 \uc2a4\uc704\uce58\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624. \uc6d0\ub798 \uc2a4\uc704\uce58\ub294 \uc228\uaca8\uc9d1\ub2c8\ub2e4." + } + } + }, + "title": "\uc2a4\uc704\uce58\uc758 \uc7a5\uce58 \uc720\ud615 \ubcc0\uacbd" +} \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/nl.json b/homeassistant/components/switch_as_x/translations/nl.json index 4fcea818b9a..2a6043fbe75 100644 --- a/homeassistant/components/switch_as_x/translations/nl.json +++ b/homeassistant/components/switch_as_x/translations/nl.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Entiteit wijzigen" - } - }, "user": { "data": { "entity_id": "Schakelaar", "target_domain": "Nieuw type" }, - "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" + "description": "Kies een schakelaar die u in Home Assistant wilt laten verschijnen als licht, klep of iets anders. De oorspronkelijke schakelaar wordt verborgen." } } }, diff --git a/homeassistant/components/switch_as_x/translations/no.json b/homeassistant/components/switch_as_x/translations/no.json index 5958c87f201..6c72f96873e 100644 --- a/homeassistant/components/switch_as_x/translations/no.json +++ b/homeassistant/components/switch_as_x/translations/no.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Bytt enhet" - } - }, "user": { "data": { "entity_id": "Bryter", "target_domain": "Ny type" }, - "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" + "description": "Velg en bryter du vil vise i Home Assistant som lys, deksel eller noe annet. Den opprinnelige bryteren vil v\u00e6re skjult." } } }, diff --git a/homeassistant/components/switch_as_x/translations/pl.json b/homeassistant/components/switch_as_x/translations/pl.json index 7491ef0d7fc..92e642e746e 100644 --- a/homeassistant/components/switch_as_x/translations/pl.json +++ b/homeassistant/components/switch_as_x/translations/pl.json @@ -1,20 +1,14 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Encja prze\u0142\u0105cznika" - } - }, "user": { "data": { "entity_id": "Prze\u0142\u0105cznik", "target_domain": "Nowy rodzaj" }, - "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" + "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 w prze\u0142\u0105czniku" + "title": "Zmiana typu urz\u0105dzenia prze\u0142\u0105cznika" } \ 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 bf8bc276781..436a99a17f9 100644 --- a/homeassistant/components/switch_as_x/translations/pt-BR.json +++ b/homeassistant/components/switch_as_x/translations/pt-BR.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Entidade de interruptor" - } - }, "user": { "data": { "entity_id": "Interruptor", "target_domain": "Novo tipo" }, - "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" + "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." } } }, diff --git a/homeassistant/components/switch_as_x/translations/ru.json b/homeassistant/components/switch_as_x/translations/ru.json index 3074365dd76..731a5c31c3c 100644 --- a/homeassistant/components/switch_as_x/translations/ru.json +++ b/homeassistant/components/switch_as_x/translations/ru.json @@ -1,18 +1,12 @@ { "config": { "step": { - "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": "\u041d\u043e\u0432\u044b\u0439 \u0442\u0438\u043f" }, - "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" + "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." } } }, diff --git a/homeassistant/components/switch_as_x/translations/sv.json b/homeassistant/components/switch_as_x/translations/sv.json index 96419c39bf1..d21eefa5a1a 100644 --- a/homeassistant/components/switch_as_x/translations/sv.json +++ b/homeassistant/components/switch_as_x/translations/sv.json @@ -1,16 +1,10 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "Kontakt-entitet" - } - }, "user": { "data": { "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 index b793be6baf0..39fd95784a4 100644 --- a/homeassistant/components/switch_as_x/translations/tr.json +++ b/homeassistant/components/switch_as_x/translations/tr.json @@ -1,18 +1,12 @@ { "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" + "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." } } }, diff --git a/homeassistant/components/switch_as_x/translations/zh-Hans.json b/homeassistant/components/switch_as_x/translations/zh-Hans.json index e765a436849..244fab0c4e9 100644 --- a/homeassistant/components/switch_as_x/translations/zh-Hans.json +++ b/homeassistant/components/switch_as_x/translations/zh-Hans.json @@ -1,18 +1,12 @@ { "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" + "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" } } }, diff --git a/homeassistant/components/switch_as_x/translations/zh-Hant.json b/homeassistant/components/switch_as_x/translations/zh-Hant.json index bd6a1e15ba0..17813905e2a 100644 --- a/homeassistant/components/switch_as_x/translations/zh-Hant.json +++ b/homeassistant/components/switch_as_x/translations/zh-Hant.json @@ -1,18 +1,12 @@ { "config": { "step": { - "config": { - "user": { - "entity_id": "\u958b\u95dc\u5be6\u9ad4" - } - }, "user": { "data": { "entity_id": "\u958b\u95dc", "target_domain": "\u65b0\u589e\u985e\u5225" }, - "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" + "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" } } }, diff --git a/homeassistant/components/switchbot/translations/bg.json b/homeassistant/components/switchbot/translations/bg.json index 5a5b74b2318..05b3a4459bb 100644 --- a/homeassistant/components/switchbot/translations/bg.json +++ b/homeassistant/components/switchbot/translations/bg.json @@ -6,9 +6,6 @@ "no_unconfigured_devices": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u043d\u0435\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, - "error": { - "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/ca.json b/homeassistant/components/switchbot/translations/ca.json index 6409efcbab7..576aeda3b16 100644 --- a/homeassistant/components/switchbot/translations/ca.json +++ b/homeassistant/components/switchbot/translations/ca.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "Tipus de Switchbot no compatible.", "unknown": "Error inesperat" }, - "error": { - "cannot_connect": "Ha fallat la connexi\u00f3" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/cs.json b/homeassistant/components/switchbot/translations/cs.json index 6f16b6faac5..f92951a0e15 100644 --- a/homeassistant/components/switchbot/translations/cs.json +++ b/homeassistant/components/switchbot/translations/cs.json @@ -5,9 +5,6 @@ "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, - "error": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/de.json b/homeassistant/components/switchbot/translations/de.json index f499712718e..439524c8aa6 100644 --- a/homeassistant/components/switchbot/translations/de.json +++ b/homeassistant/components/switchbot/translations/de.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "Nicht unterst\u00fctzter Switchbot-Typ.", "unknown": "Unerwarteter Fehler" }, - "error": { - "cannot_connect": "Verbindung fehlgeschlagen" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/el.json b/homeassistant/components/switchbot/translations/el.json index fe3b7448ac8..b191c90f53c 100644 --- a/homeassistant/components/switchbot/translations/el.json +++ b/homeassistant/components/switchbot/translations/el.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "\u039c\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03bf\u03c2 \u03c4\u03cd\u03c0\u03bf\u03c2 Switchbot.", "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" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/en.json b/homeassistant/components/switchbot/translations/en.json index 4ea3d21de65..1cfaee8750f 100644 --- a/homeassistant/components/switchbot/translations/en.json +++ b/homeassistant/components/switchbot/translations/en.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "Unsupported Switchbot Type.", "unknown": "Unexpected error" }, - "error": { - "cannot_connect": "Failed to connect" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/es.json b/homeassistant/components/switchbot/translations/es.json index 324c7200244..6fc4d59f693 100644 --- a/homeassistant/components/switchbot/translations/es.json +++ b/homeassistant/components/switchbot/translations/es.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "Tipo de Switchbot no compatible.", "unknown": "Error inesperado" }, - "error": { - "cannot_connect": "Fall\u00f3 al conectar" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/et.json b/homeassistant/components/switchbot/translations/et.json index cc746796195..358a4748724 100644 --- a/homeassistant/components/switchbot/translations/et.json +++ b/homeassistant/components/switchbot/translations/et.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "Toetamata Switchboti t\u00fc\u00fcp.", "unknown": "Ootamatu t\u00f5rge" }, - "error": { - "cannot_connect": "\u00dchendamine nurjus" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/fr.json b/homeassistant/components/switchbot/translations/fr.json index aca51b4e4c4..75eff0a9b7c 100644 --- a/homeassistant/components/switchbot/translations/fr.json +++ b/homeassistant/components/switchbot/translations/fr.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "Type Switchbot non pris en charge.", "unknown": "Erreur inattendue" }, - "error": { - "cannot_connect": "\u00c9chec de connexion" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/he.json b/homeassistant/components/switchbot/translations/he.json index 09f62069706..836cd8b06b4 100644 --- a/homeassistant/components/switchbot/translations/he.json +++ b/homeassistant/components/switchbot/translations/he.json @@ -5,9 +5,6 @@ "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" }, - "error": { - "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/hu.json b/homeassistant/components/switchbot/translations/hu.json index 2b80fbedbd8..b870e577426 100644 --- a/homeassistant/components/switchbot/translations/hu.json +++ b/homeassistant/components/switchbot/translations/hu.json @@ -8,7 +8,6 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s", "one": "\u00dcres", "other": "\u00dcres" }, diff --git a/homeassistant/components/switchbot/translations/id.json b/homeassistant/components/switchbot/translations/id.json index 4619f421811..f3a9cd169ef 100644 --- a/homeassistant/components/switchbot/translations/id.json +++ b/homeassistant/components/switchbot/translations/id.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "Jenis Switchbot yang tidak didukung.", "unknown": "Kesalahan yang tidak diharapkan" }, - "error": { - "cannot_connect": "Gagal terhubung" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/it.json b/homeassistant/components/switchbot/translations/it.json index f589046d4db..b8997f9247b 100644 --- a/homeassistant/components/switchbot/translations/it.json +++ b/homeassistant/components/switchbot/translations/it.json @@ -8,7 +8,6 @@ "unknown": "Errore imprevisto" }, "error": { - "cannot_connect": "Impossibile connettersi", "one": "Vuoto", "other": "Vuoti" }, diff --git a/homeassistant/components/switchbot/translations/ja.json b/homeassistant/components/switchbot/translations/ja.json index 41fb320428f..91d87431774 100644 --- a/homeassistant/components/switchbot/translations/ja.json +++ b/homeassistant/components/switchbot/translations/ja.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u306a\u3044\u7a2e\u985e\u306eSwitchbot", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, - "error": { - "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/nl.json b/homeassistant/components/switchbot/translations/nl.json index fb1e55f6b9d..becb7173194 100644 --- a/homeassistant/components/switchbot/translations/nl.json +++ b/homeassistant/components/switchbot/translations/nl.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "Niet-ondersteund Switchbot-type.", "unknown": "Onverwachte fout" }, - "error": { - "cannot_connect": "Kan geen verbinding maken" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/no.json b/homeassistant/components/switchbot/translations/no.json index 4d8cb95061a..1d7836f6776 100644 --- a/homeassistant/components/switchbot/translations/no.json +++ b/homeassistant/components/switchbot/translations/no.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "Switchbot-type st\u00f8ttes ikke.", "unknown": "Uventet feil" }, - "error": { - "cannot_connect": "Tilkobling mislyktes" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/pl.json b/homeassistant/components/switchbot/translations/pl.json index 15e64b86e5a..156ddbb9924 100644 --- a/homeassistant/components/switchbot/translations/pl.json +++ b/homeassistant/components/switchbot/translations/pl.json @@ -8,7 +8,6 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "few": "Puste", "many": "Pustych", "one": "Pusty", diff --git a/homeassistant/components/switchbot/translations/pt-BR.json b/homeassistant/components/switchbot/translations/pt-BR.json index bf9cb746dcb..3959425cbd3 100644 --- a/homeassistant/components/switchbot/translations/pt-BR.json +++ b/homeassistant/components/switchbot/translations/pt-BR.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "Tipo de Switchbot sem suporte.", "unknown": "Erro inesperado" }, - "error": { - "cannot_connect": "Falha ao conectar" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/ru.json b/homeassistant/components/switchbot/translations/ru.json index 5eaa1cdbc4f..9ca076ff499 100644 --- a/homeassistant/components/switchbot/translations/ru.json +++ b/homeassistant/components/switchbot/translations/ru.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "\u041d\u0435\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0439 \u0442\u0438\u043f Switchbot.", "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." - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switchbot/translations/tr.json b/homeassistant/components/switchbot/translations/tr.json index 77a2921fa38..40b80dc4a3c 100644 --- a/homeassistant/components/switchbot/translations/tr.json +++ b/homeassistant/components/switchbot/translations/tr.json @@ -8,7 +8,6 @@ "unknown": "Beklenmeyen hata" }, "error": { - "cannot_connect": "Ba\u011flanma hatas\u0131", "one": "Bo\u015f", "other": "Bo\u015f" }, diff --git a/homeassistant/components/switchbot/translations/zh-Hant.json b/homeassistant/components/switchbot/translations/zh-Hant.json index 8e7b4495328..617129167ed 100644 --- a/homeassistant/components/switchbot/translations/zh-Hant.json +++ b/homeassistant/components/switchbot/translations/zh-Hant.json @@ -7,9 +7,6 @@ "switchbot_unsupported_type": "\u4e0d\u652f\u6301\u7684 Switchbot \u985e\u5225\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, - "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557" - }, "flow_title": "{name}", "step": { "user": { diff --git a/homeassistant/components/switcher_kis/translations/ko.json b/homeassistant/components/switcher_kis/translations/ko.json new file mode 100644 index 00000000000..20ad990e862 --- /dev/null +++ b/homeassistant/components/switcher_kis/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/switcher_kis/translations/nl.json b/homeassistant/components/switcher_kis/translations/nl.json index 0671f0b3674..6fc4a03e824 100644 --- a/homeassistant/components/switcher_kis/translations/nl.json +++ b/homeassistant/components/switcher_kis/translations/nl.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/syncthing/translations/nl.json b/homeassistant/components/syncthing/translations/nl.json index 4f66222ada8..dd941e4bc75 100644 --- a/homeassistant/components/syncthing/translations/nl.json +++ b/homeassistant/components/syncthing/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/syncthru/translations/es.json b/homeassistant/components/syncthru/translations/es.json index 513cbf17fc0..da3002496db 100644 --- a/homeassistant/components/syncthru/translations/es.json +++ b/homeassistant/components/syncthru/translations/es.json @@ -8,7 +8,7 @@ "syncthru_not_supported": "El dispositivo no es compatible con SyncThru", "unknown_state": "Estado de la impresora desconocido, verifica la URL y la conectividad de la red" }, - "flow_title": "Impresora Samsung SyncThru: {name}", + "flow_title": "{name}", "step": { "confirm": { "data": { diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 0881d5a85e9..ece38bf7326 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -22,12 +22,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_MAC, CONF_SCAN_INTERVAL, CONF_VERIFY_SSL from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from homeassistant.helpers import device_registry -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.device_registry import ( - DeviceEntry, - async_get_registry as get_dev_reg, -) +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -59,8 +54,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Synology DSM sensors.""" # Migrate device indentifiers - dev_reg = await get_dev_reg(hass) - devices: list[DeviceEntry] = device_registry.async_entries_for_config_entry( + dev_reg = dr.async_get(hass) + devices: list[dr.DeviceEntry] = dr.async_entries_for_config_entry( dev_reg, entry.entry_id ) for device in devices: diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index cab2536187c..0a6934b45a7 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -154,6 +154,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera): async def async_added_to_hass(self) -> None: """Subscribe to signal.""" self._listen_source_updates() + await super().async_added_to_hass() def camera_image( self, width: int | None = None, height: int | None = None diff --git a/homeassistant/components/synology_dsm/translations/bg.json b/homeassistant/components/synology_dsm/translations/bg.json index acf35c4c2a4..a3a107a36e2 100644 --- a/homeassistant/components/synology_dsm/translations/bg.json +++ b/homeassistant/components/synology_dsm/translations/bg.json @@ -22,15 +22,7 @@ "port": "\u041f\u043e\u0440\u0442", "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" }, - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "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": "\u041f\u0440\u0438\u0447\u0438\u043d\u0430: {details}" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 {name} ({host})?" }, "reauth_confirm": { "data": { @@ -44,8 +36,7 @@ "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" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/ca.json b/homeassistant/components/synology_dsm/translations/ca.json index bad95d30836..110e1cabdae 100644 --- a/homeassistant/components/synology_dsm/translations/ca.json +++ b/homeassistant/components/synology_dsm/translations/ca.json @@ -28,16 +28,7 @@ "username": "Nom d'usuari", "verify_ssl": "Verifica el certificat SSL" }, - "description": "Vols configurar {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Contrasenya", - "username": "Nom d'usuari" - }, - "description": "Motiu: {details}", - "title": "Reautenticaci\u00f3 de la integraci\u00f3 Synology DSM" + "description": "Vols configurar {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Utilitza un certificat SSL", "username": "Nom d'usuari", "verify_ssl": "Verifica el certificat SSL" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/cs.json b/homeassistant/components/synology_dsm/translations/cs.json index ae77a93a790..dfa0a5fd347 100644 --- a/homeassistant/components/synology_dsm/translations/cs.json +++ b/homeassistant/components/synology_dsm/translations/cs.json @@ -26,15 +26,7 @@ "username": "U\u017eivatelsk\u00e9 jm\u00e9no", "verify_ssl": "Ov\u011b\u0159it certifik\u00e1t SSL" }, - "description": "Chcete nastavit {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Heslo", - "username": "U\u017eivatelsk\u00e9 jm\u00e9no" - }, - "title": "Synology DSM Znovu ov\u011b\u0159it integraci" + "description": "Chcete nastavit {name} ({host})?" }, "reauth_confirm": { "data": { @@ -50,8 +42,7 @@ "ssl": "Pou\u017e\u00edv\u00e1 SSL certifik\u00e1t", "username": "U\u017eivatelsk\u00e9 jm\u00e9no", "verify_ssl": "Ov\u011b\u0159it certifik\u00e1t SSL" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/de.json b/homeassistant/components/synology_dsm/translations/de.json index 6b945c7bda2..247eba408c9 100644 --- a/homeassistant/components/synology_dsm/translations/de.json +++ b/homeassistant/components/synology_dsm/translations/de.json @@ -28,16 +28,7 @@ "username": "Benutzername", "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" }, - "description": "M\u00f6chtest du {name} ({host}) einrichten?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Passwort", - "username": "Benutzername" - }, - "description": "Grund: {details}", - "title": "Synology DSM Integration erneut authentifizieren" + "description": "M\u00f6chtest du {name} ({host}) einrichten?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Verwendet ein SSL-Zertifikat", "username": "Benutzername", "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/el.json b/homeassistant/components/synology_dsm/translations/el.json index 1cf10eb6175..fc4e0124056 100644 --- a/homeassistant/components/synology_dsm/translations/el.json +++ b/homeassistant/components/synology_dsm/translations/el.json @@ -28,16 +28,7 @@ "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": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});", - "title": "Synology DSM" - }, - "reauth": { - "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": "\u0391\u03b9\u03c4\u03af\u03b1: {details}", - "title": "Synology DSM \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" + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ad\u03bd\u03b1 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL", "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" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/en.json b/homeassistant/components/synology_dsm/translations/en.json index ce3b8083965..267eb772e13 100644 --- a/homeassistant/components/synology_dsm/translations/en.json +++ b/homeassistant/components/synology_dsm/translations/en.json @@ -28,16 +28,7 @@ "username": "Username", "verify_ssl": "Verify SSL certificate" }, - "description": "Do you want to setup {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Password", - "username": "Username" - }, - "description": "Reason: {details}", - "title": "Synology DSM Reauthenticate Integration" + "description": "Do you want to setup {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Uses an SSL certificate", "username": "Username", "verify_ssl": "Verify SSL certificate" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/es-419.json b/homeassistant/components/synology_dsm/translations/es-419.json index 2886a8ef624..8b319773410 100644 --- a/homeassistant/components/synology_dsm/translations/es-419.json +++ b/homeassistant/components/synology_dsm/translations/es-419.json @@ -23,8 +23,7 @@ "ssl": "Utilice SSL/TLS para conectarse a su NAS", "username": "Nombre de usuario" }, - "description": "\u00bfDesea configurar {name} ({host})?", - "title": "Synology DSM" + "description": "\u00bfDesea configurar {name} ({host})?" }, "user": { "data": { @@ -33,8 +32,7 @@ "port": "Puerto (opcional)", "ssl": "Utilice SSL/TLS para conectarse a su NAS", "username": "Nombre de usuario" - }, - "title": "Synology DSM" + } } } } diff --git a/homeassistant/components/synology_dsm/translations/es.json b/homeassistant/components/synology_dsm/translations/es.json index 134f99cd75d..779996d7023 100644 --- a/homeassistant/components/synology_dsm/translations/es.json +++ b/homeassistant/components/synology_dsm/translations/es.json @@ -10,7 +10,7 @@ "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "missing_data": "Faltan datos: por favor, vuelva a intentarlo m\u00e1s tarde o pruebe con otra configuraci\u00f3n", "otp_failed": "La autenticaci\u00f3n de dos pasos fall\u00f3, vuelva a intentar con un nuevo c\u00f3digo de acceso", - "unknown": "Error desconocido: por favor, consulta logs para obtener m\u00e1s detalles" + "unknown": "Error inesperado" }, "flow_title": "{name} ({host})", "step": { @@ -28,16 +28,7 @@ "username": "Usuario", "verify_ssl": "Verificar certificado SSL" }, - "description": "\u00bfQuieres configurar {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Contrase\u00f1a", - "username": "Usuario" - }, - "description": "Raz\u00f3n: {details}", - "title": "Volver a autenticar la integraci\u00f3n Synology DSM" + "description": "\u00bfQuieres configurar {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Usar SSL/TLS para conectar con tu NAS", "username": "Usuario", "verify_ssl": "Verificar certificado SSL" - }, - "title": "Synology DSM" + } } } }, @@ -64,6 +54,7 @@ "init": { "data": { "scan_interval": "Minutos entre escaneos", + "snap_profile_type": "Calidad de las fotos de la c\u00e1mara (0:alta, 1:media, 2:baja)", "timeout": "Tiempo de espera (segundos)" } } diff --git a/homeassistant/components/synology_dsm/translations/et.json b/homeassistant/components/synology_dsm/translations/et.json index 691059a56e6..69341cab488 100644 --- a/homeassistant/components/synology_dsm/translations/et.json +++ b/homeassistant/components/synology_dsm/translations/et.json @@ -28,16 +28,7 @@ "username": "Kasutajanimi", "verify_ssl": "Kontrolli SSL sertifikaati" }, - "description": "Kas soovid seadistada {name}({host})?", - "title": "" - }, - "reauth": { - "data": { - "password": "Salas\u00f5na", - "username": "Kasutajanimi" - }, - "description": "P\u00f5hjus: {details}", - "title": "Synology DSM: Taastuvasta sidumine" + "description": "Kas soovid seadistada {name}({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Kasutab SSL sertifikaati", "username": "Kasutajanimi", "verify_ssl": "Kontrolli SSL sertifikaati" - }, - "title": "" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/fi.json b/homeassistant/components/synology_dsm/translations/fi.json index 8e1cb61abce..4f5f2cc19fa 100644 --- a/homeassistant/components/synology_dsm/translations/fi.json +++ b/homeassistant/components/synology_dsm/translations/fi.json @@ -8,9 +8,6 @@ "data": { "otp_code": "Koodi" } - }, - "reauth": { - "description": "Syy: {details}" } } }, diff --git a/homeassistant/components/synology_dsm/translations/fr.json b/homeassistant/components/synology_dsm/translations/fr.json index c6488ea7356..3917d9f800e 100644 --- a/homeassistant/components/synology_dsm/translations/fr.json +++ b/homeassistant/components/synology_dsm/translations/fr.json @@ -28,16 +28,7 @@ "username": "Nom d'utilisateur", "verify_ssl": "V\u00e9rifier le certificat SSL" }, - "description": "Voulez-vous configurer {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Mot de passe", - "username": "Nom d'utilisateur" - }, - "description": "Raison: {details}", - "title": "Synology DSM R\u00e9-authentifier l'int\u00e9gration" + "description": "Voulez-vous configurer {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Utilise un certificat SSL", "username": "Nom d'utilisateur", "verify_ssl": "V\u00e9rifier le certificat SSL" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/he.json b/homeassistant/components/synology_dsm/translations/he.json index 7adcac9af84..2e7772fc56f 100644 --- a/homeassistant/components/synology_dsm/translations/he.json +++ b/homeassistant/components/synology_dsm/translations/he.json @@ -23,12 +23,6 @@ }, "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea {name} ({host})?" }, - "reauth": { - "data": { - "password": "\u05e1\u05d9\u05e1\u05de\u05d4", - "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" - } - }, "reauth_confirm": { "data": { "password": "\u05e1\u05d9\u05e1\u05de\u05d4", diff --git a/homeassistant/components/synology_dsm/translations/hu.json b/homeassistant/components/synology_dsm/translations/hu.json index f23702ba33f..12f2bae011e 100644 --- a/homeassistant/components/synology_dsm/translations/hu.json +++ b/homeassistant/components/synology_dsm/translations/hu.json @@ -28,16 +28,7 @@ "username": "Felhaszn\u00e1l\u00f3n\u00e9v", "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" }, - "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Jelsz\u00f3", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - }, - "description": "Indokl\u00e1s: {details}", - "title": "Synology DSM Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", "username": "Felhaszn\u00e1l\u00f3n\u00e9v", "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/id.json b/homeassistant/components/synology_dsm/translations/id.json index 7f0242fedd0..204b7b372fa 100644 --- a/homeassistant/components/synology_dsm/translations/id.json +++ b/homeassistant/components/synology_dsm/translations/id.json @@ -28,16 +28,7 @@ "username": "Nama Pengguna", "verify_ssl": "Verifikasi sertifikat SSL" }, - "description": "Ingin menyiapkan {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Kata Sandi", - "username": "Nama Pengguna" - }, - "description": "Alasan: {details}", - "title": "Autentikasi Ulang Integrasi Synology DSM" + "description": "Ingin menyiapkan {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Menggunakan sertifikat SSL", "username": "Nama Pengguna", "verify_ssl": "Verifikasi sertifikat SSL" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/it.json b/homeassistant/components/synology_dsm/translations/it.json index 54d119c55b0..e0337ffaea9 100644 --- a/homeassistant/components/synology_dsm/translations/it.json +++ b/homeassistant/components/synology_dsm/translations/it.json @@ -28,16 +28,7 @@ "username": "Nome utente", "verify_ssl": "Verifica il certificato SSL" }, - "description": "Vuoi impostare {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Password", - "username": "Nome utente" - }, - "description": "Motivo: {details}", - "title": "Synology DSM Autentica nuovamente l'integrazione" + "description": "Vuoi impostare {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Utilizza un certificato SSL", "username": "Nome utente", "verify_ssl": "Verifica il certificato SSL" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/ja.json b/homeassistant/components/synology_dsm/translations/ja.json index 2cf84781cc0..47245b2ceb8 100644 --- a/homeassistant/components/synology_dsm/translations/ja.json +++ b/homeassistant/components/synology_dsm/translations/ja.json @@ -28,16 +28,7 @@ "username": "\u30e6\u30fc\u30b6\u30fc\u540d", "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" }, - "description": "{name} ({host})\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", - "username": "\u30e6\u30fc\u30b6\u30fc\u540d" - }, - "description": "\u7406\u7531: {details}", - "title": "Synology DSM \u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" + "description": "{name} ({host})\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "SSL\u8a3c\u660e\u66f8\u3092\u4f7f\u7528\u3059\u308b", "username": "\u30e6\u30fc\u30b6\u30fc\u540d", "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/ko.json b/homeassistant/components/synology_dsm/translations/ko.json index da61e46731e..5de99988192 100644 --- a/homeassistant/components/synology_dsm/translations/ko.json +++ b/homeassistant/components/synology_dsm/translations/ko.json @@ -26,8 +26,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, - "description": "{name} ({host})\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Synology DSM" + "description": "{name} ({host})\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { @@ -37,8 +36,7 @@ "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/lb.json b/homeassistant/components/synology_dsm/translations/lb.json index 4360b8685b3..d1faf4dca2f 100644 --- a/homeassistant/components/synology_dsm/translations/lb.json +++ b/homeassistant/components/synology_dsm/translations/lb.json @@ -26,8 +26,7 @@ "username": "Benotzernumm", "verify_ssl": "SSL Zertifikat iwwerpr\u00e9iwen" }, - "description": "Soll {name} ({host}) konfigur\u00e9iert ginn?", - "title": "Synology DSM" + "description": "Soll {name} ({host}) konfigur\u00e9iert ginn?" }, "user": { "data": { @@ -37,8 +36,7 @@ "ssl": "Benotzt ee SSL Zertifikat", "username": "Benotzernumm", "verify_ssl": "SSL Zertifikat iwwerpr\u00e9iwen" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/nb.json b/homeassistant/components/synology_dsm/translations/nb.json index 3a397e1f7d3..2ba01a2ddc4 100644 --- a/homeassistant/components/synology_dsm/translations/nb.json +++ b/homeassistant/components/synology_dsm/translations/nb.json @@ -1,11 +1,6 @@ { "config": { "step": { - "reauth": { - "data": { - "username": "Brukernavn" - } - }, "reauth_confirm": { "data": { "username": "Brukernavn" diff --git a/homeassistant/components/synology_dsm/translations/nl.json b/homeassistant/components/synology_dsm/translations/nl.json index 801c1d7fe82..c43f41b4277 100644 --- a/homeassistant/components/synology_dsm/translations/nl.json +++ b/homeassistant/components/synology_dsm/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol", + "reauth_successful": "Herauthenticatie geslaagd", "reconfigure_successful": "Herconfiguratie was succesvol" }, "error": { @@ -10,7 +10,7 @@ "invalid_auth": "Ongeldige authenticatie", "missing_data": "Ontbrekende gegevens: probeer het later opnieuw of een andere configuratie", "otp_failed": "Tweestapsverificatie is mislukt, probeer het opnieuw met een nieuwe toegangscode", - "unknown": "Onbekende fout: controleer de logs voor meer informatie" + "unknown": "Onverwachte fout" }, "flow_title": "{name} ({host})", "step": { @@ -24,20 +24,11 @@ "data": { "password": "Wachtwoord", "port": "Poort", - "ssl": "Gebruik een SSL-certificaat", + "ssl": "Maakt gebruik van een SSL-certificaat", "username": "Gebruikersnaam", - "verify_ssl": "Controleer het SSL-certificaat" + "verify_ssl": "SSL-certificaat verifi\u00ebren" }, - "description": "Wil je {name} ({host}) instellen?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Wachtwoord", - "username": "Gebruikersnaam" - }, - "description": "Reden: {details}", - "title": "Synology DSM Verifieer de integratie opnieuw" + "description": "Wil je {name} ({host}) instellen?" }, "reauth_confirm": { "data": { @@ -51,11 +42,10 @@ "host": "Host", "password": "Wachtwoord", "port": "Poort", - "ssl": "Gebruik een SSL-certificaat", + "ssl": "Maakt gebruik van een SSL-certificaat", "username": "Gebruikersnaam", - "verify_ssl": "Controleer het SSL-certificaat" - }, - "title": "Synology DSM" + "verify_ssl": "SSL-certificaat verifi\u00ebren" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/no.json b/homeassistant/components/synology_dsm/translations/no.json index 41f56c135aa..89ca80b168e 100644 --- a/homeassistant/components/synology_dsm/translations/no.json +++ b/homeassistant/components/synology_dsm/translations/no.json @@ -28,16 +28,7 @@ "username": "Brukernavn", "verify_ssl": "Verifisere SSL-sertifikat" }, - "description": "Vil du konfigurere {name} ({host})?", - "title": "" - }, - "reauth": { - "data": { - "password": "Passord", - "username": "Brukernavn" - }, - "description": "\u00c5rsak: {details}", - "title": "Synology DSM Godkjenne integrering p\u00e5 nytt" + "description": "Vil du konfigurere {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Bruker et SSL-sertifikat", "username": "Brukernavn", "verify_ssl": "Verifisere SSL-sertifikat" - }, - "title": "" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/pl.json b/homeassistant/components/synology_dsm/translations/pl.json index 9e285f65f84..a5c78b4390f 100644 --- a/homeassistant/components/synology_dsm/translations/pl.json +++ b/homeassistant/components/synology_dsm/translations/pl.json @@ -28,16 +28,7 @@ "username": "Nazwa u\u017cytkownika", "verify_ssl": "Weryfikacja certyfikatu SSL" }, - "description": "Czy chcesz skonfigurowa\u0107 {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Has\u0142o", - "username": "Nazwa u\u017cytkownika" - }, - "description": "Pow\u00f3d: {details}", - "title": "Ponownie uwierzytelnij integracj\u0119 Synology DSM" + "description": "Czy chcesz skonfigurowa\u0107 {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Certyfikat SSL", "username": "Nazwa u\u017cytkownika", "verify_ssl": "Weryfikacja certyfikatu SSL" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/pt-BR.json b/homeassistant/components/synology_dsm/translations/pt-BR.json index ddde3e1e29d..3410658fafe 100644 --- a/homeassistant/components/synology_dsm/translations/pt-BR.json +++ b/homeassistant/components/synology_dsm/translations/pt-BR.json @@ -28,16 +28,7 @@ "username": "Usu\u00e1rio", "verify_ssl": "Verifique o certificado SSL" }, - "description": "Voc\u00ea quer configurar o {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Senha", - "username": "Usu\u00e1rio" - }, - "description": "Motivo: {details}", - "title": "Synology DSM Reautenticar Integra\u00e7\u00e3o" + "description": "Voc\u00ea quer configurar o {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "Usar um certificado SSL", "username": "Usu\u00e1rio", "verify_ssl": "Verifique o certificado SSL" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/ru.json b/homeassistant/components/synology_dsm/translations/ru.json index 3f73d41d740..007504c4828 100644 --- a/homeassistant/components/synology_dsm/translations/ru.json +++ b/homeassistant/components/synology_dsm/translations/ru.json @@ -28,16 +28,7 @@ "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": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?", - "title": "Synology DSM" - }, - "reauth": { - "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": "\u041f\u0440\u0438\u0447\u0438\u043d\u0430: {details}", - "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f Synology DSM" + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", "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" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/sk.json b/homeassistant/components/synology_dsm/translations/sk.json index e9c37059842..83965998f0e 100644 --- a/homeassistant/components/synology_dsm/translations/sk.json +++ b/homeassistant/components/synology_dsm/translations/sk.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "Zariadenie je u\u017e nakonfigurovan\u00e9", "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" }, "error": { diff --git a/homeassistant/components/synology_dsm/translations/sl.json b/homeassistant/components/synology_dsm/translations/sl.json index 164abf7e2cc..91d32273e62 100644 --- a/homeassistant/components/synology_dsm/translations/sl.json +++ b/homeassistant/components/synology_dsm/translations/sl.json @@ -23,8 +23,7 @@ "ssl": "Uporabite SSL/TLS za povezavo z va\u0161im NAS-om", "username": "Uporabni\u0161ko ime" }, - "description": "Ali \u017eelite nastaviti {name} ({host})?", - "title": "Synology DSM" + "description": "Ali \u017eelite nastaviti {name} ({host})?" }, "user": { "data": { @@ -33,8 +32,7 @@ "port": "Vrata (Izbirno)", "ssl": "Uporabite SSL/TLS za povezavo z va\u0161im NAS-om", "username": "Uporabni\u0161ko ime" - }, - "title": "Synology DSM" + } } } } diff --git a/homeassistant/components/synology_dsm/translations/sv.json b/homeassistant/components/synology_dsm/translations/sv.json index a6f5c496f22..04814596518 100644 --- a/homeassistant/components/synology_dsm/translations/sv.json +++ b/homeassistant/components/synology_dsm/translations/sv.json @@ -10,8 +10,7 @@ "port": "Port (Valfri)", "username": "Anv\u00e4ndarnamn" }, - "description": "Do vill du konfigurera {name} ({host})?", - "title": "Synology DSM" + "description": "Do vill du konfigurera {name} ({host})?" }, "user": { "data": { diff --git a/homeassistant/components/synology_dsm/translations/tr.json b/homeassistant/components/synology_dsm/translations/tr.json index 0d527a34504..bcd2085218f 100644 --- a/homeassistant/components/synology_dsm/translations/tr.json +++ b/homeassistant/components/synology_dsm/translations/tr.json @@ -28,16 +28,7 @@ "username": "Kullan\u0131c\u0131 Ad\u0131", "verify_ssl": "SSL sertifikalar\u0131n\u0131 do\u011frula" }, - "description": "{name} ( {host} ) kurulumu yapmak istiyor musunuz?", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "Parola", - "username": "Kullan\u0131c\u0131 Ad\u0131" - }, - "description": "Sebep: {details}", - "title": "Synology DSM Entegrasyonu Yeniden Do\u011frula" + "description": "{name} ( {host} ) kurulumu yapmak istiyor musunuz?" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "SSL sertifikas\u0131 kullan\u0131r", "username": "Kullan\u0131c\u0131 Ad\u0131", "verify_ssl": "SSL sertifikalar\u0131n\u0131 do\u011frula" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/uk.json b/homeassistant/components/synology_dsm/translations/uk.json index 4d80350989f..21b0f4a68f0 100644 --- a/homeassistant/components/synology_dsm/translations/uk.json +++ b/homeassistant/components/synology_dsm/translations/uk.json @@ -26,8 +26,7 @@ "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" }, - "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name} ({host})?", - "title": "Synology DSM" + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name} ({host})?" }, "user": { "data": { @@ -37,8 +36,7 @@ "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/zh-Hans.json b/homeassistant/components/synology_dsm/translations/zh-Hans.json index 862f526c38d..a62a79ae868 100644 --- a/homeassistant/components/synology_dsm/translations/zh-Hans.json +++ b/homeassistant/components/synology_dsm/translations/zh-Hans.json @@ -26,14 +26,7 @@ "ssl": "\u4f7f\u7528 SSL \u8bc1\u4e66", "username": "\u7528\u6237\u540d" }, - "description": "\u60a8\u60f3\u8981\u914d\u7f6e {name} ({host}) \u5417\uff1f", - "title": "Synology DSM" - }, - "reauth": { - "data": { - "password": "\u5bc6\u7801", - "username": "\u7528\u6237\u540d" - } + "description": "\u60a8\u60f3\u8981\u914d\u7f6e {name} ({host}) \u5417\uff1f" }, "user": { "data": { @@ -43,8 +36,7 @@ "ssl": "\u4f7f\u7528 SSL \u8bc1\u4e66", "username": "\u7528\u6237\u540d", "verify_ssl": "\u9a8c\u8bc1 SSL \u8bc1\u4e66" - }, - "title": "Synology DSM" + } } } }, diff --git a/homeassistant/components/synology_dsm/translations/zh-Hant.json b/homeassistant/components/synology_dsm/translations/zh-Hant.json index 33211d9a5a3..504e1bec32d 100644 --- a/homeassistant/components/synology_dsm/translations/zh-Hant.json +++ b/homeassistant/components/synology_dsm/translations/zh-Hant.json @@ -28,16 +28,7 @@ "username": "\u4f7f\u7528\u8005\u540d\u7a31", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" }, - "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} ({host})\uff1f", - "title": "\u7fa4\u6689 DSM" - }, - "reauth": { - "data": { - "password": "\u5bc6\u78bc", - "username": "\u4f7f\u7528\u8005\u540d\u7a31" - }, - "description": "\u8a73\u7d30\u8cc7\u8a0a\uff1a{details}", - "title": "Synology DSM \u91cd\u65b0\u8a8d\u8b49\u6574\u5408" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} ({host})\uff1f" }, "reauth_confirm": { "data": { @@ -54,8 +45,7 @@ "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "username": "\u4f7f\u7528\u8005\u540d\u7a31", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" - }, - "title": "\u7fa4\u6689 DSM" + } } } }, diff --git a/homeassistant/components/system_bridge/translations/ko.json b/homeassistant/components/system_bridge/translations/ko.json new file mode 100644 index 00000000000..c40a7a55814 --- /dev/null +++ b/homeassistant/components/system_bridge/translations/ko.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "flow_title": "{name}", + "step": { + "authenticate": { + "data": { + "api_key": "API \ud0a4" + }, + "description": "{name} \uc5d0 \ub300\ud55c \uad6c\uc131\uc5d0\uc11c \uc124\uc815\ud55c API \ud0a4\ub97c \uc785\ub825\ud558\uc2ed\uc2dc\uc624." + }, + "user": { + "data": { + "api_key": "API \ud0a4", + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8" + }, + "description": "\uc5f0\uacb0 \uc138\ubd80 \uc815\ubcf4\ub97c \uc785\ub825\ud558\uc138\uc694." + } + } + } +} \ 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 c419123e50d..c9d1c45d6a3 100644 --- a/homeassistant/components/system_bridge/translations/nl.json +++ b/homeassistant/components/system_bridge/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol", + "reauth_successful": "Herauthenticatie geslaagd", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/system_bridge/translations/sk.json b/homeassistant/components/system_bridge/translations/sk.json index 276eac51dd4..4a6f823bbe6 100644 --- a/homeassistant/components/system_bridge/translations/sk.json +++ b/homeassistant/components/system_bridge/translations/sk.json @@ -4,7 +4,8 @@ "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" }, "error": { - "invalid_auth": "Neplatn\u00e9 overenie" + "invalid_auth": "Neplatn\u00e9 overenie", + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" }, "step": { "authenticate": { diff --git a/homeassistant/components/tado/translations/es.json b/homeassistant/components/tado/translations/es.json index c9a26a1a9ab..db71d41f789 100644 --- a/homeassistant/components/tado/translations/es.json +++ b/homeassistant/components/tado/translations/es.json @@ -23,7 +23,7 @@ "step": { "init": { "data": { - "fallback": "Activar modo de salvaguarda." + "fallback": "Elige el modo alternativo." }, "description": "El modo de salvaguarda volver\u00e1 a la Planificaci\u00f3n Inteligente en el siguiente cambio de programaci\u00f3n despu\u00e9s de ajustar manualmente una zona.", "title": "Ajustar las opciones de Tado" diff --git a/homeassistant/components/tailscale/translations/ko.json b/homeassistant/components/tailscale/translations/ko.json new file mode 100644 index 00000000000..cd273d60ca0 --- /dev/null +++ b/homeassistant/components/tailscale/translations/ko.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "description": "Tailscale API \ud1a0\ud070\uc740 90\uc77c \ub3d9\uc548 \uc720\ud6a8\ud569\ub2c8\ub2e4. https://login.tailscale.com/admin/settings/authkeys\uc5d0\uc11c \uc0c8\ub85c\uc6b4 Tailscale API \ud0a4\ub97c \uc0dd\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "user": { + "data": { + "tailnet": "\ud14c\uc77c\ub137" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tailscale/translations/nl.json b/homeassistant/components/tailscale/translations/nl.json index 1a59a3ca029..5e05b0ebcb6 100644 --- a/homeassistant/components/tailscale/translations/nl.json +++ b/homeassistant/components/tailscale/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/tankerkoenig/__init__.py b/homeassistant/components/tankerkoenig/__init__.py index 08520c8f5cc..e63add83fad 100644 --- a/homeassistant/components/tankerkoenig/__init__.py +++ b/homeassistant/components/tankerkoenig/__init__.py @@ -11,6 +11,7 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( + ATTR_ID, CONF_API_KEY, CONF_LATITUDE, CONF_LOCATION, @@ -24,8 +25,14 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.typing import ConfigType -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) from .const import ( CONF_FUEL_TYPES, @@ -109,9 +116,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set a tankerkoenig configuration entry up.""" hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][ - entry.unique_id - ] = coordinator = TankerkoenigDataUpdateCoordinator( + hass.data[DOMAIN][entry.entry_id] = coordinator = TankerkoenigDataUpdateCoordinator( hass, entry, _LOGGER, @@ -140,7 +145,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload Tankerkoenig config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - hass.data[DOMAIN].pop(entry.unique_id) + hass.data[DOMAIN].pop(entry.entry_id) return unload_ok @@ -172,7 +177,6 @@ class TankerkoenigDataUpdateCoordinator(DataUpdateCoordinator): self._api_key: str = entry.data[CONF_API_KEY] self._selected_stations: list[str] = entry.data[CONF_STATIONS] - self._hass = hass self.stations: dict[str, dict] = {} self.fuel_types: list[str] = entry.data[CONF_FUEL_TYPES] self.show_on_map: bool = entry.options[CONF_SHOW_ON_MAP] @@ -195,7 +199,7 @@ class TankerkoenigDataUpdateCoordinator(DataUpdateCoordinator): station_id, station_data["message"], ) - return False + continue self.add_station(station_data["station"]) if len(self.stations) > 10: _LOGGER.warning( @@ -215,7 +219,7 @@ class TankerkoenigDataUpdateCoordinator(DataUpdateCoordinator): # The API seems to only return at most 10 results, so split the list in chunks of 10 # and merge it together. for index in range(ceil(len(station_ids) / 10)): - data = await self._hass.async_add_executor_job( + data = await self.hass.async_add_executor_job( pytankerkoenig.getPriceList, self._api_key, station_ids[index * 10 : (index + 1) * 10], @@ -223,13 +227,11 @@ class TankerkoenigDataUpdateCoordinator(DataUpdateCoordinator): _LOGGER.debug("Received data: %s", data) if not data["ok"]: - _LOGGER.error( - "Error fetching data from tankerkoenig.de: %s", data["message"] - ) raise UpdateFailed(data["message"]) if "prices" not in data: - _LOGGER.error("Did not receive price information from tankerkoenig.de") - raise UpdateFailed("No prices in data") + raise UpdateFailed( + "Did not receive price information from tankerkoenig.de" + ) prices.update(data["prices"]) return prices @@ -244,3 +246,20 @@ class TankerkoenigDataUpdateCoordinator(DataUpdateCoordinator): self.stations[station_id] = station _LOGGER.debug("add_station called for station: %s", station) + + +class TankerkoenigCoordinatorEntity(CoordinatorEntity): + """Tankerkoenig base entity.""" + + def __init__( + self, coordinator: TankerkoenigDataUpdateCoordinator, station: dict + ) -> None: + """Initialize the Tankerkoenig base entity.""" + super().__init__(coordinator) + self._attr_device_info = DeviceInfo( + identifiers={(ATTR_ID, station["id"])}, + name=f"{station['brand']} {station['street']} {station['houseNumber']}", + model=station["brand"], + configuration_url="https://www.tankerkoenig.de", + entry_type=DeviceEntryType.SERVICE, + ) diff --git a/homeassistant/components/tankerkoenig/binary_sensor.py b/homeassistant/components/tankerkoenig/binary_sensor.py index 9a2b048e0b8..5f10b54f704 100644 --- a/homeassistant/components/tankerkoenig/binary_sensor.py +++ b/homeassistant/components/tankerkoenig/binary_sensor.py @@ -8,13 +8,11 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE +from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import TankerkoenigDataUpdateCoordinator +from . import TankerkoenigCoordinatorEntity, TankerkoenigDataUpdateCoordinator from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -25,7 +23,7 @@ async def async_setup_entry( ) -> None: """Set up the tankerkoenig binary sensors.""" - coordinator: TankerkoenigDataUpdateCoordinator = hass.data[DOMAIN][entry.unique_id] + coordinator: TankerkoenigDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] stations = coordinator.stations.values() entities = [] @@ -41,7 +39,7 @@ async def async_setup_entry( async_add_entities(entities) -class StationOpenBinarySensorEntity(CoordinatorEntity, BinarySensorEntity): +class StationOpenBinarySensorEntity(TankerkoenigCoordinatorEntity, BinarySensorEntity): """Shows if a station is open or closed.""" _attr_device_class = BinarySensorDeviceClass.DOOR @@ -53,18 +51,12 @@ class StationOpenBinarySensorEntity(CoordinatorEntity, BinarySensorEntity): show_on_map: bool, ) -> None: """Initialize the sensor.""" - super().__init__(coordinator) + super().__init__(coordinator, station) self._station_id = station["id"] self._attr_name = ( f"{station['brand']} {station['street']} {station['houseNumber']} status" ) self._attr_unique_id = f"{station['id']}_status" - self._attr_device_info = DeviceInfo( - identifiers={(ATTR_ID, station["id"])}, - name=f"{station['brand']} {station['street']} {station['houseNumber']}", - model=station["brand"], - configuration_url="https://www.tankerkoenig.de", - ) if show_on_map: self._attr_extra_state_attributes = { ATTR_LATITUDE: station["lat"], diff --git a/homeassistant/components/tankerkoenig/config_flow.py b/homeassistant/components/tankerkoenig/config_flow.py index 65c367d1ba4..345b034b027 100644 --- a/homeassistant/components/tankerkoenig/config_flow.py +++ b/homeassistant/components/tankerkoenig/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Tankerkoenig.""" from __future__ import annotations +from collections.abc import Mapping from typing import Any from pytankerkoenig import customException, getNearbyStations @@ -17,7 +18,7 @@ from homeassistant.const import ( CONF_SHOW_ON_MAP, LENGTH_KILOMETERS, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.selector import ( @@ -29,6 +30,24 @@ from homeassistant.helpers.selector import ( from .const import CONF_FUEL_TYPES, CONF_STATIONS, DEFAULT_RADIUS, DOMAIN, FUEL_TYPES +async def async_get_nearby_stations( + hass: HomeAssistant, data: Mapping[str, Any] +) -> dict[str, Any]: + """Fetch nearby stations.""" + try: + return await hass.async_add_executor_job( + getNearbyStations, + data[CONF_API_KEY], + data[CONF_LOCATION][CONF_LATITUDE], + data[CONF_LOCATION][CONF_LONGITUDE], + data[CONF_RADIUS], + "all", + "dist", + ) + except customException as err: + return {"ok": False, "message": err, "exception": True} + + class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow.""" @@ -57,7 +76,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): selected_station_ids: list[str] = [] # add all nearby stations - nearby_stations = await self._get_nearby_stations(config) + nearby_stations = await async_get_nearby_stations(self.hass, config) for station in nearby_stations.get("stations", []): selected_station_ids.append(station["id"]) @@ -91,19 +110,17 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) self._abort_if_unique_id_configured() - data = await self._get_nearby_stations(user_input) + data = await async_get_nearby_stations(self.hass, user_input) if not data.get("ok"): return self._show_form_user( user_input, errors={CONF_API_KEY: "invalid_auth"} ) - if stations := data.get("stations"): - for station in stations: - self._stations[ - station["id"] - ] = f"{station['brand']} {station['street']} {station['houseNumber']} - ({station['dist']}km)" - - else: + if len(stations := data.get("stations", [])) == 0: return self._show_form_user(user_input, errors={CONF_RADIUS: "no_stations"}) + for station in stations: + self._stations[ + station["id"] + ] = f"{station['brand']} {station['street']} {station['houseNumber']} - ({station['dist']}km)" self._data = user_input @@ -162,7 +179,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONF_RADIUS, default=user_input.get(CONF_RADIUS, DEFAULT_RADIUS) ): NumberSelector( NumberSelectorConfig( - min=0.1, + min=1.0, max=25, step=0.1, unit_of_measurement=LENGTH_KILOMETERS, @@ -182,21 +199,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): options=options, ) - async def _get_nearby_stations(self, data: dict[str, Any]) -> dict[str, Any]: - """Fetch nearby stations.""" - try: - return await self.hass.async_add_executor_job( - getNearbyStations, - data[CONF_API_KEY], - data[CONF_LOCATION][CONF_LATITUDE], - data[CONF_LOCATION][CONF_LONGITUDE], - data[CONF_RADIUS], - "all", - "dist", - ) - except customException as err: - return {"ok": False, "message": err, "exception": True} - class OptionsFlowHandler(config_entries.OptionsFlow): """Handle an options flow.""" @@ -204,14 +206,36 @@ class OptionsFlowHandler(config_entries.OptionsFlow): def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry + self._stations: dict[str, str] = {} async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle options flow.""" if user_input is not None: + self.hass.config_entries.async_update_entry( + self.config_entry, + data={ + **self.config_entry.data, + CONF_STATIONS: user_input.pop(CONF_STATIONS), + }, + ) return self.async_create_entry(title="", data=user_input) + nearby_stations = await async_get_nearby_stations( + self.hass, self.config_entry.data + ) + if stations := nearby_stations.get("stations"): + for station in stations: + self._stations[ + station["id"] + ] = f"{station['brand']} {station['street']} {station['houseNumber']} - ({station['dist']}km)" + + # add possible extra selected stations from import + for selected_station in self.config_entry.data[CONF_STATIONS]: + if selected_station not in self._stations: + self._stations[selected_station] = f"id: {selected_station}" + return self.async_show_form( step_id="init", data_schema=vol.Schema( @@ -220,6 +244,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): CONF_SHOW_ON_MAP, default=self.config_entry.options[CONF_SHOW_ON_MAP], ): bool, + vol.Required( + CONF_STATIONS, default=self.config_entry.data[CONF_STATIONS] + ): cv.multi_select(self._stations), } ), ) diff --git a/homeassistant/components/tankerkoenig/sensor.py b/homeassistant/components/tankerkoenig/sensor.py index 898a38c3c14..c63b0ea0e7e 100644 --- a/homeassistant/components/tankerkoenig/sensor.py +++ b/homeassistant/components/tankerkoenig/sensor.py @@ -7,17 +7,14 @@ from homeassistant.components.sensor import SensorEntity, SensorStateClass from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ATTRIBUTION, - ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, CURRENCY_EURO, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import TankerkoenigDataUpdateCoordinator +from . import TankerkoenigCoordinatorEntity, TankerkoenigDataUpdateCoordinator from .const import ( ATTR_BRAND, ATTR_CITY, @@ -39,7 +36,7 @@ async def async_setup_entry( ) -> None: """Set up the tankerkoenig sensors.""" - coordinator: TankerkoenigDataUpdateCoordinator = hass.data[DOMAIN][entry.unique_id] + coordinator: TankerkoenigDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] stations = coordinator.stations.values() entities = [] @@ -62,7 +59,7 @@ async def async_setup_entry( async_add_entities(entities) -class FuelPriceSensor(CoordinatorEntity, SensorEntity): +class FuelPriceSensor(TankerkoenigCoordinatorEntity, SensorEntity): """Contains prices for fuel in a given station.""" _attr_state_class = SensorStateClass.MEASUREMENT @@ -70,19 +67,12 @@ class FuelPriceSensor(CoordinatorEntity, SensorEntity): def __init__(self, fuel_type, station, coordinator, show_on_map): """Initialize the sensor.""" - super().__init__(coordinator) + super().__init__(coordinator, station) self._station_id = station["id"] self._fuel_type = fuel_type self._attr_name = f"{station['brand']} {station['street']} {station['houseNumber']} {FUEL_TYPES[fuel_type]}" self._attr_native_unit_of_measurement = CURRENCY_EURO self._attr_unique_id = f"{station['id']}_{fuel_type}" - self._attr_device_info = DeviceInfo( - identifiers={(ATTR_ID, station["id"])}, - name=f"{station['brand']} {station['street']} {station['houseNumber']}", - model=station["brand"], - configuration_url="https://www.tankerkoenig.de", - ) - attrs = { ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_BRAND: station["brand"], diff --git a/homeassistant/components/tankerkoenig/strings.json b/homeassistant/components/tankerkoenig/strings.json index 7c1ba54fcc0..5e0c367c192 100644 --- a/homeassistant/components/tankerkoenig/strings.json +++ b/homeassistant/components/tankerkoenig/strings.json @@ -32,7 +32,7 @@ "init": { "title": "Tankerkoenig options", "data": { - "scan_interval": "Update Interval", + "stations": "Stations", "show_on_map": "Show stations on map" } } diff --git a/homeassistant/components/tankerkoenig/translations/en.json b/homeassistant/components/tankerkoenig/translations/en.json index 399788de8f4..83cc36fd4c8 100644 --- a/homeassistant/components/tankerkoenig/translations/en.json +++ b/homeassistant/components/tankerkoenig/translations/en.json @@ -31,8 +31,8 @@ "step": { "init": { "data": { - "scan_interval": "Update Interval", - "show_on_map": "Show stations on map" + "show_on_map": "Show stations on map", + "stations": "Stations" }, "title": "Tankerkoenig options" } diff --git a/homeassistant/components/tankerkoenig/translations/es.json b/homeassistant/components/tankerkoenig/translations/es.json new file mode 100644 index 00000000000..ec97b5886d3 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/es.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "La ubicaci\u00f3n ya est\u00e1 configurada" + }, + "error": { + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "no_stations": "No se pudo encontrar ninguna estaci\u00f3n al alcance." + }, + "step": { + "select_station": { + "data": { + "stations": "Estaciones" + }, + "title": "Selecciona las estaciones a a\u00f1adir" + }, + "user": { + "data": { + "name": "Nombre de la regi\u00f3n" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Muestra las estaciones en el mapa" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/nl.json b/homeassistant/components/tankerkoenig/translations/nl.json index 96de058ad74..66d442a71f6 100644 --- a/homeassistant/components/tankerkoenig/translations/nl.json +++ b/homeassistant/components/tankerkoenig/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/tankerkoenig/translations/sk.json b/homeassistant/components/tankerkoenig/translations/sk.json new file mode 100644 index 00000000000..06c74b52725 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "radius": "Polomer vyh\u013ead\u00e1vania" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/cover.py b/homeassistant/components/tasmota/cover.py index 172e460c29b..4b851a86406 100644 --- a/homeassistant/components/tasmota/cover.py +++ b/homeassistant/components/tasmota/cover.py @@ -9,6 +9,7 @@ from hatasmota.models import DiscoveryHashType from homeassistant.components.cover import ( ATTR_POSITION, + ATTR_TILT_POSITION, DOMAIN as COVER_DOMAIN, CoverEntity, CoverEntityFeature, @@ -55,23 +56,32 @@ 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: """Initialize the Tasmota cover.""" self._direction: int | None = None self._position: int | None = None + self._tilt_position: int | None = None super().__init__( **kwds, ) + self._attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION + ) + if self._tasmota_entity.supports_tilt: + self._attr_supported_features |= ( + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION + ) + async def async_added_to_hass(self) -> None: """Subscribe to MQTT events.""" self._tasmota_entity.set_on_state_callback(self.cover_state_updated) @@ -82,6 +92,7 @@ class TasmotaCover( """Handle state updates.""" self._direction = kwargs["direction"] self._position = kwargs["position"] + self._tilt_position = kwargs["tilt"] self.async_write_ha_state() @property @@ -92,6 +103,14 @@ class TasmotaCover( """ return self._position + @property + def current_cover_tilt_position(self) -> int | None: + """Return current tilt position of cover. + + None is unknown, 0 is closed, 100 is fully open. + """ + return self._tilt_position + @property def is_opening(self) -> bool: """Return if the cover is opening or not.""" @@ -125,3 +144,20 @@ class TasmotaCover( async def async_stop_cover(self, **kwargs: Any) -> None: """Stop the cover.""" await self._tasmota_entity.stop() + + async def async_open_cover_tilt(self, **kwargs: Any) -> None: + """Open the cover tilt.""" + await self._tasmota_entity.open_tilt() + + async def async_close_cover_tilt(self, **kwargs: Any) -> None: + """Close cover tilt.""" + await self._tasmota_entity.close_tilt() + + async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: + """Move the cover tilt to a specific position.""" + tilt = kwargs[ATTR_TILT_POSITION] + await self._tasmota_entity.set_tilt_position(tilt) + + async def async_stop_cover_tilt(self, **kwargs: Any) -> None: + """Stop the cover tilt.""" + await self._tasmota_entity.stop() diff --git a/homeassistant/components/tasmota/device_trigger.py b/homeassistant/components/tasmota/device_trigger.py index aca5a2848e3..69cb3a0bd72 100644 --- a/homeassistant/components/tasmota/device_trigger.py +++ b/homeassistant/components/tasmota/device_trigger.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections.abc import Callable import logging -from typing import Any import attr from hatasmota.models import DiscoveryHashType @@ -189,11 +188,13 @@ async def async_setup_trigger( _LOGGER.debug( "Got update for trigger with hash: %s '%s'", discovery_hash, trigger_config ) + device_triggers: dict[str, Trigger] = hass.data[DEVICE_TRIGGERS] if not trigger_config.is_active: # Empty trigger_config: Remove trigger _LOGGER.debug("Removing trigger: %s", discovery_hash) - if discovery_id in hass.data[DEVICE_TRIGGERS]: - device_trigger = hass.data[DEVICE_TRIGGERS][discovery_id] + if discovery_id in device_triggers: + device_trigger = device_triggers[discovery_id] + assert device_trigger.tasmota_trigger await device_trigger.tasmota_trigger.unsubscribe_topics() device_trigger.detach_trigger() clear_discovery_hash(hass, discovery_hash) @@ -201,7 +202,8 @@ async def async_setup_trigger( remove_update_signal() return - device_trigger = hass.data[DEVICE_TRIGGERS][discovery_id] + device_trigger = device_triggers[discovery_id] + assert device_trigger.tasmota_trigger if device_trigger.tasmota_trigger.config_same(trigger_config): # Unchanged payload: Ignore to avoid unnecessary unsubscribe / subscribe _LOGGER.debug("Ignoring unchanged update for: %s", discovery_hash) @@ -210,6 +212,7 @@ async def async_setup_trigger( # Non-empty, changed trigger_config: Update trigger _LOGGER.debug("Updating trigger: %s", discovery_hash) device_trigger.tasmota_trigger.config_update(trigger_config) + assert remove_update_signal await device_trigger.update_tasmota_trigger( trigger_config, remove_update_signal ) @@ -231,7 +234,8 @@ async def async_setup_trigger( if DEVICE_TRIGGERS not in hass.data: hass.data[DEVICE_TRIGGERS] = {} - if discovery_id not in hass.data[DEVICE_TRIGGERS]: + device_triggers: dict[str, Trigger] = hass.data[DEVICE_TRIGGERS] + if discovery_id not in device_triggers: device_trigger = Trigger( hass=hass, device_id=device.id, @@ -241,10 +245,10 @@ async def async_setup_trigger( type=tasmota_trigger.cfg.type, remove_update_signal=remove_update_signal, ) - hass.data[DEVICE_TRIGGERS][discovery_id] = device_trigger + device_triggers[discovery_id] = device_trigger else: # This Tasmota trigger is wanted by device trigger(s), set them up - device_trigger = hass.data[DEVICE_TRIGGERS][discovery_id] + device_trigger = device_triggers[discovery_id] await device_trigger.set_tasmota_trigger(tasmota_trigger, remove_update_signal) await device_trigger.arm_tasmota_trigger() @@ -252,27 +256,34 @@ async def async_setup_trigger( async def async_remove_triggers(hass: HomeAssistant, device_id: str) -> None: """Cleanup any device triggers for a Tasmota device.""" triggers = await async_get_triggers(hass, device_id) + + if not triggers: + return + device_triggers: dict[str, Trigger] = hass.data[DEVICE_TRIGGERS] for trig in triggers: - device_trigger = hass.data[DEVICE_TRIGGERS].pop(trig[CONF_DISCOVERY_ID]) + device_trigger = device_triggers.pop(trig[CONF_DISCOVERY_ID]) if device_trigger: discovery_hash = device_trigger.discovery_hash + assert device_trigger.tasmota_trigger await device_trigger.tasmota_trigger.unsubscribe_topics() device_trigger.detach_trigger() clear_discovery_hash(hass, discovery_hash) + assert device_trigger.remove_update_signal device_trigger.remove_update_signal() async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for a Tasmota device.""" triggers: list[dict[str, str]] = [] if DEVICE_TRIGGERS not in hass.data: return triggers - for discovery_id, trig in hass.data[DEVICE_TRIGGERS].items(): + device_triggers: dict[str, Trigger] = hass.data[DEVICE_TRIGGERS] + for discovery_id, trig in device_triggers.items(): if trig.device_id != device_id or trig.tasmota_trigger is None: continue @@ -292,18 +303,19 @@ async def async_get_triggers( async def async_attach_trigger( hass: HomeAssistant, config: ConfigType, - action: Callable, + action: AutomationActionType, automation_info: AutomationTriggerInfo, ) -> CALLBACK_TYPE: """Attach a device trigger.""" if DEVICE_TRIGGERS not in hass.data: hass.data[DEVICE_TRIGGERS] = {} + device_triggers: dict[str, Trigger] = hass.data[DEVICE_TRIGGERS] device_id = config[CONF_DEVICE_ID] discovery_id = config[CONF_DISCOVERY_ID] - if discovery_id not in hass.data[DEVICE_TRIGGERS]: + if discovery_id not in device_triggers: # The trigger has not (yet) been discovered, prepare it for later - hass.data[DEVICE_TRIGGERS][discovery_id] = Trigger( + device_triggers[discovery_id] = Trigger( hass=hass, device_id=device_id, discovery_hash=None, @@ -312,5 +324,5 @@ async def async_attach_trigger( subtype=config[CONF_SUBTYPE], tasmota_trigger=None, ) - trigger: Trigger = hass.data[DEVICE_TRIGGERS][discovery_id] + trigger: Trigger = device_triggers[discovery_id] return await trigger.add_trigger(action, automation_info) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index 6a743683d94..4268c4198b2 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.1"], + "requirements": ["hatasmota==0.5.1"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"], diff --git a/homeassistant/components/tasmota/translations/bg.json b/homeassistant/components/tasmota/translations/bg.json index a2321080a6a..425bec1cad5 100644 --- a/homeassistant/components/tasmota/translations/bg.json +++ b/homeassistant/components/tasmota/translations/bg.json @@ -4,9 +4,6 @@ "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": { - "config": { - "title": "Tasmota" - }, "confirm": { "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Tasmota?" } diff --git a/homeassistant/components/tasmota/translations/ca.json b/homeassistant/components/tasmota/translations/ca.json index 0a414bc5cfe..f5adb5b0694 100644 --- a/homeassistant/components/tasmota/translations/ca.json +++ b/homeassistant/components/tasmota/translations/ca.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Prefix del topic de descoberta (discovery)" - }, - "description": "Introdureix la configuraci\u00f3 de Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "Vols configurar Tasmota?" diff --git a/homeassistant/components/tasmota/translations/cs.json b/homeassistant/components/tasmota/translations/cs.json index 4fb0d555c63..673126f1cf0 100644 --- a/homeassistant/components/tasmota/translations/cs.json +++ b/homeassistant/components/tasmota/translations/cs.json @@ -4,10 +4,6 @@ "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." }, "step": { - "config": { - "description": "Pros\u00edm zajdete nastaven\u00ed pro Tasmota.", - "title": "Tasmota" - }, "confirm": { "description": "Chcete nastavit Tasmota?" } diff --git a/homeassistant/components/tasmota/translations/de.json b/homeassistant/components/tasmota/translations/de.json index 7e654d982c5..128b99a75b9 100644 --- a/homeassistant/components/tasmota/translations/de.json +++ b/homeassistant/components/tasmota/translations/de.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Themenpr\u00e4fix f\u00fcr die Erkennung" - }, - "description": "Bitte die Tasmota-Konfiguration einstellen.", - "title": "Tasmota" + } }, "confirm": { "description": "M\u00f6chtest du Tasmota einrichten?" diff --git a/homeassistant/components/tasmota/translations/el.json b/homeassistant/components/tasmota/translations/el.json index cb9bc10730c..732724ab2ab 100644 --- a/homeassistant/components/tasmota/translations/el.json +++ b/homeassistant/components/tasmota/translations/el.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "\u03a0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03b8\u03ad\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7\u03c2" - }, - "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd Tasmota;" diff --git a/homeassistant/components/tasmota/translations/en.json b/homeassistant/components/tasmota/translations/en.json index ddd7e726079..3e8b0b43bce 100644 --- a/homeassistant/components/tasmota/translations/en.json +++ b/homeassistant/components/tasmota/translations/en.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Discovery topic prefix" - }, - "description": "Please enter the Tasmota configuration.", - "title": "Tasmota" + } }, "confirm": { "description": "Do you want to set up Tasmota?" diff --git a/homeassistant/components/tasmota/translations/es.json b/homeassistant/components/tasmota/translations/es.json index f5a5532f180..0d3b7317330 100644 --- a/homeassistant/components/tasmota/translations/es.json +++ b/homeassistant/components/tasmota/translations/es.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Prefijo del tema para descubrimiento" - }, - "description": "Introduce la configuraci\u00f3n de Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "\u00bfQuieres configurar Tasmota?" diff --git a/homeassistant/components/tasmota/translations/et.json b/homeassistant/components/tasmota/translations/et.json index e689dcf7e43..09ba6e5c328 100644 --- a/homeassistant/components/tasmota/translations/et.json +++ b/homeassistant/components/tasmota/translations/et.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Tuvastusteema eesliide" - }, - "description": "Sisesta Tasmota konfiguratsioon.", - "title": "Tasmota" + } }, "confirm": { "description": "Kas soovid seadistada Tasmota sidumist?" diff --git a/homeassistant/components/tasmota/translations/fr.json b/homeassistant/components/tasmota/translations/fr.json index 901de884bcd..7521004ba2f 100644 --- a/homeassistant/components/tasmota/translations/fr.json +++ b/homeassistant/components/tasmota/translations/fr.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Pr\u00e9fixe du sujet de d\u00e9couverte" - }, - "description": "Veuillez entrer la configuration Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "Voulez-vous configurer Tasmota ?" diff --git a/homeassistant/components/tasmota/translations/he.json b/homeassistant/components/tasmota/translations/he.json index a2a5db62b37..2bc04cca267 100644 --- a/homeassistant/components/tasmota/translations/he.json +++ b/homeassistant/components/tasmota/translations/he.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "\u05d2\u05d9\u05dc\u05d5\u05d9 \u05dc\u05e4\u05d9 \u05e7\u05d9\u05d3\u05d5\u05de\u05ea \u05e0\u05d5\u05e9\u05d0" - }, - "description": "\u05e0\u05d0 \u05dc\u05d4\u05d6\u05d9\u05df \u05d0\u05ea \u05d0\u05ea \u05ea\u05e6\u05d5\u05e8\u05ea Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea Tasmota?" diff --git a/homeassistant/components/tasmota/translations/hu.json b/homeassistant/components/tasmota/translations/hu.json index 7c77caadc8e..990bde11d58 100644 --- a/homeassistant/components/tasmota/translations/hu.json +++ b/homeassistant/components/tasmota/translations/hu.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Felder\u00edt\u00e9si (discovery) topik el\u0151tagja" - }, - "description": "Adja meg a Tasmota konfigur\u00e1ci\u00f3t.", - "title": "Tasmota" + } }, "confirm": { "description": "Szeretn\u00e9 b\u00e1ll\u00edtani a Tasmota-t?" diff --git a/homeassistant/components/tasmota/translations/id.json b/homeassistant/components/tasmota/translations/id.json index a11acf50390..23ca02192db 100644 --- a/homeassistant/components/tasmota/translations/id.json +++ b/homeassistant/components/tasmota/translations/id.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Prefiks topik penemuan" - }, - "description": "Masukkan konfigurasi Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "Ingin menyiapkan Tasmota?" diff --git a/homeassistant/components/tasmota/translations/it.json b/homeassistant/components/tasmota/translations/it.json index 6cb52a616b0..22269eafd9f 100644 --- a/homeassistant/components/tasmota/translations/it.json +++ b/homeassistant/components/tasmota/translations/it.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Prefisso dell'argomento di individuazione" - }, - "description": "Inserire la configurazione Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "Vuoi configurare Tasmota?" diff --git a/homeassistant/components/tasmota/translations/ja.json b/homeassistant/components/tasmota/translations/ja.json index 353b3020e8c..8aad41de32f 100644 --- a/homeassistant/components/tasmota/translations/ja.json +++ b/homeassistant/components/tasmota/translations/ja.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "(\u691c\u51fa)Discovery topic prefix" - }, - "description": "Tasmota\u306e\u8a2d\u5b9a\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "Tasmota" + } }, "confirm": { "description": "Tasmota\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" diff --git a/homeassistant/components/tasmota/translations/ko.json b/homeassistant/components/tasmota/translations/ko.json index 45cac13f622..d647088c55d 100644 --- a/homeassistant/components/tasmota/translations/ko.json +++ b/homeassistant/components/tasmota/translations/ko.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "\uac80\uc0c9 \ud1a0\ud53d \uc811\ub450\uc0ac" - }, - "description": "Tasmota \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "Tasmota" + } }, "confirm": { "description": "Tasmota\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" diff --git a/homeassistant/components/tasmota/translations/lb.json b/homeassistant/components/tasmota/translations/lb.json index a7b8d6d0ce6..6208f54b7c0 100644 --- a/homeassistant/components/tasmota/translations/lb.json +++ b/homeassistant/components/tasmota/translations/lb.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Discovery topic prefix" - }, - "description": "F\u00ebll Tasmota Konfiguratioun aus.", - "title": "Tasmota" + } }, "confirm": { "description": "Soll Tasmota konfigur\u00e9iert ginn?" diff --git a/homeassistant/components/tasmota/translations/nl.json b/homeassistant/components/tasmota/translations/nl.json index da16eb72bc3..a116e840977 100644 --- a/homeassistant/components/tasmota/translations/nl.json +++ b/homeassistant/components/tasmota/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Is al geconfigureerd. Er is maar een configuratie mogelijk" + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "invalid_discovery_topic": "Invalid discovery topic prefix." @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Discovery-onderwerpvoorvoegsel" - }, - "description": "Vul de Tasmota gegevens in", - "title": "Tasmota" + } }, "confirm": { "description": "Wil je Tasmota instellen?" diff --git a/homeassistant/components/tasmota/translations/no.json b/homeassistant/components/tasmota/translations/no.json index 3c68c280086..7f0a67e7c9f 100644 --- a/homeassistant/components/tasmota/translations/no.json +++ b/homeassistant/components/tasmota/translations/no.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Prefiks for oppdagelsesemne" - }, - "description": "Vennligst skriv inn Tasmota-konfigurasjonen.", - "title": "" + } }, "confirm": { "description": "Vil du sette opp Tasmota?" diff --git a/homeassistant/components/tasmota/translations/pl.json b/homeassistant/components/tasmota/translations/pl.json index b6bbf3fe953..70ffeb5c7e2 100644 --- a/homeassistant/components/tasmota/translations/pl.json +++ b/homeassistant/components/tasmota/translations/pl.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Prefiks tematu wykrywania" - }, - "description": "Wprowad\u017a konfiguracj\u0119 dla Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" diff --git a/homeassistant/components/tasmota/translations/pt-BR.json b/homeassistant/components/tasmota/translations/pt-BR.json index 6fa1f064e88..afd1c76c25f 100644 --- a/homeassistant/components/tasmota/translations/pt-BR.json +++ b/homeassistant/components/tasmota/translations/pt-BR.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Prefixo do t\u00f3pico de descoberta" - }, - "description": "Por favor, insira a configura\u00e7\u00e3o do Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "Deseja configurar o Tasmota?" diff --git a/homeassistant/components/tasmota/translations/pt.json b/homeassistant/components/tasmota/translations/pt.json index 3df19f11fa9..508ab27abb0 100644 --- a/homeassistant/components/tasmota/translations/pt.json +++ b/homeassistant/components/tasmota/translations/pt.json @@ -10,8 +10,7 @@ "config": { "data": { "discovery_prefix": "Prefixo do t\u00f3pico para descoberta" - }, - "title": "" + } } } } diff --git a/homeassistant/components/tasmota/translations/ru.json b/homeassistant/components/tasmota/translations/ru.json index 4f01d164030..14e336a9b58 100644 --- a/homeassistant/components/tasmota/translations/ru.json +++ b/homeassistant/components/tasmota/translations/ru.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441 \u0442\u043e\u043f\u0438\u043a\u0430 \u0430\u0432\u0442\u043e\u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\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 Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Tasmota?" diff --git a/homeassistant/components/tasmota/translations/tr.json b/homeassistant/components/tasmota/translations/tr.json index 95e4e41b11e..71c38ef1bc0 100644 --- a/homeassistant/components/tasmota/translations/tr.json +++ b/homeassistant/components/tasmota/translations/tr.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "Ba\u015fl\u0131k Ke\u015ffet" - }, - "description": "L\u00fctfen Tasmota yap\u0131land\u0131rmas\u0131n\u0131 girin.", - "title": "Tasmota" + } }, "confirm": { "description": "Tasmota'y\u0131 kurmak istiyor musunuz?" diff --git a/homeassistant/components/tasmota/translations/uk.json b/homeassistant/components/tasmota/translations/uk.json index 5b57f950866..d1445e5c0e7 100644 --- a/homeassistant/components/tasmota/translations/uk.json +++ b/homeassistant/components/tasmota/translations/uk.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "\u041f\u0440\u0435\u0444\u0456\u043a\u0441 \u0442\u0435\u043c\u0438 \u0430\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f" - }, - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Tasmota.", - "title": "Tasmota" + } }, "confirm": { "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Tasmota?" diff --git a/homeassistant/components/tasmota/translations/zh-Hant.json b/homeassistant/components/tasmota/translations/zh-Hant.json index e823937f972..58833747f46 100644 --- a/homeassistant/components/tasmota/translations/zh-Hant.json +++ b/homeassistant/components/tasmota/translations/zh-Hant.json @@ -10,9 +10,7 @@ "config": { "data": { "discovery_prefix": "\u63a2\u7d22\u4e3b\u984c prefix" - }, - "description": "\u8acb\u8f38\u5165 Tasmota \u8a2d\u5b9a\u3002", - "title": "Tasmota" + } }, "confirm": { "description": "\u662f\u5426\u8981\u8a2d\u5b9a Tasmota\uff1f" diff --git a/homeassistant/components/tautulli/translations/bg.json b/homeassistant/components/tautulli/translations/bg.json new file mode 100644 index 00000000000..fb4e836f98d --- /dev/null +++ b/homeassistant/components/tautulli/translations/bg.json @@ -0,0 +1,26 @@ +{ + "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", + "single_instance_allowed": "\u0412\u0435\u0447\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", + "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": { + "reauth_confirm": { + "data": { + "api_key": "API \u043a\u043b\u044e\u0447" + } + }, + "user": { + "data": { + "api_key": "API \u043a\u043b\u044e\u0447", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/ca.json b/homeassistant/components/tautulli/translations/ca.json new file mode 100644 index 00000000000..6e53dd83be5 --- /dev/null +++ b/homeassistant/components/tautulli/translations/ca.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Clau API" + }, + "description": "Per trobar la teva clau API, obre la web de Tautulli i v\u00e9s a Configuraci\u00f3 i despr\u00e9s a Interf\u00edcie web. La clau API estar\u00e0 a la part inferior d'aquesta p\u00e0gina.", + "title": "Re-autenticaci\u00f3 de Tautulli" + }, + "user": { + "data": { + "api_key": "Clau API", + "url": "URL", + "verify_ssl": "Verifica el certificat SSL" + }, + "description": "Per trobar la teva clau API, obre la web de Tautulli i v\u00e9s a Configuraci\u00f3 i despr\u00e9s a Interf\u00edcie web. La clau API estar\u00e0 a la part inferior d'aquesta p\u00e0gina.\n\nExemple de l'URL: ```http://192.168.0.10:8181``` on 8181 \u00e9s el port predeterminat." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/es.json b/homeassistant/components/tautulli/translations/es.json new file mode 100644 index 00000000000..0d6fd1d3b9e --- /dev/null +++ b/homeassistant/components/tautulli/translations/es.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente", + "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." + }, + "error": { + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Clave API" + }, + "title": "Re-autenticaci\u00f3n de Tautulli" + }, + "user": { + "data": { + "url": "URL", + "verify_ssl": "Verifica el certificat SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/he.json b/homeassistant/components/tautulli/translations/he.json new file mode 100644 index 00000000000..80d0bba902b --- /dev/null +++ b/homeassistant/components/tautulli/translations/he.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7", + "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": { + "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_key": "\u05de\u05e4\u05ea\u05d7 API" + } + }, + "user": { + "data": { + "api_key": "\u05de\u05e4\u05ea\u05d7 API", + "url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8", + "verify_ssl": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8 SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/ja.json b/homeassistant/components/tautulli/translations/ja.json new file mode 100644 index 00000000000..6f733e1cad4 --- /dev/null +++ b/homeassistant/components/tautulli/translations/ja.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f", + "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", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API\u30ad\u30fc" + }, + "description": "API\u30ad\u30fc\u3092\u898b\u3064\u3051\u308b\u306b\u306f\u3001Tautulli Web\u30da\u30fc\u30b8\u3092\u958b\u304d\u3001Settings\u306b\u79fb\u52d5\u3057\u3066\u304b\u3089\u3001Web interface\u306b\u79fb\u52d5\u3057\u307e\u3059\u3002API\u30ad\u30fc\u306f\u3001\u305d\u306e\u30da\u30fc\u30b8\u306e\u4e00\u756a\u4e0b\u306b\u3042\u308a\u307e\u3059\u3002", + "title": "Tautulli\u3092\u518d\u8a8d\u8a3c\u3057\u307e\u3059" + }, + "user": { + "data": { + "api_key": "API\u30ad\u30fc", + "url": "URL", + "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" + }, + "description": "API\u30ad\u30fc\u3092\u898b\u3064\u3051\u308b\u306b\u306f\u3001Tautulli Web\u30da\u30fc\u30b8\u3092\u958b\u304d\u3001Settings\u306b\u79fb\u52d5\u3057\u3066\u304b\u3089\u3001Web interface\u306b\u79fb\u52d5\u3057\u307e\u3059\u3002API\u30ad\u30fc\u306f\u3001\u305d\u306e\u30da\u30fc\u30b8\u306e\u4e00\u756a\u4e0b\u306b\u3042\u308a\u307e\u3059\u3002\n\nURL\u306e\u4f8b: ```http://192.168.0.10:8181``` \u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u30dd\u30fc\u30c8\u306f\u30018181 \u3067\u3059\u3002" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/ko.json b/homeassistant/components/tautulli/translations/ko.json new file mode 100644 index 00000000000..effc12fad5a --- /dev/null +++ b/homeassistant/components/tautulli/translations/ko.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" + }, + "description": "API \ud0a4\ub97c \ucc3e\uc73c\ub824\uba74 Tautulli \uc6f9 \ud398\uc774\uc9c0\ub97c \uc5f4\uace0 \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud55c \ub2e4\uc74c \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\ub85c \uc774\ub3d9\ud569\ub2c8\ub2e4. API \ud0a4\ub294 \ud574\ub2f9 \ud398\uc774\uc9c0\uc758 \ub9e8 \uc544\ub798\uc5d0 \uc788\uc2b5\ub2c8\ub2e4.\n\nURL\uc758 \uc608: '''http://192.168.0.10:8181''''(\uae30\ubcf8\ud3ec\ud2b8: 8181)" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/nl.json b/homeassistant/components/tautulli/translations/nl.json index eeecbd56477..5e791e33a8f 100644 --- a/homeassistant/components/tautulli/translations/nl.json +++ b/homeassistant/components/tautulli/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "reauth_successful": "Herauthenticatie was succesvol", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "reauth_successful": "Herauthenticatie geslaagd", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -14,6 +14,7 @@ "data": { "api_key": "API-sleutel" }, + "description": "Om uw API sleutel te vinden, opent u de Tautulli webpagina en navigeert u naar Instellingen en vervolgens naar Webinterface. De API sleutel vindt u onderaan de pagina.", "title": "Herauthenticeer Tautulli" }, "user": { @@ -21,7 +22,8 @@ "api_key": "API-sleutel", "url": "URL", "verify_ssl": "SSL-certificaat verifi\u00ebren" - } + }, + "description": "Om uw API sleutel te vinden, opent u de Tautulli webpagina en navigeert u naar Instellingen en vervolgens naar Webinterface. De API sleutel vindt u onderaan de pagina.\n\nVoorbeeld van de URL: ```http://192.168.0.10:8181`` met 8181 als standaard poort." } } } diff --git a/homeassistant/components/tautulli/translations/sv.json b/homeassistant/components/tautulli/translations/sv.json new file mode 100644 index 00000000000..3354c6053dc --- /dev/null +++ b/homeassistant/components/tautulli/translations/sv.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "description": "F\u00f6r att hitta din API-nyckel, \u00f6ppna Tautullis webbsida och navigera till Inst\u00e4llningar och sedan till webbgr\u00e4nssnitt. API-nyckeln finns l\u00e4ngst ner p\u00e5 sidan." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/tr.json b/homeassistant/components/tautulli/translations/tr.json new file mode 100644 index 00000000000..b52d2a7abad --- /dev/null +++ b/homeassistant/components/tautulli/translations/tr.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "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", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API Anahtar\u0131" + }, + "description": "API anahtar\u0131n\u0131z\u0131 bulmak i\u00e7in Tautulli web sayfas\u0131n\u0131 a\u00e7\u0131n ve Ayarlar'a ve ard\u0131ndan Web aray\u00fcz\u00fcne gidin. API anahtar\u0131 o sayfan\u0131n alt\u0131nda olacakt\u0131r.", + "title": "Tautulli'yi yeniden do\u011frulay\u0131n" + }, + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "url": "URL", + "verify_ssl": "SSL sertifikas\u0131n\u0131 do\u011frulay\u0131n" + }, + "description": "API anahtar\u0131n\u0131z\u0131 bulmak i\u00e7in Tautulli web sayfas\u0131n\u0131 a\u00e7\u0131n ve Ayarlar'a ve ard\u0131ndan Web aray\u00fcz\u00fcne gidin. API anahtar\u0131 o sayfan\u0131n alt\u0131nda olacakt\u0131r. \n\n URL \u00f6rne\u011fi: ```http://192.168.0.10:8181``` 8181 varsay\u0131lan ba\u011flant\u0131 noktas\u0131d\u0131r." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index 572950b816b..92feb69c60c 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -10,7 +10,7 @@ from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_SCAN_INTERVAL from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later from homeassistant.helpers.typing import ConfigType @@ -84,7 +84,7 @@ async def async_new_client(hass, session, entry): _LOGGER.debug("Update interval %s seconds", interval) client = TelldusLiveClient(hass, entry, session, interval) hass.data[DOMAIN] = client - dev_reg = await hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(hass) for hub in await client.async_get_hubs(): _LOGGER.debug("Connected hub %s", hub["name"]) dev_reg.async_get_or_create( diff --git a/homeassistant/components/tellduslive/translations/es.json b/homeassistant/components/tellduslive/translations/es.json index 882e553d46e..5971c89f43e 100644 --- a/homeassistant/components/tellduslive/translations/es.json +++ b/homeassistant/components/tellduslive/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "TelldusLive ya est\u00e1 configurado", + "already_configured": "El servicio ya est\u00e1 configurado", "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n", "unknown": "Se produjo un error desconocido", "unknown_authorize_url_generation": "Error desconocido al generar una URL de autorizaci\u00f3n." diff --git a/homeassistant/components/tellduslive/translations/nl.json b/homeassistant/components/tellduslive/translations/nl.json index d0c03a341f0..07ba94774f1 100644 --- a/homeassistant/components/tellduslive/translations/nl.json +++ b/homeassistant/components/tellduslive/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", - "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "already_configured": "Dienst is al geconfigureerd", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", "unknown": "Onverwachte fout", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." }, diff --git a/homeassistant/components/tellstick/__init__.py b/homeassistant/components/tellstick/__init__.py index 8611c99b654..1007867362a 100644 --- a/homeassistant/components/tellstick/__init__.py +++ b/homeassistant/components/tellstick/__init__.py @@ -17,7 +17,10 @@ from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType @@ -147,8 +150,8 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: @callback def async_handle_callback(tellcore_id, tellcore_command, tellcore_data, cid): """Handle the actual callback from Tellcore.""" - hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_TELLCORE_CALLBACK, tellcore_id, tellcore_command, tellcore_data + async_dispatcher_send( + hass, SIGNAL_TELLCORE_CALLBACK, tellcore_id, tellcore_command, tellcore_data ) # Register callback diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index a1231708b91..47b51853bcd 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -19,6 +19,7 @@ from homeassistant.helpers import ( update_coordinator, ) from homeassistant.helpers.reload import async_reload_integration_platforms +from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_integration @@ -54,9 +55,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.bus.async_fire(f"event_{DOMAIN}_reloaded", context=call.context) - hass.helpers.service.async_register_admin_service( - DOMAIN, SERVICE_RELOAD, _reload_config - ) + async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, _reload_config) return True diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index 9d81dce28fe..a8a88c57bd3 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -230,7 +230,9 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity): self._state = state optimistic_set = True - await script.async_run({ATTR_CODE: code}, context=self._context) + await self.async_run_script( + script, run_variables={ATTR_CODE: code}, context=self._context + ) if optimistic_set: self.async_write_ha_state() diff --git a/homeassistant/components/template/button.py b/homeassistant/components/template/button.py index d6f41649734..ac83f76ca91 100644 --- a/homeassistant/components/template/button.py +++ b/homeassistant/components/template/button.py @@ -93,4 +93,4 @@ class TemplateButtonEntity(TemplateEntity, ButtonEntity): async def async_press(self) -> None: """Press the button.""" - await self._command_press.async_run(context=self._context) + await self.async_run_script(self._command_press, context=self._context) diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index e1df61bf4a2..82c5cc2578c 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -242,7 +242,7 @@ class CoverTemplate(TemplateEntity, CoverEntity): if state < 0 or state > 100: self._position = None _LOGGER.error( - "Cover position value must be" " between 0 and 100." " Value was: %.2f", + "Cover position value must be between 0 and 100. Value was: %.2f", state, ) else: @@ -323,10 +323,12 @@ class CoverTemplate(TemplateEntity, CoverEntity): async def async_open_cover(self, **kwargs): """Move the cover up.""" if self._open_script: - await self._open_script.async_run(context=self._context) + await self.async_run_script(self._open_script, context=self._context) elif self._position_script: - await self._position_script.async_run( - {"position": 100}, context=self._context + await self.async_run_script( + self._position_script, + run_variables={"position": 100}, + context=self._context, ) if self._optimistic: self._position = 100 @@ -335,10 +337,12 @@ class CoverTemplate(TemplateEntity, CoverEntity): async def async_close_cover(self, **kwargs): """Move the cover down.""" if self._close_script: - await self._close_script.async_run(context=self._context) + await self.async_run_script(self._close_script, context=self._context) elif self._position_script: - await self._position_script.async_run( - {"position": 0}, context=self._context + await self.async_run_script( + self._position_script, + run_variables={"position": 0}, + context=self._context, ) if self._optimistic: self._position = 0 @@ -347,13 +351,15 @@ class CoverTemplate(TemplateEntity, CoverEntity): async def async_stop_cover(self, **kwargs): """Fire the stop action.""" if self._stop_script: - await self._stop_script.async_run(context=self._context) + await self.async_run_script(self._stop_script, context=self._context) async def async_set_cover_position(self, **kwargs): """Set cover position.""" self._position = kwargs[ATTR_POSITION] - await self._position_script.async_run( - {"position": self._position}, context=self._context + await self.async_run_script( + self._position_script, + run_variables={"position": self._position}, + context=self._context, ) if self._optimistic: self.async_write_ha_state() @@ -361,8 +367,10 @@ class CoverTemplate(TemplateEntity, CoverEntity): async def async_open_cover_tilt(self, **kwargs): """Tilt the cover open.""" self._tilt_value = 100 - await self._tilt_script.async_run( - {"tilt": self._tilt_value}, context=self._context + await self.async_run_script( + self._tilt_script, + run_variables={"tilt": self._tilt_value}, + context=self._context, ) if self._tilt_optimistic: self.async_write_ha_state() @@ -370,8 +378,10 @@ class CoverTemplate(TemplateEntity, CoverEntity): async def async_close_cover_tilt(self, **kwargs): """Tilt the cover closed.""" self._tilt_value = 0 - await self._tilt_script.async_run( - {"tilt": self._tilt_value}, context=self._context + await self.async_run_script( + self._tilt_script, + run_variables={"tilt": self._tilt_value}, + context=self._context, ) if self._tilt_optimistic: self.async_write_ha_state() @@ -379,8 +389,10 @@ class CoverTemplate(TemplateEntity, CoverEntity): async def async_set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" self._tilt_value = kwargs[ATTR_TILT_POSITION] - await self._tilt_script.async_run( - {"tilt": self._tilt_value}, context=self._context + await self.async_run_script( + self._tilt_script, + run_variables={"tilt": self._tilt_value}, + context=self._context, ) if self._tilt_optimistic: self.async_write_ha_state() diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 6b0fdefc2f9..2c8d7247967 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -251,8 +251,9 @@ class TemplateFan(TemplateEntity, FanEntity): **kwargs, ) -> None: """Turn on the fan.""" - await self._on_script.async_run( - { + await self.async_run_script( + self._on_script, + run_variables={ ATTR_PERCENTAGE: percentage, ATTR_PRESET_MODE: preset_mode, }, @@ -267,7 +268,7 @@ class TemplateFan(TemplateEntity, FanEntity): async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the fan.""" - await self._off_script.async_run(context=self._context) + await self.async_run_script(self._off_script, context=self._context) self._state = STATE_OFF async def async_set_percentage(self, percentage: int) -> None: @@ -277,8 +278,10 @@ class TemplateFan(TemplateEntity, FanEntity): self._preset_mode = None if self._set_percentage_script: - await self._set_percentage_script.async_run( - {ATTR_PERCENTAGE: self._percentage}, context=self._context + await self.async_run_script( + self._set_percentage_script, + run_variables={ATTR_PERCENTAGE: self._percentage}, + context=self._context, ) async def async_set_preset_mode(self, preset_mode: str) -> None: @@ -297,8 +300,10 @@ class TemplateFan(TemplateEntity, FanEntity): self._percentage = None if self._set_preset_mode_script: - await self._set_preset_mode_script.async_run( - {ATTR_PRESET_MODE: self._preset_mode}, context=self._context + await self.async_run_script( + self._set_preset_mode_script, + run_variables={ATTR_PRESET_MODE: self._preset_mode}, + context=self._context, ) async def async_oscillate(self, oscillating: bool) -> None: @@ -307,8 +312,10 @@ class TemplateFan(TemplateEntity, FanEntity): return self._oscillating = oscillating - await self._set_oscillating_script.async_run( - {ATTR_OSCILLATING: oscillating}, context=self._context + await self.async_run_script( + self._set_oscillating_script, + run_variables={ATTR_OSCILLATING: self.oscillating}, + context=self._context, ) async def async_set_direction(self, direction: str) -> None: @@ -318,8 +325,10 @@ class TemplateFan(TemplateEntity, FanEntity): if direction in _VALID_DIRECTIONS: self._direction = direction - await self._set_direction_script.async_run( - {ATTR_DIRECTION: direction}, context=self._context + await self.async_run_script( + self._set_direction_script, + run_variables={ATTR_DIRECTION: direction}, + context=self._context, ) else: _LOGGER.error( diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 5a79b3db8fc..eafb0b3f4d0 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -16,10 +16,9 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, LightEntity, + LightEntityFeature, ) from homeassistant.const import ( CONF_ENTITY_ID, @@ -49,53 +48,58 @@ from .template_entity import ( _LOGGER = logging.getLogger(__name__) _VALID_STATES = [STATE_ON, STATE_OFF, "true", "false"] -CONF_ON_ACTION = "turn_on" -CONF_OFF_ACTION = "turn_off" -CONF_LEVEL_ACTION = "set_level" -CONF_LEVEL_TEMPLATE = "level_template" -CONF_TEMPERATURE_TEMPLATE = "temperature_template" -CONF_TEMPERATURE_ACTION = "set_temperature" -CONF_COLOR_TEMPLATE = "color_template" CONF_COLOR_ACTION = "set_color" -CONF_WHITE_VALUE_TEMPLATE = "white_value_template" -CONF_WHITE_VALUE_ACTION = "set_white_value" +CONF_COLOR_TEMPLATE = "color_template" CONF_EFFECT_ACTION = "set_effect" CONF_EFFECT_LIST_TEMPLATE = "effect_list_template" CONF_EFFECT_TEMPLATE = "effect_template" +CONF_LEVEL_ACTION = "set_level" +CONF_LEVEL_TEMPLATE = "level_template" CONF_MAX_MIREDS_TEMPLATE = "max_mireds_template" CONF_MIN_MIREDS_TEMPLATE = "min_mireds_template" +CONF_OFF_ACTION = "turn_off" +CONF_ON_ACTION = "turn_on" CONF_SUPPORTS_TRANSITION = "supports_transition_template" +CONF_TEMPERATURE_ACTION = "set_temperature" +CONF_TEMPERATURE_TEMPLATE = "temperature_template" +CONF_WHITE_VALUE_ACTION = "set_white_value" +CONF_WHITE_VALUE_TEMPLATE = "white_value_template" LIGHT_SCHEMA = vol.All( cv.deprecated(CONF_ENTITY_ID), vol.Schema( { - vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, - vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_LEVEL_ACTION): cv.SCRIPT_SCHEMA, - vol.Optional(CONF_LEVEL_TEMPLATE): cv.template, - vol.Optional(CONF_FRIENDLY_NAME): cv.string, - vol.Optional(CONF_ENTITY_ID): cv.entity_ids, - vol.Optional(CONF_TEMPERATURE_TEMPLATE): cv.template, - vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA, - vol.Optional(CONF_COLOR_TEMPLATE): cv.template, vol.Optional(CONF_COLOR_ACTION): cv.SCRIPT_SCHEMA, - vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_WHITE_VALUE_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_COLOR_TEMPLATE): cv.template, + vol.Inclusive(CONF_EFFECT_ACTION, "effect"): cv.SCRIPT_SCHEMA, vol.Inclusive(CONF_EFFECT_LIST_TEMPLATE, "effect"): cv.template, vol.Inclusive(CONF_EFFECT_TEMPLATE, "effect"): cv.template, - vol.Inclusive(CONF_EFFECT_ACTION, "effect"): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_FRIENDLY_NAME): cv.string, + vol.Optional(CONF_LEVEL_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_LEVEL_TEMPLATE): cv.template, vol.Optional(CONF_MAX_MIREDS_TEMPLATE): cv.template, vol.Optional(CONF_MIN_MIREDS_TEMPLATE): cv.template, + vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA, + vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_SUPPORTS_TRANSITION): cv.template, + vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_TEMPERATURE_TEMPLATE): cv.template, vol.Optional(CONF_UNIQUE_ID): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_WHITE_VALUE_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template, } ).extend(TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY.schema), ) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Required(CONF_LIGHTS): cv.schema_with_slug_keys(LIGHT_SCHEMA)} +PLATFORM_SCHEMA = vol.All( + # CONF_WHITE_VALUE_* is deprecated, support will be removed in release 2022.9 + cv.deprecated(CONF_WHITE_VALUE_ACTION), + cv.deprecated(CONF_WHITE_VALUE_TEMPLATE), + PLATFORM_SCHEMA.extend( + {vol.Required(CONF_LIGHTS): cv.schema_with_slug_keys(LIGHT_SCHEMA)} + ), ) @@ -249,9 +253,9 @@ class LightTemplate(TemplateEntity, LightEntity): if self._white_value_script is not None: supported_features |= SUPPORT_WHITE_VALUE if self._effect_script is not None: - supported_features |= SUPPORT_EFFECT + supported_features |= LightEntityFeature.EFFECT if self._supports_transition is True: - supported_features |= SUPPORT_TRANSITION + supported_features |= LightEntityFeature.TRANSITION return supported_features @property @@ -348,25 +352,37 @@ class LightTemplate(TemplateEntity, LightEntity): optimistic_set = True if self._level_template is None and ATTR_BRIGHTNESS in kwargs: - _LOGGER.info( + _LOGGER.debug( "Optimistically setting brightness to %s", kwargs[ATTR_BRIGHTNESS] ) self._brightness = kwargs[ATTR_BRIGHTNESS] optimistic_set = True if self._white_value_template is None and ATTR_WHITE_VALUE in kwargs: - _LOGGER.info( + _LOGGER.debug( "Optimistically setting white value to %s", kwargs[ATTR_WHITE_VALUE] ) self._white_value = kwargs[ATTR_WHITE_VALUE] optimistic_set = True if self._temperature_template is None and ATTR_COLOR_TEMP in kwargs: - _LOGGER.info( + _LOGGER.debug( "Optimistically setting color temperature to %s", kwargs[ATTR_COLOR_TEMP], ) self._temperature = kwargs[ATTR_COLOR_TEMP] + if self._color_template is None: + self._color = None + optimistic_set = True + + if self._color_template is None and ATTR_HS_COLOR in kwargs: + _LOGGER.debug( + "Optimistically setting color to %s", + kwargs[ATTR_HS_COLOR], + ) + self._color = kwargs[ATTR_HS_COLOR] + if self._temperature_template is None: + self._temperature = None optimistic_set = True common_params = {} @@ -380,14 +396,18 @@ class LightTemplate(TemplateEntity, LightEntity): if ATTR_COLOR_TEMP in kwargs and self._temperature_script: common_params["color_temp"] = kwargs[ATTR_COLOR_TEMP] - await self._temperature_script.async_run( - common_params, context=self._context + await self.async_run_script( + self._temperature_script, + run_variables=common_params, + context=self._context, ) elif ATTR_WHITE_VALUE in kwargs and self._white_value_script: common_params["white_value"] = kwargs[ATTR_WHITE_VALUE] - await self._white_value_script.async_run( - common_params, context=self._context + await self.async_run_script( + self._white_value_script, + run_variables=common_params, + context=self._context, ) elif ATTR_EFFECT in kwargs and self._effect_script: effect = kwargs[ATTR_EFFECT] @@ -402,21 +422,26 @@ class LightTemplate(TemplateEntity, LightEntity): common_params["effect"] = effect - await self._effect_script.async_run(common_params, context=self._context) + await self.async_run_script( + self._effect_script, run_variables=common_params, context=self._context + ) elif ATTR_HS_COLOR in kwargs and self._color_script: hs_value = kwargs[ATTR_HS_COLOR] common_params["hs"] = hs_value common_params["h"] = int(hs_value[0]) common_params["s"] = int(hs_value[1]) - await self._color_script.async_run( - common_params, - context=self._context, + await self.async_run_script( + self._color_script, run_variables=common_params, context=self._context ) elif ATTR_BRIGHTNESS in kwargs and self._level_script: - await self._level_script.async_run(common_params, context=self._context) + await self.async_run_script( + self._level_script, run_variables=common_params, context=self._context + ) else: - await self._on_script.async_run(common_params, context=self._context) + await self.async_run_script( + self._on_script, run_variables=common_params, context=self._context + ) if optimistic_set: self.async_write_ha_state() @@ -424,11 +449,13 @@ class LightTemplate(TemplateEntity, LightEntity): async def async_turn_off(self, **kwargs): """Turn the light off.""" if ATTR_TRANSITION in kwargs and self._supports_transition is True: - await self._off_script.async_run( - {"transition": kwargs[ATTR_TRANSITION]}, context=self._context + await self.async_run_script( + self._off_script, + run_variables={"transition": kwargs[ATTR_TRANSITION]}, + context=self._context, ) else: - await self._off_script.async_run(context=self._context) + await self.async_run_script(self._off_script, context=self._context) if self._template is None: self._state = False self.async_write_ha_state() diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index 78141c0d25e..1d94194be63 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -141,11 +141,11 @@ class TemplateLock(TemplateEntity, LockEntity): if self._optimistic: self._state = True self.async_write_ha_state() - await self._command_lock.async_run(context=self._context) + await self.async_run_script(self._command_lock, context=self._context) async def async_unlock(self, **kwargs): """Unlock the device.""" if self._optimistic: self._state = False self.async_write_ha_state() - await self._command_unlock.async_run(context=self._context) + await self.async_run_script(self._command_unlock, context=self._context) diff --git a/homeassistant/components/template/number.py b/homeassistant/components/template/number.py index 89adb45a6d0..54131990a26 100644 --- a/homeassistant/components/template/number.py +++ b/homeassistant/components/template/number.py @@ -157,8 +157,10 @@ class TemplateNumber(TemplateEntity, NumberEntity): if self._optimistic: self._attr_value = value self.async_write_ha_state() - await self._command_set_value.async_run( - {ATTR_VALUE: value}, context=self._context + await self.async_run_script( + self._command_set_value, + run_variables={ATTR_VALUE: value}, + context=self._context, ) diff --git a/homeassistant/components/template/select.py b/homeassistant/components/template/select.py index fa2668c1e81..19f17096178 100644 --- a/homeassistant/components/template/select.py +++ b/homeassistant/components/template/select.py @@ -133,8 +133,10 @@ class TemplateSelect(TemplateEntity, SelectEntity): if self._optimistic: self._attr_current_option = option self.async_write_ha_state() - await self._command_select_option.async_run( - {ATTR_OPTION: option}, context=self._context + await self.async_run_script( + self._command_select_option, + run_variables={ATTR_OPTION: option}, + context=self._context, ) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 8a3955db007..ac01bc66812 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -156,14 +156,14 @@ class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity): async def async_turn_on(self, **kwargs): """Fire the on action.""" - await self._on_script.async_run(context=self._context) + await self.async_run_script(self._on_script, context=self._context) if self._template is None: self._state = True self.async_write_ha_state() async def async_turn_off(self, **kwargs): """Fire the off action.""" - await self._off_script.async_run(context=self._context) + await self.async_run_script(self._off_script, context=self._context) if self._template is None: self._state = False self.async_write_ha_state() diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index a6d1cba78e1..6e0b7f6f48f 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -19,7 +19,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, STATE_UNKNOWN, ) -from homeassistant.core import CoreState, Event, State, callback +from homeassistant.core import Context, CoreState, Event, State, callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -28,6 +28,7 @@ from homeassistant.helpers.event import ( TrackTemplateResult, async_track_template_result, ) +from homeassistant.helpers.script import Script, _VarsType from homeassistant.helpers.template import ( Template, TemplateStateFromEntityId, @@ -455,3 +456,21 @@ class TemplateEntity(Entity): async def async_update(self) -> None: """Call for forced update.""" self._async_update() + + async def async_run_script( + self, + script: Script, + *, + run_variables: _VarsType | None = None, + context: Context | None = None, + ) -> None: + """Run an action script.""" + if run_variables is None: + run_variables = {} + return await script.async_run( + run_variables={ + "this": TemplateStateFromEntityId(self.hass, self.entity_id), + **run_variables, + }, + context=context, + ) diff --git a/homeassistant/components/template/trigger.py b/homeassistant/components/template/trigger.py index d2e50de53fd..33ac90079b7 100644 --- a/homeassistant/components/template/trigger.py +++ b/homeassistant/components/template/trigger.py @@ -4,15 +4,20 @@ import logging import voluptuous as vol from homeassistant import exceptions +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import CONF_FOR, CONF_PLATFORM, CONF_VALUE_TEMPLATE -from homeassistant.core import HassJob, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers.event import ( TrackTemplate, async_call_later, async_track_template_result, ) -from homeassistant.helpers.template import result_as_boolean +from homeassistant.helpers.template import Template, result_as_boolean +from homeassistant.helpers.typing import ConfigType # mypy: allow-untyped-defs, no-check-untyped-defs @@ -28,11 +33,16 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( async def async_attach_trigger( - hass, config, action, automation_info, *, platform_type="template" -): + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, + *, + platform_type: str = "template", +) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" trigger_data = automation_info["trigger_data"] - value_template = config.get(CONF_VALUE_TEMPLATE) + value_template: Template = config[CONF_VALUE_TEMPLATE] value_template.hass = hass time_delta = config.get(CONF_FOR) template.attach(hass, time_delta) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 1d350d120c7..4b278ef6aec 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -204,42 +204,42 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity): async def async_start(self): """Start or resume the cleaning task.""" - await self._start_script.async_run(context=self._context) + await self.async_run_script(self._start_script, context=self._context) async def async_pause(self): """Pause the cleaning task.""" if self._pause_script is None: return - await self._pause_script.async_run(context=self._context) + await self.async_run_script(self._pause_script, context=self._context) async def async_stop(self, **kwargs): """Stop the cleaning task.""" if self._stop_script is None: return - await self._stop_script.async_run(context=self._context) + await self.async_run_script(self._stop_script, context=self._context) async def async_return_to_base(self, **kwargs): """Set the vacuum cleaner to return to the dock.""" if self._return_to_base_script is None: return - await self._return_to_base_script.async_run(context=self._context) + await self.async_run_script(self._return_to_base_script, context=self._context) async def async_clean_spot(self, **kwargs): """Perform a spot clean-up.""" if self._clean_spot_script is None: return - await self._clean_spot_script.async_run(context=self._context) + await self.async_run_script(self._clean_spot_script, context=self._context) async def async_locate(self, **kwargs): """Locate the vacuum cleaner.""" if self._locate_script is None: return - await self._locate_script.async_run(context=self._context) + await self.async_run_script(self._locate_script, context=self._context) async def async_set_fan_speed(self, fan_speed, **kwargs): """Set fan speed.""" @@ -248,8 +248,10 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity): 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 + await self.async_run_script( + self._set_fan_speed_script, + run_variables={ATTR_FAN_SPEED: fan_speed}, + context=self._context, ) else: _LOGGER.error( diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index 0f53dd61cb4..efd4f3d76d0 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.6", - "pillow==9.1.0" + "pillow==9.1.1" ], "codeowners": [], "iot_class": "local_polling", diff --git a/homeassistant/components/threshold/translations/ca.json b/homeassistant/components/threshold/translations/ca.json index b33bd8a04ed..b933b85b080 100644 --- a/homeassistant/components/threshold/translations/ca.json +++ b/homeassistant/components/threshold/translations/ca.json @@ -9,7 +9,6 @@ "entity_id": "Sensor d'entrada", "hysteresis": "Hist\u00e8resi", "lower": "L\u00edmit inferior", - "mode": "Mode llindar", "name": "Nom", "upper": "L\u00edmit superior" }, @@ -28,7 +27,6 @@ "entity_id": "Sensor d'entrada", "hysteresis": "Hist\u00e8resi", "lower": "L\u00edmit inferior", - "mode": "Mode llindar", "name": "Nom", "upper": "L\u00edmit superior" }, diff --git a/homeassistant/components/threshold/translations/de.json b/homeassistant/components/threshold/translations/de.json index 579dd4f49b0..f8f805742ca 100644 --- a/homeassistant/components/threshold/translations/de.json +++ b/homeassistant/components/threshold/translations/de.json @@ -9,7 +9,6 @@ "entity_id": "Eingangssensor", "hysteresis": "Hysterese", "lower": "Untere Grenze", - "mode": "Schwellenwertmodus", "name": "Name", "upper": "Obergrenze" }, @@ -28,7 +27,6 @@ "entity_id": "Eingangssensor", "hysteresis": "Hysterese", "lower": "Untere Grenze", - "mode": "Schwellenwertmodus", "name": "Name", "upper": "Obergrenze" }, diff --git a/homeassistant/components/threshold/translations/el.json b/homeassistant/components/threshold/translations/el.json index 2ad5f25e5f8..11735c53908 100644 --- a/homeassistant/components/threshold/translations/el.json +++ b/homeassistant/components/threshold/translations/el.json @@ -9,7 +9,6 @@ "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" }, @@ -28,7 +27,6 @@ "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" }, diff --git a/homeassistant/components/threshold/translations/en.json b/homeassistant/components/threshold/translations/en.json index 66a2bb33ddb..461ca244353 100644 --- a/homeassistant/components/threshold/translations/en.json +++ b/homeassistant/components/threshold/translations/en.json @@ -9,7 +9,6 @@ "entity_id": "Input sensor", "hysteresis": "Hysteresis", "lower": "Lower limit", - "mode": "Threshold mode", "name": "Name", "upper": "Upper limit" }, @@ -28,7 +27,6 @@ "entity_id": "Input sensor", "hysteresis": "Hysteresis", "lower": "Lower limit", - "mode": "Threshold mode", "name": "Name", "upper": "Upper limit" }, diff --git a/homeassistant/components/threshold/translations/es.json b/homeassistant/components/threshold/translations/es.json new file mode 100644 index 00000000000..0200c840243 --- /dev/null +++ b/homeassistant/components/threshold/translations/es.json @@ -0,0 +1,24 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_id": "Sensor de entrada", + "hysteresis": "Hist\u00e9resis", + "lower": "L\u00edmite inferior", + "upper": "L\u00edmite superior" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "hysteresis": "Hist\u00e9resis" + } + } + } + }, + "title": "Sensor de umbral" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/et.json b/homeassistant/components/threshold/translations/et.json index 7a41c0bfc3f..c98f370e47d 100644 --- a/homeassistant/components/threshold/translations/et.json +++ b/homeassistant/components/threshold/translations/et.json @@ -9,7 +9,6 @@ "entity_id": "Sisendandur", "hysteresis": "H\u00fcsterees", "lower": "Alampiir", - "mode": "L\u00e4vendi kasutamine", "name": "Nimi", "upper": "\u00dclempiir" }, @@ -28,7 +27,6 @@ "entity_id": "Sisendandur", "hysteresis": "H\u00fcsterees", "lower": "Alampiir", - "mode": "L\u00e4vendi kasutamine", "name": "Nimi", "upper": "\u00dclempiir" }, diff --git a/homeassistant/components/threshold/translations/fr.json b/homeassistant/components/threshold/translations/fr.json index 29bc21e9cd5..8c6015f29a7 100644 --- a/homeassistant/components/threshold/translations/fr.json +++ b/homeassistant/components/threshold/translations/fr.json @@ -9,7 +9,6 @@ "entity_id": "Capteur d'entr\u00e9e", "hysteresis": "Hyst\u00e9r\u00e9sis", "lower": "Limite inf\u00e9rieure", - "mode": "Mode de seuil", "name": "Nom", "upper": "Limite sup\u00e9rieure" }, @@ -28,7 +27,6 @@ "entity_id": "Capteur d'entr\u00e9e", "hysteresis": "Hyst\u00e9r\u00e9sis", "lower": "Limite inf\u00e9rieure", - "mode": "Mode de seuil", "name": "Nom", "upper": "Limite sup\u00e9rieure" }, diff --git a/homeassistant/components/threshold/translations/hu.json b/homeassistant/components/threshold/translations/hu.json index 944a13680d3..3c582792d69 100644 --- a/homeassistant/components/threshold/translations/hu.json +++ b/homeassistant/components/threshold/translations/hu.json @@ -9,7 +9,6 @@ "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" }, @@ -28,7 +27,6 @@ "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" }, diff --git a/homeassistant/components/threshold/translations/id.json b/homeassistant/components/threshold/translations/id.json index 7356dd772e1..474f742324b 100644 --- a/homeassistant/components/threshold/translations/id.json +++ b/homeassistant/components/threshold/translations/id.json @@ -9,7 +9,6 @@ "entity_id": "Sensor input", "hysteresis": "Histeresis", "lower": "Batas bawah", - "mode": "Mode ambang batas", "name": "Nama", "upper": "Batas atas" }, @@ -28,7 +27,6 @@ "entity_id": "Sensor input", "hysteresis": "Histeresis", "lower": "Batas bawah", - "mode": "Mode ambang batas", "name": "Nama", "upper": "Batas atas" }, diff --git a/homeassistant/components/threshold/translations/it.json b/homeassistant/components/threshold/translations/it.json index 6bc62dce765..79c0e24b2af 100644 --- a/homeassistant/components/threshold/translations/it.json +++ b/homeassistant/components/threshold/translations/it.json @@ -9,7 +9,6 @@ "entity_id": "Sensore di ingresso", "hysteresis": "Isteresi", "lower": "Limite inferiore", - "mode": "Modalit\u00e0 soglia", "name": "Nome", "upper": "Limite superiore" }, @@ -28,7 +27,6 @@ "entity_id": "Sensore di ingresso", "hysteresis": "Isteresi", "lower": "Limite inferiore", - "mode": "Modalit\u00e0 soglia", "name": "Nome", "upper": "Limite superiore" }, diff --git a/homeassistant/components/threshold/translations/ja.json b/homeassistant/components/threshold/translations/ja.json index 7d5a6cc2ce7..1978fa4f3c5 100644 --- a/homeassistant/components/threshold/translations/ja.json +++ b/homeassistant/components/threshold/translations/ja.json @@ -1,7 +1,7 @@ { "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" + "need_lower_upper": "\u4e0b\u9650\u3068\u4e0a\u9650\u306e\u4e21\u65b9\u3092\u7a7a\u767d\u306b\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093" }, "step": { "user": { @@ -9,7 +9,6 @@ "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" }, @@ -20,7 +19,7 @@ }, "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" + "need_lower_upper": "\u4e0b\u9650\u3068\u4e0a\u9650\u306e\u4e21\u65b9\u3092\u7a7a\u767d\u306b\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093" }, "step": { "init": { @@ -28,7 +27,6 @@ "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" }, diff --git a/homeassistant/components/threshold/translations/ko.json b/homeassistant/components/threshold/translations/ko.json new file mode 100644 index 00000000000..fb30f1e5086 --- /dev/null +++ b/homeassistant/components/threshold/translations/ko.json @@ -0,0 +1,38 @@ +{ + "config": { + "error": { + "need_lower_upper": "\ud558\ud55c\uacfc \uc0c1\ud55c\uc744 \ubaa8\ub450 \ube44\uc6cc\ub458 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "entity_id": "\uc785\ub825 \uc13c\uc11c", + "hysteresis": "\ud788\uc2a4\ud14c\ub9ac\uc2dc\uc2a4", + "lower": "\ud558\ud55c", + "name": "\uc774\ub984", + "upper": "\uc0c1\ud55c" + }, + "description": "\uc13c\uc11c \uac12\uc5d0 \ub530\ub77c \ucf1c\uc9c0\uace0 \uaebc\uc9c0\ub294 \uc774\uc9c4 \uc13c\uc11c\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.\n\n\ud558\ud55c\ub9cc \uad6c\uc131\ub41c \uacbd\uc6b0 - \uc13c\uc11c \uac12\uc774 \ud558\ud55c \uc774\ud558\uc77c \ub54c \ucf1c\uc9d1\ub2c8\ub2e4.\n\uc0c1\ud55c\ub9cc \uad6c\uc131\ub41c \uacbd\uc6b0 - \uc13c\uc11c \uac12\uc774 \uc0c1\ud55c \uc774\uc0c1\uc77c \ub54c \ucf1c\uc9d1\ub2c8\ub2e4.\n\uc0c1\ud55c, \ud558\ud55c \ubaa8\ub450 \uad6c\uc131\ub41c \uacbd\uc6b0 - \uc13c\uc11c \uac12\uc774 [\ud558\ud55c .. \uc0c1\ud55c] \ubc94\uc704\uc77c \ub54c \ucf1c\uc9d1\ub2c8\ub2e4.", + "title": "\uc784\uacc4\uac12 \uc13c\uc11c \ucd94\uac00" + } + } + }, + "options": { + "error": { + "need_lower_upper": "\ud558\ud55c\uacfc \uc0c1\ud55c\uc744 \ubaa8\ub450 \ube44\uc6cc\ub458 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "step": { + "init": { + "data": { + "entity_id": "\uc785\ub825 \uc13c\uc11c", + "hysteresis": "\ud788\uc2a4\ud14c\ub9ac\uc2dc\uc2a4", + "lower": "\ud558\ud55c", + "name": "\uc774\ub984", + "upper": "\uc0c1\ud55c" + }, + "description": "\ud558\ud55c\ub9cc \uad6c\uc131\ub41c \uacbd\uc6b0 - \uc13c\uc11c \uac12\uc774 \ud558\ud55c \uc774\ud558\uc77c \ub54c \ucf1c\uc9d1\ub2c8\ub2e4.\n\uc0c1\ud55c\ub9cc \uad6c\uc131\ub41c \uacbd\uc6b0 - \uc13c\uc11c \uac12\uc774 \uc0c1\ud55c \uc774\uc0c1\uc77c \ub54c \ucf1c\uc9d1\ub2c8\ub2e4.\n\uc0c1\ud55c, \ud558\ud55c \ubaa8\ub450 \uad6c\uc131\ub41c \uacbd\uc6b0 - \uc13c\uc11c \uac12\uc774 [\ud558\ud55c .. \uc0c1\ud55c] \ubc94\uc704\uc77c \ub54c \ucf1c\uc9d1\ub2c8\ub2e4." + } + } + }, + "title": "\uc784\uacc4\uac12 \uc13c\uc11c" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/nl.json b/homeassistant/components/threshold/translations/nl.json index 73af8fcc8b7..3bcf8bf599e 100644 --- a/homeassistant/components/threshold/translations/nl.json +++ b/homeassistant/components/threshold/translations/nl.json @@ -9,12 +9,11 @@ "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" + "title": "Drempelwaardesensor toevoegen" } } }, @@ -28,7 +27,6 @@ "entity_id": "Invoer sensor", "hysteresis": "Hyseterise", "lower": "Ondergrens", - "mode": "Drempelmodus", "name": "Naam", "upper": "Bovengrens" }, @@ -36,5 +34,5 @@ } } }, - "title": "Drempelsensor" + "title": "Drempelwaardesensor" } \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/no.json b/homeassistant/components/threshold/translations/no.json index 66c8461d8cc..1800e628f31 100644 --- a/homeassistant/components/threshold/translations/no.json +++ b/homeassistant/components/threshold/translations/no.json @@ -9,7 +9,6 @@ "entity_id": "Inngangssensor", "hysteresis": "Hysterese", "lower": "Nedre grense", - "mode": "Terskelverdi-modus", "name": "Navn", "upper": "\u00d8vre grense" }, @@ -28,7 +27,6 @@ "entity_id": "Inngangssensor", "hysteresis": "Hysterese", "lower": "Nedre grense", - "mode": "Terskelverdi-modus", "name": "Navn", "upper": "\u00d8vre grense" }, diff --git a/homeassistant/components/threshold/translations/pl.json b/homeassistant/components/threshold/translations/pl.json index 5db231947c2..e8f04d4a00b 100644 --- a/homeassistant/components/threshold/translations/pl.json +++ b/homeassistant/components/threshold/translations/pl.json @@ -9,7 +9,6 @@ "entity_id": "Sensor wej\u015bciowy", "hysteresis": "Op\u00f3\u017anienie", "lower": "Dolny limit", - "mode": "Tryb progowy", "name": "Nazwa", "upper": "G\u00f3rny limit" }, @@ -28,7 +27,6 @@ "entity_id": "Sensor wej\u015bciowy", "hysteresis": "Op\u00f3\u017anienie", "lower": "Dolny limit", - "mode": "Tryb progowy", "name": "Nazwa", "upper": "G\u00f3rny limit" }, diff --git a/homeassistant/components/threshold/translations/pt-BR.json b/homeassistant/components/threshold/translations/pt-BR.json index 1aa7358086a..fb919043313 100644 --- a/homeassistant/components/threshold/translations/pt-BR.json +++ b/homeassistant/components/threshold/translations/pt-BR.json @@ -9,7 +9,6 @@ "entity_id": "Sensor de entrada", "hysteresis": "Histerese", "lower": "Limite inferior", - "mode": "Modo Threshold", "name": "Nome", "upper": "Limite superior" }, @@ -28,7 +27,6 @@ "entity_id": "Sensor de entrada", "hysteresis": "Histerese", "lower": "Limite inferior", - "mode": "Modo Threshold", "name": "Nome", "upper": "Limite superior" }, diff --git a/homeassistant/components/threshold/translations/ru.json b/homeassistant/components/threshold/translations/ru.json index 5b8c4546823..f1be2c4f26c 100644 --- a/homeassistant/components/threshold/translations/ru.json +++ b/homeassistant/components/threshold/translations/ru.json @@ -9,7 +9,6 @@ "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" }, @@ -28,7 +27,6 @@ "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" }, diff --git a/homeassistant/components/threshold/translations/tr.json b/homeassistant/components/threshold/translations/tr.json index 0cb338ef681..0954ea81d3f 100644 --- a/homeassistant/components/threshold/translations/tr.json +++ b/homeassistant/components/threshold/translations/tr.json @@ -9,7 +9,6 @@ "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" }, @@ -28,7 +27,6 @@ "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" }, diff --git a/homeassistant/components/threshold/translations/zh-Hans.json b/homeassistant/components/threshold/translations/zh-Hans.json index 35bc919f0d1..ae46afaa740 100644 --- a/homeassistant/components/threshold/translations/zh-Hans.json +++ b/homeassistant/components/threshold/translations/zh-Hans.json @@ -9,7 +9,6 @@ "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" }, @@ -28,7 +27,6 @@ "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" }, diff --git a/homeassistant/components/threshold/translations/zh-Hant.json b/homeassistant/components/threshold/translations/zh-Hant.json index 66bdfe6915a..45ff245bb02 100644 --- a/homeassistant/components/threshold/translations/zh-Hant.json +++ b/homeassistant/components/threshold/translations/zh-Hant.json @@ -9,7 +9,6 @@ "entity_id": "\u8f38\u5165\u611f\u6e2c\u5668", "hysteresis": "\u9072\u6eef", "lower": "\u4e0b\u9650", - "mode": "\u81e8\u754c\u9ede\u5f0f", "name": "\u540d\u7a31", "upper": "\u4e0a\u9650" }, @@ -28,7 +27,6 @@ "entity_id": "\u8f38\u5165\u611f\u6e2c\u5668", "hysteresis": "\u9072\u6eef", "lower": "\u4e0b\u9650", - "mode": "\u81e8\u754c\u9ede\u5f0f", "name": "\u540d\u7a31", "upper": "\u4e0a\u9650" }, diff --git a/homeassistant/components/tibber/translations/ca.json b/homeassistant/components/tibber/translations/ca.json index bfeb2416e57..c149bd67098 100644 --- a/homeassistant/components/tibber/translations/ca.json +++ b/homeassistant/components/tibber/translations/ca.json @@ -13,8 +13,7 @@ "data": { "access_token": "Token d'acc\u00e9s" }, - "description": "Introdueix el token d'acc\u00e9s de https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "Introdueix el token d'acc\u00e9s de https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/cs.json b/homeassistant/components/tibber/translations/cs.json index 278300caa53..441c716c0a0 100644 --- a/homeassistant/components/tibber/translations/cs.json +++ b/homeassistant/components/tibber/translations/cs.json @@ -12,8 +12,7 @@ "user": { "data": { "access_token": "P\u0159\u00edstupov\u00fd token" - }, - "title": "Tibber" + } } } } diff --git a/homeassistant/components/tibber/translations/de.json b/homeassistant/components/tibber/translations/de.json index f3f722ae835..2d8c853fcfd 100644 --- a/homeassistant/components/tibber/translations/de.json +++ b/homeassistant/components/tibber/translations/de.json @@ -13,8 +13,7 @@ "data": { "access_token": "Zugangstoken" }, - "description": "Gib dein Zugangsk\u00fcrzel von https://developer.tibber.com/settings/accesstoken ein.", - "title": "Tibber" + "description": "Gib dein Zugangsk\u00fcrzel von https://developer.tibber.com/settings/accesstoken ein." } } } diff --git a/homeassistant/components/tibber/translations/el.json b/homeassistant/components/tibber/translations/el.json index 0ad70ba6ac7..ea4cda01a8c 100644 --- a/homeassistant/components/tibber/translations/el.json +++ b/homeassistant/components/tibber/translations/el.json @@ -13,8 +13,7 @@ "data": { "access_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" }, - "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/en.json b/homeassistant/components/tibber/translations/en.json index 63df31dca07..64c8cf0ab1e 100644 --- a/homeassistant/components/tibber/translations/en.json +++ b/homeassistant/components/tibber/translations/en.json @@ -13,8 +13,7 @@ "data": { "access_token": "Access Token" }, - "description": "Enter your access token from https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "Enter your access token from https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/es.json b/homeassistant/components/tibber/translations/es.json index cdf80422023..0840339ac24 100644 --- a/homeassistant/components/tibber/translations/es.json +++ b/homeassistant/components/tibber/translations/es.json @@ -13,8 +13,7 @@ "data": { "access_token": "Token de acceso" }, - "description": "Introduzca su token de acceso desde https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "Introduzca su token de acceso desde https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/et.json b/homeassistant/components/tibber/translations/et.json index 5edaac058c6..c00ab6821ad 100644 --- a/homeassistant/components/tibber/translations/et.json +++ b/homeassistant/components/tibber/translations/et.json @@ -13,8 +13,7 @@ "data": { "access_token": "Juurdep\u00e4\u00e4sut\u00f5end" }, - "description": "Sisesta oma juurdep\u00e4\u00e4suluba saidilt https://developer.tibber.com/settings/accesstoken", - "title": "" + "description": "Sisesta oma juurdep\u00e4\u00e4suluba saidilt https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/fi.json b/homeassistant/components/tibber/translations/fi.json deleted file mode 100644 index 87e0bef29b2..00000000000 --- a/homeassistant/components/tibber/translations/fi.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "Tibber" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/fr.json b/homeassistant/components/tibber/translations/fr.json index 256516c44a6..1834878d731 100644 --- a/homeassistant/components/tibber/translations/fr.json +++ b/homeassistant/components/tibber/translations/fr.json @@ -13,8 +13,7 @@ "data": { "access_token": "Jeton d'acc\u00e8s" }, - "description": "Entrez votre jeton d'acc\u00e8s depuis https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "Entrez votre jeton d'acc\u00e8s depuis https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/he.json b/homeassistant/components/tibber/translations/he.json index 3deeeef2e9c..d599be8e8cd 100644 --- a/homeassistant/components/tibber/translations/he.json +++ b/homeassistant/components/tibber/translations/he.json @@ -13,8 +13,7 @@ "data": { "access_token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d2\u05d9\u05e9\u05d4" }, - "description": "\u05d4\u05d6\u05df \u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d4\u05d2\u05d9\u05e9\u05d4 \u05e9\u05dc\u05da \u05de\u05behttps://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "\u05d4\u05d6\u05df \u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d4\u05d2\u05d9\u05e9\u05d4 \u05e9\u05dc\u05da \u05de\u05behttps://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/hu.json b/homeassistant/components/tibber/translations/hu.json index 1ff558b7280..57731668a90 100644 --- a/homeassistant/components/tibber/translations/hu.json +++ b/homeassistant/components/tibber/translations/hu.json @@ -13,8 +13,7 @@ "data": { "access_token": "Hozz\u00e1f\u00e9r\u00e9si token" }, - "description": "Adja meg a hozz\u00e1f\u00e9r\u00e9si tokent a https://developer.tibber.com/settings/accesstoken c\u00edmr\u0151l", - "title": "Tibber" + "description": "Adja meg a hozz\u00e1f\u00e9r\u00e9si tokent a https://developer.tibber.com/settings/accesstoken c\u00edmr\u0151l" } } } diff --git a/homeassistant/components/tibber/translations/id.json b/homeassistant/components/tibber/translations/id.json index 479cf83f8c7..a80aec5ee3f 100644 --- a/homeassistant/components/tibber/translations/id.json +++ b/homeassistant/components/tibber/translations/id.json @@ -13,8 +13,7 @@ "data": { "access_token": "Token Akses" }, - "description": "Masukkan token akses Anda dari https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "Masukkan token akses Anda dari https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/it.json b/homeassistant/components/tibber/translations/it.json index aca7d94083a..2058c2260ad 100644 --- a/homeassistant/components/tibber/translations/it.json +++ b/homeassistant/components/tibber/translations/it.json @@ -13,8 +13,7 @@ "data": { "access_token": "Token di accesso" }, - "description": "Immettere il token di accesso da https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "Immettere il token di accesso da https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/ja.json b/homeassistant/components/tibber/translations/ja.json index ed3c2f71357..0d16d720159 100644 --- a/homeassistant/components/tibber/translations/ja.json +++ b/homeassistant/components/tibber/translations/ja.json @@ -13,8 +13,7 @@ "data": { "access_token": "\u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3" }, - "description": "https://developer.tibber.com/settings/accesstoken \u304b\u3089\u306e\u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3\u3092\u5165\u529b\u3057\u307e\u3059", - "title": "Tibber" + "description": "https://developer.tibber.com/settings/accesstoken \u304b\u3089\u306e\u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3\u3092\u5165\u529b\u3057\u307e\u3059" } } } diff --git a/homeassistant/components/tibber/translations/ko.json b/homeassistant/components/tibber/translations/ko.json index 5ba1f62e4ed..ff15748a948 100644 --- a/homeassistant/components/tibber/translations/ko.json +++ b/homeassistant/components/tibber/translations/ko.json @@ -13,8 +13,7 @@ "data": { "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070" }, - "description": "https://developer.tibber.com/settings/accesstoken \uc5d0\uc11c \uc0dd\uc131\ud55c \uc561\uc138\uc2a4 \ud1a0\ud070\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", - "title": "Tibber" + "description": "https://developer.tibber.com/settings/accesstoken \uc5d0\uc11c \uc0dd\uc131\ud55c \uc561\uc138\uc2a4 \ud1a0\ud070\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/tibber/translations/lb.json b/homeassistant/components/tibber/translations/lb.json index fc84bd1af6f..33ef47fe2de 100644 --- a/homeassistant/components/tibber/translations/lb.json +++ b/homeassistant/components/tibber/translations/lb.json @@ -13,8 +13,7 @@ "data": { "access_token": "Acc\u00e8ss Jeton" }, - "description": "F\u00ebll d\u00e4in Acc\u00e8s Jeton vun https://developer.tibber.com/settings/accesstoken aus", - "title": "Tibber" + "description": "F\u00ebll d\u00e4in Acc\u00e8s Jeton vun https://developer.tibber.com/settings/accesstoken aus" } } } diff --git a/homeassistant/components/tibber/translations/nl.json b/homeassistant/components/tibber/translations/nl.json index 4a5e518f306..efa896e08e6 100644 --- a/homeassistant/components/tibber/translations/nl.json +++ b/homeassistant/components/tibber/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -13,8 +13,7 @@ "data": { "access_token": "Toegangstoken" }, - "description": "Voer uw toegangstoken in van https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "Voer uw toegangstoken in van https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/no.json b/homeassistant/components/tibber/translations/no.json index 14bf482e381..00d56edf5d4 100644 --- a/homeassistant/components/tibber/translations/no.json +++ b/homeassistant/components/tibber/translations/no.json @@ -13,8 +13,7 @@ "data": { "access_token": "Tilgangstoken" }, - "description": "Fyll inn din tilgangstoken fra [https://developer.tibber.com/settings/accesstoken](https://developer.tibber.com/settings/accesstoken)", - "title": "" + "description": "Fyll inn din tilgangstoken fra [https://developer.tibber.com/settings/accesstoken](https://developer.tibber.com/settings/accesstoken)" } } } diff --git a/homeassistant/components/tibber/translations/pl.json b/homeassistant/components/tibber/translations/pl.json index d62027da3d9..7019bb3d17b 100644 --- a/homeassistant/components/tibber/translations/pl.json +++ b/homeassistant/components/tibber/translations/pl.json @@ -13,8 +13,7 @@ "data": { "access_token": "Token dost\u0119pu" }, - "description": "Wprowad\u017a token dost\u0119pu z https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "Wprowad\u017a token dost\u0119pu z https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/pt-BR.json b/homeassistant/components/tibber/translations/pt-BR.json index 7ff66347009..57eb82ff948 100644 --- a/homeassistant/components/tibber/translations/pt-BR.json +++ b/homeassistant/components/tibber/translations/pt-BR.json @@ -13,8 +13,7 @@ "data": { "access_token": "Token de acesso" }, - "description": "Insira seu token de acesso em https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "Insira seu token de acesso em https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/pt.json b/homeassistant/components/tibber/translations/pt.json index 941089ee0cb..50b5806457b 100644 --- a/homeassistant/components/tibber/translations/pt.json +++ b/homeassistant/components/tibber/translations/pt.json @@ -11,8 +11,7 @@ "user": { "data": { "access_token": "" - }, - "title": "" + } } } } diff --git a/homeassistant/components/tibber/translations/ru.json b/homeassistant/components/tibber/translations/ru.json index 7519f581352..6453e65830e 100644 --- a/homeassistant/components/tibber/translations/ru.json +++ b/homeassistant/components/tibber/translations/ru.json @@ -13,8 +13,7 @@ "data": { "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/sl.json b/homeassistant/components/tibber/translations/sl.json index 33ec5ab773b..285c07434ce 100644 --- a/homeassistant/components/tibber/translations/sl.json +++ b/homeassistant/components/tibber/translations/sl.json @@ -12,8 +12,7 @@ "data": { "access_token": "Dostopni \u017eeton" }, - "description": "Vnesite svoj dostopni \u017eeton s strani https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "Vnesite svoj dostopni \u017eeton s strani https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/tr.json b/homeassistant/components/tibber/translations/tr.json index a34aab70d6c..d7a98f49760 100644 --- a/homeassistant/components/tibber/translations/tr.json +++ b/homeassistant/components/tibber/translations/tr.json @@ -13,8 +13,7 @@ "data": { "access_token": "Eri\u015fim Belirteci" }, - "description": "https://developer.tibber.com/settings/accesstoken adresinden eri\u015fim anahtar\u0131n\u0131z\u0131 girin", - "title": "Tibber" + "description": "https://developer.tibber.com/settings/accesstoken adresinden eri\u015fim anahtar\u0131n\u0131z\u0131 girin" } } } diff --git a/homeassistant/components/tibber/translations/uk.json b/homeassistant/components/tibber/translations/uk.json index b1240116856..ba38a372472 100644 --- a/homeassistant/components/tibber/translations/uk.json +++ b/homeassistant/components/tibber/translations/uk.json @@ -13,8 +13,7 @@ "data": { "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443, \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 \u043d\u0430 \u0441\u0430\u0439\u0442\u0456 https://developer.tibber.com/settings/accesstoken", - "title": "Tibber" + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443, \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 \u043d\u0430 \u0441\u0430\u0439\u0442\u0456 https://developer.tibber.com/settings/accesstoken" } } } diff --git a/homeassistant/components/tibber/translations/zh-Hant.json b/homeassistant/components/tibber/translations/zh-Hant.json index e4d0ec10e23..3bc02f810d5 100644 --- a/homeassistant/components/tibber/translations/zh-Hant.json +++ b/homeassistant/components/tibber/translations/zh-Hant.json @@ -13,8 +13,7 @@ "data": { "access_token": "\u5b58\u53d6\u6b0a\u6756" }, - "description": "\u8f38\u5165\u7531 https://developer.tibber.com/settings/accesstoken \u6240\u7372\u5f97\u7684\u5b58\u53d6\u6b0a\u6756", - "title": "Tibber" + "description": "\u8f38\u5165\u7531 https://developer.tibber.com/settings/accesstoken \u6240\u7372\u5f97\u7684\u5b58\u53d6\u6b0a\u6756" } } } diff --git a/homeassistant/components/tile/translations/es.json b/homeassistant/components/tile/translations/es.json index 42eb3527a6f..c13488183d3 100644 --- a/homeassistant/components/tile/translations/es.json +++ b/homeassistant/components/tile/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n fue exitosa" }, "error": { diff --git a/homeassistant/components/tile/translations/nl.json b/homeassistant/components/tile/translations/nl.json index d2c66b4fe65..8a6270009a8 100644 --- a/homeassistant/components/tile/translations/nl.json +++ b/homeassistant/components/tile/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie" diff --git a/homeassistant/components/timer/manifest.json b/homeassistant/components/timer/manifest.json index 19748332221..160c96f9664 100644 --- a/homeassistant/components/timer/manifest.json +++ b/homeassistant/components/timer/manifest.json @@ -1,6 +1,7 @@ { "domain": "timer", "name": "Timer", + "integration_type": "helper", "documentation": "https://www.home-assistant.io/integrations/timer", "codeowners": [], "quality_scale": "internal" diff --git a/homeassistant/components/tod/translations/ca.json b/homeassistant/components/tod/translations/ca.json index 3908cb7761f..2f62e5c8fb4 100644 --- a/homeassistant/components/tod/translations/ca.json +++ b/homeassistant/components/tod/translations/ca.json @@ -3,9 +3,7 @@ "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" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/de.json b/homeassistant/components/tod/translations/de.json index eeb3d4dd8f9..663dc21c993 100644 --- a/homeassistant/components/tod/translations/de.json +++ b/homeassistant/components/tod/translations/de.json @@ -3,9 +3,7 @@ "step": { "user": { "data": { - "after": "Angeschaltet nach", "after_time": "Einschaltzeit", - "before": "Ausgeschaltet nach", "before_time": "Ausschaltzeit", "name": "Name" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/el.json b/homeassistant/components/tod/translations/el.json index ffa426365a4..453e36c4020 100644 --- a/homeassistant/components/tod/translations/el.json +++ b/homeassistant/components/tod/translations/el.json @@ -3,9 +3,7 @@ "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" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/en.json b/homeassistant/components/tod/translations/en.json index ced14151519..2ecb2c695c8 100644 --- a/homeassistant/components/tod/translations/en.json +++ b/homeassistant/components/tod/translations/en.json @@ -3,9 +3,7 @@ "step": { "user": { "data": { - "after": "On after", "after_time": "On time", - "before": "Off after", "before_time": "Off time", "name": "Name" }, @@ -18,12 +16,9 @@ "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/es.json b/homeassistant/components/tod/translations/es.json new file mode 100644 index 00000000000..302dc6cfdd9 --- /dev/null +++ b/homeassistant/components/tod/translations/es.json @@ -0,0 +1,24 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after_time": "Tiempo de activaci\u00f3n", + "before_time": "Tiempo de desactivaci\u00f3n" + }, + "description": "Crea un sensor binario que se activa o desactiva en funci\u00f3n de la hora.", + "title": "A\u00f1ade sensor tiempo del d\u00eda" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after_time": "Tiempo de activaci\u00f3n" + } + } + } + }, + "title": "Sensor tiempo del d\u00eda" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/et.json b/homeassistant/components/tod/translations/et.json index 06b40e0ce72..9d3fb544309 100644 --- a/homeassistant/components/tod/translations/et.json +++ b/homeassistant/components/tod/translations/et.json @@ -3,9 +3,7 @@ "step": { "user": { "data": { - "after": "Kestus sissel\u00fclitumisest", "after_time": "Seesoleku aeg", - "before": "Kestus v\u00e4ljal\u00fclitumisest", "before_time": "V\u00e4ljasoleku aeg", "name": "Nimi" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/fr.json b/homeassistant/components/tod/translations/fr.json index 799e286343b..d430ede1295 100644 --- a/homeassistant/components/tod/translations/fr.json +++ b/homeassistant/components/tod/translations/fr.json @@ -3,9 +3,7 @@ "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" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/he.json b/homeassistant/components/tod/translations/he.json index b10f9e2b1ca..efa0a9dc244 100644 --- a/homeassistant/components/tod/translations/he.json +++ b/homeassistant/components/tod/translations/he.json @@ -3,9 +3,7 @@ "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" }, @@ -18,12 +16,9 @@ "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." + } } } } diff --git a/homeassistant/components/tod/translations/hu.json b/homeassistant/components/tod/translations/hu.json index 28af029f230..feb4d66e40e 100644 --- a/homeassistant/components/tod/translations/hu.json +++ b/homeassistant/components/tod/translations/hu.json @@ -3,9 +3,7 @@ "step": { "user": { "data": { - "after": "BE ut\u00e1n", "after_time": "BE id\u0151pont", - "before": "KI ut\u00e1n", "before_time": "KI id\u0151pont", "name": "Elnevez\u00e9s" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/id.json b/homeassistant/components/tod/translations/id.json index 2ceef6c3a40..2a3681e7d78 100644 --- a/homeassistant/components/tod/translations/id.json +++ b/homeassistant/components/tod/translations/id.json @@ -3,9 +3,7 @@ "step": { "user": { "data": { - "after": "Nyala setelah", "after_time": "Nyala pada", - "before": "Mati setelah", "before_time": "Mati pada", "name": "Nama" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/it.json b/homeassistant/components/tod/translations/it.json index 072cc80f5ff..69015bf02f7 100644 --- a/homeassistant/components/tod/translations/it.json +++ b/homeassistant/components/tod/translations/it.json @@ -3,9 +3,7 @@ "step": { "user": { "data": { - "after": "Acceso dopo", "after_time": "Ora di accensione", - "before": "Spento dopo", "before_time": "Ora di spegnimento", "name": "Nome" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/ja.json b/homeassistant/components/tod/translations/ja.json index a497f3e3c98..10bad2407d0 100644 --- a/homeassistant/components/tod/translations/ja.json +++ b/homeassistant/components/tod/translations/ja.json @@ -3,9 +3,7 @@ "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" }, @@ -18,12 +16,9 @@ "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" + } } } }, diff --git a/homeassistant/components/tod/translations/ko.json b/homeassistant/components/tod/translations/ko.json new file mode 100644 index 00000000000..474497f6557 --- /dev/null +++ b/homeassistant/components/tod/translations/ko.json @@ -0,0 +1,3 @@ +{ + "title": "\ubc94\uc704\uc2dc\uac04 \uc13c\uc11c" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/nl.json b/homeassistant/components/tod/translations/nl.json index 82f06ea3f4f..374e295f691 100644 --- a/homeassistant/components/tod/translations/nl.json +++ b/homeassistant/components/tod/translations/nl.json @@ -3,9 +3,7 @@ "step": { "user": { "data": { - "after": "Aan na", "after_time": "Op tijd", - "before": "Uit na", "before_time": "Uit tijd", "name": "Naam\n" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/no.json b/homeassistant/components/tod/translations/no.json index a2cbf7e6427..196c00663fe 100644 --- a/homeassistant/components/tod/translations/no.json +++ b/homeassistant/components/tod/translations/no.json @@ -3,9 +3,7 @@ "step": { "user": { "data": { - "after": "P\u00e5 etter", "after_time": "P\u00e5 tide", - "before": "Av etter", "before_time": "Utenfor arbeidstid", "name": "Navn" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/pl.json b/homeassistant/components/tod/translations/pl.json index 8b03a798f5c..14d74f12b27 100644 --- a/homeassistant/components/tod/translations/pl.json +++ b/homeassistant/components/tod/translations/pl.json @@ -3,9 +3,7 @@ "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" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/pt-BR.json b/homeassistant/components/tod/translations/pt-BR.json index e9784076fd7..0275ad3e148 100644 --- a/homeassistant/components/tod/translations/pt-BR.json +++ b/homeassistant/components/tod/translations/pt-BR.json @@ -3,9 +3,7 @@ "step": { "user": { "data": { - "after": "Ligado depois", "after_time": "Ligado na hora", - "before": "Desligado depois", "before_time": "Desligado na hora", "name": "Nome" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/ru.json b/homeassistant/components/tod/translations/ru.json index eda3e4efd77..c470dea348d 100644 --- a/homeassistant/components/tod/translations/ru.json +++ b/homeassistant/components/tod/translations/ru.json @@ -3,9 +3,7 @@ "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" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/tr.json b/homeassistant/components/tod/translations/tr.json index e2f04757369..8013dff322b 100644 --- a/homeassistant/components/tod/translations/tr.json +++ b/homeassistant/components/tod/translations/tr.json @@ -3,9 +3,7 @@ "step": { "user": { "data": { - "after": "Daha sonra", "after_time": "Vaktinde", - "before": "Sonra kapat", "before_time": "Kapatma zaman\u0131", "name": "Ad" }, @@ -18,12 +16,9 @@ "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." + } } } }, diff --git a/homeassistant/components/tod/translations/zh-Hans.json b/homeassistant/components/tod/translations/zh-Hans.json index 1c0b6ba1597..32306b99659 100644 --- a/homeassistant/components/tod/translations/zh-Hans.json +++ b/homeassistant/components/tod/translations/zh-Hans.json @@ -3,9 +3,7 @@ "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" }, @@ -18,12 +16,9 @@ "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" + } } } }, diff --git a/homeassistant/components/tod/translations/zh-Hant.json b/homeassistant/components/tod/translations/zh-Hant.json index 0a47c33a318..333d092a613 100644 --- a/homeassistant/components/tod/translations/zh-Hant.json +++ b/homeassistant/components/tod/translations/zh-Hant.json @@ -3,9 +3,7 @@ "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" }, @@ -18,12 +16,9 @@ "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" + } } } }, diff --git a/homeassistant/components/tolo/translations/bg.json b/homeassistant/components/tolo/translations/bg.json index f1c33573305..5f89c6bd4bd 100644 --- a/homeassistant/components/tolo/translations/bg.json +++ b/homeassistant/components/tolo/translations/bg.json @@ -1,8 +1,7 @@ { "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", - "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" + "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" diff --git a/homeassistant/components/tolo/translations/ca.json b/homeassistant/components/tolo/translations/ca.json index 06d201141a5..567f4467f0b 100644 --- a/homeassistant/components/tolo/translations/ca.json +++ b/homeassistant/components/tolo/translations/ca.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat", - "no_devices_found": "No s'han trobat dispositius a la xarxa" + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3" diff --git a/homeassistant/components/tolo/translations/de.json b/homeassistant/components/tolo/translations/de.json index 6002d2ada8b..3fc78255507 100644 --- a/homeassistant/components/tolo/translations/de.json +++ b/homeassistant/components/tolo/translations/de.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen" diff --git a/homeassistant/components/tolo/translations/el.json b/homeassistant/components/tolo/translations/el.json index 6b745e37a8e..16604a30baa 100644 --- a/homeassistant/components/tolo/translations/el.json +++ b/homeassistant/components/tolo/translations/el.json @@ -1,8 +1,7 @@ { "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", - "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" + "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" diff --git a/homeassistant/components/tolo/translations/en.json b/homeassistant/components/tolo/translations/en.json index 488c2f7ae69..dea5a3b30df 100644 --- a/homeassistant/components/tolo/translations/en.json +++ b/homeassistant/components/tolo/translations/en.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Device is already configured", - "no_devices_found": "No devices found on the network" + "already_configured": "Device is already configured" }, "error": { "cannot_connect": "Failed to connect" diff --git a/homeassistant/components/tolo/translations/es.json b/homeassistant/components/tolo/translations/es.json index 573d6b7c344..76cb6c73275 100644 --- a/homeassistant/components/tolo/translations/es.json +++ b/homeassistant/components/tolo/translations/es.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado", - "no_devices_found": "No se encontraron dispositivos en la red" + "already_configured": "El dispositivo ya est\u00e1 configurado" }, "error": { "cannot_connect": "No se pudo conectar" diff --git a/homeassistant/components/tolo/translations/et.json b/homeassistant/components/tolo/translations/et.json index 57d59b85713..ed09907df82 100644 --- a/homeassistant/components/tolo/translations/et.json +++ b/homeassistant/components/tolo/translations/et.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Seade on juba h\u00e4\u00e4lestatud", - "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi seadet" + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" }, "error": { "cannot_connect": "\u00dchendamine nurjus" diff --git a/homeassistant/components/tolo/translations/fr.json b/homeassistant/components/tolo/translations/fr.json index 40b61d012a2..c27da305516 100644 --- a/homeassistant/components/tolo/translations/fr.json +++ b/homeassistant/components/tolo/translations/fr.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", - "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau" + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, "error": { "cannot_connect": "\u00c9chec de connexion" diff --git a/homeassistant/components/tolo/translations/he.json b/homeassistant/components/tolo/translations/he.json index 9da8a69a4fe..39ea73680a3 100644 --- a/homeassistant/components/tolo/translations/he.json +++ b/homeassistant/components/tolo/translations/he.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", - "no_devices_found": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05db\u05e9\u05d9\u05e8\u05d9\u05dd \u05d1\u05e8\u05e9\u05ea" + "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" diff --git a/homeassistant/components/tolo/translations/hu.json b/homeassistant/components/tolo/translations/hu.json index 55239599c16..246edba220d 100644 --- a/homeassistant/components/tolo/translations/hu.json +++ b/homeassistant/components/tolo/translations/hu.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" diff --git a/homeassistant/components/tolo/translations/id.json b/homeassistant/components/tolo/translations/id.json index 53ea0e46cb1..88e22f45b0b 100644 --- a/homeassistant/components/tolo/translations/id.json +++ b/homeassistant/components/tolo/translations/id.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Perangkat sudah dikonfigurasi", - "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + "already_configured": "Perangkat sudah dikonfigurasi" }, "error": { "cannot_connect": "Gagal terhubung" diff --git a/homeassistant/components/tolo/translations/it.json b/homeassistant/components/tolo/translations/it.json index 83704e3e767..169aaa7ebce 100644 --- a/homeassistant/components/tolo/translations/it.json +++ b/homeassistant/components/tolo/translations/it.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "no_devices_found": "Nessun dispositivo trovato sulla rete" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { "cannot_connect": "Impossibile connettersi" diff --git a/homeassistant/components/tolo/translations/ja.json b/homeassistant/components/tolo/translations/ja.json index f8d4a1646ae..f901986b6d2 100644 --- a/homeassistant/components/tolo/translations/ja.json +++ b/homeassistant/components/tolo/translations/ja.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", - "no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093" + "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" diff --git a/homeassistant/components/tolo/translations/ko.json b/homeassistant/components/tolo/translations/ko.json new file mode 100644 index 00000000000..20ad990e862 --- /dev/null +++ b/homeassistant/components/tolo/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tolo/translations/nl.json b/homeassistant/components/tolo/translations/nl.json index f65f6bae7c1..31d417dd9e7 100644 --- a/homeassistant/components/tolo/translations/nl.json +++ b/homeassistant/components/tolo/translations/nl.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd", - "no_devices_found": "Geen apparaten gevonden op het netwerk" + "already_configured": "Apparaat is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken" @@ -10,7 +9,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" }, "user": { "data": { diff --git a/homeassistant/components/tolo/translations/no.json b/homeassistant/components/tolo/translations/no.json index 20311dd8f69..16b9c0f2ead 100644 --- a/homeassistant/components/tolo/translations/no.json +++ b/homeassistant/components/tolo/translations/no.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert", - "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket" + "already_configured": "Enheten er allerede konfigurert" }, "error": { "cannot_connect": "Tilkobling mislyktes" diff --git a/homeassistant/components/tolo/translations/pl.json b/homeassistant/components/tolo/translations/pl.json index 4809e00e29f..0bf5b5f75f5 100644 --- a/homeassistant/components/tolo/translations/pl.json +++ b/homeassistant/components/tolo/translations/pl.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", - "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" diff --git a/homeassistant/components/tolo/translations/pt-BR.json b/homeassistant/components/tolo/translations/pt-BR.json index c86e10b4e8e..457fb339b2d 100644 --- a/homeassistant/components/tolo/translations/pt-BR.json +++ b/homeassistant/components/tolo/translations/pt-BR.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "no_devices_found": "Nenhum dispositivo encontrado na rede" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { "cannot_connect": "Falha ao conectar" diff --git a/homeassistant/components/tolo/translations/ru.json b/homeassistant/components/tolo/translations/ru.json index 82fdffdb8b1..0243a40cf7e 100644 --- a/homeassistant/components/tolo/translations/ru.json +++ b/homeassistant/components/tolo/translations/ru.json @@ -1,8 +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.", - "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." + "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." diff --git a/homeassistant/components/tolo/translations/sl.json b/homeassistant/components/tolo/translations/sl.json index e32b3eb95ca..62f8746a028 100644 --- a/homeassistant/components/tolo/translations/sl.json +++ b/homeassistant/components/tolo/translations/sl.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Naprava je \u017ee konfigurirana", - "no_devices_found": "V omre\u017eju ni mogo\u010de najti nobene naprave" + "already_configured": "Naprava je \u017ee konfigurirana" }, "error": { "cannot_connect": "Povezava ni uspela" diff --git a/homeassistant/components/tolo/translations/tr.json b/homeassistant/components/tolo/translations/tr.json index f5dd98e93ba..8f2b5ee1939 100644 --- a/homeassistant/components/tolo/translations/tr.json +++ b/homeassistant/components/tolo/translations/tr.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", - "no_devices_found": "A\u011fda cihaz bulunamad\u0131" + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131" diff --git a/homeassistant/components/tolo/translations/zh-Hant.json b/homeassistant/components/tolo/translations/zh-Hant.json index d887eb212a1..96cef3b9605 100644 --- a/homeassistant/components/tolo/translations/zh-Hant.json +++ b/homeassistant/components/tolo/translations/zh-Hant.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e" + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557" diff --git a/homeassistant/components/tomorrowio/translations/bg.json b/homeassistant/components/tomorrowio/translations/bg.json index 783408dab84..261841d75d1 100644 --- a/homeassistant/components/tomorrowio/translations/bg.json +++ b/homeassistant/components/tomorrowio/translations/bg.json @@ -9,9 +9,7 @@ "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" } } diff --git a/homeassistant/components/tomorrowio/translations/ca.json b/homeassistant/components/tomorrowio/translations/ca.json index fc351430ffb..6a1289fd7a4 100644 --- a/homeassistant/components/tomorrowio/translations/ca.json +++ b/homeassistant/components/tomorrowio/translations/ca.json @@ -10,9 +10,7 @@ "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)." diff --git a/homeassistant/components/tomorrowio/translations/de.json b/homeassistant/components/tomorrowio/translations/de.json index 03739ce5c2f..d65b1115e4b 100644 --- a/homeassistant/components/tomorrowio/translations/de.json +++ b/homeassistant/components/tomorrowio/translations/de.json @@ -10,9 +10,7 @@ "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." diff --git a/homeassistant/components/tomorrowio/translations/el.json b/homeassistant/components/tomorrowio/translations/el.json index 28e3f56c379..aecdca57c9f 100644 --- a/homeassistant/components/tomorrowio/translations/el.json +++ b/homeassistant/components/tomorrowio/translations/el.json @@ -10,9 +10,7 @@ "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)." diff --git a/homeassistant/components/tomorrowio/translations/en.json b/homeassistant/components/tomorrowio/translations/en.json index 103f1c81679..f3706dd6a73 100644 --- a/homeassistant/components/tomorrowio/translations/en.json +++ b/homeassistant/components/tomorrowio/translations/en.json @@ -10,9 +10,7 @@ "user": { "data": { "api_key": "API Key", - "latitude": "Latitude", "location": "Location", - "longitude": "Longitude", "name": "Name" }, "description": "To get an API key, sign up at [Tomorrow.io](https://app.tomorrow.io/signup)." diff --git a/homeassistant/components/tomorrowio/translations/es.json b/homeassistant/components/tomorrowio/translations/es.json new file mode 100644 index 00000000000..e2f36a0949e --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/es.json @@ -0,0 +1,28 @@ +{ + "config": { + "error": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", + "invalid_api_key": "Clave API inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Clave API", + "location": "Ubicaci\u00f3n", + "name": "Nombre" + }, + "description": "Para obtener una clave API, reg\u00edstrate en [Tomorrow.io](https://app.tomorrow.io/signup)" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Min. entre previsiones de NowCast" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/et.json b/homeassistant/components/tomorrowio/translations/et.json index 7ac38239349..0ae1ea43448 100644 --- a/homeassistant/components/tomorrowio/translations/et.json +++ b/homeassistant/components/tomorrowio/translations/et.json @@ -10,9 +10,7 @@ "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)." diff --git a/homeassistant/components/tomorrowio/translations/fr.json b/homeassistant/components/tomorrowio/translations/fr.json index bcb97eb3fc5..559c734612f 100644 --- a/homeassistant/components/tomorrowio/translations/fr.json +++ b/homeassistant/components/tomorrowio/translations/fr.json @@ -10,9 +10,7 @@ "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)." diff --git a/homeassistant/components/tomorrowio/translations/he.json b/homeassistant/components/tomorrowio/translations/he.json index 20b48520f18..153e6fcd5f5 100644 --- a/homeassistant/components/tomorrowio/translations/he.json +++ b/homeassistant/components/tomorrowio/translations/he.json @@ -9,9 +9,7 @@ "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" } } diff --git a/homeassistant/components/tomorrowio/translations/hu.json b/homeassistant/components/tomorrowio/translations/hu.json index 8f90392234e..d619b6346a4 100644 --- a/homeassistant/components/tomorrowio/translations/hu.json +++ b/homeassistant/components/tomorrowio/translations/hu.json @@ -10,9 +10,7 @@ "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." diff --git a/homeassistant/components/tomorrowio/translations/id.json b/homeassistant/components/tomorrowio/translations/id.json index b428648e799..364e697783f 100644 --- a/homeassistant/components/tomorrowio/translations/id.json +++ b/homeassistant/components/tomorrowio/translations/id.json @@ -10,9 +10,7 @@ "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)." @@ -25,7 +23,7 @@ "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.", + "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" } } diff --git a/homeassistant/components/tomorrowio/translations/it.json b/homeassistant/components/tomorrowio/translations/it.json index 9a79896a3d8..8446eba5336 100644 --- a/homeassistant/components/tomorrowio/translations/it.json +++ b/homeassistant/components/tomorrowio/translations/it.json @@ -10,9 +10,7 @@ "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)." diff --git a/homeassistant/components/tomorrowio/translations/ja.json b/homeassistant/components/tomorrowio/translations/ja.json index 17d31f74214..c935416c89b 100644 --- a/homeassistant/components/tomorrowio/translations/ja.json +++ b/homeassistant/components/tomorrowio/translations/ja.json @@ -10,9 +10,7 @@ "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" diff --git a/homeassistant/components/tomorrowio/translations/nl.json b/homeassistant/components/tomorrowio/translations/nl.json index d1efb7d75c3..8b6b585ef11 100644 --- a/homeassistant/components/tomorrowio/translations/nl.json +++ b/homeassistant/components/tomorrowio/translations/nl.json @@ -10,9 +10,7 @@ "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)." diff --git a/homeassistant/components/tomorrowio/translations/no.json b/homeassistant/components/tomorrowio/translations/no.json index bf366895a70..acab85a03a4 100644 --- a/homeassistant/components/tomorrowio/translations/no.json +++ b/homeassistant/components/tomorrowio/translations/no.json @@ -10,9 +10,7 @@ "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)." diff --git a/homeassistant/components/tomorrowio/translations/pl.json b/homeassistant/components/tomorrowio/translations/pl.json index 4637a553aa4..b715d9e2cab 100644 --- a/homeassistant/components/tomorrowio/translations/pl.json +++ b/homeassistant/components/tomorrowio/translations/pl.json @@ -10,9 +10,7 @@ "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)." diff --git a/homeassistant/components/tomorrowio/translations/pt-BR.json b/homeassistant/components/tomorrowio/translations/pt-BR.json index 83f78e31b8e..d0b7a33abc9 100644 --- a/homeassistant/components/tomorrowio/translations/pt-BR.json +++ b/homeassistant/components/tomorrowio/translations/pt-BR.json @@ -10,9 +10,7 @@ "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)." diff --git a/homeassistant/components/tomorrowio/translations/ru.json b/homeassistant/components/tomorrowio/translations/ru.json index 08e24c5a47a..eeb7eb58488 100644 --- a/homeassistant/components/tomorrowio/translations/ru.json +++ b/homeassistant/components/tomorrowio/translations/ru.json @@ -10,9 +10,7 @@ "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)." diff --git a/homeassistant/components/tomorrowio/translations/sensor.es.json b/homeassistant/components/tomorrowio/translations/sensor.es.json new file mode 100644 index 00000000000..03820d30265 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.es.json @@ -0,0 +1,16 @@ +{ + "state": { + "tomorrowio__health_concern": { + "unhealthy": "Poco saludable", + "unhealthy_for_sensitive_groups": "No saludable para grupos sensibles", + "very_unhealthy": "Nada saludable" + }, + "tomorrowio__pollen_index": { + "medium": "Medio", + "very_low": "Muy bajo" + }, + "tomorrowio__precipitation_type": { + "snow": "Nieve" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.ko.json b/homeassistant/components/tomorrowio/translations/sensor.ko.json new file mode 100644 index 00000000000..de8aaf71402 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.ko.json @@ -0,0 +1,7 @@ +{ + "state": { + "tomorrowio__precipitation_type": { + "freezing_rain": "\uc5b4\ub294 \ube44" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.sk.json b/homeassistant/components/tomorrowio/translations/sensor.sk.json new file mode 100644 index 00000000000..3dd3dede27b --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.sk.json @@ -0,0 +1,7 @@ +{ + "state": { + "tomorrowio__pollen_index": { + "low": "N\u00edzka" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/zh-Hans.json b/homeassistant/components/tomorrowio/translations/sk.json similarity index 58% rename from homeassistant/components/climacell/translations/zh-Hans.json rename to homeassistant/components/tomorrowio/translations/sk.json index 315d060bc69..7f480c9778c 100644 --- a/homeassistant/components/climacell/translations/zh-Hans.json +++ b/homeassistant/components/tomorrowio/translations/sk.json @@ -3,8 +3,8 @@ "step": { "user": { "data": { - "api_key": "API Key", - "name": "\u540d\u5b57" + "api_key": "API k\u013e\u00fa\u010d", + "name": "Meno" } } } diff --git a/homeassistant/components/tomorrowio/translations/tr.json b/homeassistant/components/tomorrowio/translations/tr.json index 4193428459c..61802d7f328 100644 --- a/homeassistant/components/tomorrowio/translations/tr.json +++ b/homeassistant/components/tomorrowio/translations/tr.json @@ -10,9 +10,7 @@ "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." diff --git a/homeassistant/components/tomorrowio/translations/zh-Hant.json b/homeassistant/components/tomorrowio/translations/zh-Hant.json index bf7043b0225..00f5d5e0fcc 100644 --- a/homeassistant/components/tomorrowio/translations/zh-Hant.json +++ b/homeassistant/components/tomorrowio/translations/zh-Hant.json @@ -10,9 +10,7 @@ "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" diff --git a/homeassistant/components/toon/translations/es.json b/homeassistant/components/toon/translations/es.json index d048e53ec90..423d81dde0c 100644 --- a/homeassistant/components/toon/translations/es.json +++ b/homeassistant/components/toon/translations/es.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El acuerdo seleccionado ya est\u00e1 configurado.", - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n.", "no_agreements": "Esta cuenta no tiene pantallas Toon.", "no_url_available": "No hay URL disponible. Para obtener informaci\u00f3n sobre este error, [consulta la secci\u00f3n de ayuda]({docs_url})", diff --git a/homeassistant/components/toon/translations/nl.json b/homeassistant/components/toon/translations/nl.json index 021c6276b1a..176fa50e932 100644 --- a/homeassistant/components/toon/translations/nl.json +++ b/homeassistant/components/toon/translations/nl.json @@ -2,10 +2,10 @@ "config": { "abort": { "already_configured": "De geselecteerde overeenkomst is al geconfigureerd.", - "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "De component is niet geconfigureerd. Gelieve de documentatie volgen.", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", "no_agreements": "Dit account heeft geen Toon schermen.", - "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout [check the help section] ( {docs_url} )", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." }, "step": { diff --git a/homeassistant/components/totalconnect/__init__.py b/homeassistant/components/totalconnect/__init__.py index 8a4aee0debb..87977e5c1db 100644 --- a/homeassistant/components/totalconnect/__init__.py +++ b/homeassistant/components/totalconnect/__init__.py @@ -17,7 +17,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed import homeassistant.helpers.config_validation as cv from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import CONF_USERCODES, DOMAIN +from .const import AUTO_BYPASS, CONF_USERCODES, DOMAIN PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR] @@ -31,6 +31,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: conf = entry.data username = conf[CONF_USERNAME] password = conf[CONF_PASSWORD] + bypass = entry.options.get(AUTO_BYPASS, False) if CONF_USERCODES not in conf: # should only happen for those who used UI before we added usercodes @@ -41,7 +42,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: client = await hass.async_add_executor_job( - TotalConnectClient, username, password, usercodes + TotalConnectClient, username, password, usercodes, bypass ) except AuthenticationError as exception: raise ConfigEntryAuthFailed( @@ -54,6 +55,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + entry.async_on_unload(entry.add_update_listener(update_listener)) + return True @@ -66,6 +70,14 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Update listener.""" + bypass = entry.options.get(AUTO_BYPASS, False) + client = hass.data[DOMAIN][entry.entry_id].client + for location_id in client.locations: + client.locations[location_id].auto_bypass_low_battery = bypass + + class TotalConnectDataUpdateCoordinator(DataUpdateCoordinator): """Class to fetch data from TotalConnect.""" diff --git a/homeassistant/components/totalconnect/config_flow.py b/homeassistant/components/totalconnect/config_flow.py index 013b08b50be..49e60b5b46e 100644 --- a/homeassistant/components/totalconnect/config_flow.py +++ b/homeassistant/components/totalconnect/config_flow.py @@ -5,8 +5,9 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_LOCATION, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback -from .const import CONF_USERCODES, DOMAIN +from .const import AUTO_BYPASS, CONF_USERCODES, DOMAIN PASSWORD_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) @@ -162,3 +163,34 @@ class TotalConnectConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return self.async_abort(reason="reauth_successful") + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get options flow.""" + return TotalConnectOptionsFlowHandler(config_entry) + + +class TotalConnectOptionsFlowHandler(config_entries.OptionsFlow): + """TotalConnect options flow handler.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Required( + AUTO_BYPASS, + default=self.config_entry.options.get(AUTO_BYPASS, False), + ): bool + } + ), + ) diff --git a/homeassistant/components/totalconnect/const.py b/homeassistant/components/totalconnect/const.py index ba217bd4ca7..5012a303b69 100644 --- a/homeassistant/components/totalconnect/const.py +++ b/homeassistant/components/totalconnect/const.py @@ -2,6 +2,8 @@ DOMAIN = "totalconnect" CONF_USERCODES = "usercodes" +CONF_LOCATION = "location" +AUTO_BYPASS = "auto_bypass_low_battery" # Most TotalConnect alarms will work passing '-1' as usercode DEFAULT_USERCODE = "-1" diff --git a/homeassistant/components/totalconnect/diagnostics.py b/homeassistant/components/totalconnect/diagnostics.py new file mode 100644 index 00000000000..4a9a73c89a9 --- /dev/null +++ b/homeassistant/components/totalconnect/diagnostics.py @@ -0,0 +1,113 @@ +"""Provides diagnostics for TotalConnect.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN + +TO_REDACT = [ + "username", + "Password", + "Usercode", + "UserID", + "Serial Number", + "serial_number", + "sensor_serial_number", +] + +# Private variable access needed for diagnostics +# pylint: disable=protected-access + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + client = hass.data[DOMAIN][config_entry.entry_id].client + + data: dict[str, Any] = {} + data["client"] = { + "auto_bypass_low_battery": client.auto_bypass_low_battery, + "module_flags": client._module_flags, + "retry_delay": client.retry_delay, + "invalid_credentials": client._invalid_credentials, + } + + data["user"] = { + "master": client._user._master_user, + "user_admin": client._user._user_admin, + "config_admin": client._user._config_admin, + "security_problem": client._user.security_problem(), + "features": client._user._features, + } + + data["locations"] = [] + for location in client.locations.values(): + new_location = { + "location_id": location.location_id, + "name": location.location_name, + "module_flags": location._module_flags, + "security_device_id": location.security_device_id, + "ac_loss": location.ac_loss, + "low_battery": location.low_battery, + "auto_bypass_low_battery": location.auto_bypass_low_battery, + "cover_tampered": location.cover_tampered, + "arming_state": location.arming_state, + } + + new_location["devices"] = [] + for device in location.devices.values(): + new_device = { + "device_id": device.deviceid, + "name": device.name, + "class_id": device.class_id, + "serial_number": device.serial_number, + "security_panel_type_id": device.security_panel_type_id, + "serial_text": device.serial_text, + "flags": device.flags, + } + new_location["devices"].append(new_device) + + new_location["partitions"] = [] + for partition in location.partitions.values(): + new_partition = { + "partition_id": partition.partitionid, + "name": partition.name, + "is_stay_armed": partition.is_stay_armed, + "is_fire_enabled": partition.is_fire_enabled, + "is_common_enabled": partition.is_common_enabled, + "is_locked": partition.is_locked, + "is_new_partition": partition.is_new_partition, + "is_night_stay_enabled": partition.is_night_stay_enabled, + "exit_delay_timer": partition.exit_delay_timer, + } + new_location["partitions"].append(new_partition) + + new_location["zones"] = [] + for zone in location.zones.values(): + new_zone = { + "zone_id": zone.zoneid, + "description": zone.description, + "partition": zone.partition, + "status": zone.status, + "zone_type_id": zone.zone_type_id, + "can_be_bypassed": zone.can_be_bypassed, + "battery_level": zone.battery_level, + "signal_strength": zone.signal_strength, + "sensor_serial_number": zone.sensor_serial_number, + "loop_number": zone.loop_number, + "response_type": zone.response_type, + "alarm_report_state": zone.alarm_report_state, + "supervision_type": zone.supervision_type, + "chime_state": zone.chime_state, + "device_type": zone.device_type, + } + new_location["zones"].append(new_zone) + + data["locations"].append(new_location) + + return async_redact_data(data, TO_REDACT) diff --git a/homeassistant/components/totalconnect/strings.json b/homeassistant/components/totalconnect/strings.json index 64ca1beafd8..346ea7ef403 100644 --- a/homeassistant/components/totalconnect/strings.json +++ b/homeassistant/components/totalconnect/strings.json @@ -28,5 +28,16 @@ "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "no_locations": "No locations are available for this user, check TotalConnect settings" } + }, + "options": { + "step": { + "init": { + "title": "TotalConnect Options", + "description": "Automatically bypass zones the moment they report a low battery.", + "data": { + "auto_bypass_low_battery": "Auto bypass low battery" + } + } + } } } diff --git a/homeassistant/components/totalconnect/translations/ca.json b/homeassistant/components/totalconnect/translations/ca.json index 0edad920cdf..404e07d6b69 100644 --- a/homeassistant/components/totalconnect/translations/ca.json +++ b/homeassistant/components/totalconnect/translations/ca.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Ubicaci\u00f3", "usercode": "Codi d'usuari" }, "description": "Introdueix el codi de l'usuari en la ubicaci\u00f3 {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Contrasenya", "username": "Nom d'usuari" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/cs.json b/homeassistant/components/totalconnect/translations/cs.json index ad3b8dd6618..ed81d8cafd1 100644 --- a/homeassistant/components/totalconnect/translations/cs.json +++ b/homeassistant/components/totalconnect/translations/cs.json @@ -8,11 +8,6 @@ "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" }, "step": { - "locations": { - "data": { - "location": "Um\u00edst\u011bn\u00ed" - } - }, "reauth_confirm": { "title": "Znovu ov\u011b\u0159it integraci" }, @@ -20,8 +15,7 @@ "data": { "password": "Heslo", "username": "U\u017eivatelsk\u00e9 jm\u00e9no" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/de.json b/homeassistant/components/totalconnect/translations/de.json index b89b99fc2d4..890bbb753d9 100644 --- a/homeassistant/components/totalconnect/translations/de.json +++ b/homeassistant/components/totalconnect/translations/de.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Standort", "usercode": "Benutzercode" }, "description": "Gib den Benutzercode f\u00fcr den Benutzer {location_id} an dieser Stelle ein", @@ -26,8 +25,7 @@ "data": { "password": "Passwort", "username": "Benutzername" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/el.json b/homeassistant/components/totalconnect/translations/el.json index e2e19a56b0a..4f60b082484 100644 --- a/homeassistant/components/totalconnect/translations/el.json +++ b/homeassistant/components/totalconnect/translations/el.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1", "usercode": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c3\u03c4\u03b7\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 {location_id}", @@ -26,8 +25,7 @@ "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" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/en.json b/homeassistant/components/totalconnect/translations/en.json index f3a96550cba..8df2b00a936 100644 --- a/homeassistant/components/totalconnect/translations/en.json +++ b/homeassistant/components/totalconnect/translations/en.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Location", "usercode": "Usercode" }, "description": "Enter the usercode for this user at location {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Password", "username": "Username" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/es-419.json b/homeassistant/components/totalconnect/translations/es-419.json index b8507f2b8a7..7560aaa35d2 100644 --- a/homeassistant/components/totalconnect/translations/es-419.json +++ b/homeassistant/components/totalconnect/translations/es-419.json @@ -8,8 +8,7 @@ "data": { "password": "Contrase\u00f1a", "username": "Nombre de usuario" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/es.json b/homeassistant/components/totalconnect/translations/es.json index 5c402fb76af..7983aa3a11e 100644 --- a/homeassistant/components/totalconnect/translations/es.json +++ b/homeassistant/components/totalconnect/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "no_locations": "No hay ubicaciones disponibles para este usuario, compruebe la configuraci\u00f3n de TotalConnect", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Localizaci\u00f3n", "usercode": "Codigo de usuario" }, "description": "Introduce el c\u00f3digo de usuario para este usuario en la ubicaci\u00f3n {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Contrase\u00f1a", "username": "Usuario" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/et.json b/homeassistant/components/totalconnect/translations/et.json index e2df8dd51e8..c4bca75a558 100644 --- a/homeassistant/components/totalconnect/translations/et.json +++ b/homeassistant/components/totalconnect/translations/et.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Asukoht", "usercode": "Kasutajakood" }, "description": "Sisesta kasutaja kood asukohale {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Salas\u00f5na", "username": "Kasutajanimi" - }, - "title": "" + } } } } diff --git a/homeassistant/components/totalconnect/translations/fr.json b/homeassistant/components/totalconnect/translations/fr.json index f6dcefac2bb..fcda553a018 100644 --- a/homeassistant/components/totalconnect/translations/fr.json +++ b/homeassistant/components/totalconnect/translations/fr.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Emplacement", "usercode": "Code d'utilisateur" }, "description": "Entrez le code utilisateur pour cet utilisateur \u00e0 l'emplacement {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Mot de passe", "username": "Nom d'utilisateur" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/he.json b/homeassistant/components/totalconnect/translations/he.json index 712c12a1062..ec280bc5f69 100644 --- a/homeassistant/components/totalconnect/translations/he.json +++ b/homeassistant/components/totalconnect/translations/he.json @@ -10,7 +10,6 @@ "step": { "locations": { "data": { - "location": "\u05de\u05d9\u05e7\u05d5\u05dd", "usercode": "\u05e7\u05d5\u05d3 \u05de\u05e9\u05ea\u05de\u05e9" } }, diff --git a/homeassistant/components/totalconnect/translations/hu.json b/homeassistant/components/totalconnect/translations/hu.json index 3d40f84d262..3bb2b4136c9 100644 --- a/homeassistant/components/totalconnect/translations/hu.json +++ b/homeassistant/components/totalconnect/translations/hu.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Elhelyezked\u00e9s", "usercode": "Felhaszn\u00e1l\u00f3i k\u00f3d" }, "description": "Adja meg ennek a felhaszn\u00e1l\u00f3i k\u00f3dj\u00e1t a k\u00f6vetkez\u0151 helyen: {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/id.json b/homeassistant/components/totalconnect/translations/id.json index 0c2cbbfc6e2..1702ceb5688 100644 --- a/homeassistant/components/totalconnect/translations/id.json +++ b/homeassistant/components/totalconnect/translations/id.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Lokasi", "usercode": "Kode pengguna" }, "description": "Masukkan kode pengguna untuk pengguna ini di lokasi {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Kata Sandi", "username": "Nama Pengguna" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/it.json b/homeassistant/components/totalconnect/translations/it.json index ee66d81d83d..32f0815d380 100644 --- a/homeassistant/components/totalconnect/translations/it.json +++ b/homeassistant/components/totalconnect/translations/it.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Posizione", "usercode": "Codice utente" }, "description": "Inserisci il codice utente per questo utente nella posizione {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Password", "username": "Nome utente" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/ja.json b/homeassistant/components/totalconnect/translations/ja.json index d6bffce6dae..c20b55d5583 100644 --- a/homeassistant/components/totalconnect/translations/ja.json +++ b/homeassistant/components/totalconnect/translations/ja.json @@ -12,22 +12,20 @@ "step": { "locations": { "data": { - "location": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3", "usercode": "\u30e6\u30fc\u30b6\u30fc\u30b3\u30fc\u30c9" }, "description": "\u3053\u306e\u30e6\u30fc\u30b6\u30fc\u306e\u30e6\u30fc\u30b6\u30fc\u30b3\u30fc\u30c9\u3092\u5834\u6240 {location_id} \u306b\u5165\u529b\u3057\u307e\u3059", "title": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u30e6\u30fc\u30b6\u30fc\u30b3\u30fc\u30c9" }, "reauth_confirm": { - "description": "Total Connect\u306f\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", + "description": "Total Connect\u3067\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" }, "user": { "data": { "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", "username": "\u30e6\u30fc\u30b6\u30fc\u540d" - }, - "title": "\u30c8\u30fc\u30bf\u30eb\u30b3\u30cd\u30af\u30c8" + } } } } diff --git a/homeassistant/components/totalconnect/translations/ko.json b/homeassistant/components/totalconnect/translations/ko.json index 354522154b5..e88b9aeb451 100644 --- a/homeassistant/components/totalconnect/translations/ko.json +++ b/homeassistant/components/totalconnect/translations/ko.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "no_locations": "\uc774 \uc0ac\uc6a9\uc790\uac00 \uc0ac\uc6a9\ud560 \uc218 \uc788\ub294 \uc704\uce58\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. TotalConnect \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694.", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -10,9 +11,6 @@ }, "step": { "locations": { - "data": { - "location": "\uc704\uce58" - }, "description": "\uc774 \uc704\uce58\uc758 \ud574\ub2f9 \uc0ac\uc6a9\uc790\uc5d0 \ub300\ud55c \uc0ac\uc6a9\uc790 \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "\uc704\uce58 \uc0ac\uc6a9\uc790 \ucf54\ub4dc" }, @@ -24,8 +22,7 @@ "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/lb.json b/homeassistant/components/totalconnect/translations/lb.json index af76c38d918..025e5f657da 100644 --- a/homeassistant/components/totalconnect/translations/lb.json +++ b/homeassistant/components/totalconnect/translations/lb.json @@ -11,8 +11,7 @@ "data": { "password": "Passwuert", "username": "Benotzernumm" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/nl.json b/homeassistant/components/totalconnect/translations/nl.json index 674818d8428..cf9a6bb10a1 100644 --- a/homeassistant/components/totalconnect/translations/nl.json +++ b/homeassistant/components/totalconnect/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Account is al geconfigureerd", "no_locations": "Er zijn geen locaties beschikbaar voor deze gebruiker, controleer de instellingen van TotalConnect", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie", @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Locatie", "usercode": "Gebruikerscode" }, "description": "Voer de gebruikerscode voor deze gebruiker in op locatie {location_id}", @@ -20,14 +19,13 @@ }, "reauth_confirm": { "description": "Total Connect moet uw account opnieuw verifi\u00ebren", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/no.json b/homeassistant/components/totalconnect/translations/no.json index c1624f08259..86cfedf51d4 100644 --- a/homeassistant/components/totalconnect/translations/no.json +++ b/homeassistant/components/totalconnect/translations/no.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Plassering", "usercode": "Brukerkode" }, "description": "Angi brukerkoden for denne brukeren p\u00e5 plasseringen {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Passord", "username": "Brukernavn" - }, - "title": "" + } } } } diff --git a/homeassistant/components/totalconnect/translations/pl.json b/homeassistant/components/totalconnect/translations/pl.json index 5174d717c26..d3927212c82 100644 --- a/homeassistant/components/totalconnect/translations/pl.json +++ b/homeassistant/components/totalconnect/translations/pl.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Lokalizacja", "usercode": "Kod u\u017cytkownika" }, "description": "Wprowad\u017a kod u\u017cytkownika dla u\u017cytkownika w lokalizacji {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/pt-BR.json b/homeassistant/components/totalconnect/translations/pt-BR.json index 47dfe5437e0..1ffeb1337ec 100644 --- a/homeassistant/components/totalconnect/translations/pt-BR.json +++ b/homeassistant/components/totalconnect/translations/pt-BR.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Localiza\u00e7\u00e3o", "usercode": "C\u00f3digo de usu\u00e1rio" }, "description": "Insira o c\u00f3digo de usu\u00e1rio para este usu\u00e1rio no local {location_id}", @@ -26,8 +25,7 @@ "data": { "password": "Senha", "username": "Usu\u00e1rio" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/pt.json b/homeassistant/components/totalconnect/translations/pt.json index 11ac8262267..0957880ae6c 100644 --- a/homeassistant/components/totalconnect/translations/pt.json +++ b/homeassistant/components/totalconnect/translations/pt.json @@ -8,11 +8,6 @@ "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { - "locations": { - "data": { - "location": "Localiza\u00e7\u00e3o" - } - }, "reauth_confirm": { "title": "Reautenticar integra\u00e7\u00e3o" }, diff --git a/homeassistant/components/totalconnect/translations/ru.json b/homeassistant/components/totalconnect/translations/ru.json index a46c37032a1..ee82564033c 100644 --- a/homeassistant/components/totalconnect/translations/ru.json +++ b/homeassistant/components/totalconnect/translations/ru.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435", "usercode": "\u041a\u043e\u0434 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0438 {location_id}.", @@ -26,8 +25,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/sk.json b/homeassistant/components/totalconnect/translations/sk.json index 59d045e7603..71a7aea5018 100644 --- a/homeassistant/components/totalconnect/translations/sk.json +++ b/homeassistant/components/totalconnect/translations/sk.json @@ -5,13 +5,6 @@ }, "error": { "invalid_auth": "Neplatn\u00e9 overenie" - }, - "step": { - "locations": { - "data": { - "location": "Umiestnenie" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/sl.json b/homeassistant/components/totalconnect/translations/sl.json index 7214ff6aeb5..bbddcdf2459 100644 --- a/homeassistant/components/totalconnect/translations/sl.json +++ b/homeassistant/components/totalconnect/translations/sl.json @@ -8,8 +8,7 @@ "data": { "password": "Geslo", "username": "Uporabni\u0161ko ime" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/tr.json b/homeassistant/components/totalconnect/translations/tr.json index 925353f05a4..ef50457f846 100644 --- a/homeassistant/components/totalconnect/translations/tr.json +++ b/homeassistant/components/totalconnect/translations/tr.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "Konum", "usercode": "Kullan\u0131c\u0131 kodu" }, "description": "Bu kullan\u0131c\u0131n\u0131n kullan\u0131c\u0131 kodunu {location_id} konumuna girin", @@ -26,8 +25,7 @@ "data": { "password": "Parola", "username": "Kullan\u0131c\u0131 Ad\u0131" - }, - "title": "Toplam Ba\u011flant\u0131" + } } } } diff --git a/homeassistant/components/totalconnect/translations/uk.json b/homeassistant/components/totalconnect/translations/uk.json index f34a279d598..afe3dfab539 100644 --- a/homeassistant/components/totalconnect/translations/uk.json +++ b/homeassistant/components/totalconnect/translations/uk.json @@ -11,8 +11,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/totalconnect/translations/zh-Hant.json b/homeassistant/components/totalconnect/translations/zh-Hant.json index beaeaa5d9bf..3b960a8bc43 100644 --- a/homeassistant/components/totalconnect/translations/zh-Hant.json +++ b/homeassistant/components/totalconnect/translations/zh-Hant.json @@ -12,7 +12,6 @@ "step": { "locations": { "data": { - "location": "\u5ea7\u6a19", "usercode": "\u4f7f\u7528\u8005\u4ee3\u78bc" }, "description": "\u8f38\u5165\u4f7f\u7528\u8005\u65bc\u6b64\u5ea7\u6a19 {location_id} \u4e4b\u4f7f\u7528\u8005\u4ee3\u78bc", @@ -26,8 +25,7 @@ "data": { "password": "\u5bc6\u78bc", "username": "\u4f7f\u7528\u8005\u540d\u7a31" - }, - "title": "Total Connect" + } } } } diff --git a/homeassistant/components/tplink/entity.py b/homeassistant/components/tplink/entity.py index 173d1d7930f..471d32631c4 100644 --- a/homeassistant/components/tplink/entity.py +++ b/homeassistant/components/tplink/entity.py @@ -19,8 +19,8 @@ _P = ParamSpec("_P") def async_refresh_after( - func: Callable[Concatenate[_T, _P], Awaitable[None]] # type: ignore[misc] -) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]: # type: ignore[misc] + func: Callable[Concatenate[_T, _P], Awaitable[None]] +) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]: """Define a wrapper to refresh after.""" async def _async_wrap(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: diff --git a/homeassistant/components/tplink/translations/bg.json b/homeassistant/components/tplink/translations/bg.json index 33ae523d8cf..b31dd804044 100644 --- a/homeassistant/components/tplink/translations/bg.json +++ b/homeassistant/components/tplink/translations/bg.json @@ -2,17 +2,13 @@ "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", - "no_devices_found": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 TP-Link \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430.", - "single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + "no_devices_found": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 TP-Link \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430." }, "error": { "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 TP-Link \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430?" - }, "discovery_confirm": { "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 {name} {model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/ca.json b/homeassistant/components/tplink/translations/ca.json index 4dfb749a9d7..490dbf5f56b 100644 --- a/homeassistant/components/tplink/translations/ca.json +++ b/homeassistant/components/tplink/translations/ca.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "no_devices_found": "No s'han trobat dispositius a la xarxa", - "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + "no_devices_found": "No s'han trobat dispositius a la xarxa" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "Vols configurar dispositius intel\u00b7ligents TP-Link?" - }, "discovery_confirm": { "description": "Vols configurar {name} {model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/cs.json b/homeassistant/components/tplink/translations/cs.json index 5ee79c8e6f6..bb063ffbcc7 100644 --- a/homeassistant/components/tplink/translations/cs.json +++ b/homeassistant/components/tplink/translations/cs.json @@ -2,16 +2,12 @@ "config": { "abort": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", - "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed", - "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." + "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed" }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, "step": { - "confirm": { - "description": "Chcete nastavit inteligentn\u00ed za\u0159\u00edzen\u00ed TP-Link?" - }, "pick_device": { "data": { "device": "Za\u0159\u00edzen\u00ed" diff --git a/homeassistant/components/tplink/translations/da.json b/homeassistant/components/tplink/translations/da.json index e6fc3c895a3..e9cd7b269e2 100644 --- a/homeassistant/components/tplink/translations/da.json +++ b/homeassistant/components/tplink/translations/da.json @@ -1,13 +1,7 @@ { "config": { "abort": { - "no_devices_found": "Ingen TP-Link enheder kunne findes p\u00e5 netv\u00e6rket.", - "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning." - }, - "step": { - "confirm": { - "description": "Vil du konfigurere TP-Link-smartenheder?" - } + "no_devices_found": "Ingen TP-Link enheder kunne findes p\u00e5 netv\u00e6rket." } } } \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/de.json b/homeassistant/components/tplink/translations/de.json index 4d6a07b881e..1fd05c1325a 100644 --- a/homeassistant/components/tplink/translations/de.json +++ b/homeassistant/components/tplink/translations/de.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", - "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "M\u00f6chtest du TP-Link Smart Devices einrichten?" - }, "discovery_confirm": { "description": "M\u00f6chtest du {name} {model} ({host}) einrichten?" }, diff --git a/homeassistant/components/tplink/translations/el.json b/homeassistant/components/tplink/translations/el.json index bea659e039a..d7c4825b514 100644 --- a/homeassistant/components/tplink/translations/el.json +++ b/homeassistant/components/tplink/translations/el.json @@ -2,17 +2,13 @@ "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", - "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." + "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" }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03ad\u03be\u03c5\u03c0\u03bd\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 TP-Link;" - }, "discovery_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} {model} ({host});" }, diff --git a/homeassistant/components/tplink/translations/en.json b/homeassistant/components/tplink/translations/en.json index da4681145d8..0697974e708 100644 --- a/homeassistant/components/tplink/translations/en.json +++ b/homeassistant/components/tplink/translations/en.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Device is already configured", - "no_devices_found": "No devices found on the network", - "single_instance_allowed": "Already configured. Only a single configuration possible." + "no_devices_found": "No devices found on the network" }, "error": { "cannot_connect": "Failed to connect" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "Do you want to setup TP-Link smart devices?" - }, "discovery_confirm": { "description": "Do you want to setup {name} {model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/es-419.json b/homeassistant/components/tplink/translations/es-419.json index 4113e802e1a..302d49fa29e 100644 --- a/homeassistant/components/tplink/translations/es-419.json +++ b/homeassistant/components/tplink/translations/es-419.json @@ -1,13 +1,7 @@ { "config": { "abort": { - "no_devices_found": "No se encontraron dispositivos TP-Link en la red.", - "single_instance_allowed": "Solo es necesaria una \u00fanica configuraci\u00f3n." - }, - "step": { - "confirm": { - "description": "\u00bfDesea configurar dispositivos inteligentes TP-Link?" - } + "no_devices_found": "No se encontraron dispositivos TP-Link en la red." } } } \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/es.json b/homeassistant/components/tplink/translations/es.json index 81bb3f47433..e083acf61ed 100644 --- a/homeassistant/components/tplink/translations/es.json +++ b/homeassistant/components/tplink/translations/es.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "El dispositivo ya est\u00e1 configurado", - "no_devices_found": "No se encontraron dispositivos en la red", - "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." + "no_devices_found": "No se encontraron dispositivos en la red" }, "error": { "cannot_connect": "No se pudo conectar" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "\u00bfQuieres configurar dispositivos inteligentes de TP-Link?" - }, "discovery_confirm": { "description": "\u00bfQuieres configurar {name} {model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/et.json b/homeassistant/components/tplink/translations/et.json index 12c4f3d6f84..01dabe57ea7 100644 --- a/homeassistant/components/tplink/translations/et.json +++ b/homeassistant/components/tplink/translations/et.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Seade on juba h\u00e4\u00e4lestatud", - "no_devices_found": "V\u00f5rgust ei leitud seadmeid", - "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + "no_devices_found": "V\u00f5rgust ei leitud seadmeid" }, "error": { "cannot_connect": "\u00dchendamine nurjus" }, "flow_title": "{name} {model} ( {host} )", "step": { - "confirm": { - "description": "Kas soovid seadistada TP-Linki nutiseadmeid?" - }, "discovery_confirm": { "description": "Kas seadistada {name}{model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/fr.json b/homeassistant/components/tplink/translations/fr.json index d3a5fad5939..e1105ea00e0 100644 --- a/homeassistant/components/tplink/translations/fr.json +++ b/homeassistant/components/tplink/translations/fr.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", - "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau", - "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau" }, "error": { "cannot_connect": "\u00c9chec de connexion" }, "flow_title": "{name} {model} ( {host} )", "step": { - "confirm": { - "description": "Voulez-vous configurer TP-Link smart devices?" - }, "discovery_confirm": { "description": "Voulez-vous configurer {name} {model} ( {host} )\u00a0?" }, diff --git a/homeassistant/components/tplink/translations/he.json b/homeassistant/components/tplink/translations/he.json index 6ac16c36476..fc44b0d7ae7 100644 --- a/homeassistant/components/tplink/translations/he.json +++ b/homeassistant/components/tplink/translations/he.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", - "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." + "no_devices_found": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05db\u05e9\u05d9\u05e8\u05d9\u05dd \u05d1\u05e8\u05e9\u05ea" }, "error": { "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d4\u05ea\u05e7\u05e0\u05d9\u05dd \u05d7\u05db\u05de\u05d9\u05dd \u05e9\u05dc TP-Link ?" - }, "pick_device": { "data": { "device": "\u05d4\u05ea\u05e7\u05df" diff --git a/homeassistant/components/tplink/translations/hu.json b/homeassistant/components/tplink/translations/hu.json index c00744d0dcf..ba9ba65a1c6 100644 --- a/homeassistant/components/tplink/translations/hu.json +++ b/homeassistant/components/tplink/translations/hu.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r be van konfigur\u00e1lva", - "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." + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" }, "error": { "cannot_connect": "A csatlakoz\u00e1s sikertelen" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "Szeretn\u00e9 be\u00e1ll\u00edtani a TP-Link intelligens eszk\u00f6zeit?" - }, "discovery_confirm": { "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name} {model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/id.json b/homeassistant/components/tplink/translations/id.json index 2a435ac1ac1..b7c0be074e7 100644 --- a/homeassistant/components/tplink/translations/id.json +++ b/homeassistant/components/tplink/translations/id.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Perangkat sudah dikonfigurasi", - "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", - "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" }, "error": { "cannot_connect": "Gagal terhubung" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "Ingin menyiapkan perangkat cerdas TP-Link?" - }, "discovery_confirm": { "description": "Ingin menyiapkan {name} {model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/it.json b/homeassistant/components/tplink/translations/it.json index 6ab46615373..4a35356d604 100644 --- a/homeassistant/components/tplink/translations/it.json +++ b/homeassistant/components/tplink/translations/it.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "no_devices_found": "Nessun dispositivo trovato sulla rete", - "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + "no_devices_found": "Nessun dispositivo trovato sulla rete" }, "error": { "cannot_connect": "Impossibile connettersi" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "Vuoi configurare i dispositivi intelligenti TP-Link?" - }, "discovery_confirm": { "description": "Vuoi configurare {name} {model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/ja.json b/homeassistant/components/tplink/translations/ja.json index f78e4adec9c..1df87f71518 100644 --- a/homeassistant/components/tplink/translations/ja.json +++ b/homeassistant/components/tplink/translations/ja.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", - "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" + "no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "TP-Link\u30b9\u30de\u30fc\u30c8\u30c7\u30d0\u30a4\u30b9\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" - }, "discovery_confirm": { "description": "{name} {model} ({host}) \u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u884c\u3044\u307e\u3059\u304b\uff1f" }, @@ -25,7 +21,7 @@ "data": { "host": "\u30db\u30b9\u30c8" }, - "description": "\u30db\u30b9\u30c8\u3092\u7a7a\u306b\u3057\u3066\u304a\u304f\u3068\u3001\u30c7\u30a3\u30b9\u30ab\u30d0\u30ea\u30fc(discovery)\u3092\u4f7f\u3063\u3066\u30c7\u30d0\u30a4\u30b9\u3092\u691c\u7d22\u3057\u307e\u3059\u3002" + "description": "\u30db\u30b9\u30c8\u3092\u7a7a\u767d\u306b\u3057\u3066\u304a\u304f\u3068\u3001\u30c7\u30a3\u30b9\u30ab\u30d0\u30ea\u30fc(discovery)\u3092\u4f7f\u3063\u3066\u30c7\u30d0\u30a4\u30b9\u3092\u691c\u7d22\u3057\u307e\u3059\u3002" } } } diff --git a/homeassistant/components/tplink/translations/ko.json b/homeassistant/components/tplink/translations/ko.json index a1bfb59ca07..f063f54207c 100644 --- a/homeassistant/components/tplink/translations/ko.json +++ b/homeassistant/components/tplink/translations/ko.json @@ -1,12 +1,11 @@ { "config": { "abort": { - "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "step": { - "confirm": { - "description": "TP-Link \uc2a4\ub9c8\ud2b8 \uae30\uae30\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "discovery_confirm": { + "description": "{name} {model} ( {host} )\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/tplink/translations/lb.json b/homeassistant/components/tplink/translations/lb.json index 1560b68d3e4..47a897ef157 100644 --- a/homeassistant/components/tplink/translations/lb.json +++ b/homeassistant/components/tplink/translations/lb.json @@ -1,13 +1,7 @@ { "config": { "abort": { - "no_devices_found": "Keng Apparater am Netzwierk fonnt.", - "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." - }, - "step": { - "confirm": { - "description": "Soll TP-Link Smart Home konfigur\u00e9iert ginn?" - } + "no_devices_found": "Keng Apparater am Netzwierk fonnt." } } } \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/nl.json b/homeassistant/components/tplink/translations/nl.json index f6cf6a21e72..67f79e90223 100644 --- a/homeassistant/components/tplink/translations/nl.json +++ b/homeassistant/components/tplink/translations/nl.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "no_devices_found": "Geen apparaten gevonden op het netwerk" }, "error": { - "cannot_connect": "Kon geen verbinding maken" + "cannot_connect": "Kan geen verbinding maken" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "Wil je TP-Link slimme apparaten instellen?" - }, "discovery_confirm": { "description": "Wilt u {name} {model} ({host}) instellen?" }, diff --git a/homeassistant/components/tplink/translations/no.json b/homeassistant/components/tplink/translations/no.json index 6c7bd7dcbf4..8ae66b3dce1 100644 --- a/homeassistant/components/tplink/translations/no.json +++ b/homeassistant/components/tplink/translations/no.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", - "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", - "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket" }, "error": { "cannot_connect": "Tilkobling mislyktes" }, "flow_title": "{name} {model} ( {host} )", "step": { - "confirm": { - "description": "Vil du konfigurere TP-Link smart enheter?" - }, "discovery_confirm": { "description": "Vil du konfigurere {name} {model} ( {host} )?" }, diff --git a/homeassistant/components/tplink/translations/pl.json b/homeassistant/components/tplink/translations/pl.json index 35e1e7f5354..3e6283a6d5e 100644 --- a/homeassistant/components/tplink/translations/pl.json +++ b/homeassistant/components/tplink/translations/pl.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", - "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci", - "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" - }, "discovery_confirm": { "description": "Czy chcesz skonfigurowa\u0107 {name} {model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/pt-BR.json b/homeassistant/components/tplink/translations/pt-BR.json index a034b44b761..7ce4d557844 100644 --- a/homeassistant/components/tplink/translations/pt-BR.json +++ b/homeassistant/components/tplink/translations/pt-BR.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", - "no_devices_found": "Nenhum dispositivo encontrado na rede", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + "no_devices_found": "Nenhum dispositivo encontrado na rede" }, "error": { "cannot_connect": "Falha ao conectar" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "Deseja configurar dispositivos inteligentes TP-Link?" - }, "discovery_confirm": { "description": "Deseja configurar {name} {model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/pt.json b/homeassistant/components/tplink/translations/pt.json index 9df803c325e..90afdfcf10a 100644 --- a/homeassistant/components/tplink/translations/pt.json +++ b/homeassistant/components/tplink/translations/pt.json @@ -1,13 +1,7 @@ { "config": { "abort": { - "no_devices_found": "Nenhum dispositivo TP-Link encontrado na rede.", - "single_instance_allowed": "S\u00f3 \u00e9 necess\u00e1ria uma \u00fanica configura\u00e7\u00e3o." - }, - "step": { - "confirm": { - "description": "Deseja configurar os dispositivos inteligentes TP-Link?" - } + "no_devices_found": "Nenhum dispositivo TP-Link encontrado na rede." } } } \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/ru.json b/homeassistant/components/tplink/translations/ru.json index 47f1459e572..b622a77c77d 100644 --- a/homeassistant/components/tplink/translations/ru.json +++ b/homeassistant/components/tplink/translations/ru.json @@ -2,17 +2,13 @@ "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.", - "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." + "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." }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c TP-Link Smart Home?" - }, "discovery_confirm": { "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} {model} ({host})?" }, diff --git a/homeassistant/components/tplink/translations/sl.json b/homeassistant/components/tplink/translations/sl.json index 6c49eaf8d0a..039bf9966a7 100644 --- a/homeassistant/components/tplink/translations/sl.json +++ b/homeassistant/components/tplink/translations/sl.json @@ -1,13 +1,7 @@ { "config": { "abort": { - "no_devices_found": "TP-Link naprav ni mogo\u010de najti v omre\u017eju.", - "single_instance_allowed": "Potrebna je samo ena konfiguracija." - }, - "step": { - "confirm": { - "description": "\u017delite namestiti pametne naprave TP-Link?" - } + "no_devices_found": "TP-Link naprav ni mogo\u010de najti v omre\u017eju." } } } \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/sv.json b/homeassistant/components/tplink/translations/sv.json index 70ab1740f0d..c11ec674c36 100644 --- a/homeassistant/components/tplink/translations/sv.json +++ b/homeassistant/components/tplink/translations/sv.json @@ -1,13 +1,7 @@ { "config": { "abort": { - "no_devices_found": "Inga TP-Link enheter hittades p\u00e5 n\u00e4tverket.", - "single_instance_allowed": "Endast en enda konfiguration \u00e4r n\u00f6dv\u00e4ndig." - }, - "step": { - "confirm": { - "description": "Vill du konfigurera TP-Link smart enheter?" - } + "no_devices_found": "Inga TP-Link enheter hittades p\u00e5 n\u00e4tverket." } } } \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/tr.json b/homeassistant/components/tplink/translations/tr.json index 5e4dca23c37..616997a0976 100644 --- a/homeassistant/components/tplink/translations/tr.json +++ b/homeassistant/components/tplink/translations/tr.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", - "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." + "no_devices_found": "A\u011fda cihaz bulunamad\u0131" }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "TP-Link ak\u0131ll\u0131 cihazlar\u0131 kurmak istiyor musunuz?" - }, "discovery_confirm": { "description": "{name} {model} ( {host} ) kurulumu yapmak istiyor musunuz?" }, diff --git a/homeassistant/components/tplink/translations/uk.json b/homeassistant/components/tplink/translations/uk.json index abbfc076f7e..1efd10692f9 100644 --- a/homeassistant/components/tplink/translations/uk.json +++ b/homeassistant/components/tplink/translations/uk.json @@ -1,13 +1,7 @@ { "config": { "abort": { - "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456.", - "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." - }, - "step": { - "confirm": { - "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 TP-Link Smart Home?" - } + "no_devices_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456." } } } \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/zh-Hans.json b/homeassistant/components/tplink/translations/zh-Hans.json index 710cc0fe166..9b46cb915c5 100644 --- a/homeassistant/components/tplink/translations/zh-Hans.json +++ b/homeassistant/components/tplink/translations/zh-Hans.json @@ -1,13 +1,7 @@ { "config": { "abort": { - "no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 TP-Link \u8bbe\u5907\u3002", - "single_instance_allowed": "\u53ea\u80fd\u914d\u7f6e\u4e00\u6b21\u3002" - }, - "step": { - "confirm": { - "description": "\u60a8\u60f3\u8981\u914d\u7f6e TP-Link \u667a\u80fd\u8bbe\u5907\u5417\uff1f" - } + "no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 TP-Link \u8bbe\u5907\u3002" } } } \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/zh-Hant.json b/homeassistant/components/tplink/translations/zh-Hant.json index 31bfeea42a2..47e976a7e30 100644 --- a/homeassistant/components/tplink/translations/zh-Hant.json +++ b/homeassistant/components/tplink/translations/zh-Hant.json @@ -2,17 +2,13 @@ "config": { "abort": { "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "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" + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "flow_title": "{name} {model} ({host})", "step": { - "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a TP-Link \u667a\u80fd\u88dd\u7f6e\uff1f" - }, "discovery_confirm": { "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} {model} ({host})\uff1f" }, diff --git a/homeassistant/components/traccar/device_tracker.py b/homeassistant/components/traccar/device_tracker.py index 74ce3ead901..0ed13bceefa 100644 --- a/homeassistant/components/traccar/device_tracker.py +++ b/homeassistant/components/traccar/device_tracker.py @@ -144,7 +144,7 @@ async def async_setup_entry( ] = async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) # Restore previously loaded devices - dev_reg = await device_registry.async_get_registry(hass) + dev_reg = device_registry.async_get(hass) dev_ids = { identifier[1] for device in dev_reg.devices.values() diff --git a/homeassistant/components/traccar/translations/es.json b/homeassistant/components/traccar/translations/es.json index a87e7aae2c1..851984b0024 100644 --- a/homeassistant/components/traccar/translations/es.json +++ b/homeassistant/components/traccar/translations/es.json @@ -5,12 +5,12 @@ "webhook_not_internet_accessible": "Tu instancia de Home Assistant debe estar accesible desde Internet para recibir mensajes webhook." }, "create_entry": { - "default": "Para enviar eventos a Home Assistant, necesitar\u00e1 configurar la funci\u00f3n de webhook en Traccar.\n\nUtilice la siguiente url: ``{webhook_url}``\n\nConsulte la [documentaci\u00f3n]({docs_url}) para m\u00e1s detalles." + "default": "Para enviar eventos a Home Assistant, tendr\u00e1s que configurar la opci\u00f3n webhook de Traccar.\n\nUtilice la siguiente url: `{webhook_url}`\n\nConsulte la [documentaci\u00f3n]({docs_url}) para m\u00e1s detalles." }, "step": { "user": { - "description": "\u00bfEst\u00e1 seguro de querer configurar Traccar?", - "title": "Configurar Traccar" + "description": "\u00bfEst\u00e1s seguro de que quieres configurar Traccar?", + "title": "Configura Traccar" } } } diff --git a/homeassistant/components/traccar/translations/fr.json b/homeassistant/components/traccar/translations/fr.json index ab5d254dd98..79e881b674b 100644 --- a/homeassistant/components/traccar/translations/fr.json +++ b/homeassistant/components/traccar/translations/fr.json @@ -10,7 +10,7 @@ }, "step": { "user": { - "description": "\u00cates-vous s\u00fbr de vouloir configurer Traccar?", + "description": "Voulez-vous vraiment configurer Traccar\u00a0?", "title": "Configurer Traccar" } } diff --git a/homeassistant/components/traccar/translations/nl.json b/homeassistant/components/traccar/translations/nl.json index 45a70a42b43..098b2c9376f 100644 --- a/homeassistant/components/traccar/translations/nl.json +++ b/homeassistant/components/traccar/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." + "webhook_not_internet_accessible": "Je Home Assistant-instantie moet toegankelijk zijn vanaf internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Voor het verzenden van gebeurtenissen naar Home Assistant, moet u de webhook-functie in Traccar instellen.\n\nGebruik de volgende URL: ' {webhook_url} '\n\nZie [de documentatie] ({docs_url}) voor meer informatie." diff --git a/homeassistant/components/tractive/translations/nl.json b/homeassistant/components/tractive/translations/nl.json index b0e1f17cdc3..910131316da 100644 --- a/homeassistant/components/tractive/translations/nl.json +++ b/homeassistant/components/tractive/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "reauth_failed_existing": "Kon het configuratie-item niet bijwerken, verwijder de integratie en stel deze opnieuw in.", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/tractive/translations/sensor.ko.json b/homeassistant/components/tractive/translations/sensor.ko.json new file mode 100644 index 00000000000..459ec08bec5 --- /dev/null +++ b/homeassistant/components/tractive/translations/sensor.ko.json @@ -0,0 +1,9 @@ +{ + "state": { + "tractive__tracker_state": { + "operational": "\uc6b4\uc601", + "system_shutdown_user": "\uc2dc\uc2a4\ud15c \uc885\ub8cc \uc0ac\uc6a9\uc790", + "system_startup": "\uc2dc\uc2a4\ud15c \uc2dc\uc791" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index e4d13568e6d..e79cd8396e1 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -84,7 +84,7 @@ async def async_setup_entry( await factory.shutdown() raise ConfigEntryNotReady from exc - dev_reg = await hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(hass) dev_reg.async_get_or_create( config_entry_id=entry.entry_id, connections=set(), diff --git a/homeassistant/components/tradfri/translations/es.json b/homeassistant/components/tradfri/translations/es.json index 1f342c4630e..531b11cc0a1 100644 --- a/homeassistant/components/tradfri/translations/es.json +++ b/homeassistant/components/tradfri/translations/es.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "La pasarela ya est\u00e1 configurada", - "already_in_progress": "La configuraci\u00f3n de la pasarela ya est\u00e1 en marcha." + "already_configured": "El dispositivo ya est\u00e1 configurado", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso" }, "error": { "cannot_authenticate": "No se puede autenticar, \u00bfGateway est\u00e1 emparejado con otro servidor como, por ejemplo, Homekit?", diff --git a/homeassistant/components/tradfri/translations/nl.json b/homeassistant/components/tradfri/translations/nl.json index 752270e78c6..02bffc6b60d 100644 --- a/homeassistant/components/tradfri/translations/nl.json +++ b/homeassistant/components/tradfri/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang" + "already_in_progress": "De configuratie is momenteel al bezig" }, "error": { "cannot_authenticate": "Kan niet authenticeren, is Gateway gekoppeld met een andere server zoals bijv. Homekit?", diff --git a/homeassistant/components/trafikverket_ferry/coordinator.py b/homeassistant/components/trafikverket_ferry/coordinator.py index 052341cd12e..7c2c64d49f0 100644 --- a/homeassistant/components/trafikverket_ferry/coordinator.py +++ b/homeassistant/components/trafikverket_ferry/coordinator.py @@ -13,7 +13,7 @@ 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 homeassistant.util import dt from .const import CONF_FROM, CONF_TIME, CONF_TO, DOMAIN @@ -58,25 +58,29 @@ class TVDataUpdateCoordinator(DataUpdateCoordinator): ) 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._time: time | None = dt.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() + current_time = dt.now() when = ( - datetime.combine(departure_day, self._time) + datetime.combine( + departure_day, self._time, dt.get_time_zone(self.hass.config.time_zone) + ) if self._time - else datetime.now() + else dt.now() ) - if currenttime > when: - when = currenttime + if current_time > when: + when = current_time try: - routedata: FerryStop = await self._ferry_api.async_get_next_ferry_stop( - self._from, self._to, when + routedata: list[ + FerryStop + ] = await self._ferry_api.async_get_next_ferry_stops( + self._from, self._to, when, 3 ) except ValueError as error: raise UpdateFailed( @@ -84,10 +88,13 @@ class TVDataUpdateCoordinator(DataUpdateCoordinator): ) 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, + "departure_time": routedata[0].departure_time, + "departure_from": routedata[0].from_harbor_name, + "departure_to": routedata[0].to_harbor_name, + "departure_modified": routedata[0].modified_time, + "departure_information": routedata[0].other_information, + "departure_time_next": routedata[1].departure_time, + "departure_time_next_next": routedata[2].departure_time, } + _LOGGER.debug("States: %s", states) return states diff --git a/homeassistant/components/trafikverket_ferry/manifest.json b/homeassistant/components/trafikverket_ferry/manifest.json index 90864c7e358..d333473f169 100644 --- a/homeassistant/components/trafikverket_ferry/manifest.json +++ b/homeassistant/components/trafikverket_ferry/manifest.json @@ -2,7 +2,7 @@ "domain": "trafikverket_ferry", "name": "Trafikverket Ferry", "documentation": "https://www.home-assistant.io/integrations/trafikverket_ferry", - "requirements": ["pytrafikverket==0.1.6.2"], + "requirements": ["pytrafikverket==0.2.0.1"], "codeowners": ["@gjohansson-ST"], "config_flow": true, "iot_class": "cloud_polling", diff --git a/homeassistant/components/trafikverket_ferry/sensor.py b/homeassistant/components/trafikverket_ferry/sensor.py index 682c2073f76..bab73d72210 100644 --- a/homeassistant/components/trafikverket_ferry/sensor.py +++ b/homeassistant/components/trafikverket_ferry/sensor.py @@ -3,8 +3,7 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass -from datetime import timedelta -import logging +from datetime import datetime, timedelta from typing import Any from homeassistant.components.sensor import ( @@ -20,12 +19,11 @@ 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 homeassistant.util.dt import as_utc 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" @@ -39,8 +37,8 @@ SCAN_INTERVAL = timedelta(minutes=5) class TrafikverketRequiredKeysMixin: """Mixin for required keys.""" - value_fn: Callable[[dict[str, Any]], StateType] - info_fn: Callable[[dict[str, Any]], StateType | list] + value_fn: Callable[[dict[str, Any]], StateType | datetime] + info_fn: Callable[[dict[str, Any]], StateType | list] | None @dataclass @@ -56,7 +54,7 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( name="Departure Time", icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, - value_fn=lambda data: data["departure_time"], + value_fn=lambda data: as_utc(data["departure_time"]), info_fn=lambda data: data["departure_information"], ), TrafikverketSensorEntityDescription( @@ -78,10 +76,28 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( name="Departure Modified", icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, - value_fn=lambda data: data["departure_modified"], + value_fn=lambda data: as_utc(data["departure_modified"]), info_fn=lambda data: data["departure_information"], entity_registry_enabled_default=False, ), + TrafikverketSensorEntityDescription( + key="departure_time_next", + name="Departure Time Next", + icon="mdi:clock", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=lambda data: as_utc(data["departure_time_next"]), + info_fn=None, + entity_registry_enabled_default=False, + ), + TrafikverketSensorEntityDescription( + key="departure_time_next_next", + name="Departure Time Next After", + icon="mdi:clock", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=lambda data: as_utc(data["departure_time_next_next"]), + info_fn=None, + entity_registry_enabled_default=False, + ), ) @@ -122,7 +138,7 @@ class FerrySensor(CoordinatorEntity[TVDataUpdateCoordinator], SensorEntity): entry_type=DeviceEntryType.SERVICE, identifiers={(DOMAIN, entry_id)}, manufacturer="Trafikverket", - model="v1.2", + model="v2.0", name=name, configuration_url="https://api.trafikinfo.trafikverket.se/", ) @@ -133,9 +149,13 @@ class FerrySensor(CoordinatorEntity[TVDataUpdateCoordinator], SensorEntity): 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), - } + + if self.entity_description.info_fn: + self._attr_extra_state_attributes = { + "other_information": self.entity_description.info_fn( + self.coordinator.data + ), + } @callback def _handle_coordinator_update(self) -> None: diff --git a/homeassistant/components/trafikverket_ferry/translations/bg.json b/homeassistant/components/trafikverket_ferry/translations/bg.json new file mode 100644 index 00000000000..72b2aa2cf7b --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/bg.json @@ -0,0 +1,26 @@ +{ + "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", + "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", + "incorrect_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447 \u0437\u0430 \u0438\u0437\u0431\u0440\u0430\u043d\u0438\u044f \u0430\u043a\u0430\u0443\u043d\u0442", + "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": "\u0420\u0430\u0431\u043e\u0442\u043d\u0438 \u0434\u043d\u0438" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/ca.json b/homeassistant/components/trafikverket_ferry/translations/ca.json new file mode 100644 index 00000000000..0af4ba34155 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/ca.json @@ -0,0 +1,30 @@ +{ + "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_route": "No s'ha pogut trobar la ruta amb la informaci\u00f3 proporcionada" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Clau API" + } + }, + "user": { + "data": { + "api_key": "Clau API", + "from": "Des del port", + "time": "Hora", + "to": "Al port", + "weekday": "Laborables" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/es.json b/homeassistant/components/trafikverket_ferry/translations/es.json new file mode 100644 index 00000000000..26532fbce5e --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/es.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "reauth_successful": "Re-autenticaci\u00f3n realizada correctamente" + }, + "error": { + "incorrect_api_key": "Clave API inv\u00e1lida para la cuenta seleccionada", + "invalid_route": "No se pudo encontrar la ruta con la informaci\u00f3n proporcionada" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Clave API" + } + }, + "user": { + "data": { + "api_key": "Clave API", + "from": "Des del puerto", + "time": "Hora", + "to": "Al puerto" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/he.json b/homeassistant/components/trafikverket_ferry/translations/he.json new file mode 100644 index 00000000000..29f4fc720da --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/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_ferry/translations/ja.json b/homeassistant/components/trafikverket_ferry/translations/ja.json new file mode 100644 index 00000000000..251117bc773 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/ja.json @@ -0,0 +1,30 @@ +{ + "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_route": "\u63d0\u4f9b\u3055\u308c\u305f\u60c5\u5831\u3067\u30eb\u30fc\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API\u30ad\u30fc" + } + }, + "user": { + "data": { + "api_key": "API\u30ad\u30fc", + "from": "\u6e2f\u304b\u3089", + "time": "\u6642\u9593", + "to": "\u6e2f\u3078", + "weekday": "\u5e73\u65e5" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/ko.json b/homeassistant/components/trafikverket_ferry/translations/ko.json new file mode 100644 index 00000000000..c50ce7212d1 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/ko.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "incorrect_api_key": "\uc120\ud0dd\ud55c \uacc4\uc815\uc5d0 \ub300\ud55c \uc798\ubabb\ub41c API \ud0a4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_route": "\uc81c\uacf5\ub41c \uc815\ubcf4\ub85c \uacbd\ub85c\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API \ud0a4" + } + }, + "user": { + "data": { + "api_key": "API \ud0a4", + "time": "Time", + "weekday": "\ud3c9\uc77c" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/nl.json b/homeassistant/components/trafikverket_ferry/translations/nl.json index f84232839c8..edaeb14fd48 100644 --- a/homeassistant/components/trafikverket_ferry/translations/nl.json +++ b/homeassistant/components/trafikverket_ferry/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/trafikverket_ferry/translations/tr.json b/homeassistant/components/trafikverket_ferry/translations/tr.json new file mode 100644 index 00000000000..341de06c8a5 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/tr.json @@ -0,0 +1,30 @@ +{ + "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_route": "Sa\u011flanan bilgilerle rota bulunamad\u0131" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API Anahtar\u0131" + } + }, + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "from": "\u0130stasyondan", + "time": "Zaman", + "to": "\u0130stasyona", + "weekday": "Hafta i\u00e7i" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/config_flow.py b/homeassistant/components/trafikverket_train/config_flow.py index 79c5978de2c..823b393f7b1 100644 --- a/homeassistant/components/trafikverket_train/config_flow.py +++ b/homeassistant/components/trafikverket_train/config_flow.py @@ -102,11 +102,6 @@ class TVTrainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, config: dict[str, Any] | None) -> FlowResult: - """Import a configuration from config.yaml.""" - - return await self.async_step_user(user_input=config) - async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: diff --git a/homeassistant/components/trafikverket_train/const.py b/homeassistant/components/trafikverket_train/const.py index f0a6a1d6a18..253383b4b5a 100644 --- a/homeassistant/components/trafikverket_train/const.py +++ b/homeassistant/components/trafikverket_train/const.py @@ -5,7 +5,6 @@ DOMAIN = "trafikverket_train" 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_train/manifest.json b/homeassistant/components/trafikverket_train/manifest.json index b3bf23ce5c4..0432670f15c 100644 --- a/homeassistant/components/trafikverket_train/manifest.json +++ b/homeassistant/components/trafikverket_train/manifest.json @@ -2,7 +2,7 @@ "domain": "trafikverket_train", "name": "Trafikverket Train", "documentation": "https://www.home-assistant.io/integrations/trafikverket_train", - "requirements": ["pytrafikverket==0.1.6.2"], + "requirements": ["pytrafikverket==0.2.0.1"], "codeowners": ["@endor-force", "@gjohansson-ST"], "config_flow": true, "iot_class": "cloud_polling", diff --git a/homeassistant/components/trafikverket_train/sensor.py b/homeassistant/components/trafikverket_train/sensor.py index 7bcd796bb16..4a419ff3b33 100644 --- a/homeassistant/components/trafikverket_train/sensor.py +++ b/homeassistant/components/trafikverket_train/sensor.py @@ -7,26 +7,19 @@ from typing import Any from pytrafikverket import TrafikverketTrain from pytrafikverket.trafikverket_train import StationInfo, TrainStop -import voluptuous as vol -from homeassistant.components.sensor import ( - PLATFORM_SCHEMA, - SensorDeviceClass, - SensorEntity, -) -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_WEEKDAY, WEEKDAYS from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession -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.typing import ConfigType, DiscoveryInfoType -from homeassistant.util.dt import as_utc, get_time_zone, parse_time +from homeassistant.util import dt -from .const import CONF_FROM, CONF_TIME, CONF_TO, CONF_TRAINS, DOMAIN +from .const import CONF_FROM, CONF_TIME, CONF_TO, DOMAIN from .util import create_unique_id _LOGGER = logging.getLogger(__name__) @@ -42,54 +35,6 @@ ATTR_DEVIATIONS = "deviations" ICON = "mdi:train" SCAN_INTERVAL = timedelta(minutes=5) -STOCKHOLM_TIMEZONE = get_time_zone("Europe/Stockholm") - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_TRAINS): [ - { - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_TO): cv.string, - vol.Required(CONF_FROM): cv.string, - vol.Optional(CONF_TIME): cv.time, - vol.Optional(CONF_WEEKDAY, default=WEEKDAYS): vol.All( - cv.ensure_list, [vol.In(WEEKDAYS)] - ), - } - ], - } -) - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Import Trafikverket Train configuration from YAML.""" - _LOGGER.warning( - # Config flow added in Home Assistant Core 2022.3, remove import flow in 2022.7 - "Loading Trafikverket Train via platform setup is deprecated; Please remove it from your configuration" - ) - - for train in config[CONF_TRAINS]: - - new_config = { - CONF_API_KEY: config[CONF_API_KEY], - CONF_FROM: train[CONF_FROM], - CONF_TO: train[CONF_TO], - CONF_TIME: str(train.get(CONF_TIME)), - CONF_WEEKDAY: train.get(CONF_WEEKDAY, WEEKDAYS), - } - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=new_config, - ) - ) async def async_setup_entry( @@ -111,7 +56,9 @@ async def async_setup_entry( ) from error train_time = ( - parse_time(entry.data.get(CONF_TIME, "")) if entry.data.get(CONF_TIME) else None + dt.parse_time(entry.data.get(CONF_TIME, "")) + if entry.data.get(CONF_TIME) + else None ) async_add_entities( @@ -153,7 +100,7 @@ def next_departuredate(departure: list[str]) -> date: def _to_iso_format(traintime: datetime) -> str: """Return isoformatted utc time.""" - return as_utc(traintime.replace(tzinfo=STOCKHOLM_TIMEZONE)).isoformat() + return dt.as_utc(traintime).isoformat() class TrainSensor(SensorEntity): @@ -183,7 +130,7 @@ class TrainSensor(SensorEntity): entry_type=DeviceEntryType.SERVICE, identifiers={(DOMAIN, entry_id)}, manufacturer="Trafikverket", - model="v1.2", + model="v2.0", name=name, configuration_url="https://api.trafikinfo.trafikverket.se/", ) @@ -193,12 +140,12 @@ class TrainSensor(SensorEntity): async def async_update(self) -> None: """Retrieve latest state.""" - when = datetime.now() + when = dt.now() _state: TrainStop | None = None if self._time: departure_day = next_departuredate(self._weekday) - when = datetime.combine(departure_day, self._time).replace( - tzinfo=STOCKHOLM_TIMEZONE + when = datetime.combine( + departure_day, self._time, dt.get_time_zone(self.hass.config.time_zone) ) try: if self._time: @@ -222,17 +169,11 @@ class TrainSensor(SensorEntity): self._attr_available = True # The original datetime doesn't provide a timezone so therefore attaching it here. - self._attr_native_value = _state.advertised_time_at_location.replace( - tzinfo=STOCKHOLM_TIMEZONE - ) + self._attr_native_value = dt.as_utc(_state.advertised_time_at_location) if _state.time_at_location: - self._attr_native_value = _state.time_at_location.replace( - tzinfo=STOCKHOLM_TIMEZONE - ) + self._attr_native_value = dt.as_utc(_state.time_at_location) if _state.estimated_time_at_location: - self._attr_native_value = _state.estimated_time_at_location.replace( - tzinfo=STOCKHOLM_TIMEZONE - ) + self._attr_native_value = dt.as_utc(_state.estimated_time_at_location) self._update_attributes(_state) diff --git a/homeassistant/components/trafikverket_train/translations/es.json b/homeassistant/components/trafikverket_train/translations/es.json new file mode 100644 index 00000000000..4ce1da04b02 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/es.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", + "incorrect_api_key": "Clave API inv\u00e1lida para la cuenta seleccionada", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "from": "Desde la estaci\u00f3n", + "time": "Hora (opcional)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/ko.json b/homeassistant/components/trafikverket_train/translations/ko.json new file mode 100644 index 00000000000..82cb120d44d --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/ko.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "api_key": "API \ud0a4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/nl.json b/homeassistant/components/trafikverket_train/translations/nl.json index d076f8b2961..3143a28cf16 100644 --- a/homeassistant/components/trafikverket_train/translations/nl.json +++ b/homeassistant/components/trafikverket_train/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/trafikverket_weatherstation/manifest.json b/homeassistant/components/trafikverket_weatherstation/manifest.json index 4001856b703..e7efca9b24a 100644 --- a/homeassistant/components/trafikverket_weatherstation/manifest.json +++ b/homeassistant/components/trafikverket_weatherstation/manifest.json @@ -2,7 +2,7 @@ "domain": "trafikverket_weatherstation", "name": "Trafikverket Weather Station", "documentation": "https://www.home-assistant.io/integrations/trafikverket_weatherstation", - "requirements": ["pytrafikverket==0.1.6.2"], + "requirements": ["pytrafikverket==0.2.0.1"], "codeowners": ["@endor-force", "@gjohansson-ST"], "config_flow": true, "iot_class": "cloud_polling", diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py index e659e42f82b..c54c9f67388 100644 --- a/homeassistant/components/trafikverket_weatherstation/sensor.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -24,13 +24,11 @@ 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 homeassistant.util.dt import as_utc, get_time_zone +from homeassistant.util.dt import as_utc from .const import ATTRIBUTION, CONF_STATION, DOMAIN, NONE_IS_ZERO_SENSORS from .coordinator import TVDataUpdateCoordinator -STOCKHOLM_TIMEZONE = get_time_zone("Europe/Stockholm") - @dataclass class TrafikverketRequiredKeysMixin: @@ -156,8 +154,8 @@ async def async_setup_entry( def _to_datetime(measuretime: str) -> datetime: """Return isoformatted utc time.""" - time_obj = datetime.strptime(measuretime, "%Y-%m-%dT%H:%M:%S") - return as_utc(time_obj.replace(tzinfo=STOCKHOLM_TIMEZONE)) + time_obj = datetime.strptime(measuretime, "%Y-%m-%dT%H:%M:%S.%f%z") + return as_utc(time_obj) class TrafikverketWeatherStation( @@ -184,7 +182,7 @@ class TrafikverketWeatherStation( entry_type=DeviceEntryType.SERVICE, identifiers={(DOMAIN, entry_id)}, manufacturer="Trafikverket", - model="v1.2", + model="v2.0", name=sensor_station, configuration_url="https://api.trafikinfo.trafikverket.se/", ) diff --git a/homeassistant/components/trafikverket_weatherstation/translations/bg.json b/homeassistant/components/trafikverket_weatherstation/translations/bg.json index 2e96cb420e6..07c54468c03 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/bg.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/bg.json @@ -11,7 +11,6 @@ "user": { "data": { "api_key": "API \u043a\u043b\u044e\u0447", - "name": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435", "station": "\u0421\u0442\u0430\u043d\u0446\u0438\u044f" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/ca.json b/homeassistant/components/trafikverket_weatherstation/translations/ca.json index 80fe04ba83f..2ca00e2b4df 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/ca.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/ca.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "Clau API", - "conditions": "Condicions monitoritzades", - "name": "Nom d'usuari", "station": "Estaci\u00f3" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/de.json b/homeassistant/components/trafikverket_weatherstation/translations/de.json index 47d3dfc2e9b..c57b913e42a 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/de.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/de.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "API-Schl\u00fcssel", - "conditions": "\u00dcberwachte Bedingungen", - "name": "Benutzername", "station": "Station" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/el.json b/homeassistant/components/trafikverket_weatherstation/translations/el.json index e790e3d3d97..83f046e2c41 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/el.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/el.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", - "conditions": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03bf\u03cd\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b8\u03ae\u03ba\u03b5\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", "station": "\u03a3\u03c4\u03b1\u03b8\u03bc\u03cc\u03c2/\u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/en.json b/homeassistant/components/trafikverket_weatherstation/translations/en.json index 0c0c15f5bb9..c2b5ba0b598 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/en.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/en.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "API Key", - "conditions": "Monitored conditions", - "name": "Username", "station": "Station" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/es.json b/homeassistant/components/trafikverket_weatherstation/translations/es.json index 009c57bb275..9513ae65221 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/es.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/es.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "Clave API", - "conditions": "Condiciones monitoreadas", - "name": "Nombre de usuario", "station": "Estaci\u00f3n" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/et.json b/homeassistant/components/trafikverket_weatherstation/translations/et.json index 7350031bd36..8485525e233 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/et.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/et.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "API v\u00f5ti", - "conditions": "J\u00e4lgitavad elemendid", - "name": "Kasutajanimi", "station": "Seirejaam" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/fr.json b/homeassistant/components/trafikverket_weatherstation/translations/fr.json index 55a88c6a746..6a92e2249d9 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/fr.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/fr.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "Cl\u00e9 d'API", - "conditions": "Conditions surveill\u00e9es", - "name": "Nom d'utilisateur", "station": "Station" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/he.json b/homeassistant/components/trafikverket_weatherstation/translations/he.json index e6eceda994c..6801953a80c 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/he.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/he.json @@ -11,7 +11,6 @@ "user": { "data": { "api_key": "\u05de\u05e4\u05ea\u05d7 API", - "name": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9", "station": "\u05ea\u05d7\u05e0\u05d4" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/hu.json b/homeassistant/components/trafikverket_weatherstation/translations/hu.json index c4f830b20fa..57c2bf2e526 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/hu.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/hu.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "API kulcs", - "conditions": "Megfigyelt k\u00f6r\u00fclm\u00e9nyek", - "name": "Felhaszn\u00e1l\u00f3n\u00e9v", "station": "\u00c1llom\u00e1s" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/id.json b/homeassistant/components/trafikverket_weatherstation/translations/id.json index 0db961235d4..e3d04cc2a45 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/id.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/id.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "Kunci API", - "conditions": "Kondisi yang dipantau", - "name": "Nama Pengguna", "station": "Stasiun" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/it.json b/homeassistant/components/trafikverket_weatherstation/translations/it.json index a073a528586..927b8bdd19a 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/it.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/it.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "Chiave API", - "conditions": "Condizioni monitorate", - "name": "Nome utente", "station": "Stazione" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/ja.json b/homeassistant/components/trafikverket_weatherstation/translations/ja.json index 5a022f011e2..5710ea7b693 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/ja.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/ja.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "API\u30ad\u30fc", - "conditions": "\u30e2\u30cb\u30bf\u30fc\u306e\u72b6\u614b", - "name": "\u30e6\u30fc\u30b6\u30fc\u540d", "station": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/ko.json b/homeassistant/components/trafikverket_weatherstation/translations/ko.json new file mode 100644 index 00000000000..97d9717113c --- /dev/null +++ b/homeassistant/components/trafikverket_weatherstation/translations/ko.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "invalid_station": "\uc9c0\uc815\ub41c \uc774\ub984\uc758 \uae30\uc0c1 \uad00\uce21\uc18c\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "more_stations": "\uc9c0\uc815\ub41c \uc774\ub984\uc744 \uac00\uc9c4 \uc5ec\ub7ec \uae30\uc0c1 \uad00\uce21\uc18c\ub97c \ucc3e\uc558\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "station": "\uc2a4\ud14c\uc774\uc158" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_weatherstation/translations/nb.json b/homeassistant/components/trafikverket_weatherstation/translations/nb.json index 19fbf894f8d..0d3a32ccb1c 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/nb.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/nb.json @@ -10,8 +10,7 @@ "step": { "user": { "data": { - "api_key": "API-n\u00f8kkel", - "name": "Brukernavn" + "api_key": "API-n\u00f8kkel" } } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/nl.json b/homeassistant/components/trafikverket_weatherstation/translations/nl.json index 1e8d8ae9f3a..ce958cb3d86 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/nl.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/nl.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "API-sleutel", - "conditions": "Gemonitorde condities", - "name": "Gebruikersnaam", "station": "Station" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/no.json b/homeassistant/components/trafikverket_weatherstation/translations/no.json index fea917f9c38..7b39c2f9b58 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/no.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/no.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "API-n\u00f8kkel", - "conditions": "Overv\u00e5kede forhold", - "name": "Brukernavn", "station": "Stasjon" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/pl.json b/homeassistant/components/trafikverket_weatherstation/translations/pl.json index 2dfd13bf268..2500bcc2b51 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/pl.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/pl.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "Klucz API", - "conditions": "Monitorowane warunki pogodowe", - "name": "Nazwa u\u017cytkownika", "station": "Stacja" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/pt-BR.json b/homeassistant/components/trafikverket_weatherstation/translations/pt-BR.json index f73ab8555da..70d870a6dba 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/pt-BR.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/pt-BR.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "Chave da API", - "conditions": "Condi\u00e7\u00f5es monitoradas", - "name": "Usu\u00e1rio", "station": "Esta\u00e7\u00e3o" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/ru.json b/homeassistant/components/trafikverket_weatherstation/translations/ru.json index d7658cda81b..7a903d8f624 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/ru.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/ru.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API", - "conditions": "\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u0435\u043c\u044b\u0435 \u0443\u0441\u043b\u043e\u0432\u0438\u044f", - "name": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "station": "\u0421\u0442\u0430\u043d\u0446\u0438\u044f" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/tr.json b/homeassistant/components/trafikverket_weatherstation/translations/tr.json index e999f7b50d1..89c8288a9ee 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/tr.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/tr.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "API Anahtar\u0131", - "conditions": "\u0130zlenen ko\u015fullar", - "name": "Kullan\u0131c\u0131 Ad\u0131", "station": "\u0130stasyon" } } diff --git a/homeassistant/components/trafikverket_weatherstation/translations/zh-Hant.json b/homeassistant/components/trafikverket_weatherstation/translations/zh-Hant.json index 47394b08af9..f0d928c3aba 100644 --- a/homeassistant/components/trafikverket_weatherstation/translations/zh-Hant.json +++ b/homeassistant/components/trafikverket_weatherstation/translations/zh-Hant.json @@ -13,8 +13,6 @@ "user": { "data": { "api_key": "API \u91d1\u9470", - "conditions": "\u5df2\u76e3\u63a7\u72c0\u614b", - "name": "\u4f7f\u7528\u8005\u540d\u7a31", "station": "\u76e3\u63a7\u7ad9" } } diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 4628ec8768a..706122c174c 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -21,6 +21,7 @@ import yarl from homeassistant.components.http import HomeAssistantView from homeassistant.components.media_player.const import ( + ATTR_MEDIA_ANNOUNCE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, DOMAIN as DOMAIN_MP, @@ -224,6 +225,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: str(yarl.URL.build(path=p_type, query=params)), ), ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, + ATTR_MEDIA_ANNOUNCE: True, }, blocking=True, context=service.context, diff --git a/homeassistant/components/tuya/binary_sensor.py b/homeassistant/components/tuya/binary_sensor.py index 2c61151bfaf..d5e4a9b22b0 100644 --- a/homeassistant/components/tuya/binary_sensor.py +++ b/homeassistant/components/tuya/binary_sensor.py @@ -46,6 +46,79 @@ TAMPER_BINARY_SENSOR = TuyaBinarySensorEntityDescription( # end up being a binary sensor. # https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq BINARY_SENSORS: dict[str, tuple[TuyaBinarySensorEntityDescription, ...]] = { + # Multi-functional Sensor + # https://developer.tuya.com/en/docs/iot/categorydgnbj?id=Kaiuz3yorvzg3 + "dgnbj": ( + TuyaBinarySensorEntityDescription( + key=DPCode.GAS_SENSOR_STATE, + name="Gas", + icon="mdi:gas-cylinder", + device_class=BinarySensorDeviceClass.SAFETY, + on_value="alarm", + ), + TuyaBinarySensorEntityDescription( + key=DPCode.CH4_SENSOR_STATE, + name="Methane", + device_class=BinarySensorDeviceClass.GAS, + on_value="alarm", + ), + TuyaBinarySensorEntityDescription( + key=DPCode.VOC_STATE, + name="Volatile Organic Compound", + device_class=BinarySensorDeviceClass.SAFETY, + on_value="alarm", + ), + TuyaBinarySensorEntityDescription( + key=DPCode.PM25_STATE, + name="Particulate Matter 2.5 µm", + device_class=BinarySensorDeviceClass.SAFETY, + on_value="alarm", + ), + TuyaBinarySensorEntityDescription( + key=DPCode.CO_STATE, + name="Carbon Monoxide", + icon="mdi:molecule-co", + device_class=BinarySensorDeviceClass.SAFETY, + on_value="alarm", + ), + TuyaBinarySensorEntityDescription( + key=DPCode.CO2_STATE, + icon="mdi:molecule-co2", + name="Carbon Dioxide", + device_class=BinarySensorDeviceClass.SAFETY, + on_value="alarm", + ), + TuyaBinarySensorEntityDescription( + key=DPCode.CH2O_STATE, + name="Formaldehyde", + device_class=BinarySensorDeviceClass.SAFETY, + on_value="alarm", + ), + TuyaBinarySensorEntityDescription( + key=DPCode.DOORCONTACT_STATE, + name="Door", + device_class=BinarySensorDeviceClass.DOOR, + ), + TuyaBinarySensorEntityDescription( + key=DPCode.WATERSENSOR_STATE, + name="Water Leak", + device_class=BinarySensorDeviceClass.MOISTURE, + on_value="alarm", + ), + TuyaBinarySensorEntityDescription( + key=DPCode.PRESSURE_STATE, + name="Pressure", + on_value="alarm", + ), + TuyaBinarySensorEntityDescription( + key=DPCode.SMOKE_SENSOR_STATE, + name="Smoke", + icon="mdi:smoke-detector", + device_class=BinarySensorDeviceClass.SMOKE, + on_value="alarm", + ), + TAMPER_BINARY_SENSOR, + ), # CO2 Detector # https://developer.tuya.com/en/docs/iot/categoryco2bj?id=Kaiuz3wes7yuy "co2bj": ( diff --git a/homeassistant/components/tuya/number.py b/homeassistant/components/tuya/number.py index d9cde61a276..35efd78871f 100644 --- a/homeassistant/components/tuya/number.py +++ b/homeassistant/components/tuya/number.py @@ -18,6 +18,15 @@ from .const import DOMAIN, TUYA_DISCOVERY_NEW, DPCode, DPType # default instructions set of each category end up being a number. # https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { + # Multi-functional Sensor + # https://developer.tuya.com/en/docs/iot/categorydgnbj?id=Kaiuz3yorvzg3 + "dgnbj": ( + NumberEntityDescription( + key=DPCode.ALARM_TIME, + name="Time", + entity_category=EntityCategory.CONFIG, + ), + ), # Smart Kettle # https://developer.tuya.com/en/docs/iot/fbh?id=K9gf484m21yq7 "bh": ( diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index d9103b916f4..974268c109f 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -18,6 +18,15 @@ from .const import DOMAIN, TUYA_DISCOVERY_NEW, DPCode, DPType, TuyaDeviceClass # default instructions set of each category end up being a select. # https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { + # Multi-functional Sensor + # https://developer.tuya.com/en/docs/iot/categorydgnbj?id=Kaiuz3yorvzg3 + "dgnbj": ( + SelectEntityDescription( + key=DPCode.ALARM_VOLUME, + name="Volume", + entity_category=EntityCategory.CONFIG, + ), + ), # Coffee maker # https://developer.tuya.com/en/docs/iot/categorykfj?id=Kaiuz2p12pc7f "kfj": ( diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index 3ee88d2d57b..acb2ffe7987 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -82,6 +82,84 @@ BATTERY_SENSORS: tuple[TuyaSensorEntityDescription, ...] = ( # end up being a sensor. # https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { + # Multi-functional Sensor + # https://developer.tuya.com/en/docs/iot/categorydgnbj?id=Kaiuz3yorvzg3 + "dgnbj": ( + TuyaSensorEntityDescription( + key=DPCode.GAS_SENSOR_VALUE, + name="Gas", + icon="mdi:gas-cylinder", + device_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.CH4_SENSOR_VALUE, + name="Methane", + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.VOC_VALUE, + name="Volatile Organic Compound", + device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.PM25_VALUE, + name="Particulate Matter 2.5 µm", + device_class=SensorDeviceClass.PM25, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.CO_VALUE, + name="Carbon Monoxide", + icon="mdi:molecule-co", + device_class=SensorDeviceClass.CO, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.CO2_VALUE, + name="Carbon Dioxide", + icon="mdi:molecule-co2", + device_class=SensorDeviceClass.CO2, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.CH2O_VALUE, + name="Formaldehyde", + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.BRIGHT_STATE, + name="Luminosity", + icon="mdi:brightness-6", + ), + TuyaSensorEntityDescription( + key=DPCode.BRIGHT_VALUE, + name="Luminosity", + icon="mdi:brightness-6", + device_class=SensorDeviceClass.ILLUMINANCE, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.TEMP_CURRENT, + name="Temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.HUMIDITY_VALUE, + name="Humidity", + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + ), + TuyaSensorEntityDescription( + key=DPCode.SMOKE_SENSOR_VALUE, + name="Smoke Amount", + icon="mdi:smoke-detector", + entity_category=EntityCategory.DIAGNOSTIC, + device_class=SensorStateClass.MEASUREMENT, + ), + *BATTERY_SENSORS, + ), # Smart Kettle # https://developer.tuya.com/en/docs/iot/fbh?id=K9gf484m21yq7 "bh": ( diff --git a/homeassistant/components/tuya/siren.py b/homeassistant/components/tuya/siren.py index dcb26871bb2..a60e24eca86 100644 --- a/homeassistant/components/tuya/siren.py +++ b/homeassistant/components/tuya/siren.py @@ -22,6 +22,14 @@ from .const import DOMAIN, TUYA_DISCOVERY_NEW, DPCode # All descriptions can be found here: # https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq SIRENS: dict[str, tuple[SirenEntityDescription, ...]] = { + # Multi-functional Sensor + # https://developer.tuya.com/en/docs/iot/categorydgnbj?id=Kaiuz3yorvzg3 + "dgnbj": ( + SirenEntityDescription( + key=DPCode.ALARM_SWITCH, + name="Siren", + ), + ), # Siren Alarm # https://developer.tuya.com/en/docs/iot/categorysgbj?id=Kaiuz37tlpbnu "sgbj": ( diff --git a/homeassistant/components/tuya/translations/af.json b/homeassistant/components/tuya/translations/af.json deleted file mode 100644 index 71ac741b6b8..00000000000 --- a/homeassistant/components/tuya/translations/af.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "options": { - "error": { - "dev_not_config": "Ger\u00e4tetyp nicht konfigurierbar", - "dev_not_found": "Ger\u00e4t nicht gefunden" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/bg.json b/homeassistant/components/tuya/translations/bg.json index 7a393100351..5bd4cc0b745 100644 --- a/homeassistant/components/tuya/translations/bg.json +++ b/homeassistant/components/tuya/translations/bg.json @@ -1,51 +1,17 @@ { "config": { - "abort": { - "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \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", - "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": { "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", "login_error": "\u0413\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0432\u043b\u0438\u0437\u0430\u043d\u0435 ({code}): {msg}" }, - "flow_title": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Tuya", "step": { - "login": { - "data": { - "country_code": "\u041a\u043e\u0434 \u043d\u0430 \u0434\u044a\u0440\u0436\u0430\u0432\u0430\u0442\u0430", - "password": "\u041f\u0430\u0440\u043e\u043b\u0430", - "tuya_app_type": "\u041c\u043e\u0431\u0438\u043b\u043d\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435", - "username": "\u0410\u043a\u0430\u0443\u043d\u0442" - }, - "description": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438\u0442\u0435 \u0441\u0438 \u0434\u0430\u043d\u043d\u0438 \u0437\u0430 Tuya", - "title": "Tuya" - }, "user": { "data": { "country_code": "\u0414\u044a\u0440\u0436\u0430\u0432\u0430", "password": "\u041f\u0430\u0440\u043e\u043b\u0430", - "platform": "\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e, \u0432 \u043a\u043e\u0435\u0442\u043e \u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0430\u043d \u0412\u0430\u0448\u0438\u044f\u0442 \u0430\u043a\u0430\u0443\u043d\u0442", - "region": "\u0420\u0435\u0433\u0438\u043e\u043d", "username": "\u0410\u043a\u0430\u0443\u043d\u0442" }, - "description": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0430\u0448\u0438\u0442\u0435 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u0434\u0430\u043d\u043d\u0438 \u0437\u0430 Tuya", - "title": "Tuya \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" - }, - "error": { - "dev_not_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0435 \u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043e" - }, - "step": { - "device": { - "data": { - "unit_of_measurement": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u043d\u0430 \u0435\u0434\u0438\u043d\u0438\u0446\u0430, \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0430 \u043e\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e" - } + "description": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0430\u0448\u0438\u0442\u0435 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u0434\u0430\u043d\u043d\u0438 \u0437\u0430 Tuya" } } } diff --git a/homeassistant/components/tuya/translations/ca.json b/homeassistant/components/tuya/translations/ca.json index 5a9dd4ac44b..52ef20e69b9 100644 --- a/homeassistant/components/tuya/translations/ca.json +++ b/homeassistant/components/tuya/translations/ca.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Ha fallat la connexi\u00f3", - "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", - "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." - }, "error": { "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "login_error": "Error d'inici de sessi\u00f3 ({code}): {msg}" }, - "flow_title": "Configuraci\u00f3 de Tuya", "step": { - "login": { - "data": { - "access_id": "ID d'acc\u00e9s", - "access_secret": "Secret d'acc\u00e9s", - "country_code": "Codi de pa\u00eds", - "endpoint": "Zona de disponibilitat", - "password": "Contrasenya", - "tuya_app_type": "Aplicaci\u00f3 per a m\u00f2bil", - "username": "Compte" - }, - "description": "Introdueix la credencial de Tuya", - "title": "Tuya" - }, "user": { "data": { "access_id": "ID d'acc\u00e9s de Tuya IoT", "access_secret": "Secret d'acc\u00e9s de Tuya IoT", "country_code": "Pa\u00eds", "password": "Contrasenya", - "platform": "L'aplicaci\u00f3 on es registra el teu compte", - "region": "Regi\u00f3", - "tuya_project_type": "Tipus de projecte al n\u00favol de Tuya", "username": "Compte" }, - "description": "Introdueix les teves credencial de Tuya", - "title": "Integraci\u00f3 Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Ha fallat la connexi\u00f3" - }, - "error": { - "dev_multi_type": "Per configurar una selecci\u00f3 de m\u00faltiples dispositius, aquests han de ser del mateix tipus", - "dev_not_config": "El tipus d'aquest dispositiu no \u00e9s configurable", - "dev_not_found": "No s'ha trobat el dispositiu." - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Rang de brillantor utilitzat pel dispositiu", - "curr_temp_divider": "Divisor del valor de temperatura actual (0 = predeterminat)", - "max_kelvin": "Temperatura del color m\u00e0xima suportada, en Kelvin", - "max_temp": "Temperatura desitjada m\u00e0xima (utilitza min i max = 0 per defecte)", - "min_kelvin": "Temperatura del color m\u00ednima suportada, en Kelvin", - "min_temp": "Temperatura desitjada m\u00ednima (utilitza min i max = 0 per defecte)", - "set_temp_divided": "Utilitza el valor de temperatura dividit per a ordres de configuraci\u00f3 de temperatura", - "support_color": "For\u00e7a el suport de color", - "temp_divider": "Divisor del valor de temperatura (0 = predeterminat)", - "temp_step_override": "Pas de temperatura objectiu", - "tuya_max_coltemp": "Temperatura de color m\u00e0xima enviada pel dispositiu", - "unit_of_measurement": "Unitat de temperatura utilitzada pel dispositiu" - }, - "description": "Configura les opcions per ajustar la informaci\u00f3 mostrada pel dispositiu {device_type} `{device_name}`", - "title": "Configuraci\u00f3 de dispositiu Tuya" - }, - "init": { - "data": { - "discovery_interval": "Interval de sondeig del dispositiu de descoberta, en segons", - "list_devices": "Selecciona els dispositius a configurar o deixa-ho buit per desar la configuraci\u00f3", - "query_device": "Selecciona el dispositiu que utilitzar\u00e0 m\u00e8tode de consulta, per actualitzacions d'estat m\u00e9s freq\u00fcents", - "query_interval": "Interval de sondeig de consultes del dispositiu, en segons" - }, - "description": "No estableixis valors d'interval de sondeig massa baixos ja que les crides fallaran i generaran missatges d'error al registre", - "title": "Configuraci\u00f3 d'opcions de Tuya" + "description": "Introdueix les teves credencial de Tuya" } } } diff --git a/homeassistant/components/tuya/translations/cs.json b/homeassistant/components/tuya/translations/cs.json index 9a406ffcb4b..0364baac560 100644 --- a/homeassistant/components/tuya/translations/cs.json +++ b/homeassistant/components/tuya/translations/cs.json @@ -1,65 +1,16 @@ { "config": { - "abort": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", - "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." - }, "error": { "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" }, - "flow_title": "Konfigurace Tuya", "step": { - "login": { - "data": { - "country_code": "K\u00f3d zem\u011b" - } - }, "user": { "data": { "country_code": "Zem\u011b", "password": "Heslo", - "platform": "Aplikace, ve kter\u00e9 m\u00e1te zaregistrovan\u00fd \u00fa\u010det", - "region": "Region", "username": "\u00da\u010det" }, - "description": "Zadejte sv\u00e9 p\u0159ihla\u0161ovac\u00ed \u00fadaje k Tuya.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" - }, - "error": { - "dev_multi_type": "V\u00edce vybran\u00fdch za\u0159\u00edzen\u00ed k nastaven\u00ed mus\u00ed b\u00fdt stejn\u00e9ho typu", - "dev_not_config": "Typ za\u0159\u00edzen\u00ed nelze nastavit", - "dev_not_found": "Za\u0159\u00edzen\u00ed nenalezeno" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Rozsah jasu pou\u017e\u00edvan\u00fd za\u0159\u00edzen\u00edm", - "max_kelvin": "Maxim\u00e1ln\u00ed podporovan\u00e1 teplota barev v kelvinech", - "max_temp": "Maxim\u00e1ln\u00ed c\u00edlov\u00e1 teplota (pou\u017eijte min a max = 0 jako v\u00fdchoz\u00ed)", - "min_kelvin": "Maxim\u00e1ln\u00ed podporovan\u00e1 teplota barev v kelvinech", - "min_temp": "Minim\u00e1ln\u00ed c\u00edlov\u00e1 teplota (pou\u017eijte min a max = 0 jako v\u00fdchoz\u00ed)", - "support_color": "Vynutit podporu barev", - "tuya_max_coltemp": "Maxim\u00e1ln\u00ed teplota barev nahl\u00e1\u0161en\u00e1 za\u0159\u00edzen\u00edm", - "unit_of_measurement": "Jednotka teploty pou\u017e\u00edvan\u00e1 za\u0159\u00edzen\u00edm" - }, - "title": "Nastavte za\u0159\u00edzen\u00ed Tuya" - }, - "init": { - "data": { - "discovery_interval": "Interval objevov\u00e1n\u00ed za\u0159\u00edzen\u00ed v sekund\u00e1ch", - "list_devices": "Vyberte za\u0159\u00edzen\u00ed, kter\u00e1 chcete nastavit, nebo ponechte pr\u00e1zdn\u00e9, abyste konfiguraci ulo\u017eili", - "query_device": "Vyberte za\u0159\u00edzen\u00ed, kter\u00e9 bude pou\u017e\u00edvat metodu dotaz\u016f pro rychlej\u0161\u00ed aktualizaci stavu", - "query_interval": "Interval dotazov\u00e1n\u00ed za\u0159\u00edzen\u00ed v sekund\u00e1ch" - }, - "description": "Nenastavujte intervalu dotazov\u00e1n\u00ed p\u0159\u00edli\u0161 n\u00edzk\u00e9 hodnoty, jinak se dotazov\u00e1n\u00ed nezda\u0159\u00ed a bude generovat chybov\u00e9 zpr\u00e1vy do logu", - "title": "Nastavte mo\u017enosti Tuya" + "description": "Zadejte sv\u00e9 p\u0159ihla\u0161ovac\u00ed \u00fadaje k Tuya." } } } diff --git a/homeassistant/components/tuya/translations/de.json b/homeassistant/components/tuya/translations/de.json index 61bdf308246..492159d4d6c 100644 --- a/homeassistant/components/tuya/translations/de.json +++ b/homeassistant/components/tuya/translations/de.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Verbindung fehlgeschlagen", - "invalid_auth": "Ung\u00fcltige Authentifizierung", - "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." - }, "error": { "invalid_auth": "Ung\u00fcltige Authentifizierung", "login_error": "Anmeldefehler ({code}): {msg}" }, - "flow_title": "Tuya Konfiguration", "step": { - "login": { - "data": { - "access_id": "Zugangs-ID", - "access_secret": "Zugangsgeheimnis", - "country_code": "L\u00e4ndercode", - "endpoint": "Verf\u00fcgbarkeitsbereich", - "password": "Passwort", - "tuya_app_type": "Mobile App", - "username": "Konto" - }, - "description": "Gib deine Tuya-Anmeldedaten ein", - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT-Zugriffs-ID", "access_secret": "Tuya IoT-Zugriffsgeheimnis", "country_code": "Land", "password": "Passwort", - "platform": "Die App, in der dein Konto registriert ist", - "region": "Region", - "tuya_project_type": "Tuya Cloud Projekttyp", "username": "Konto" }, - "description": "Gib deine Tuya-Anmeldeinformationen ein.", - "title": "Tuya-Integration" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Verbindung fehlgeschlagen" - }, - "error": { - "dev_multi_type": "Mehrere ausgew\u00e4hlte Ger\u00e4te zur Konfiguration m\u00fcssen vom gleichen Typ sein", - "dev_not_config": "Ger\u00e4tetyp nicht konfigurierbar", - "dev_not_found": "Ger\u00e4t nicht gefunden" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Vom Ger\u00e4t genutzter Helligkeitsbereich", - "curr_temp_divider": "Aktueller Temperaturwert-Teiler (0 = Standard verwenden)", - "max_kelvin": "Maximal unterst\u00fctzte Farbtemperatur in Kelvin", - "max_temp": "Maximale Solltemperatur (f\u00fcr Voreinstellung min und max = 0 verwenden)", - "min_kelvin": "Minimale unterst\u00fctzte Farbtemperatur in Kelvin", - "min_temp": "Minimal Solltemperatur (f\u00fcr Voreinstellung min und max = 0 verwenden)", - "set_temp_divided": "Geteilten Temperaturwert f\u00fcr Solltemperaturbefehl verwenden", - "support_color": "Farbunterst\u00fctzung erzwingen", - "temp_divider": "Teiler f\u00fcr Temperaturwerte (0 = Standard verwenden)", - "temp_step_override": "Zieltemperaturschritt", - "tuya_max_coltemp": "Vom Ger\u00e4t gemeldete maximale Farbtemperatur", - "unit_of_measurement": "Vom Ger\u00e4t verwendete Temperatureinheit" - }, - "description": "Optionen zur Anpassung der angezeigten Informationen f\u00fcr das Ger\u00e4t `{device_name}` vom Typ: {device_type}konfigurieren", - "title": "Tuya-Ger\u00e4t konfigurieren" - }, - "init": { - "data": { - "discovery_interval": "Abfrageintervall f\u00fcr Ger\u00e4teabruf in Sekunden", - "list_devices": "W\u00e4hle die zu konfigurierenden Ger\u00e4te aus oder lasse sie leer, um die Konfiguration zu speichern", - "query_device": "W\u00e4hle ein Ger\u00e4t aus, das die Abfragemethode f\u00fcr eine schnellere Statusaktualisierung verwendet.", - "query_interval": "Ger\u00e4teabrufintervall in Sekunden" - }, - "description": "Stelle das Abfrageintervall nicht zu niedrig ein, sonst schlagen die Aufrufe fehl und erzeugen eine Fehlermeldung im Protokoll", - "title": "Tuya-Optionen konfigurieren" + "description": "Gib deine Tuya-Anmeldeinformationen ein." } } } diff --git a/homeassistant/components/tuya/translations/el.json b/homeassistant/components/tuya/translations/el.json index 63ba3b2696a..7bbf8790316 100644 --- a/homeassistant/components/tuya/translations/el.json +++ b/homeassistant/components/tuya/translations/el.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "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", - "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": { "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", "login_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 ({code}): {msg}" }, - "flow_title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Tuya", "step": { - "login": { - "data": { - "access_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "access_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "country_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c7\u03ce\u03c1\u03b1\u03c2", - "endpoint": "\u0396\u03ce\u03bd\u03b7 \u03b4\u03b9\u03b1\u03b8\u03b5\u03c3\u03b9\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "tuya_app_type": "Mobile App", - "username": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2" - }, - "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2 Tuya", - "title": "Tuya" - }, "user": { "data": { "access_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 Tuya IoT", "access_secret": "\u039c\u03c5\u03c3\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 Tuya IoT", "country_code": "\u03a7\u03ce\u03c1\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "platform": "\u0397 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c3\u03c4\u03b7\u03bd \u03bf\u03c0\u03bf\u03af\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03b3\u03b3\u03b5\u03b3\u03c1\u03b1\u03bc\u03bc\u03ad\u03bd\u03bf\u03c2 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c3\u03b1\u03c2", - "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae", - "tuya_project_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03ad\u03c1\u03b3\u03bf\u03c5 Tuya cloud", "username": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2" }, - "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2 Tuya", - "title": "\u0395\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" - }, - "error": { - "dev_multi_type": "\u03a0\u03bf\u03bb\u03bb\u03ad\u03c2 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03bf\u03c5\u03bd \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03c4\u03cd\u03c0\u03bf", - "dev_not_config": "\u039f \u03c4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", - "dev_not_found": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u0395\u03cd\u03c1\u03bf\u03c2 \u03c6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", - "curr_temp_divider": "\u0394\u03b9\u03b1\u03b9\u03c1\u03ad\u03c4\u03b7\u03c2 \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 (0 = \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae\u03c2)", - "max_kelvin": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf kelvin", - "max_temp": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1-\u03c3\u03c4\u03cc\u03c7\u03bf\u03c2 (\u03c7\u03c1\u03ae\u03c3\u03b7 min \u03ba\u03b1\u03b9 max = 0 \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae)", - "min_kelvin": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03b5 kelvin", - "min_temp": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1-\u03c3\u03c4\u03cc\u03c7\u03bf\u03c2 (\u03c7\u03c1\u03ae\u03c3\u03b7 min \u03ba\u03b1\u03b9 max = 0 \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae)", - "set_temp_divided": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b9\u03b1\u03b9\u03c1\u03b5\u03bc\u03ad\u03bd\u03b7\u03c2 \u03c4\u03b9\u03bc\u03ae\u03c2 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2", - "support_color": "\u0391\u03bd\u03b1\u03b3\u03ba\u03b1\u03c3\u03c4\u03b9\u03ba\u03ae \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2", - "temp_divider": "\u0394\u03b9\u03b1\u03b9\u03c1\u03ad\u03c4\u03b7\u03c2 \u03c4\u03b9\u03bc\u03ce\u03bd \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 (0 = \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae\u03c2)", - "temp_step_override": "\u0392\u03ae\u03bc\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c3\u03c4\u03cc\u03c7\u03bf\u03c5", - "tuya_max_coltemp": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", - "unit_of_measurement": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" - }, - "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03c4\u03c9\u03bd \u03b5\u03bc\u03c6\u03b1\u03bd\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae {device_type} `{device_name}`", - "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 Tuya" - }, - "init": { - "data": { - "discovery_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b4\u03b7\u03bc\u03bf\u03c3\u03ba\u03cc\u03c0\u03b7\u03c3\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03c3\u03b5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1", - "list_devices": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03ae \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03ba\u03b5\u03bd\u03ad\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7", - "query_device": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03b7 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf \u03b5\u03c1\u03c9\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b1\u03c7\u03cd\u03c4\u03b5\u03c1\u03b7 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2", - "query_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b4\u03b7\u03bc\u03bf\u03c3\u03ba\u03cc\u03c0\u03b7\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03c3\u03b5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1" - }, - "description": "\u039c\u03b7\u03bd \u03bf\u03c1\u03af\u03b6\u03b5\u03c4\u03b5 \u03c0\u03bf\u03bb\u03cd \u03c7\u03b1\u03bc\u03b7\u03bb\u03ad\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b4\u03b9\u03b1\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b4\u03b7\u03bc\u03bf\u03c3\u03ba\u03bf\u03c0\u03ae\u03c3\u03b5\u03c9\u03bd, \u03b1\u03bb\u03bb\u03b9\u03ce\u03c2 \u03bf\u03b9 \u03ba\u03bb\u03ae\u03c3\u03b5\u03b9\u03c2 \u03b8\u03b1 \u03b1\u03c0\u03bf\u03c4\u03cd\u03c7\u03bf\u03c5\u03bd \u03ba\u03b1\u03b9 \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c3\u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2.", - "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd Tuya" + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2 Tuya" } } } diff --git a/homeassistant/components/tuya/translations/en.json b/homeassistant/components/tuya/translations/en.json index e928dc37d57..e69872fd309 100644 --- a/homeassistant/components/tuya/translations/en.json +++ b/homeassistant/components/tuya/translations/en.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Failed to connect", - "invalid_auth": "Invalid authentication", - "single_instance_allowed": "Already configured. Only a single configuration possible." - }, "error": { "invalid_auth": "Invalid authentication", "login_error": "Login error ({code}): {msg}" }, - "flow_title": "Tuya configuration", "step": { - "login": { - "data": { - "access_id": "Access ID", - "access_secret": "Access Secret", - "country_code": "Country Code", - "endpoint": "Availability Zone", - "password": "Password", - "tuya_app_type": "Mobile App", - "username": "Account" - }, - "description": "Enter your Tuya credential", - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT Access ID", "access_secret": "Tuya IoT Access Secret", "country_code": "Country", "password": "Password", - "platform": "The app where your account is registered", - "region": "Region", - "tuya_project_type": "Tuya cloud project type", "username": "Account" }, - "description": "Enter your Tuya credentials", - "title": "Tuya Integration" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Failed to connect" - }, - "error": { - "dev_multi_type": "Multiple selected devices to configure must be of the same type", - "dev_not_config": "Device type not configurable", - "dev_not_found": "Device not found" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Brightness range used by device", - "curr_temp_divider": "Current Temperature value divider (0 = use default)", - "max_kelvin": "Max color temperature supported in kelvin", - "max_temp": "Max target temperature (use min and max = 0 for default)", - "min_kelvin": "Min color temperature supported in kelvin", - "min_temp": "Min target temperature (use min and max = 0 for default)", - "set_temp_divided": "Use divided Temperature value for set temperature command", - "support_color": "Force color support", - "temp_divider": "Temperature values divider (0 = use default)", - "temp_step_override": "Target Temperature step", - "tuya_max_coltemp": "Max color temperature reported by device", - "unit_of_measurement": "Temperature unit used by device" - }, - "description": "Configure options to adjust displayed information for {device_type} device `{device_name}`", - "title": "Configure Tuya Device" - }, - "init": { - "data": { - "discovery_interval": "Discovery device polling interval in seconds", - "list_devices": "Select the devices to configure or leave empty to save configuration", - "query_device": "Select device that will use query method for faster status update", - "query_interval": "Query device polling interval in seconds" - }, - "description": "Do not set pollings interval values too low or the calls will fail generating error message in the log", - "title": "Configure Tuya Options" + "description": "Enter your Tuya credentials" } } } diff --git a/homeassistant/components/tuya/translations/en_GB.json b/homeassistant/components/tuya/translations/en_GB.json deleted file mode 100644 index 90df003a190..00000000000 --- a/homeassistant/components/tuya/translations/en_GB.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "options": { - "step": { - "device": { - "data": { - "max_kelvin": "Max colour temperature supported in Kelvin", - "min_kelvin": "Min colour temperature supported in Kelvin", - "support_color": "Force colour support", - "tuya_max_coltemp": "Max colour temperature reported by device" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/es.json b/homeassistant/components/tuya/translations/es.json index bcd83f85af9..a15408bf96d 100644 --- a/homeassistant/components/tuya/translations/es.json +++ b/homeassistant/components/tuya/translations/es.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "No se pudo conectar", - "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", - "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." - }, "error": { "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "login_error": "Error de inicio de sesi\u00f3n ({code}): {msg}" }, - "flow_title": "Configuraci\u00f3n Tuya", "step": { - "login": { - "data": { - "access_id": "ID de acceso", - "access_secret": "Acceso secreto", - "country_code": "C\u00f3digo de pa\u00eds", - "endpoint": "Zona de disponibilidad", - "password": "Contrase\u00f1a", - "tuya_app_type": "Aplicaci\u00f3n m\u00f3vil", - "username": "Cuenta" - }, - "description": "Ingrese su credencial Tuya", - "title": "Tuya" - }, "user": { "data": { "access_id": "ID de acceso de Tuya IoT", "access_secret": "Tuya IoT Access Secret", "country_code": "C\u00f3digo de pais de tu cuenta (por ejemplo, 1 para USA o 86 para China)", "password": "Contrase\u00f1a", - "platform": "La aplicaci\u00f3n en la cual registraste tu cuenta", - "region": "Regi\u00f3n", - "tuya_project_type": "Tipo de proyecto en la nube de Tuya", "username": "Usuario" }, - "description": "Introduce tu credencial Tuya.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "No se pudo conectar" - }, - "error": { - "dev_multi_type": "Los m\u00faltiples dispositivos seleccionados para configurar deben ser del mismo tipo", - "dev_not_config": "Tipo de dispositivo no configurable", - "dev_not_found": "Dispositivo no encontrado" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Rango de brillo utilizado por el dispositivo", - "curr_temp_divider": "Divisor del valor de la temperatura actual (0 = usar valor por defecto)", - "max_kelvin": "Temperatura de color m\u00e1xima admitida en kelvin", - "max_temp": "Temperatura objetivo m\u00e1xima (usa m\u00edn. y m\u00e1x. = 0 por defecto)", - "min_kelvin": "Temperatura de color m\u00ednima soportada en kelvin", - "min_temp": "Temperatura objetivo m\u00ednima (usa m\u00edn. y m\u00e1x. = 0 por defecto)", - "set_temp_divided": "Use el valor de temperatura dividido para el comando de temperatura establecida", - "support_color": "Forzar soporte de color", - "temp_divider": "Divisor de los valores de temperatura (0 = usar valor por defecto)", - "temp_step_override": "Temperatura deseada", - "tuya_max_coltemp": "Temperatura de color m\u00e1xima notificada por dispositivo", - "unit_of_measurement": "Unidad de temperatura utilizada por el dispositivo" - }, - "description": "Configura las opciones para ajustar la informaci\u00f3n mostrada para {device_type} dispositivo `{device_name}`", - "title": "Configurar dispositivo Tuya" - }, - "init": { - "data": { - "discovery_interval": "Intervalo de sondeo del descubrimiento al dispositivo en segundos", - "list_devices": "Selecciona los dispositivos a configurar o d\u00e9jalos en blanco para guardar la configuraci\u00f3n", - "query_device": "Selecciona el dispositivo que utilizar\u00e1 el m\u00e9todo de consulta para una actualizaci\u00f3n de estado m\u00e1s r\u00e1pida", - "query_interval": "Intervalo de sondeo de la consulta al dispositivo en segundos" - }, - "description": "No establezcas valores de intervalo de sondeo demasiado bajos o las llamadas fallar\u00e1n generando un mensaje de error en el registro", - "title": "Configurar opciones de Tuya" + "description": "Introduce tus credencial de Tuya" } } } diff --git a/homeassistant/components/tuya/translations/et.json b/homeassistant/components/tuya/translations/et.json index bd23136cb1b..de84ef4aa2f 100644 --- a/homeassistant/components/tuya/translations/et.json +++ b/homeassistant/components/tuya/translations/et.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "\u00dchendamine nurjus", - "invalid_auth": "Tuvastamise viga", - "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks sidumine." - }, "error": { "invalid_auth": "Tuvastamise viga", "login_error": "Sisenemine nurjus ( {code} ): {msg}" }, - "flow_title": "Tuya seaded", "step": { - "login": { - "data": { - "access_id": "Juurdep\u00e4\u00e4su ID", - "access_secret": "API salas\u00f5na", - "country_code": "Riigi kood", - "endpoint": "Seadmete regioon", - "password": "Salas\u00f5na", - "tuya_app_type": "Mobiilirakendus", - "username": "Konto" - }, - "description": "Sisesta oma Tuya mandaat", - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT kasutajatunnus", "access_secret": "Tuya IoT salas\u00f5na", "country_code": "Riik", "password": "Salas\u00f5na", - "platform": "\u00c4pp kus konto registreeriti", - "region": "Piirkond", - "tuya_project_type": "Tuya pilveprojekti t\u00fc\u00fcp", "username": "Kasutajanimi" }, - "description": "Sisesta oma Tuya konto andmed.", - "title": "Tuya sidumine" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u00dchendamine nurjus" - }, - "error": { - "dev_multi_type": "Mitu h\u00e4\u00e4lestatavat seadet peavad olema sama t\u00fc\u00fcpi", - "dev_not_config": "Seda t\u00fc\u00fcpi seade pole seadistatav", - "dev_not_found": "Seadet ei leitud" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Seadme kasutatav heledusvahemik", - "curr_temp_divider": "Praeguse temperatuuri v\u00e4\u00e4rtuse eraldaja (0 = kasuta vaikev\u00e4\u00e4rtust)", - "max_kelvin": "Maksimaalne v\u00f5imalik v\u00e4rvitemperatuur (Kelvinites)", - "max_temp": "Maksimaalne sihttemperatuur (vaikimisi kasuta min ja max = 0)", - "min_kelvin": "Minimaalne v\u00f5imalik v\u00e4rvitemperatuur (Kelvinites)", - "min_temp": "Minimaalne sihttemperatuur (vaikimisi kasuta min ja max = 0)", - "set_temp_divided": "M\u00e4\u00e4ratud temperatuuri k\u00e4su jaoks kasuta jagatud temperatuuri v\u00e4\u00e4rtust", - "support_color": "Luba v\u00e4rvuse juhtimine", - "temp_divider": "Temperatuuri v\u00e4\u00e4rtuse eraldaja (0 = kasuta vaikev\u00e4\u00e4rtust)", - "temp_step_override": "Sihttemperatuuri samm", - "tuya_max_coltemp": "Seadme teatatud maksimaalne v\u00e4rvitemperatuur", - "unit_of_measurement": "Seadme temperatuuri\u00fchik" - }, - "description": "Suvandid \u00fcksuse {device_type} {device_name} kuvatava teabe muutmiseks", - "title": "H\u00e4\u00e4lesta Tuya seade" - }, - "init": { - "data": { - "discovery_interval": "Seadme leidmisp\u00e4ringute intervall (sekundites)", - "list_devices": "Vali seadistatavad seadmed v\u00f5i j\u00e4ta s\u00e4tete salvestamiseks t\u00fchjaks", - "query_device": "Vali seade, mis kasutab oleku kiiremaks v\u00e4rskendamiseks p\u00e4ringumeetodit", - "query_interval": "P\u00e4ringute intervall (sekundites)" - }, - "description": "\u00c4ra m\u00e4\u00e4ra k\u00fcsitlusintervalli v\u00e4\u00e4rtusi liiga madalaks, vastasel korral v\u00f5ivad p\u00e4ringud logis t\u00f5rketeate genereerida", - "title": "Tuya suvandite seadistamine" + "description": "Sisesta oma Tuya konto andmed." } } } diff --git a/homeassistant/components/tuya/translations/fi.json b/homeassistant/components/tuya/translations/fi.json index 3c74a9b8eeb..aeceadba0fc 100644 --- a/homeassistant/components/tuya/translations/fi.json +++ b/homeassistant/components/tuya/translations/fi.json @@ -1,16 +1,13 @@ { "config": { - "flow_title": "Tuya-asetukset", "step": { "user": { "data": { "country_code": "Tilisi maakoodi (esim. 1 Yhdysvalloissa, 358 Suomessa)", "password": "Salasana", - "platform": "Sovellus, johon tili rekister\u00f6id\u00e4\u00e4n", "username": "K\u00e4ytt\u00e4j\u00e4tunnus" }, - "description": "Anna Tuya-tunnistetietosi.", - "title": "Tuya" + "description": "Anna Tuya-tunnistetietosi." } } } diff --git a/homeassistant/components/tuya/translations/fr.json b/homeassistant/components/tuya/translations/fr.json index c28e62e7ab4..a8049917f41 100644 --- a/homeassistant/components/tuya/translations/fr.json +++ b/homeassistant/components/tuya/translations/fr.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "\u00c9chec de connexion", - "invalid_auth": "Authentification non valide", - "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." - }, "error": { "invalid_auth": "Authentification non valide", "login_error": "Erreur de connexion ( {code} ): {msg}" }, - "flow_title": "Configuration Tuya", "step": { - "login": { - "data": { - "access_id": "Identifiant d'acc\u00e8s", - "access_secret": "Access Secret", - "country_code": "Code pays", - "endpoint": "Zone de disponibilit\u00e9", - "password": "Mot de passe", - "tuya_app_type": "Application mobile", - "username": "Compte" - }, - "description": "Entrez votre identifiant Tuya", - "title": "Tuya" - }, "user": { "data": { "access_id": "Identifiant d'acc\u00e8s Tuya IoT", "access_secret": "Tuya IoT Access Secret", "country_code": "Pays", "password": "Mot de passe", - "platform": "L'application dans laquelle votre compte est enregistr\u00e9", - "region": "R\u00e9gion", - "tuya_project_type": "Type de projet cloud Tuya", "username": "Compte" }, - "description": "Saisissez vos informations d'identification Tuya.", - "title": "Int\u00e9gration de Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u00c9chec de connexion" - }, - "error": { - "dev_multi_type": "Si plusieurs appareils sont s\u00e9lectionn\u00e9s pour \u00eatre configur\u00e9s, ils doivent tous \u00eatre du m\u00eame type", - "dev_not_config": "Type d'appareil non configurable", - "dev_not_found": "Appareil non trouv\u00e9" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Plage de luminosit\u00e9 utilis\u00e9e par l'appareil", - "curr_temp_divider": "Diviseur de valeur de temp\u00e9rature actuelle (0 = utiliser la valeur par d\u00e9faut)", - "max_kelvin": "Temp\u00e9rature de couleur maximale prise en charge en Kelvin", - "max_temp": "Temp\u00e9rature cible maximale (utilisez min et max = 0 par d\u00e9faut)", - "min_kelvin": "Temp\u00e9rature de couleur minimale prise en charge en kelvin", - "min_temp": "Temp\u00e9rature cible minimale (utilisez min et max = 0 par d\u00e9faut)", - "set_temp_divided": "Utilisez la valeur de temp\u00e9rature divis\u00e9e pour la commande de temp\u00e9rature d\u00e9finie", - "support_color": "Forcer la prise en charge des couleurs", - "temp_divider": "Diviseur de valeurs de temp\u00e9rature (0 = utiliser la valeur par d\u00e9faut)", - "temp_step_override": "Pas de temp\u00e9rature cible", - "tuya_max_coltemp": "Temp\u00e9rature de couleur maximale rapport\u00e9e par l'appareil", - "unit_of_measurement": "Unit\u00e9 de temp\u00e9rature utilis\u00e9e par l'appareil" - }, - "description": "Configurer les options pour ajuster les informations affich\u00e9es pour l'appareil {device_type} ` {device_name} `", - "title": "Configurer l'appareil Tuya" - }, - "init": { - "data": { - "discovery_interval": "Intervalle de d\u00e9couverte de l'appareil en secondes", - "list_devices": "S\u00e9lectionnez les appareils \u00e0 configurer ou laissez vide pour enregistrer la configuration", - "query_device": "S\u00e9lectionnez l'appareil qui utilisera la m\u00e9thode de requ\u00eate pour une mise \u00e0 jour plus rapide de l'\u00e9tat", - "query_interval": "Intervalle d'interrogation de l'appareil en secondes" - }, - "description": "Ne d\u00e9finissez pas des valeurs d'intervalle d'interrogation trop faibles ou les appels \u00e9choueront \u00e0 g\u00e9n\u00e9rer un message d'erreur dans le journal", - "title": "Configurer les options de Tuya" + "description": "Saisissez vos informations d'identification Tuya." } } } diff --git a/homeassistant/components/tuya/translations/he.json b/homeassistant/components/tuya/translations/he.json index 02e2dc94776..737ca754202 100644 --- a/homeassistant/components/tuya/translations/he.json +++ b/homeassistant/components/tuya/translations/he.json @@ -1,67 +1,17 @@ { "config": { - "abort": { - "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", - "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": { "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", "login_error": "\u05e9\u05d2\u05d9\u05d0\u05ea \u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea ({code}): {msg}" }, - "flow_title": "\u05ea\u05e6\u05d5\u05e8\u05ea Tuya", "step": { - "login": { - "data": { - "access_id": "\u05de\u05d6\u05d4\u05d4 \u05d2\u05d9\u05e9\u05d4", - "access_secret": "\u05e1\u05d5\u05d3 \u05d2\u05d9\u05e9\u05d4", - "country_code": "\u05e7\u05d5\u05d3 \u05de\u05d3\u05d9\u05e0\u05d4", - "endpoint": "\u05d0\u05d6\u05d5\u05e8 \u05d6\u05de\u05d9\u05e0\u05d5\u05ea", - "password": "\u05e1\u05d9\u05e1\u05de\u05d4", - "tuya_app_type": "\u05d9\u05d9\u05e9\u05d5\u05dd \u05dc\u05e0\u05d9\u05d9\u05d3", - "username": "\u05d7\u05e9\u05d1\u05d5\u05df" - }, - "description": "\u05d4\u05d6\u05e0\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8 \u05d4-Tuya \u05e9\u05dc\u05da", - "title": "Tuya" - }, "user": { "data": { "country_code": "\u05de\u05d3\u05d9\u05e0\u05d4", "password": "\u05e1\u05d9\u05e1\u05de\u05d4", - "platform": "\u05d4\u05d9\u05d9\u05e9\u05d5\u05dd \u05d1\u05d5 \u05e8\u05e9\u05d5\u05dd \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05e9\u05dc\u05da", - "region": "\u05d0\u05d9\u05d6\u05d5\u05e8", - "tuya_project_type": "\u05e1\u05d5\u05d2 \u05e4\u05e8\u05d5\u05d9\u05d9\u05e7\u05d8 \u05d4\u05e2\u05e0\u05df \u05e9\u05dc Tuya", "username": "\u05d7\u05e9\u05d1\u05d5\u05df" }, - "description": "\u05d4\u05d6\u05e0\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8\u05d9 \u05d4-Tuya \u05e9\u05dc\u05da.", - "title": "\u05e9\u05d9\u05dc\u05d5\u05d1 Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" - }, - "error": { - "dev_multi_type": "\u05d4\u05ea\u05e7\u05e0\u05d9\u05dd \u05e0\u05d1\u05d7\u05e8\u05d9\u05dd \u05de\u05e8\u05d5\u05d1\u05d9\u05dd \u05dc\u05e7\u05d1\u05d9\u05e2\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05d7\u05d9\u05d9\u05d1\u05d9\u05dd \u05dc\u05d4\u05d9\u05d5\u05ea \u05de\u05d0\u05d5\u05ea\u05d5 \u05e1\u05d5\u05d2", - "dev_not_config": "\u05e1\u05d5\u05d2 \u05d4\u05d4\u05ea\u05e7\u05df \u05d0\u05d9\u05e0\u05d5 \u05e0\u05d9\u05ea\u05df \u05dc\u05d4\u05d2\u05d3\u05e8\u05d4", - "dev_not_found": "\u05d4\u05d4\u05ea\u05e7\u05df \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u05d8\u05d5\u05d5\u05d7 \u05d1\u05d4\u05d9\u05e8\u05d5\u05ea \u05d4\u05de\u05e9\u05de\u05e9 \u05d0\u05ea \u05d4\u05d4\u05ea\u05e7\u05df", - "max_kelvin": "\u05d8\u05de\u05e4\u05e8\u05d8\u05d5\u05e8\u05ea \u05e6\u05d1\u05e2 \u05de\u05e8\u05d1\u05d9\u05ea \u05d4\u05e0\u05ea\u05de\u05db\u05ea \u05d1\u05e7\u05dc\u05d5\u05d5\u05d9\u05df", - "min_kelvin": "\u05d8\u05de\u05e4\u05e8\u05d8\u05d5\u05e8\u05ea \u05e6\u05d1\u05e2 \u05de\u05d9\u05e0\u05d9\u05de\u05dc\u05d9\u05ea \u05d4\u05e0\u05ea\u05de\u05db\u05ea \u05d1\u05e7\u05dc\u05d5\u05d5\u05d9\u05df", - "set_temp_divided": "\u05d4\u05e9\u05ea\u05de\u05e9 \u05d1\u05e2\u05e8\u05da \u05d8\u05de\u05e4\u05e8\u05d8\u05d5\u05e8\u05d4 \u05de\u05d7\u05d5\u05dc\u05e7 \u05e2\u05d1\u05d5\u05e8 \u05d4\u05e4\u05e7\u05d5\u05d3\u05d4 '\u05d8\u05de\u05e4\u05e8\u05d8\u05d5\u05e8\u05d4 \u05de\u05d5\u05d2\u05d3\u05e8\u05ea'", - "support_color": "\u05db\u05e4\u05d4 \u05ea\u05de\u05d9\u05db\u05d4 \u05d1\u05e6\u05d1\u05e2", - "temp_step_override": "\u05e9\u05dc\u05d1 \u05d8\u05de\u05e4\u05e8\u05d8\u05d5\u05e8\u05ea \u05d4\u05d9\u05e2\u05d3", - "unit_of_measurement": "\u05d9\u05d7\u05d9\u05d3\u05ea \u05d8\u05de\u05e4\u05e8\u05d8\u05d5\u05e8\u05d4 \u05d4\u05de\u05e9\u05de\u05e9\u05ea \u05d0\u05ea \u05d4\u05d4\u05ea\u05e7\u05df" - }, - "title": "\u05e7\u05d1\u05d9\u05e2\u05ea \u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc \u05d4\u05ea\u05e7\u05df \u05d8\u05d5\u05d9\u05d4" - }, - "init": { - "title": "\u05e7\u05d1\u05d9\u05e2\u05ea \u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05d8\u05d5\u05d9\u05d4" + "description": "\u05d4\u05d6\u05e0\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8\u05d9 \u05d4-Tuya \u05e9\u05dc\u05da." } } } diff --git a/homeassistant/components/tuya/translations/hu.json b/homeassistant/components/tuya/translations/hu.json index 2c2969e589c..37414cbebf9 100644 --- a/homeassistant/components/tuya/translations/hu.json +++ b/homeassistant/components/tuya/translations/hu.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", - "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." - }, "error": { "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "login_error": "Bejelentkez\u00e9si hiba ({code}): {msg}" }, - "flow_title": "Tuya konfigur\u00e1ci\u00f3", "step": { - "login": { - "data": { - "access_id": "Hozz\u00e1f\u00e9r\u00e9si azonos\u00edt\u00f3", - "access_secret": "Hozz\u00e1f\u00e9r\u00e9si token", - "country_code": "Orsz\u00e1g k\u00f3d", - "endpoint": "El\u00e9rhet\u0151s\u00e9gi z\u00f3na", - "password": "Jelsz\u00f3", - "tuya_app_type": "Mobil app", - "username": "Fi\u00f3k" - }, - "description": "Adja meg Tuya hiteles\u00edt\u0151 adatait.", - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT azonos\u00edt\u00f3", "access_secret": "Tuya IoT hozz\u00e1f\u00e9r\u00e9si jelsz\u00f3", "country_code": "A fi\u00f3k orsz\u00e1gk\u00f3dja (pl. 1 USA, 36 Magyarorsz\u00e1g, vagy 86 K\u00edna)", "password": "Jelsz\u00f3", - "platform": "Az alkalmaz\u00e1s, ahol a fi\u00f3k regisztr\u00e1lt", - "region": "R\u00e9gi\u00f3", - "tuya_project_type": "Tuya felh\u0151 projekt t\u00edpusa", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, - "description": "Adja meg Tuya hiteles\u00edt\u0151 adatait.", - "title": "Tuya integr\u00e1ci\u00f3" - } - } - }, - "options": { - "abort": { - "cannot_connect": "A kapcsol\u00f3d\u00e1s nem siker\u00fclt" - }, - "error": { - "dev_multi_type": "T\u00f6bb kiv\u00e1lasztott konfigur\u00e1land\u00f3 eszk\u00f6z eset\u00e9n, azonos t\u00edpus\u00fanak kell lennie", - "dev_not_config": "Ez az eszk\u00f6zt\u00edpus nem konfigur\u00e1lhat\u00f3", - "dev_not_found": "Eszk\u00f6z nem tal\u00e1lhat\u00f3" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Az eszk\u00f6z \u00e1ltal haszn\u00e1lt f\u00e9nyer\u0151 tartom\u00e1ny", - "curr_temp_divider": "Aktu\u00e1lis h\u0151m\u00e9rs\u00e9klet-\u00e9rt\u00e9k oszt\u00f3 (0 = alap\u00e9rtelmezetten)", - "max_kelvin": "Maxim\u00e1lis t\u00e1mogatott sz\u00ednh\u0151m\u00e9rs\u00e9klet kelvinben", - "max_temp": "Maxim\u00e1lis k\u00edv\u00e1nt h\u0151m\u00e9rs\u00e9klet (alap\u00e9rtelmezettnek min \u00e9s max 0)", - "min_kelvin": "Minimum t\u00e1mogatott sz\u00ednh\u0151m\u00e9rs\u00e9klet kelvinben", - "min_temp": "Minim\u00e1lis k\u00edv\u00e1nt h\u0151m\u00e9rs\u00e9klet (alap\u00e9rtelmezettnek min \u00e9s max 0)", - "set_temp_divided": "A h\u0151m\u00e9rs\u00e9klet be\u00e1ll\u00edt\u00e1s\u00e1hoz osztott h\u0151m\u00e9rs\u00e9kleti \u00e9rt\u00e9ket haszn\u00e1ljon", - "support_color": "Sz\u00ednt\u00e1mogat\u00e1s k\u00e9nyszer\u00edt\u00e9se", - "temp_divider": "Sz\u00ednh\u0151m\u00e9rs\u00e9klet-\u00e9rt\u00e9kek oszt\u00f3ja (0 = alap\u00e9rtelmezett)", - "temp_step_override": "C\u00e9lh\u0151m\u00e9rs\u00e9klet l\u00e9pcs\u0151", - "tuya_max_coltemp": "Az eszk\u00f6z \u00e1ltal megadott maxim\u00e1lis sz\u00ednh\u0151m\u00e9rs\u00e9klet", - "unit_of_measurement": "Az eszk\u00f6z \u00e1ltal haszn\u00e1lt h\u0151m\u00e9rs\u00e9kleti egys\u00e9g" - }, - "description": "Konfigur\u00e1l\u00e1si lehet\u0151s\u00e9gek {device_type} t\u00edpus\u00fa `{device_name}` eszk\u00f6z megjelen\u00edtett inform\u00e1ci\u00f3inak be\u00e1ll\u00edt\u00e1s\u00e1hoz", - "title": "Tuya eszk\u00f6z konfigur\u00e1l\u00e1sa" - }, - "init": { - "data": { - "discovery_interval": "Felfedez\u0151 eszk\u00f6z lek\u00e9rdez\u00e9si intervalluma m\u00e1sodpercben", - "list_devices": "V\u00e1lassza ki a konfigur\u00e1lni k\u00edv\u00e1nt eszk\u00f6z\u00f6ket, vagy hagyja \u00fcresen a konfigur\u00e1ci\u00f3 ment\u00e9s\u00e9hez", - "query_device": "V\u00e1lassza ki azt az eszk\u00f6zt, amely a lek\u00e9rdez\u00e9si m\u00f3dszert haszn\u00e1lja a gyorsabb \u00e1llapotfriss\u00edt\u00e9shez", - "query_interval": "Eszk\u00f6z lek\u00e9rdez\u00e9si id\u0151k\u00f6ze m\u00e1sodpercben" - }, - "description": "Ne \u00e1ll\u00edtsa t\u00fal alacsonyra a lek\u00e9rdez\u00e9si intervallum \u00e9rt\u00e9keit, k\u00fcl\u00f6nben a h\u00edv\u00e1sok nem fognak hiba\u00fczenetet gener\u00e1lni a napl\u00f3ban", - "title": "Tuya be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" + "description": "Adja meg Tuya hiteles\u00edt\u0151 adatait." } } } diff --git a/homeassistant/components/tuya/translations/id.json b/homeassistant/components/tuya/translations/id.json index 91bf29f3ec8..bbbd74822eb 100644 --- a/homeassistant/components/tuya/translations/id.json +++ b/homeassistant/components/tuya/translations/id.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Gagal terhubung", - "invalid_auth": "Autentikasi tidak valid", - "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." - }, "error": { "invalid_auth": "Autentikasi tidak valid", "login_error": "Kesalahan masuk ({code}): {msg}" }, - "flow_title": "Konfigurasi Tuya", "step": { - "login": { - "data": { - "access_id": "ID Akses", - "access_secret": "Kode Rahasia Akses", - "country_code": "Kode Negara", - "endpoint": "Zona Ketersediaan", - "password": "Kata Sandi", - "tuya_app_type": "Aplikasi Seluler", - "username": "Akun" - }, - "description": "Masukkan kredensial Tuya Anda", - "title": "Tuya" - }, "user": { "data": { "access_id": "ID Akses Tuya IoT", "access_secret": "Kode Rahasia Akses Tuya IoT", "country_code": "Negara", "password": "Kata Sandi", - "platform": "Aplikasi tempat akun Anda terdaftar", - "region": "Wilayah", - "tuya_project_type": "Jenis proyek awan Tuya", "username": "Akun" }, - "description": "Masukkan kredensial Tuya Anda.", - "title": "Integrasi Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Gagal terhubung" - }, - "error": { - "dev_multi_type": "Untuk konfigurasi sekaligus, beberapa perangkat yang dipilih harus berjenis sama", - "dev_not_config": "Jenis perangkat tidak dapat dikonfigurasi", - "dev_not_found": "Perangkat tidak ditemukan" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Rentang kecerahan yang digunakan oleh perangkat", - "curr_temp_divider": "Pembagi nilai suhu saat ini (0 = gunakan bawaan)", - "max_kelvin": "Suhu warna maksimal yang didukung dalam Kelvin", - "max_temp": "Suhu target maksimal (gunakan min dan maks = 0 untuk bawaan)", - "min_kelvin": "Suhu warna minimal yang didukung dalam Kelvin", - "min_temp": "Suhu target minimal (gunakan min dan maks = 0 untuk bawaan)", - "set_temp_divided": "Gunakan nilai suhu terbagi untuk mengirimkan perintah mengatur suhu", - "support_color": "Paksa dukungan warna", - "temp_divider": "Pembagi nilai suhu (0 = gunakan bawaan)", - "temp_step_override": "Langkah Suhu Target", - "tuya_max_coltemp": "Suhu warna maksimal yang dilaporkan oleh perangkat", - "unit_of_measurement": "Satuan suhu yang digunakan oleh perangkat" - }, - "description": "Konfigurasikan opsi untuk menyesuaikan informasi yang ditampilkan untuk perangkat {device_type} `{device_name}`", - "title": "Konfigurasi Perangkat Tuya" - }, - "init": { - "data": { - "discovery_interval": "Interval polling penemuan perangkat dalam detik", - "list_devices": "Pilih perangkat yang akan dikonfigurasi atau biarkan kosong untuk menyimpan konfigurasi", - "query_device": "Pilih perangkat yang akan menggunakan metode kueri untuk pembaruan status lebih cepat", - "query_interval": "Interval polling perangkat kueri dalam detik" - }, - "description": "Jangan atur nilai interval polling terlalu rendah karena panggilan akan gagal menghasilkan pesan kesalahan dalam log", - "title": "Konfigurasikan Opsi Tuya" + "description": "Masukkan kredensial Tuya Anda." } } } diff --git a/homeassistant/components/tuya/translations/is.json b/homeassistant/components/tuya/translations/is.json index fc7a4878a25..ce1e5122e65 100644 --- a/homeassistant/components/tuya/translations/is.json +++ b/homeassistant/components/tuya/translations/is.json @@ -4,23 +4,10 @@ "login_error": "Innskr\u00e1ningarvilla ( {code} ): {msg}" }, "step": { - "login": { - "data": { - "access_id": "A\u00f0gangsau\u00f0kenni", - "access_secret": "A\u00f0gangsleyndarm\u00e1l", - "country_code": "Landsn\u00famer", - "endpoint": "Frambo\u00f0ssv\u00e6\u00f0i", - "password": "Lykilor\u00f0", - "tuya_app_type": "App", - "username": "Reikningur" - }, - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT a\u00f0gangsau\u00f0kenni", - "access_secret": "Tuya IoT a\u00f0gangsleyndarm\u00e1l", - "region": "Landsv\u00e6\u00f0i" + "access_secret": "Tuya IoT a\u00f0gangsleyndarm\u00e1l" } } } diff --git a/homeassistant/components/tuya/translations/it.json b/homeassistant/components/tuya/translations/it.json index 0ab73ab3b60..f8013882fb2 100644 --- a/homeassistant/components/tuya/translations/it.json +++ b/homeassistant/components/tuya/translations/it.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Impossibile connettersi", - "invalid_auth": "Autenticazione non valida", - "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." - }, "error": { "invalid_auth": "Autenticazione non valida", "login_error": "Errore di accesso ({code}): {msg}" }, - "flow_title": "Configurazione di Tuya", "step": { - "login": { - "data": { - "access_id": "Access ID", - "access_secret": "Access Secret", - "country_code": "Prefisso internazionale", - "endpoint": "Zona di disponibilit\u00e0", - "password": "Password", - "tuya_app_type": "App per dispositivi mobili", - "username": "Account" - }, - "description": "Inserisci le tue credenziali Tuya", - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT Access ID", "access_secret": "Tuya IoT Access Secret", "country_code": "Nazione", "password": "Password", - "platform": "L'app in cui \u00e8 registrato il tuo account", - "region": "Area geografica", - "tuya_project_type": "Tipo di progetto Tuya cloud", "username": "Account" }, - "description": "Inserisci le tue credenziali Tuya", - "title": "Integrazione Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Impossibile connettersi" - }, - "error": { - "dev_multi_type": "I dispositivi multipli selezionati da configurare devono essere dello stesso tipo", - "dev_not_config": "Tipo di dispositivo non configurabile", - "dev_not_found": "Dispositivo non trovato" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Intervallo di luminosit\u00e0 utilizzato dal dispositivo", - "curr_temp_divider": "Divisore del valore della temperatura corrente (0 = usa il valore predefinito)", - "max_kelvin": "Temperatura colore massima supportata in kelvin", - "max_temp": "Temperatura di destinazione massima (utilizzare min e max = 0 per impostazione predefinita)", - "min_kelvin": "Temperatura colore minima supportata in kelvin", - "min_temp": "Temperatura di destinazione minima (utilizzare min e max = 0 per impostazione predefinita)", - "set_temp_divided": "Utilizzare il valore temperatura diviso per impostare il comando temperatura", - "support_color": "Forza il supporto del colore", - "temp_divider": "Divisore dei valori di temperatura (0 = utilizzare il valore predefinito)", - "temp_step_override": "Passo della temperatura da raggiungere", - "tuya_max_coltemp": "Temperatura di colore massima riportata dal dispositivo", - "unit_of_measurement": "Unit\u00e0 di temperatura utilizzata dal dispositivo" - }, - "description": "Configura le opzioni per regolare le informazioni visualizzate per il dispositivo {device_type} `{device_name}`", - "title": "Configura il dispositivo Tuya" - }, - "init": { - "data": { - "discovery_interval": "Intervallo di scansione di rilevamento dispositivo in secondi", - "list_devices": "Seleziona i dispositivi da configurare o lascia vuoto per salvare la configurazione", - "query_device": "Seleziona il dispositivo che utilizzer\u00e0 il metodo di interrogazione per un pi\u00f9 rapido aggiornamento dello stato", - "query_interval": "Intervallo di scansione di interrogazione dispositivo in secondi" - }, - "description": "Non impostare valori dell'intervallo di scansione troppo bassi o le chiamate non riusciranno a generare un messaggio di errore nel registro", - "title": "Configura le opzioni Tuya" + "description": "Inserisci le tue credenziali Tuya" } } } diff --git a/homeassistant/components/tuya/translations/ja.json b/homeassistant/components/tuya/translations/ja.json index 6733e70b472..d54a17f7eff 100644 --- a/homeassistant/components/tuya/translations/ja.json +++ b/homeassistant/components/tuya/translations/ja.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", - "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", - "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": { "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "login_error": "\u30ed\u30b0\u30a4\u30f3\u30a8\u30e9\u30fc ({code}): {msg}" }, - "flow_title": "Tuya\u306e\u8a2d\u5b9a", "step": { - "login": { - "data": { - "access_id": "\u30a2\u30af\u30bb\u30b9ID", - "access_secret": "\u30a2\u30af\u30bb\u30b9\u30b7\u30fc\u30af\u30ec\u30c3\u30c8", - "country_code": "\u56fd\u5225\u30b3\u30fc\u30c9", - "endpoint": "\u5229\u7528\u53ef\u80fd\u30be\u30fc\u30f3", - "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", - "tuya_app_type": "\u30e2\u30d0\u30a4\u30eb\u30a2\u30d7\u30ea", - "username": "\u30a2\u30ab\u30a6\u30f3\u30c8" - }, - "description": "Tuya\u306e\u8a8d\u8a3c\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT Access ID", "access_secret": "Tuya IoT Access Secret", "country_code": "\u56fd\u5225\u30b3\u30fc\u30c9", "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", - "platform": "\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u767b\u9332\u3055\u308c\u3066\u3044\u308b\u30a2\u30d7\u30ea", - "region": "\u30ea\u30fc\u30b8\u30e7\u30f3", - "tuya_project_type": "Tuya Cloud\u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u30bf\u30a4\u30d7", "username": "\u30a2\u30ab\u30a6\u30f3\u30c8" }, - "description": "Tuya\u306e\u8a8d\u8a3c\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "Tuya\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" - }, - "error": { - "dev_multi_type": "\u69cb\u6210\u3059\u308b\u8907\u6570\u306e\u9078\u629e\u3055\u308c\u305f\u30c7\u30d0\u30a4\u30b9\u306f\u3001\u540c\u3058\u30bf\u30a4\u30d7\u3067\u3042\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", - "dev_not_config": "\u30c7\u30d0\u30a4\u30b9\u30bf\u30a4\u30d7\u304c\u8a2d\u5b9a\u3067\u304d\u307e\u305b\u3093", - "dev_not_found": "\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u30c7\u30d0\u30a4\u30b9\u304c\u4f7f\u7528\u3059\u308b\u8f1d\u5ea6\u7bc4\u56f2", - "curr_temp_divider": "\u73fe\u5728\u306e\u6e29\u5ea6\u5024\u306e\u533a\u5207\u308a(0 = \u30c7\u30d5\u30a9\u30eb\u30c8\u3092\u4f7f\u7528)", - "max_kelvin": "\u30b1\u30eb\u30d3\u30f3\u3067\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u308b\u6700\u5927\u8272\u6e29\u5ea6", - "max_temp": "\u6700\u5927\u76ee\u6a19\u6e29\u5ea6(\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6700\u5c0f\u304a\u3088\u3073\u6700\u5927 = 0\u3092\u4f7f\u7528)", - "min_kelvin": "\u30b1\u30eb\u30d3\u30f3\u3067\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u308b\u6700\u5c0f\u8272\u6e29\u5ea6", - "min_temp": "\u6700\u5c0f\u76ee\u6a19\u6e29\u5ea6(\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6700\u5c0f\u304a\u3088\u3073\u6700\u5927 = 0\u3092\u4f7f\u7528)", - "set_temp_divided": "\u8a2d\u5b9a\u6e29\u5ea6\u30b3\u30de\u30f3\u30c9\u306b\u533a\u5207\u3089\u308c\u305f\u6e29\u5ea6\u5024\u3092\u4f7f\u7528", - "support_color": "\u5f37\u5236\u7684\u306b\u30ab\u30e9\u30fc\u3092\u30b5\u30dd\u30fc\u30c8", - "temp_divider": "\u6e29\u5ea6\u5024\u306e\u533a\u5207\u308a(0 = \u30c7\u30d5\u30a9\u30eb\u30c8\u3092\u4f7f\u7528)", - "temp_step_override": "\u76ee\u6a19\u6e29\u5ea6\u30b9\u30c6\u30c3\u30d7", - "tuya_max_coltemp": "\u30c7\u30d0\u30a4\u30b9\u306b\u3088\u3063\u3066\u5831\u544a\u3055\u308c\u305f\u6700\u5927\u8272\u6e29\u5ea6", - "unit_of_measurement": "\u30c7\u30d0\u30a4\u30b9\u304c\u4f7f\u7528\u3059\u308b\u6e29\u5ea6\u5358\u4f4d" - }, - "description": "{device_type} \u30c7\u30d0\u30a4\u30b9 `{device_name}` \u306e\u8868\u793a\u60c5\u5831\u3092\u8abf\u6574\u3059\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u69cb\u6210\u3057\u307e\u3059", - "title": "Tuya\u30c7\u30d0\u30a4\u30b9\u306e\u8a2d\u5b9a" - }, - "init": { - "data": { - "discovery_interval": "\u30c7\u30d0\u30a4\u30b9\u691c\u51fa\u306e\u30dd\u30fc\u30ea\u30f3\u30b0\u9593\u9694(\u79d2\u5358\u4f4d)", - "list_devices": "\u8a2d\u5b9a\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3059\u308b\u304b\u3001\u7a7a\u6b04\u306e\u307e\u307e\u306b\u3057\u3066\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3059", - "query_device": "\u30b9\u30c6\u30fc\u30bf\u30b9\u306e\u66f4\u65b0\u3092\u9ad8\u901f\u5316\u3059\u308b\u305f\u3081\u306b\u30af\u30a8\u30ea\u306e\u65b9\u6cd5(query method)\u3092\u4f7f\u7528\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3057\u307e\u3059", - "query_interval": "\u30af\u30a8\u30ea\u30c7\u30d0\u30a4\u30b9\u306e\u30dd\u30fc\u30ea\u30f3\u30b0\u9593\u9694(\u79d2)" - }, - "description": "\u30dd\u30fc\u30ea\u30f3\u30b0\u9593\u9694\u306e\u5024\u3092\u4f4e\u304f\u8a2d\u5b9a\u3057\u3059\u304e\u306a\u3044\u3067\u304f\u3060\u3055\u3044\u3002\u30b3\u30fc\u30eb\u306b\u5931\u6557\u3057\u3066\u30ed\u30b0\u306b\u30a8\u30e9\u30fc\u30e1\u30c3\u30bb\u30fc\u30b8\u304c\u751f\u6210\u3055\u308c\u307e\u3059\u3002", - "title": "Tuya\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u8a2d\u5b9a" + "description": "Tuya\u306e\u8a8d\u8a3c\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044" } } } diff --git a/homeassistant/components/tuya/translations/ka.json b/homeassistant/components/tuya/translations/ka.json deleted file mode 100644 index 7c80ef1ffba..00000000000 --- a/homeassistant/components/tuya/translations/ka.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "options": { - "error": { - "dev_multi_type": "\u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d0\u10ea\u10d8\u10d8\u10e1\u10d7\u10d5\u10d8\u10e1 \u10e8\u10d4\u10e0\u10e9\u10d4\u10e3\u10da\u10d8 \u10db\u10e0\u10d0\u10d5\u10da\u10dd\u10d1\u10d8\u10d7\u10d8 \u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d0 \u10e3\u10dc\u10d3\u10d0 \u10d8\u10e7\u10dd\u10e1 \u10d4\u10e0\u10d7\u10dc\u10d0\u10d8\u10e0\u10d8 \u10e2\u10d8\u10de\u10d8\u10e1", - "dev_not_config": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10e2\u10d8\u10de\u10d8 \u10d0\u10e0 \u10d0\u10e0\u10d8\u10e1 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d1\u10d0\u10d3\u10d8", - "dev_not_found": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d0 \u10d5\u10d4\u10e0 \u10db\u10dd\u10d8\u10eb\u10d4\u10d1\u10dc\u10d0" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10db\u10d8\u10d4\u10e0 \u10d2\u10d0\u10db\u10dd\u10e7\u10d4\u10dc\u10d4\u10d1\u10e3\u10da\u10d8 \u10e1\u10d8\u10d9\u10d0\u10e8\u10d9\u10d0\u10e8\u10d8\u10e1 \u10d3\u10d8\u10d0\u10de\u10d0\u10d6\u10dd\u10dc\u10d8", - "curr_temp_divider": "\u10db\u10d8\u10db\u10d3\u10d8\u10dc\u10d0\u10e0\u10d4 \u10e2\u10d4\u10db\u10d4\u10de\u10e0\u10d0\u10e2\u10e3\u10e0\u10d8\u10e1 \u10db\u10dc\u10d8\u10e8\u10d5\u10dc\u10d4\u10da\u10d8\u10e1 \u10d2\u10d0\u10db\u10e7\u10dd\u10e4\u10d8 (0 - \u10dc\u10d0\u10d2\u10e3\u10da\u10d8\u10e1\u10ee\u10db\u10d4\u10d5\u10d8)", - "max_kelvin": "\u10db\u10ee\u10d0\u10e0\u10d3\u10d0\u10ed\u10d4\u10e0\u10d8\u10da\u10d8 \u10db\u10d0\u10e5\u10e1\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10d8\u10e1 \u10e4\u10d4\u10e0\u10d8 \u10d9\u10d4\u10da\u10d5\u10d8\u10dc\u10d4\u10d1\u10e8\u10d8", - "max_temp": "\u10db\u10d0\u10e5\u10e1\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10db\u10d8\u10d6\u10dc\u10dd\u10d1\u10e0\u10d8\u10d5\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10d0 (\u10db\u10d8\u10dc\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10d3\u10d0 \u10db\u10d0\u10e5\u10e1\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8\u10e1\u10d0\u10d7\u10d5\u10d8\u10e1 \u10dc\u10d0\u10d2\u10e3\u10da\u10d8\u10e1\u10ee\u10db\u10d4\u10d5\u10d8\u10d0 0)", - "min_kelvin": "\u10db\u10ee\u10d0\u10e0\u10d3\u10d0\u10ed\u10d4\u10e0\u10d8\u10da\u10d8 \u10db\u10d8\u10dc\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10d8\u10e1 \u10e4\u10d4\u10e0\u10d8 \u10d9\u10d4\u10da\u10d5\u10d8\u10dc\u10d4\u10d1\u10e8\u10d8", - "min_temp": "\u10db\u10d8\u10dc\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10db\u10d8\u10d6\u10dc\u10dd\u10d1\u10e0\u10d8\u10d5\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10d0 (\u10db\u10d8\u10dc\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10d3\u10d0 \u10db\u10d0\u10e5\u10e1\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8\u10e1\u10d0\u10d7\u10d5\u10d8\u10e1 \u10dc\u10d0\u10d2\u10e3\u10da\u10d8\u10e1\u10ee\u10db\u10d4\u10d5\u10d8\u10d0 0)", - "support_color": "\u10e4\u10d4\u10e0\u10d8\u10e1 \u10db\u10ee\u10d0\u10e0\u10d3\u10d0\u10ed\u10d4\u10e0\u10d0 \u10d8\u10eb\u10e3\u10da\u10d4\u10d1\u10d8\u10d7", - "temp_divider": "\u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10e3\u10da\u10d8 \u10db\u10dc\u10d8\u10e8\u10d5\u10dc\u10d4\u10da\u10d8\u10e1 \u10d2\u10d0\u10db\u10e7\u10dd\u10e4\u10d8 (0 = \u10dc\u10d0\u10d2\u10e3\u10da\u10d8\u10e1\u10ee\u10db\u10d4\u10d5\u10d8)", - "tuya_max_coltemp": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10db\u10d8\u10d4\u10e0 \u10db\u10dd\u10ec\u10dd\u10d3\u10d4\u10d1\u10e3\u10da\u10d8 \u10e4\u10d4\u10e0\u10d8\u10e1 \u10db\u10d0\u10e5\u10e1\u10d8\u10db\u10d0\u10da\u10e3\u10e0\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10d0", - "unit_of_measurement": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10db\u10d8\u10d4\u10e0 \u10d2\u10d0\u10db\u10dd\u10e7\u10d4\u10dc\u10d4\u10d1\u10e3\u10da\u10d8 \u10e2\u10d4\u10db\u10de\u10d4\u10e0\u10d0\u10e2\u10e3\u10e0\u10e3\u10da\u10d8 \u10d4\u10e0\u10d7\u10d4\u10e3\u10da\u10d8" - }, - "description": "\u10d3\u10d0\u10d0\u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d3 {device_type} `{device_name}` \u10de\u10d0\u10e0\u10d0\u10db\u10d4\u10e2\u10d4\u10e0\u10d1\u10d8 \u10d8\u10dc\u10e4\u10dd\u10e0\u10db\u10d0\u10ea\u10d8\u10d8\u10e1 \u10e9\u10d5\u10d4\u10dc\u10d4\u10d1\u10d8\u10e1 \u10db\u10dd\u10e1\u10d0\u10e0\u10d2\u10d4\u10d1\u10d0\u10d3", - "title": "Tuya-\u10e1 \u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d1\u10d0" - }, - "init": { - "data": { - "discovery_interval": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10d0\u10e6\u10db\u10dd\u10e9\u10d4\u10dc\u10d8\u10e1 \u10d2\u10d0\u10db\u10dd\u10d9\u10d8\u10d7\u10ee\u10d5\u10d8\u10e1 \u10d8\u10dc\u10e2\u10d4\u10e0\u10d5\u10d0\u10da\u10d8 \u10ec\u10d0\u10db\u10d4\u10d1\u10e8\u10d8", - "list_devices": "\u10d0\u10d8\u10e0\u10e9\u10d8\u10d4\u10d7 \u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d0 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d0\u10ea\u10d8\u10d8\u10e1\u10d7\u10d5\u10d8\u10e1 \u10d0\u10dc \u10d3\u10d0\u10e2\u10dd\u10d5\u10d4\u10d7 \u10ea\u10d0\u10e0\u10d8\u10d4\u10da\u10d8 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d0\u10ea\u10d8\u10d8\u10e1 \u10e8\u10d4\u10e1\u10d0\u10dc\u10d0\u10ee\u10d0\u10d3", - "query_device": "\u10d0\u10d8\u10e0\u10e9\u10d8\u10d4\u10d7 \u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d0, \u10e0\u10dd\u10db\u10d4\u10da\u10d8\u10ea \u10d2\u10d0\u10db\u10dd\u10d8\u10e7\u10d4\u10dc\u10d4\u10d1\u10e1 \u10db\u10dd\u10d7\u10ee\u10dd\u10d5\u10dc\u10d8\u10e1 \u10db\u10d4\u10d7\u10dd\u10d3\u10e1 \u10e1\u10e2\u10d0\u10e2\u10e3\u10e1\u10d8\u10e1 \u10e1\u10ec\u10e0\u10d0\u10e4\u10d8 \u10d2\u10d0\u10dc\u10d0\u10ee\u10da\u10d4\u10d1\u10d8\u10e1\u10d7\u10d5\u10d8\u10e1", - "query_interval": "\u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10d2\u10d0\u10db\u10dd\u10d9\u10d8\u10d7\u10ee\u10d5\u10d8\u10e1 \u10db\u10dd\u10d7\u10ee\u10dd\u10d5\u10dc\u10d8\u10e1 \u10d8\u10dc\u10e2\u10d4\u10e0\u10d5\u10d0\u10da\u10d8 \u10ec\u10d0\u10db\u10d4\u10d1\u10e8\u10d8" - }, - "description": "\u10d0\u10e0 \u10d3\u10d0\u10d0\u10e7\u10d4\u10dc\u10dd\u10d7 \u10d2\u10d0\u10db\u10dd\u10d9\u10d8\u10d7\u10ee\u10d5\u10d8\u10e1 \u10d8\u10dc\u10e2\u10d4\u10e0\u10d5\u10d0\u10da\u10d8\u10e1 \u10db\u10dc\u10d8\u10e8\u10d5\u10dc\u10d4\u10da\u10dd\u10d1\u10d4\u10d1\u10d8 \u10eb\u10d0\u10da\u10d8\u10d0\u10dc \u10db\u10ea\u10d8\u10e0\u10d4 \u10d7\u10dd\u10e0\u10d4\u10d1 \u10d2\u10d0\u10db\u10dd\u10eb\u10d0\u10ee\u10d4\u10d1\u10d4\u10d1\u10d8 \u10d3\u10d0\u10d0\u10d2\u10d4\u10dc\u10d4\u10e0\u10d8\u10e0\u10d4\u10d1\u10d4\u10dc \u10e8\u10d4\u10ea\u10d3\u10dd\u10db\u10d4\u10d1\u10e1 \u10da\u10dd\u10d2\u10e8\u10d8", - "title": "Tuya-\u10e1 \u10de\u10d0\u10e0\u10d0\u10db\u10d4\u10e2\u10e0\u10d4\u10d1\u10d8\u10e1 \u10d9\u10dd\u10dc\u10e4\u10d8\u10d2\u10e3\u10e0\u10d8\u10e0\u10d4\u10d1\u10d0" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/ko.json b/homeassistant/components/tuya/translations/ko.json index afa2541e7b9..be389e13b06 100644 --- a/homeassistant/components/tuya/translations/ko.json +++ b/homeassistant/components/tuya/translations/ko.json @@ -1,64 +1,16 @@ { "config": { - "abort": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." - }, "error": { "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, - "flow_title": "Tuya \uad6c\uc131\ud558\uae30", "step": { "user": { "data": { "country_code": "\uacc4\uc815 \uad6d\uac00 \ucf54\ub4dc (\uc608 : \ubbf8\uad6d\uc758 \uacbd\uc6b0 1, \uc911\uad6d\uc758 \uacbd\uc6b0 86)", "password": "\ube44\ubc00\ubc88\ud638", - "platform": "\uacc4\uc815\uc774 \ub4f1\ub85d\ub41c \uc571", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "Tuya \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" - }, - "error": { - "dev_multi_type": "\uc120\ud0dd\ud55c \uc5ec\ub7ec \uae30\uae30\ub97c \uad6c\uc131\ud558\ub824\uba74 \uc720\ud615\uc774 \ub3d9\uc77c\ud574\uc57c \ud569\ub2c8\ub2e4", - "dev_not_config": "\uae30\uae30 \uc720\ud615\uc744 \uad6c\uc131\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "dev_not_found": "\uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \ubc1d\uae30 \ubc94\uc704", - "curr_temp_divider": "\ud604\uc7ac \uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", - "max_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\ub300 \uc0c9\uc628\ub3c4", - "max_temp": "\ucd5c\ub300 \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc19f\uac12 \ubc0f \ucd5c\ub313\uac12 = 0)", - "min_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\uc18c \uc0c9\uc628\ub3c4", - "min_temp": "\ucd5c\uc18c \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc19f\uac12 \ubc0f \ucd5c\ub313\uac12 = 0)", - "set_temp_divided": "\uc124\uc815 \uc628\ub3c4 \uba85\ub839\uc5d0 \ubd84\ud560\ub41c \uc628\ub3c4 \uac12 \uc0ac\uc6a9\ud558\uae30", - "support_color": "\uc0c9\uc0c1 \uc9c0\uc6d0 \uac15\uc81c \uc801\uc6a9\ud558\uae30", - "temp_divider": "\uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", - "temp_step_override": "\ud76c\ub9dd \uc628\ub3c4 \ub2e8\uacc4", - "tuya_max_coltemp": "\uae30\uae30\uc5d0\uc11c \ubcf4\uace0\ud55c \ucd5c\ub300 \uc0c9\uc628\ub3c4", - "unit_of_measurement": "\uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \uc628\ub3c4 \ub2e8\uc704" - }, - "description": "{device_type} `{device_name}` \uae30\uae30\uc5d0 \ub300\ud574 \ud45c\uc2dc\ub418\ub294 \uc815\ubcf4\ub97c \uc870\uc815\ud558\ub294 \uc635\uc158 \uad6c\uc131\ud558\uae30", - "title": "Tuya \uae30\uae30 \uad6c\uc131\ud558\uae30" - }, - "init": { - "data": { - "discovery_interval": "\uae30\uae30 \uac80\uc0c9 \ud3f4\ub9c1 \uac04\uaca9 (\ucd08)", - "list_devices": "\uad6c\uc131\uc744 \uc800\uc7a5\ud558\ub824\uba74 \uad6c\uc131\ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud558\uac70\ub098 \ube44\uc6cc \ub450\uc138\uc694", - "query_device": "\ube60\ub978 \uc0c1\ud0dc \uc5c5\ub370\uc774\ud2b8\ub97c \uc704\ud574 \ucffc\ub9ac \ubc29\ubc95\uc744 \uc0ac\uc6a9\ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", - "query_interval": "\uae30\uae30 \ucffc\ub9ac \ud3f4\ub9c1 \uac04\uaca9 (\ucd08)" - }, - "description": "\ud3f4\ub9c1 \uac04\uaca9 \uac12\uc744 \ub108\ubb34 \ub0ae\uac8c \uc124\uc815\ud558\uc9c0 \ub9d0\uc544 \uc8fc\uc138\uc694. \uadf8\ub807\uc9c0 \uc54a\uc73c\uba74 \ud638\ucd9c\uc5d0 \uc2e4\ud328\ud558\uace0 \ub85c\uadf8\uc5d0 \uc624\ub958 \uba54\uc2dc\uc9c0\uac00 \uc0dd\uc131\ub429\ub2c8\ub2e4.", - "title": "Tuya \uc635\uc158 \uad6c\uc131\ud558\uae30" + "description": "Tuya \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/tuya/translations/lb.json b/homeassistant/components/tuya/translations/lb.json index 0000f9ef6e6..8d0b16a3ff8 100644 --- a/homeassistant/components/tuya/translations/lb.json +++ b/homeassistant/components/tuya/translations/lb.json @@ -1,58 +1,16 @@ { "config": { - "abort": { - "cannot_connect": "Feeler beim verbannen", - "invalid_auth": "Ong\u00eblteg Authentifikatioun", - "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." - }, "error": { "invalid_auth": "Ong\u00eblteg Authentifikatioun" }, - "flow_title": "Tuya Konfiguratioun", "step": { "user": { "data": { "country_code": "De L\u00e4nner Code fir d\u00e4i Kont (beispill 1 fir USA oder 86 fir China)", "password": "Passwuert", - "platform": "d'App wou den Kont registr\u00e9iert ass", "username": "Benotzernumm" }, - "description": "F\u00ebll deng Tuya Umeldungs Informatiounen aus.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Feeler beim verbannen" - }, - "error": { - "dev_multi_type": "Multiple ausgewielte Ger\u00e4ter fir ze konfigur\u00e9ieren musse vum selwechten Typ sinn", - "dev_not_config": "Typ vun Apparat net konfigur\u00e9ierbar", - "dev_not_found": "Apparat net fonnt" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Hellegkeetsber\u00e4ich vum Apparat", - "curr_temp_divider": "Aktuell Temperatur W\u00e4erter Deeler (0= benotz Standard)", - "max_kelvin": "Maximal Faarftemperatur \u00ebnnerst\u00ebtzt a Kelvin", - "max_temp": "Maximal Zil Temperatur (benotz min a max = 0 fir standard)", - "min_kelvin": "Minimal Faarftemperatur \u00ebnnerst\u00ebtzt a Kelvin", - "min_temp": "Minimal Zil Temperatur (benotz min a max = 0 fir standard)", - "support_color": "Forc\u00e9ier Faarf \u00cbnnerst\u00ebtzung", - "temp_divider": "Temperatur W\u00e4erter Deeler (0= benotz Standard)", - "tuya_max_coltemp": "Max Faarftemperatur vum Apparat gemellt", - "unit_of_measurement": "Temperatur Eenheet vum Apparat" - }, - "description": "Konfigur\u00e9ier Optioune fir ugewisen Informatioune fir {device_type} Apparat `{device_name}` unzepassen", - "title": "Tuya Apparat ariichten" - }, - "init": { - "data": { - "list_devices": "Wiel d'Apparater fir ze konfigur\u00e9ieren aus oder loss se eidel fir d'Konfiguratioun ze sp\u00e4icheren" - }, - "title": "Tuya Optioune konfigur\u00e9ieren" + "description": "F\u00ebll deng Tuya Umeldungs Informatiounen aus." } } } diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index e12c3ba29f2..ed2ce07c226 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Kan geen verbinding maken", - "invalid_auth": "Ongeldige authenticatie", - "single_instance_allowed": "Al geconfigureerd. Er is maar een configuratie mogelijk." - }, "error": { "invalid_auth": "Ongeldige authenticatie", "login_error": "Aanmeldingsfout ({code}): {msg}" }, - "flow_title": "Tuya-configuratie", "step": { - "login": { - "data": { - "access_id": "Toegangs-ID", - "access_secret": "Access Secret", - "country_code": "Landcode", - "endpoint": "Beschikbaarheidszone", - "password": "Wachtwoord", - "tuya_app_type": "Mobiele app", - "username": "Account" - }, - "description": "Voer uw Tuya-inloggegevens in", - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT-toegangs-ID", "access_secret": "Tuya IoT Access Secret", "country_code": "Land", "password": "Wachtwoord", - "platform": "De app waar uw account is geregistreerd", - "region": "Regio", - "tuya_project_type": "Tuya cloud project type", "username": "Gebruikersnaam" }, - "description": "Voer uw Tuya-inloggegevens in.", - "title": "Tuya-integratie" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Kan geen verbinding maken" - }, - "error": { - "dev_multi_type": "Meerdere geselecteerde apparaten om te configureren moeten van hetzelfde type zijn", - "dev_not_config": "Apparaattype kan niet worden geconfigureerd", - "dev_not_found": "Apparaat niet gevonden" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Helderheidsbereik gebruikt door apparaat", - "curr_temp_divider": "Huidige temperatuurwaarde deler (0 = standaardwaarde)", - "max_kelvin": "Max kleurtemperatuur in kelvin", - "max_temp": "Maximale doeltemperatuur (gebruik min en max = 0 voor standaardwaarde)", - "min_kelvin": "Minimaal ondersteunde kleurtemperatuur in kelvin", - "min_temp": "Min. gewenste temperatuur (gebruik min en max = 0 voor standaard)", - "set_temp_divided": "Gedeelde temperatuurwaarde gebruiken voor ingestelde temperatuuropdracht", - "support_color": "Forceer kleurenondersteuning", - "temp_divider": "Temperatuurwaarde deler (0 = standaardwaarde)", - "temp_step_override": "Doeltemperatuur stap", - "tuya_max_coltemp": "Max. kleurtemperatuur gerapporteerd door apparaat", - "unit_of_measurement": "Temperatuureenheid gebruikt door apparaat" - }, - "description": "Configureer opties om weergegeven informatie aan te passen voor {device_type} apparaat `{device_name}`", - "title": "Configureer Tuya Apparaat" - }, - "init": { - "data": { - "discovery_interval": "Polling-interval van nieuwe apparaten in seconden", - "list_devices": "Selecteer de te configureren apparaten of laat leeg om de configuratie op te slaan", - "query_device": "Selecteer apparaat dat query-methode zal gebruiken voor snellere statusupdate", - "query_interval": "Ververstijd van het apparaat in seconden" - }, - "description": "Stel de waarden voor het pollinginterval niet te laag in, anders zullen de oproepen geen foutmelding in het logboek genereren", - "title": "Configureer Tuya opties" + "description": "Voer uw Tuya-inloggegevens in." } } } diff --git a/homeassistant/components/tuya/translations/no.json b/homeassistant/components/tuya/translations/no.json index 16657972322..7c0137b80a6 100644 --- a/homeassistant/components/tuya/translations/no.json +++ b/homeassistant/components/tuya/translations/no.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Tilkobling mislyktes", - "invalid_auth": "Ugyldig godkjenning", - "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." - }, "error": { "invalid_auth": "Ugyldig godkjenning", "login_error": "P\u00e5loggingsfeil ( {code} ): {msg}" }, - "flow_title": "Tuya konfigurasjon", "step": { - "login": { - "data": { - "access_id": "Tilgangs -ID", - "access_secret": "Tilgangshemmelighet", - "country_code": "Landskode", - "endpoint": "Tilgjengelighetssone", - "password": "Passord", - "tuya_app_type": "Mobilapp", - "username": "Konto" - }, - "description": "Skriv inn Tuya-legitimasjonen din", - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT Access ID", "access_secret": "Tuya IoT Access Secret", "country_code": "Land", "password": "Passord", - "platform": "Appen der kontoen din er registrert", - "region": "Region", - "tuya_project_type": "Tuya -skyprosjekttype", "username": "Account" }, - "description": "Skriv inn Tuya -legitimasjonen din", - "title": "Tuya Integrasjon" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Tilkobling mislyktes" - }, - "error": { - "dev_multi_type": "Flere valgte enheter som skal konfigureres, m\u00e5 v\u00e6re av samme type", - "dev_not_config": "Enhetstype kan ikke konfigureres", - "dev_not_found": "Finner ikke enheten" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Lysstyrkeomr\u00e5de som brukes av enheten", - "curr_temp_divider": "N\u00e5v\u00e6rende temperaturverdi (0 = bruk standard)", - "max_kelvin": "Maks fargetemperatur st\u00f8ttet i kelvin", - "max_temp": "Maks m\u00e5ltemperatur (bruk min og maks = 0 for standard)", - "min_kelvin": "Min fargetemperatur st\u00f8ttet i kelvin", - "min_temp": "Min m\u00e5ltemperatur (bruk min og maks = 0 for standard)", - "set_temp_divided": "Bruk delt temperaturverdi for innstilt temperaturkommando", - "support_color": "Tving fargest\u00f8tte", - "temp_divider": "Deler temperaturverdier (0 = bruk standard)", - "temp_step_override": "Trinn for m\u00e5ltemperatur", - "tuya_max_coltemp": "Maks fargetemperatur rapportert av enheten", - "unit_of_measurement": "Temperaturenhet som brukes av enheten" - }, - "description": "Konfigurer alternativer for \u00e5 justere vist informasjon for {device_type} device ` {device_name} `", - "title": "Konfigurere Tuya-enhet" - }, - "init": { - "data": { - "discovery_interval": "Avsp\u00f8rringsintervall for discovery-enheten i l\u00f8pet av sekunder", - "list_devices": "Velg enhetene du vil konfigurere, eller la de v\u00e6re tomme for \u00e5 lagre konfigurasjonen", - "query_device": "Velg enhet som skal bruke sp\u00f8rringsmetode for raskere statusoppdatering", - "query_interval": "Sp\u00f8rringsintervall for intervall i sekunder" - }, - "description": "Ikke angi pollingsintervallverdiene for lave, ellers vil ikke anropene generere feilmelding i loggen", - "title": "Konfigurer Tuya-alternativer" + "description": "Skriv inn Tuya -legitimasjonen din" } } } diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json index a191c40ca3f..e7fe9caf65a 100644 --- a/homeassistant/components/tuya/translations/pl.json +++ b/homeassistant/components/tuya/translations/pl.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "invalid_auth": "Niepoprawne uwierzytelnienie", - "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." - }, "error": { "invalid_auth": "Niepoprawne uwierzytelnienie", "login_error": "B\u0142\u0105d logowania ({code}): {msg}" }, - "flow_title": "Konfiguracja integracji Tuya", "step": { - "login": { - "data": { - "access_id": "Identyfikator dost\u0119pu", - "access_secret": "Has\u0142o dost\u0119pu", - "country_code": "Kod kraju", - "endpoint": "Strefa dost\u0119pno\u015bci", - "password": "Has\u0142o", - "tuya_app_type": "Aplikacja mobilna", - "username": "Konto" - }, - "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce Tuya", - "title": "Tuya" - }, "user": { "data": { "access_id": "Identyfikator dost\u0119pu do Tuya IoT", "access_secret": "Has\u0142o dost\u0119pu do Tuya IoT", "country_code": "Kraj", "password": "Has\u0142o", - "platform": "Aplikacja, w kt\u00f3rej zarejestrowane jest Twoje konto", - "region": "Region", - "tuya_project_type": "Typ projektu chmury Tuya", "username": "Konto" }, - "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce Tuya", - "title": "Integracja Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" - }, - "error": { - "dev_multi_type": "Wybrane urz\u0105dzenia do skonfigurowania musz\u0105 by\u0107 tego samego typu", - "dev_not_config": "Typ urz\u0105dzenia nie jest konfigurowalny", - "dev_not_found": "Nie znaleziono urz\u0105dzenia" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Zakres jasno\u015bci u\u017cywany przez urz\u0105dzenie", - "curr_temp_divider": "Dzielnik aktualnej warto\u015bci temperatury (0 = u\u017cyj warto\u015bci domy\u015blnej)", - "max_kelvin": "Maksymalna obs\u0142ugiwana temperatura barwy w kelwinach", - "max_temp": "Maksymalna temperatura docelowa (u\u017cyj min i max = 0 dla warto\u015bci domy\u015blnej)", - "min_kelvin": "Minimalna obs\u0142ugiwana temperatura barwy w kelwinach", - "min_temp": "Minimalna temperatura docelowa (u\u017cyj min i max = 0 dla warto\u015bci domy\u015blnej)", - "set_temp_divided": "U\u017cyj podzielonej warto\u015bci temperatury dla polecenia ustawienia temperatury", - "support_color": "Wymu\u015b obs\u0142ug\u0119 kolor\u00f3w", - "temp_divider": "Dzielnik warto\u015bci temperatury (0 = u\u017cyj warto\u015bci domy\u015blnej)", - "temp_step_override": "Krok docelowej temperatury", - "tuya_max_coltemp": "Maksymalna temperatura barwy raportowana przez urz\u0105dzenie", - "unit_of_measurement": "Jednostka temperatury u\u017cywana przez urz\u0105dzenie" - }, - "description": "Skonfiguruj opcje, aby dostosowa\u0107 wy\u015bwietlane informacje dla urz\u0105dzenia {device_type} `{device_name}'", - "title": "Konfiguracja urz\u0105dzenia Tuya" - }, - "init": { - "data": { - "discovery_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania nowych urz\u0105dze\u0144 (w sekundach)", - "list_devices": "Wybierz urz\u0105dzenia do skonfigurowania lub pozostaw puste, aby zapisa\u0107 konfiguracj\u0119", - "query_device": "Wybierz urz\u0105dzenie, kt\u00f3re b\u0119dzie u\u017cywa\u0107 metody odpytywania w celu szybszej aktualizacji statusu", - "query_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania odpytywanego urz\u0105dzenia w sekundach" - }, - "description": "Nie ustawiaj zbyt niskich warto\u015bci skanowania, bo zako\u0144cz\u0105 si\u0119 niepowodzeniem, generuj\u0105c komunikat o b\u0142\u0119dzie w logu", - "title": "Konfiguracja opcji Tuya" + "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce Tuya" } } } diff --git a/homeassistant/components/tuya/translations/pt-BR.json b/homeassistant/components/tuya/translations/pt-BR.json index 242d1e9ee08..5ae0382d0b8 100644 --- a/homeassistant/components/tuya/translations/pt-BR.json +++ b/homeassistant/components/tuya/translations/pt-BR.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Falha ao conectar", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." - }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "login_error": "Erro de login ({code}): {msg}" }, - "flow_title": "Configura\u00e7\u00e3o Tuya", "step": { - "login": { - "data": { - "access_id": "Tuya IoT Access ID", - "access_secret": "Tuya IoT Access Secret", - "country_code": "Pa\u00eds", - "endpoint": "Regi\u00e3o", - "password": "Senha do Aplicativo", - "tuya_app_type": "O aplicativo onde sua conta \u00e9 registrada", - "username": "Usu\u00e1rio do Aplicativo" - }, - "description": "Digite sua credencial Tuya", - "title": "Integra\u00e7\u00e3o Tuya" - }, "user": { "data": { "access_id": "Tuya IoT Access ID", "access_secret": "Tuya IoT Access Secret", "country_code": "Pa\u00eds", "password": "Senha do Aplicativo", - "platform": "O aplicativo onde sua conta \u00e9 registrada", - "region": "Regi\u00e3o", - "tuya_project_type": "Tipo de projeto de Tuya Cloud", "username": "Usu\u00e1rio do Aplicativo" }, - "description": "Digite sua credencial Tuya", - "title": "Integra\u00e7\u00e3o Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Falha ao conectar" - }, - "error": { - "dev_multi_type": "V\u00e1rios dispositivos selecionados para configurar devem ser do mesmo tipo", - "dev_not_config": "Tipo de dispositivo n\u00e3o configur\u00e1vel", - "dev_not_found": "Dispositivo n\u00e3o encontrado" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Faixa de brilho usada pelo dispositivo", - "curr_temp_divider": "Divisor do valor da temperatura atual (0 = usar o padr\u00e3o)", - "max_kelvin": "Temperatura m\u00e1xima de cor suportada em Kelvin", - "max_temp": "Temperatura m\u00e1xima do alvo (use min e max = 0 para padr\u00e3o)", - "min_kelvin": "Temperatura m\u00ednima de cor suportada em kelvin", - "min_temp": "Temperatura m\u00ednima desejada (use m\u00edn e m\u00e1x = 0 para o padr\u00e3o)", - "set_temp_divided": "Use o valor de temperatura dividido para o comando de temperatura definido", - "support_color": "For\u00e7ar suporte de cores", - "temp_divider": "Divisor de valores de temperatura (0 = usar padr\u00e3o)", - "temp_step_override": "Etapa de temperatura alvo", - "tuya_max_coltemp": "Temperatura m\u00e1xima de cor relatada pelo dispositivo", - "unit_of_measurement": "Unidade de temperatura usada pelo dispositivo" - }, - "description": "Configure as op\u00e7\u00f5es para ajustar as informa\u00e7\u00f5es exibidas para o dispositivo {device_type} `{device_name}`", - "title": "Configurar dispositivo Tuya" - }, - "init": { - "data": { - "discovery_interval": "Intervalo de pesquisa do dispositivo de descoberta em segundos", - "list_devices": "Selecione os dispositivos para configurar ou deixe em branco para salvar a configura\u00e7\u00e3o", - "query_device": "Selecione o dispositivo que usar\u00e1 o m\u00e9todo de consulta para atualiza\u00e7\u00e3o de status mais r\u00e1pida", - "query_interval": "Intervalo de sondagem do dispositivo de consulta em segundos" - }, - "description": "N\u00e3o defina valores de intervalo de sondagens muito baixos ou as chamadas falhar\u00e3o gerando mensagem de erro no log", - "title": "Configurar op\u00e7\u00f5es do Tuya" + "description": "Digite sua credencial Tuya" } } } diff --git a/homeassistant/components/tuya/translations/pt.json b/homeassistant/components/tuya/translations/pt.json index 566746538c0..f9de53a43fe 100644 --- a/homeassistant/components/tuya/translations/pt.json +++ b/homeassistant/components/tuya/translations/pt.json @@ -1,10 +1,5 @@ { "config": { - "abort": { - "cannot_connect": "Falha na liga\u00e7\u00e3o", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", - "single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel." - }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, @@ -16,10 +11,5 @@ } } } - }, - "options": { - "abort": { - "cannot_connect": "Falha na liga\u00e7\u00e3o" - } } } \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/ru.json b/homeassistant/components/tuya/translations/ru.json index e13914683f1..f58a78d1bb5 100644 --- a/homeassistant/components/tuya/translations/ru.json +++ b/homeassistant/components/tuya/translations/ru.json @@ -1,82 +1,19 @@ { "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.", - "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "login_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 ({code}): {msg}" }, - "flow_title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Tuya", "step": { - "login": { - "data": { - "access_id": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0434\u043e\u0441\u0442\u0443\u043f\u0430", - "access_secret": "\u0421\u0435\u043a\u0440\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0430", - "country_code": "\u041a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u044b", - "endpoint": "\u0417\u043e\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u0438", - "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "tuya_app_type": "\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435", - "username": "\u0423\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c" - }, - "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 Tuya.", - "title": "Tuya" - }, "user": { "data": { "access_id": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 Tuya IoT", "access_secret": "\u0421\u0435\u043a\u0440\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 Tuya IoT", "country_code": "\u0421\u0442\u0440\u0430\u043d\u0430", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "platform": "\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c", - "region": "\u0420\u0435\u0433\u0438\u043e\u043d", - "tuya_project_type": "\u0422\u0438\u043f \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 Tuya", "username": "\u0423\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c" }, - "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 Tuya.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." - }, - "error": { - "dev_multi_type": "\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430.", - "dev_not_config": "\u0422\u0438\u043f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442\u0441\u044f.", - "dev_not_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u0414\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u044f\u0440\u043a\u043e\u0441\u0442\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c", - "curr_temp_divider": "\u0414\u0435\u043b\u0438\u0442\u0435\u043b\u044c \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b (0 = \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e)", - "max_kelvin": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0438\u043d\u0430\u0445)", - "max_temp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0446\u0435\u043b\u0435\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 min \u0438 max = 0)", - "min_kelvin": "\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0438\u043d\u0430\u0445)", - "min_temp": "\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0446\u0435\u043b\u0435\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 min \u0438 max = 0)", - "set_temp_divided": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b", - "support_color": "\u041f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0446\u0432\u0435\u0442\u0430", - "temp_divider": "\u0414\u0435\u043b\u0438\u0442\u0435\u043b\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b (0 = \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e)", - "temp_step_override": "\u0428\u0430\u0433 \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b", - "tuya_max_coltemp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430, \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u043c\u0430\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c", - "unit_of_measurement": "\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, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c" - }, - "description": "\u041e\u043f\u0446\u0438\u0438 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0434\u043b\u044f {device_type} \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 `{device_name}`", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Tuya" - }, - "init": { - "data": { - "discovery_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", - "list_devices": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u043b\u0438 \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438", - "query_device": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0430", - "query_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" - }, - "description": "\u041d\u0435 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0439\u0442\u0435 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043d\u0438\u0437\u043a\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430 \u043e\u043f\u0440\u043e\u0441\u0430, \u0438\u043d\u0430\u0447\u0435 \u0432\u044b\u0437\u043e\u0432\u044b \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435 \u0432 \u0436\u0443\u0440\u043d\u0430\u043b\u0435.", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 Tuya" + "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 Tuya." } } } diff --git a/homeassistant/components/tuya/translations/select.es.json b/homeassistant/components/tuya/translations/select.es.json index 7dc4cf1067b..e29ccdc381f 100644 --- a/homeassistant/components/tuya/translations/select.es.json +++ b/homeassistant/components/tuya/translations/select.es.json @@ -27,6 +27,10 @@ "0": "Sensibilidad baja", "1": "Sensibilidad alta" }, + "tuya__fan_angle": { + "30": "30\u00b0", + "90": "90\u00b0" + }, "tuya__fingerbot_mode": { "click": "Push", "switch": "Interruptor" @@ -43,7 +47,12 @@ "level_8": "Nivel 8", "level_9": "Nivel 9" }, + "tuya__humidifier_moodlighting": { + "3": "Estado 3", + "4": "Estado 4" + }, "tuya__humidifier_spray_mode": { + "auto": "Autom\u00e1tico", "health": "Salud", "humidity": "Humedad", "sleep": "Dormir", diff --git a/homeassistant/components/tuya/translations/select.ko.json b/homeassistant/components/tuya/translations/select.ko.json new file mode 100644 index 00000000000..022505e76e5 --- /dev/null +++ b/homeassistant/components/tuya/translations/select.ko.json @@ -0,0 +1,12 @@ +{ + "state": { + "tuya__basic_anti_flickr": { + "1": "50Hz", + "2": "60Hz" + }, + "tuya__fingerbot_mode": { + "click": "\ud478\uc2dc", + "switch": "\uc2a4\uc704\uce58" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/select.nl.json b/homeassistant/components/tuya/translations/select.nl.json index e645694ae55..521a9c908ba 100644 --- a/homeassistant/components/tuya/translations/select.nl.json +++ b/homeassistant/components/tuya/translations/select.nl.json @@ -77,7 +77,7 @@ }, "tuya__light_mode": { "none": "Uit", - "pos": "Plaats van schakelaar aangeven", + "pos": "Schakelpositie aangeven", "relay": "Aan/uit-toestand aangeven" }, "tuya__motion_sensitivity": { @@ -90,8 +90,8 @@ "2": "Continu opnemen" }, "tuya__relay_status": { - "last": "Onthoud laatste staat", - "memory": "Onthoud laatste staat", + "last": "Laatste status onthouden", + "memory": "Laatste status onthouden", "off": "Uit", "on": "Aan", "power_off": "Uit", @@ -116,7 +116,7 @@ "mop": "Dweil", "part": "Deel", "partial_bow": "Boog gedeeltelijk", - "pick_zone": "Kies zone", + "pick_zone": "Zone kiezen", "point": "Punt", "pose": "Houding", "random": "Willekeurig", diff --git a/homeassistant/components/tuya/translations/select.ru.json b/homeassistant/components/tuya/translations/select.ru.json index 99f7f02771d..c2138e2490b 100644 --- a/homeassistant/components/tuya/translations/select.ru.json +++ b/homeassistant/components/tuya/translations/select.ru.json @@ -52,8 +52,17 @@ "level_8": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 8", "level_9": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c 9" }, + "tuya__humidifier_moodlighting": { + "1": "\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 1", + "2": "\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 2", + "3": "\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 3", + "4": "\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 4", + "5": "\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 5" + }, "tuya__humidifier_spray_mode": { "auto": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438", + "health": "\u0421\u0442\u0430\u0442\u0443\u0441", + "humidity": "\u0412\u043b\u0430\u0436\u043d\u043e\u0441\u0442\u044c", "sleep": "\u0421\u043e\u043d", "work": "\u0420\u0430\u0431\u043e\u0442\u0430" }, @@ -108,6 +117,7 @@ "part": "\u0427\u0430\u0441\u0442\u044c", "pick_zone": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0437\u043e\u043d\u0443", "point": "\u0422\u043e\u0447\u043a\u0430", + "pose": "\u041f\u043e\u0437\u0438\u0446\u0438\u044f", "random": "\u0421\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439", "right_bow": "\u041e\u0433\u0438\u0431\u0430\u0442\u044c \u0441\u043f\u0440\u0430\u0432\u0430", "right_spiral": "\u0421\u043f\u0438\u0440\u0430\u043b\u044c \u0432\u043f\u0440\u0430\u0432\u043e", diff --git a/homeassistant/components/tuya/translations/select.sk.json b/homeassistant/components/tuya/translations/select.sk.json new file mode 100644 index 00000000000..493576472b9 --- /dev/null +++ b/homeassistant/components/tuya/translations/select.sk.json @@ -0,0 +1,18 @@ +{ + "state": { + "tuya__countdown": { + "3h": "3 hodiny" + }, + "tuya__decibel_sensitivity": { + "1": "Vysok\u00e1 citlivos\u0165" + }, + "tuya__motion_sensitivity": { + "0": "N\u00edzka citlivos\u0165", + "1": "Stredn\u00e1 citlivos\u0165", + "2": "Vysok\u00e1 citlivos\u0165" + }, + "tuya__vacuum_mode": { + "random": "N\u00e1hodn\u00fd" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/sensor.ru.json b/homeassistant/components/tuya/translations/sensor.ru.json index 9ec7eac14c0..8b356e13db0 100644 --- a/homeassistant/components/tuya/translations/sensor.ru.json +++ b/homeassistant/components/tuya/translations/sensor.ru.json @@ -3,7 +3,8 @@ "tuya__air_quality": { "good": "\u0425\u043e\u0440\u043e\u0448\u0435\u0435", "great": "\u041e\u0442\u043b\u0438\u0447\u043d\u043e\u0435", - "mild": "\u0423\u043c\u0435\u0440\u0435\u043d\u043d\u043e\u0435" + "mild": "\u0423\u043c\u0435\u0440\u0435\u043d\u043d\u043e\u0435", + "severe": "\u0421\u0435\u0440\u044c\u0435\u0437\u043d\u044b\u0439" }, "tuya__status": { "boiling_temp": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u043a\u0438\u043f\u0435\u043d\u0438\u044f", diff --git a/homeassistant/components/tuya/translations/sensor.sk.json b/homeassistant/components/tuya/translations/sensor.sk.json new file mode 100644 index 00000000000..4f80ab106ad --- /dev/null +++ b/homeassistant/components/tuya/translations/sensor.sk.json @@ -0,0 +1,7 @@ +{ + "state": { + "tuya__status": { + "boiling_temp": "Teplota varu" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/sk.json b/homeassistant/components/tuya/translations/sk.json index 23d8822116c..93fa886f796 100644 --- a/homeassistant/components/tuya/translations/sk.json +++ b/homeassistant/components/tuya/translations/sk.json @@ -1,21 +1,12 @@ { "config": { - "abort": { - "invalid_auth": "Neplatn\u00e9 overenie" - }, "error": { "invalid_auth": "Neplatn\u00e9 overenie" }, "step": { - "login": { - "data": { - "country_code": "K\u00f3d krajiny" - } - }, "user": { "data": { - "country_code": "Krajina", - "region": "Oblas\u0165" + "country_code": "Krajina" } } } diff --git a/homeassistant/components/tuya/translations/sl.json b/homeassistant/components/tuya/translations/sl.json deleted file mode 100644 index 52d2fd3e973..00000000000 --- a/homeassistant/components/tuya/translations/sl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "region": "Regija" - } - } - } - }, - "options": { - "abort": { - "cannot_connect": "Povezovanje ni uspelo." - }, - "error": { - "dev_not_config": "Vrsta naprave ni nastavljiva", - "dev_not_found": "Naprave ni mogo\u010de najti" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/sv.json b/homeassistant/components/tuya/translations/sv.json index 85cc9c57fd3..344aea60f6f 100644 --- a/homeassistant/components/tuya/translations/sv.json +++ b/homeassistant/components/tuya/translations/sv.json @@ -1,16 +1,13 @@ { "config": { - "flow_title": "Tuya-konfiguration", "step": { "user": { "data": { "country_code": "Landskod f\u00f6r ditt konto (t.ex. 1 f\u00f6r USA eller 86 f\u00f6r Kina)", "password": "L\u00f6senord", - "platform": "Appen d\u00e4r ditt konto registreras", "username": "Anv\u00e4ndarnamn" }, - "description": "Ange dina Tuya anv\u00e4ndaruppgifter.", - "title": "Tuya" + "description": "Ange dina Tuya anv\u00e4ndaruppgifter." } } } diff --git a/homeassistant/components/tuya/translations/tr.json b/homeassistant/components/tuya/translations/tr.json index 1bcc2bc627e..fe1b81831a6 100644 --- a/homeassistant/components/tuya/translations/tr.json +++ b/homeassistant/components/tuya/translations/tr.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "Ba\u011flanma hatas\u0131", - "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", - "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." - }, "error": { "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "login_error": "Giri\u015f hatas\u0131 ( {code} ): {msg}" }, - "flow_title": "Tuya yap\u0131land\u0131rmas\u0131", "step": { - "login": { - "data": { - "access_id": "Eri\u015fim Kimli\u011fi", - "access_secret": "Eri\u015fim Anahtar\u0131", - "country_code": "\u00dclke Kodu", - "endpoint": "Kullan\u0131labilirlik Alan\u0131", - "password": "\u015eifre", - "tuya_app_type": "Tuya uygulama tipi", - "username": "Kullan\u0131c\u0131 Ad\u0131" - }, - "description": "Tuya kimlik bilgilerinizi girin", - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT Eri\u015fim Kimli\u011fi", "access_secret": "Tuya IoT Eri\u015fim Anahtar\u0131", "country_code": "\u00dclke", "password": "Parola", - "platform": "Hesab\u0131n\u0131z\u0131n kay\u0131tl\u0131 oldu\u011fu uygulama", - "region": "B\u00f6lge", - "tuya_project_type": "Tuya bulut proje t\u00fcr\u00fc", "username": "Hesap" }, - "description": "Tuya kimlik bilgilerinizi girin.", - "title": "Tuya Entegrasyonu" - } - } - }, - "options": { - "abort": { - "cannot_connect": "Ba\u011flanma hatas\u0131" - }, - "error": { - "dev_multi_type": "Yap\u0131land\u0131r\u0131lacak birden \u00e7ok se\u00e7ili cihaz ayn\u0131 t\u00fcrde olmal\u0131d\u0131r", - "dev_not_config": "Cihaz t\u00fcr\u00fc yap\u0131land\u0131r\u0131lamaz", - "dev_not_found": "Cihaz bulunamad\u0131" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "Cihaz\u0131n kulland\u0131\u011f\u0131 parlakl\u0131k aral\u0131\u011f\u0131", - "curr_temp_divider": "Mevcut S\u0131cakl\u0131k de\u011feri b\u00f6l\u00fcc\u00fc (0 = varsay\u0131lan\u0131 kullan)", - "max_kelvin": "Kelvin'de desteklenen maksimum renk s\u0131cakl\u0131\u011f\u0131", - "max_temp": "Maksimum hedef s\u0131cakl\u0131k (varsay\u0131lan olarak min ve maks = 0 kullan\u0131n)", - "min_kelvin": "Kelvin destekli min renk s\u0131cakl\u0131\u011f\u0131", - "min_temp": "Minimum hedef s\u0131cakl\u0131k (varsay\u0131lan i\u00e7in min ve maks = 0 kullan\u0131n)", - "set_temp_divided": "Ayarlanan s\u0131cakl\u0131k komutu i\u00e7in b\u00f6l\u00fcnm\u00fc\u015f S\u0131cakl\u0131k de\u011ferini kullan\u0131n", - "support_color": "Vurgu rengi", - "temp_divider": "S\u0131cakl\u0131k de\u011ferleri ay\u0131r\u0131c\u0131 (0 = varsay\u0131lan\u0131 kullan)", - "temp_step_override": "Hedef S\u0131cakl\u0131k ad\u0131m\u0131", - "tuya_max_coltemp": "Cihaz taraf\u0131ndan bildirilen maksimum renk s\u0131cakl\u0131\u011f\u0131", - "unit_of_measurement": "Cihaz\u0131n kulland\u0131\u011f\u0131 s\u0131cakl\u0131k birimi" - }, - "description": "{device_type} cihaz\u0131 ` {device_name} device_name} ` i\u00e7in g\u00f6r\u00fcnt\u00fclenen bilgileri ayarlamak \u00fczere se\u00e7enekleri yap\u0131land\u0131r\u0131n", - "title": "Tuya Cihaz\u0131n\u0131 Yap\u0131land\u0131r\u0131n" - }, - "init": { - "data": { - "discovery_interval": "Cihaz\u0131 yoklama aral\u0131\u011f\u0131 saniye cinsinden", - "list_devices": "Yap\u0131land\u0131rmay\u0131 kaydetmek i\u00e7in yap\u0131land\u0131r\u0131lacak veya bo\u015f b\u0131rak\u0131lacak cihazlar\u0131 se\u00e7in", - "query_device": "Daha h\u0131zl\u0131 durum g\u00fcncellemesi i\u00e7in sorgu y\u00f6ntemini kullanacak cihaz\u0131 se\u00e7in", - "query_interval": "Ayg\u0131t yoklama aral\u0131\u011f\u0131 saniye cinsinden" - }, - "description": "Yoklama aral\u0131\u011f\u0131 de\u011ferlerini \u00e7ok d\u00fc\u015f\u00fck ayarlamay\u0131n, aksi takdirde \u00e7a\u011fr\u0131lar g\u00fcnl\u00fckte hata mesaj\u0131 olu\u015fturarak ba\u015far\u0131s\u0131z olur", - "title": "Tuya Se\u00e7eneklerini Konfig\u00fcre Et" + "description": "Tuya kimlik bilgilerinizi girin." } } } diff --git a/homeassistant/components/tuya/translations/uk.json b/homeassistant/components/tuya/translations/uk.json index 97616e5f388..0e1535d9681 100644 --- a/homeassistant/components/tuya/translations/uk.json +++ b/homeassistant/components/tuya/translations/uk.json @@ -1,62 +1,16 @@ { "config": { - "abort": { - "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", - "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", - "single_instance_allowed": "\u0412\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u041c\u043e\u0436\u043b\u0438\u0432\u0430 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f." - }, "error": { "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." }, - "flow_title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Tuya", "step": { "user": { "data": { "country_code": "\u041a\u043e\u0434 \u043a\u0440\u0430\u0457\u043d\u0438 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 (1 \u0434\u043b\u044f \u0421\u0428\u0410 \u0430\u0431\u043e 86 \u0434\u043b\u044f \u041a\u0438\u0442\u0430\u044e)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "platform": "\u0414\u043e\u0434\u0430\u0442\u043e\u043a, \u0432 \u044f\u043a\u043e\u043c\u0443 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" }, - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Tuya.", - "title": "Tuya" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" - }, - "error": { - "dev_multi_type": "\u041a\u0456\u043b\u044c\u043a\u0430 \u043e\u0431\u0440\u0430\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0443.", - "dev_not_config": "\u0422\u0438\u043f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f.", - "dev_not_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e." - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u0414\u0456\u0430\u043f\u0430\u0437\u043e\u043d \u044f\u0441\u043a\u0440\u0430\u0432\u043e\u0441\u0442\u0456, \u044f\u043a\u0438\u0439 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c", - "curr_temp_divider": "\u0414\u0456\u043b\u044c\u043d\u0438\u043a \u043f\u043e\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438 (0 = \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c)", - "max_kelvin": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0456\u043d\u0430\u0445)", - "max_temp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u0446\u0456\u043b\u044c\u043e\u0432\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 min \u0456 max = 0)", - "min_kelvin": "\u041c\u0456\u043d\u0456\u043c\u0430\u043b\u044c\u043d\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0456\u043d\u0430\u0445)", - "min_temp": "\u041c\u0456\u043d\u0456\u043c\u0430\u043b\u044c\u043d\u0430 \u0446\u0456\u043b\u044c\u043e\u0432\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 min \u0456 max = 0)", - "support_color": "\u041f\u0440\u0438\u043c\u0443\u0441\u043e\u0432\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0430 \u043a\u043e\u043b\u044c\u043e\u0440\u0443", - "temp_divider": "\u0414\u0456\u043b\u044c\u043d\u0438\u043a \u0437\u043d\u0430\u0447\u0435\u043d\u044c \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438 (0 = \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c)", - "tuya_max_coltemp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430, \u044f\u043a\u0430 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u044f\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c", - "unit_of_measurement": "\u041e\u0434\u0438\u043d\u0438\u0446\u044f \u0432\u0438\u043c\u0456\u0440\u0443 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438, \u044f\u043a\u0430 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c" - }, - "description": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u0434\u0438\u043c\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u0434\u043b\u044f {device_type} \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e '{device_name}'", - "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Tuya" - }, - "init": { - "data": { - "discovery_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", - "list_devices": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u0431\u043e \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c \u0434\u043b\u044f \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043d\u044f \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457", - "query_device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u044f\u043a\u0438\u0439 \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043c\u0435\u0442\u043e\u0434 \u0437\u0430\u043f\u0438\u0442\u0443 \u0434\u043b\u044f \u0431\u0456\u043b\u044c\u0448 \u0448\u0432\u0438\u0434\u043a\u043e\u0433\u043e \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0443", - "query_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" - }, - "description": "\u041d\u0435 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u044e\u0439\u0442\u0435 \u0437\u0430\u043d\u0430\u0434\u0442\u043e \u043d\u0438\u0437\u044c\u043a\u0456 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0443 \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f, \u0456\u043d\u0430\u043a\u0448\u0435 \u0432\u0438\u043a\u043b\u0438\u043a\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442\u044c \u0433\u0435\u043d\u0435\u0440\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u043e \u043f\u043e\u043c\u0438\u043b\u043a\u0443 \u0432 \u0436\u0443\u0440\u043d\u0430\u043b\u0456.", - "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Tuya" + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Tuya." } } } diff --git a/homeassistant/components/tuya/translations/zh-Hans.json b/homeassistant/components/tuya/translations/zh-Hans.json index b990d5bb8a1..b5d00c5586a 100644 --- a/homeassistant/components/tuya/translations/zh-Hans.json +++ b/homeassistant/components/tuya/translations/zh-Hans.json @@ -1,81 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "\u8fde\u63a5\u5931\u8d25", - "invalid_auth": "\u8eab\u4efd\u8ba4\u8bc1\u65e0\u6548", - "single_instance_allowed": "\u8be5\u96c6\u6210\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86\uff0c\u4e14\u53ea\u80fd\u914d\u7f6e\u4e00\u6b21\u3002\u82e5\u8981\u91cd\u65b0\u914d\u7f6e\uff0c\u8bf7\u5148\u5220\u9664\u65e7\u96c6\u6210\u3002" - }, "error": { "invalid_auth": "\u8eab\u4efd\u8ba4\u8bc1\u65e0\u6548", "login_error": "\u767b\u5f55\u5931\u8d25({code}): {msg}" }, - "flow_title": "\u6d82\u9e26\u914d\u7f6e", "step": { - "login": { - "data": { - "access_id": "\u8bbf\u95ee ID", - "access_secret": "\u8bbf\u95ee\u5bc6\u94a5", - "country_code": "\u56fd\u5bb6\u4ee3\u7801", - "endpoint": "\u53ef\u7528\u533a\u57df", - "password": "\u5bc6\u7801", - "tuya_app_type": "\u79fb\u52a8\u5e94\u7528", - "username": "\u5e10\u6237" - }, - "description": "\u8bf7\u8f93\u5165\u60a8\u7684\u6d82\u9e26\u8d26\u6237\u4fe1\u606f", - "title": "\u6d82\u9e26" - }, "user": { "data": { "access_id": "\u6d82\u9e26\u7269\u8054\u7f51\u8bbe\u5907\u63a5\u5165 ID", "access_secret": "\u6d82\u9e26\u7269\u8054\u7f51\u8bbe\u5907\u63a5\u5165\u5bc6\u94a5", "country_code": "\u60a8\u7684\u5e10\u6237\u56fd\u5bb6(\u5730\u533a)\u4ee3\u7801\uff08\u4f8b\u5982\u4e2d\u56fd\u4e3a 86\uff0c\u7f8e\u56fd\u4e3a 1\uff09", "password": "\u5bc6\u7801", - "platform": "\u60a8\u6ce8\u518c\u5e10\u6237\u7684\u5e94\u7528", - "region": "\u5730\u533a", - "tuya_project_type": "\u6d82\u9e26\u4e91\u9879\u76ee\u7c7b\u578b", "username": "\u7528\u6237\u540d" }, - "description": "\u8bf7\u8f93\u5165\u6d82\u9e26\u8d26\u6237\u4fe1\u606f\u3002", - "title": "\u6d82\u9e26" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u8fde\u63a5\u5931\u8d25" - }, - "error": { - "dev_multi_type": "\u591a\u4e2a\u8981\u914d\u7f6e\u7684\u8bbe\u5907\u5fc5\u987b\u5177\u6709\u76f8\u540c\u7684\u7c7b\u578b", - "dev_not_config": "\u8bbe\u5907\u7c7b\u578b\u4e0d\u53ef\u914d\u7f6e", - "dev_not_found": "\u672a\u627e\u5230\u8bbe\u5907" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u8bbe\u5907\u4f7f\u7528\u7684\u4eae\u5ea6\u8303\u56f4", - "curr_temp_divider": "\u5f53\u524d\u6e29\u5ea6\u503c\u9664\u6570\uff080 = \u4f7f\u7528\u9ed8\u8ba4\u503c\uff09", - "max_kelvin": "\u6700\u9ad8\u652f\u6301\u8272\u6e29\uff08\u5f00\u6c0f\uff09", - "max_temp": "\u6700\u9ad8\u76ee\u6807\u6e29\u5ea6\uff08min \u548c max \u4e3a 0 \u65f6\u4f7f\u7528\u9ed8\u8ba4\uff09", - "min_kelvin": "\u6700\u4f4e\u652f\u6301\u8272\u6e29\uff08\u5f00\u6c0f\uff09", - "min_temp": "\u6700\u4f4e\u76ee\u6807\u6e29\u5ea6\uff08min \u548c max \u4e3a 0 \u65f6\u4f7f\u7528\u9ed8\u8ba4\uff09", - "set_temp_divided": "\u5bf9\u8bbe\u7f6e\u6e29\u5ea6\u547d\u4ee4\u4f7f\u7528\u7ecf\u8fc7\u9664\u6cd5\u7684\u6e29\u5ea6\u503c", - "support_color": "\u5f3a\u5236\u652f\u6301\u8c03\u8272", - "temp_divider": "\u6e29\u5ea6\u503c\u9664\u6570\uff080 = \u4f7f\u7528\u9ed8\u8ba4\u503c\uff09", - "temp_step_override": "\u76ee\u6807\u6e29\u5ea6\u6b65\u957f", - "tuya_max_coltemp": "\u8bbe\u5907\u62a5\u544a\u7684\u6700\u9ad8\u8272\u6e29", - "unit_of_measurement": "\u8bbe\u5907\u4f7f\u7528\u7684\u6e29\u5ea6\u5355\u4f4d" - }, - "title": "\u914d\u7f6e\u6d82\u9e26\u8bbe\u5907" - }, - "init": { - "data": { - "discovery_interval": "\u53d1\u73b0\u8bbe\u5907\u8f6e\u8be2\u95f4\u9694\uff08\u4ee5\u79d2\u4e3a\u5355\u4f4d\uff09", - "list_devices": "\u8bf7\u9009\u62e9\u8981\u914d\u7f6e\u7684\u8bbe\u5907\uff0c\u6216\u7559\u7a7a\u4ee5\u4fdd\u5b58\u914d\u7f6e", - "query_device": "\u8bf7\u9009\u62e9\u4f7f\u7528\u67e5\u8be2\u65b9\u6cd5\u7684\u8bbe\u5907\uff0c\u4ee5\u4fbf\u66f4\u5feb\u5730\u66f4\u65b0\u72b6\u6001", - "query_interval": "\u67e5\u8be2\u8bbe\u5907\u8f6e\u8be2\u95f4\u9694\uff08\u4ee5\u79d2\u4e3a\u5355\u4f4d\uff09" - }, - "description": "\u8bf7\u4e0d\u8981\u5c06\u8f6e\u8be2\u95f4\u9694\u8bbe\u7f6e\u5f97\u592a\u4f4e\uff0c\u5426\u5219\u5c06\u8c03\u7528\u5931\u8d25\u5e76\u5728\u65e5\u5fd7\u751f\u6210\u9519\u8bef\u6d88\u606f", - "title": "\u6d82\u9e26\u914d\u7f6e\u9009\u9879" + "description": "\u8bf7\u8f93\u5165\u6d82\u9e26\u8d26\u6237\u4fe1\u606f\u3002" } } } diff --git a/homeassistant/components/tuya/translations/zh-Hant.json b/homeassistant/components/tuya/translations/zh-Hant.json index e9898f782af..c255cb723a9 100644 --- a/homeassistant/components/tuya/translations/zh-Hant.json +++ b/homeassistant/components/tuya/translations/zh-Hant.json @@ -1,82 +1,19 @@ { "config": { - "abort": { - "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", - "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" - }, "error": { "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "login_error": "\u767b\u5165\u932f\u8aa4\uff08{code}\uff09\uff1a{msg}" }, - "flow_title": "Tuya \u8a2d\u5b9a", "step": { - "login": { - "data": { - "access_id": "Access ID", - "access_secret": "\u5b58\u53d6\u79c1\u9470", - "country_code": "\u570b\u78bc", - "endpoint": "\u53ef\u7528\u5340\u57df", - "password": "\u5bc6\u78bc", - "tuya_app_type": "\u624b\u6a5f App", - "username": "\u5e33\u865f" - }, - "description": "\u8f38\u5165 Tuya \u6191\u8b49", - "title": "Tuya" - }, "user": { "data": { "access_id": "Tuya IoT Access ID", "access_secret": "Tuya IoT \u5b58\u53d6\u79c1\u9470", "country_code": "\u570b\u5bb6", "password": "\u5bc6\u78bc", - "platform": "\u5e33\u6236\u8a3b\u518a\u6240\u5728\u4f4d\u7f6e", - "region": "\u5340\u57df", - "tuya_project_type": "Tuya \u96f2\u5c08\u6848\u985e\u5225", "username": "\u5e33\u865f" }, - "description": "\u8f38\u5165 Tuya \u6191\u8b49", - "title": "Tuya \u6574\u5408" - } - } - }, - "options": { - "abort": { - "cannot_connect": "\u9023\u7dda\u5931\u6557" - }, - "error": { - "dev_multi_type": "\u591a\u91cd\u9078\u64c7\u8a2d\u88dd\u7f6e\u4ee5\u8a2d\u5b9a\u4f7f\u7528\u76f8\u540c\u985e\u5225", - "dev_not_config": "\u88dd\u7f6e\u985e\u5225\u7121\u6cd5\u8a2d\u5b9a", - "dev_not_found": "\u627e\u4e0d\u5230\u88dd\u7f6e" - }, - "step": { - "device": { - "data": { - "brightness_range_mode": "\u88dd\u7f6e\u6240\u4f7f\u7528\u4e4b\u4eae\u5ea6\u7bc4\u570d", - "curr_temp_divider": "\u76ee\u524d\u8272\u6eab\u503c\u5206\u914d\u5668\uff080 = \u4f7f\u7528\u9810\u8a2d\uff09", - "max_kelvin": "Kelvin \u652f\u63f4\u6700\u9ad8\u8272\u6eab", - "max_temp": "\u6700\u9ad8\u76ee\u6a19\u8272\u6eab\uff08\u4f7f\u7528\u6700\u4f4e\u8207\u6700\u9ad8 = 0 \u4f7f\u7528\u9810\u8a2d\uff09", - "min_kelvin": "Kelvin \u652f\u63f4\u6700\u4f4e\u8272\u6eab", - "min_temp": "\u6700\u4f4e\u76ee\u6a19\u8272\u6eab\uff08\u4f7f\u7528\u6700\u4f4e\u8207\u6700\u9ad8 = 0 \u4f7f\u7528\u9810\u8a2d\uff09", - "set_temp_divided": "\u4f7f\u7528\u5206\u9694\u865f\u6eab\u5ea6\u503c\u4ee5\u57f7\u884c\u8a2d\u5b9a\u6eab\u5ea6\u6307\u4ee4", - "support_color": "\u5f37\u5236\u8272\u6eab\u652f\u63f4", - "temp_divider": "\u8272\u6eab\u503c\u5206\u914d\u5668\uff080 = \u4f7f\u7528\u9810\u8a2d\uff09", - "temp_step_override": "\u76ee\u6a19\u6eab\u5ea6\u8a2d\u5b9a", - "tuya_max_coltemp": "\u88dd\u7f6e\u56de\u5831\u6700\u9ad8\u8272\u6eab", - "unit_of_measurement": "\u88dd\u7f6e\u6240\u4f7f\u7528\u4e4b\u6eab\u5ea6\u55ae\u4f4d" - }, - "description": "\u8a2d\u5b9a\u9078\u9805\u4ee5\u8abf\u6574 {device_type} \u88dd\u7f6e `{device_name}` \u986f\u793a\u8cc7\u8a0a", - "title": "\u8a2d\u5b9a Tuya \u88dd\u7f6e" - }, - "init": { - "data": { - "discovery_interval": "\u641c\u7d22\u88dd\u7f6e\u66f4\u65b0\u79d2\u9593\u8ddd", - "list_devices": "\u9078\u64c7\u6240\u8981\u8a2d\u5b9a\u7684\u88dd\u7f6e\u3001\u6216\u4fdd\u6301\u7a7a\u767d\u4ee5\u5132\u5b58\u8a2d\u5b9a", - "query_device": "\u9078\u64c7\u88dd\u7f6e\u5c07\u4f7f\u7528\u67e5\u8a62\u65b9\u5f0f\u4ee5\u7372\u5f97\u66f4\u5feb\u7684\u72c0\u614b\u66f4\u65b0", - "query_interval": "\u67e5\u8a62\u88dd\u7f6e\u66f4\u65b0\u79d2\u9593\u8ddd" - }, - "description": "\u66f4\u65b0\u9593\u8ddd\u4e0d\u8981\u8a2d\u5b9a\u7684\u904e\u4f4e\u3001\u53ef\u80fd\u6703\u5c0e\u81f4\u65bc\u65e5\u8a8c\u4e2d\u7522\u751f\u932f\u8aa4\u8a0a\u606f", - "title": "\u8a2d\u5b9a Tuya \u9078\u9805" + "description": "\u8f38\u5165 Tuya \u6191\u8b49" } } } diff --git a/homeassistant/components/tuya/vacuum.py b/homeassistant/components/tuya/vacuum.py index 6dea121c980..d17452b11c6 100644 --- a/homeassistant/components/tuya/vacuum.py +++ b/homeassistant/components/tuya/vacuum.py @@ -78,51 +78,54 @@ class TuyaVacuumEntity(TuyaEntity, StateVacuumEntity): _fan_speed: EnumTypeData | None = None _battery_level: IntegerTypeData | None = None - _supported_features = 0 def __init__(self, device: TuyaDevice, device_manager: TuyaDeviceManager) -> None: """Init Tuya vacuum.""" super().__init__(device, device_manager) - self._supported_features |= VacuumEntityFeature.SEND_COMMAND + self._attr_supported_features = 0 + self._attr_fan_speed_list = [] + + self._attr_supported_features |= VacuumEntityFeature.SEND_COMMAND if self.find_dpcode(DPCode.PAUSE, prefer_function=True): - self._supported_features |= VacuumEntityFeature.PAUSE + self._attr_supported_features |= VacuumEntityFeature.PAUSE if self.find_dpcode(DPCode.SWITCH_CHARGE, prefer_function=True): - self._supported_features |= VacuumEntityFeature.RETURN_HOME + self._attr_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 |= VacuumEntityFeature.RETURN_HOME + self._attr_supported_features |= VacuumEntityFeature.RETURN_HOME if self.find_dpcode(DPCode.SEEK, prefer_function=True): - self._supported_features |= VacuumEntityFeature.LOCATE + self._attr_supported_features |= VacuumEntityFeature.LOCATE if self.find_dpcode(DPCode.STATUS, prefer_function=True): - self._supported_features |= ( + self._attr_supported_features |= ( VacuumEntityFeature.STATE | VacuumEntityFeature.STATUS ) if self.find_dpcode(DPCode.POWER, prefer_function=True): - self._supported_features |= ( + self._attr_supported_features |= ( VacuumEntityFeature.TURN_ON | VacuumEntityFeature.TURN_OFF ) if self.find_dpcode(DPCode.POWER_GO, prefer_function=True): - self._supported_features |= ( + self._attr_supported_features |= ( VacuumEntityFeature.STOP | VacuumEntityFeature.START ) if enum_type := self.find_dpcode( DPCode.SUCTION, dptype=DPType.ENUM, prefer_function=True ): - self._supported_features |= VacuumEntityFeature.FAN_SPEED self._fan_speed = enum_type + self._attr_fan_speed_list = enum_type.range + self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED if int_type := self.find_dpcode(DPCode.ELECTRICITY_LEFT, dptype=DPType.INTEGER): - self._supported_features |= VacuumEntityFeature.BATTERY + self._attr_supported_features |= VacuumEntityFeature.BATTERY self._battery_level = int_type @property @@ -139,13 +142,6 @@ class TuyaVacuumEntity(TuyaEntity, StateVacuumEntity): """Return the fan speed of the vacuum cleaner.""" return self.device.status.get(DPCode.SUCTION) - @property - def fan_speed_list(self) -> list[str]: - """Get the list of available fan speed steps of the vacuum cleaner.""" - if self._fan_speed is None: - return [] - return self._fan_speed.range - @property def state(self) -> str | None: """Return Tuya vacuum device state.""" @@ -157,11 +153,6 @@ class TuyaVacuumEntity(TuyaEntity, StateVacuumEntity): return None return TUYA_STATUS_TO_HA.get(status) - @property - def supported_features(self) -> int: - """Flag supported features.""" - return self._supported_features - def turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" self._send_command([{"code": DPCode.POWER, "value": True}]) diff --git a/homeassistant/components/twentemilieu/manifest.json b/homeassistant/components/twentemilieu/manifest.json index e8e280dcd3d..c7dd2508fd8 100644 --- a/homeassistant/components/twentemilieu/manifest.json +++ b/homeassistant/components/twentemilieu/manifest.json @@ -3,7 +3,7 @@ "name": "Twente Milieu", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/twentemilieu", - "requirements": ["twentemilieu==0.6.0"], + "requirements": ["twentemilieu==0.6.1"], "codeowners": ["@frenck"], "quality_scale": "platinum", "iot_class": "cloud_polling", diff --git a/homeassistant/components/twentemilieu/translations/es.json b/homeassistant/components/twentemilieu/translations/es.json index 259d202a9c3..93effca8aad 100644 --- a/homeassistant/components/twentemilieu/translations/es.json +++ b/homeassistant/components/twentemilieu/translations/es.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "No se pudo conectar", - "invalid_address": "Direcci\u00f3n no encontrada en el \u00e1rea de servicio de Twente Milieu." + "invalid_address": "No se ha encontrado la direcci\u00f3n en el \u00e1rea de servicio de Twente Milieu." }, "step": { "user": { @@ -14,7 +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." + "description": "Configura Twente Milieu con informaci\u00f3n de la recogida de residuos en tu direcci\u00f3n." } } } diff --git a/homeassistant/components/twentemilieu/translations/nl.json b/homeassistant/components/twentemilieu/translations/nl.json index 575c642a777..740c38da286 100644 --- a/homeassistant/components/twentemilieu/translations/nl.json +++ b/homeassistant/components/twentemilieu/translations/nl.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "Kan geen verbinding maken", - "invalid_address": "Adres niet gevonden in servicegebied Twente Milieu." + "invalid_address": "Adres niet gevonden in Twente Milieu servicegebied." }, "step": { "user": { diff --git a/homeassistant/components/twilio/translations/es.json b/homeassistant/components/twilio/translations/es.json index 46499faca59..41057c5f4a0 100644 --- a/homeassistant/components/twilio/translations/es.json +++ b/homeassistant/components/twilio/translations/es.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "No conectado a Home Assistant Cloud.", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n.", "webhook_not_internet_accessible": "Tu instancia de Home Assistant debe estar accesible desde Internet para recibir mensajes webhook." }, @@ -9,7 +10,7 @@ }, "step": { "user": { - "description": "\u00bfEst\u00e1s seguro de que quieres configurar Twilio?", + "description": "\u00bfQuieres empezar la configuraci\u00f3n?", "title": "Configurar el Webhook de Twilio" } } diff --git a/homeassistant/components/twilio/translations/nl.json b/homeassistant/components/twilio/translations/nl.json index cf180123c98..a63d0301dcd 100644 --- a/homeassistant/components/twilio/translations/nl.json +++ b/homeassistant/components/twilio/translations/nl.json @@ -3,14 +3,14 @@ "abort": { "cloud_not_connected": "Niet verbonden met Home Assistant Cloud.", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", - "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." + "webhook_not_internet_accessible": "Je Home Assistant-instantie moet toegankelijk zijn vanaf internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Om evenementen naar de Home Assistant te verzenden, moet u [Webhooks with Twilio] ( {twilio_url} ) instellen. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n - Inhoudstype: application / x-www-form-urlencoded \n\n Zie [de documentatie] ( {docs_url} ) voor informatie over het configureren van automatiseringen om binnenkomende gegevens te verwerken." }, "step": { "user": { - "description": "Wilt u beginnen met instellen?", + "description": "Wil je beginnen met instellen?", "title": "Stel de Twilio Webhook in" } } diff --git a/homeassistant/components/twinkly/translations/es.json b/homeassistant/components/twinkly/translations/es.json index e18d54adb9e..a45be8da295 100644 --- a/homeassistant/components/twinkly/translations/es.json +++ b/homeassistant/components/twinkly/translations/es.json @@ -12,7 +12,7 @@ }, "user": { "data": { - "host": "Host (o direcci\u00f3n IP) de tu dispositivo Twinkly" + "host": "Host" } } } diff --git a/homeassistant/components/ukraine_alarm/translations/bg.json b/homeassistant/components/ukraine_alarm/translations/bg.json new file mode 100644 index 00000000000..ed64f99c6b2 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/bg.json @@ -0,0 +1,28 @@ +{ + "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", + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "max_regions": "\u041c\u043e\u0433\u0430\u0442 \u0434\u0430 \u0441\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442 \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c 5 \u0440\u0435\u0433\u0438\u043e\u043d\u0430", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "community": { + "data": { + "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + } + }, + "district": { + "data": { + "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + } + }, + "user": { + "data": { + "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u0442\u0430 Ukraine Alarm. \u0417\u0430 \u0434\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u0442\u0435 API \u043a\u043b\u044e\u0447, \u043e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 {api_url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/ca.json b/homeassistant/components/ukraine_alarm/translations/ca.json new file mode 100644 index 00000000000..ea03b64dd3b --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/ca.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "La ubicaci\u00f3 ja est\u00e0 configurada", + "cannot_connect": "Ha fallat la connexi\u00f3", + "max_regions": "Es poden configurar un m\u00e0xim de 5 regions", + "rate_limit": "Massa peticions", + "timeout": "Temps m\u00e0xim d'espera per establir la connexi\u00f3 esgotat", + "unknown": "Error inesperat" + }, + "step": { + "community": { + "data": { + "region": "Regi\u00f3" + }, + "description": "Si vols monitorar no nom\u00e9s l'estat i el districte, tria la comunitat espec\u00edfica" + }, + "district": { + "data": { + "region": "Regi\u00f3" + }, + "description": "Si vols monitorar no nom\u00e9s l'estat, tria el districte espec\u00edfic" + }, + "user": { + "data": { + "region": "Regi\u00f3" + }, + "description": "Escull l'estat a monitorar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/de.json b/homeassistant/components/ukraine_alarm/translations/de.json new file mode 100644 index 00000000000..7c7c093c7cd --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/de.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "max_regions": "Es k\u00f6nnen maximal 5 Regionen konfiguriert werden", + "rate_limit": "Zu viele Anfragen", + "timeout": "Zeit\u00fcberschreitung beim Verbindungsaufbau", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "community": { + "data": { + "region": "Region" + }, + "description": "Wenn du nicht nur Bundesland und Bezirk \u00fcberwachen m\u00f6chtest, w\u00e4hle die jeweilige Gemeinde aus" + }, + "district": { + "data": { + "region": "Region" + }, + "description": "Wenn du nicht nur das Bundesland \u00fcberwachen m\u00f6chtest, w\u00e4hle seinen bestimmten Bezirk" + }, + "user": { + "data": { + "region": "Region" + }, + "description": "Zu \u00fcberwachendes Bundesland ausw\u00e4hlen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/el.json b/homeassistant/components/ukraine_alarm/translations/el.json new file mode 100644 index 00000000000..779c7dc1102 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/el.json @@ -0,0 +1,32 @@ +{ + "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", + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "max_regions": "\u039c\u03c0\u03bf\u03c1\u03bf\u03cd\u03bd \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03bf\u03cd\u03bd \u03ad\u03c9\u03c2 5 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ad\u03c2", + "rate_limit": "\u03a0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03ac \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1", + "timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "community": { + "data": { + "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae" + }, + "description": "\u0395\u03ac\u03bd \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c7\u03b9 \u03bc\u03cc\u03bd\u03bf \u03c4\u03b7\u03bd \u03c0\u03bf\u03bb\u03b9\u03c4\u03b5\u03af\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03b9\u03c6\u03ad\u03c1\u03b5\u03b9\u03b1, \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b7 \u03ba\u03bf\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03ac \u03c4\u03b7\u03c2" + }, + "district": { + "data": { + "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae" + }, + "description": "\u0391\u03bd \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c7\u03b9 \u03bc\u03cc\u03bd\u03bf \u03c4\u03b7\u03bd \u03c0\u03bf\u03bb\u03b9\u03c4\u03b5\u03af\u03b1, \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b7 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae \u03c4\u03b7\u03c2." + }, + "user": { + "data": { + "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2" + }, + "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 \u03c3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03bf\u03cd \u03c4\u03b7\u03c2 \u039f\u03c5\u03ba\u03c1\u03b1\u03bd\u03af\u03b1\u03c2. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {api_url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/es.json b/homeassistant/components/ukraine_alarm/translations/es.json new file mode 100644 index 00000000000..a9efcd0d536 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/es.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", + "max_regions": "Se pueden configurar un m\u00e1ximo de 5 regiones", + "timeout": "Tiempo m\u00e1ximo de espera para establecer la conexi\u00f3n agotado", + "unknown": "Error inesperado" + }, + "step": { + "district": { + "data": { + "region": "Regi\u00f3n" + }, + "description": "Si quieres monitorear no s\u00f3lo el estado, elige el distrito espec\u00edfico" + }, + "user": { + "data": { + "region": "Regi\u00f3n" + }, + "description": "Escoja el estado a monitorear" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/et.json b/homeassistant/components/ukraine_alarm/translations/et.json new file mode 100644 index 00000000000..1c5d74db071 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/et.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Asukoht on juba m\u00e4\u00e4ratud", + "cannot_connect": "\u00dchendamine nurjus", + "max_regions": "Seadistada saab kuni 5 piirkonda", + "rate_limit": "Liiga palju taotlusi", + "timeout": "\u00dchenduse ajal\u00f5pp", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "community": { + "data": { + "region": "Piirkond" + }, + "description": "Kui soovid j\u00e4lgida mitte ainult oblastit ja piirkonda vali konkreetne asukoht" + }, + "district": { + "data": { + "region": "Piirkond" + }, + "description": "Kui soovid j\u00e4lgida mitte ainult oblastit vali konkreetne piirkond" + }, + "user": { + "data": { + "region": "Piirkond" + }, + "description": "Vali j\u00e4lgitav oblast" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/fr.json b/homeassistant/components/ukraine_alarm/translations/fr.json new file mode 100644 index 00000000000..9a8bd95da63 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/fr.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "L'emplacement est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de connexion", + "max_regions": "Un maximum de cinq r\u00e9gions peuvent \u00eatre configur\u00e9es", + "rate_limit": "Trop de demandes", + "timeout": "D\u00e9lai d'attente pour \u00e9tablir la connexion expir\u00e9", + "unknown": "Erreur inattendue" + }, + "step": { + "community": { + "data": { + "region": "R\u00e9gion" + }, + "description": "Si vous ne d\u00e9sirez pas uniquement surveiller l'\u00c9tat et son district, s\u00e9lectionnez sa communaut\u00e9 sp\u00e9cifique" + }, + "district": { + "data": { + "region": "R\u00e9gion" + }, + "description": "Si vous ne d\u00e9sirez pas uniquement surveiller l'\u00c9tat, s\u00e9lectionnez son district sp\u00e9cifique" + }, + "user": { + "data": { + "region": "R\u00e9gion" + }, + "description": "Choisissez l'\u00c9tat \u00e0 surveiller" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/he.json b/homeassistant/components/ukraine_alarm/translations/he.json new file mode 100644 index 00000000000..6f3d95e2bb4 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/he.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05de\u05d9\u05e7\u05d5\u05dd \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "timeout": "\u05e4\u05e1\u05e7 \u05d6\u05de\u05df \u05dc\u05d9\u05e6\u05d9\u05e8\u05ea \u05d7\u05d9\u05d1\u05d5\u05e8", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "community": { + "data": { + "region": "\u05d0\u05d9\u05d6\u05d5\u05e8" + } + }, + "district": { + "data": { + "region": "\u05d0\u05d9\u05d6\u05d5\u05e8" + } + }, + "user": { + "data": { + "region": "\u05d0\u05d9\u05d6\u05d5\u05e8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/hu.json b/homeassistant/components/ukraine_alarm/translations/hu.json new file mode 100644 index 00000000000..6bbbae1eca2 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/hu.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "max_regions": "Legfeljebb 5 r\u00e9gi\u00f3 konfigur\u00e1lhat\u00f3", + "rate_limit": "T\u00fal sok k\u00e9r\u00e9s", + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a kapcsolat l\u00e9trehoz\u00e1sa sor\u00e1n", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "community": { + "data": { + "region": "R\u00e9gi\u00f3" + }, + "description": "Ha nem csak a megy\u00e9t \u00e9s a ker\u00fcletet szeretn\u00e9 figyelni, v\u00e1lassza ki az adott k\u00f6z\u00f6ss\u00e9get" + }, + "district": { + "data": { + "region": "R\u00e9gi\u00f3" + }, + "description": "Ha nem csak megy\u00e9t szeretne figyelni, v\u00e1lassza ki az adott ker\u00fcletet" + }, + "user": { + "data": { + "region": "R\u00e9gi\u00f3" + }, + "description": "V\u00e1lassza ki a megfigyelni k\u00edv\u00e1nt megy\u00e9t" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/id.json b/homeassistant/components/ukraine_alarm/translations/id.json new file mode 100644 index 00000000000..1751c132e9d --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "max_regions": "Maksimal 5 wilayah dapat dikonfigurasi", + "rate_limit": "Terlalu banyak permintaan", + "timeout": "Tenggang waktu membuat koneksi habis", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "community": { + "data": { + "region": "Wilayah" + }, + "description": "Jika ingin memantau tidak hanya negara bagian dan distrik, pilih komunitas tertentu" + }, + "district": { + "data": { + "region": "Wilayah" + }, + "description": "Jika ingin memantau tidak hanya negara bagian, pilih distrik tertentu" + }, + "user": { + "data": { + "region": "Wilayah" + }, + "description": "Pilih negara bagian untuk dipantau" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/it.json b/homeassistant/components/ukraine_alarm/translations/it.json new file mode 100644 index 00000000000..7c02193a43b --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/it.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "La posizione \u00e8 gi\u00e0 configurata", + "cannot_connect": "Impossibile connettersi", + "max_regions": "\u00c8 possibile configurare al massimo 5 regioni", + "rate_limit": "Troppe richieste", + "timeout": "Tempo scaduto per stabile la connessione.", + "unknown": "Errore imprevisto" + }, + "step": { + "community": { + "data": { + "region": "[%key:component::ukraine_alarm::config::step::state::data::region%]" + }, + "description": "Se vuoi monitorare non solo stato e distretto, scegli la sua comunit\u00e0 specifica" + }, + "district": { + "data": { + "region": "[%key:component::ukraine_alarm::config::step::state::data::region%]" + }, + "description": "Se vuoi monitorare non solo lo stato, scegli il suo distretto specifico" + }, + "user": { + "data": { + "region": "Regione" + }, + "description": "Scegli lo stato da monitorare" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/ja.json b/homeassistant/components/ukraine_alarm/translations/ja.json new file mode 100644 index 00000000000..81bdabdc8d3 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/ja.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "max_regions": "\u6700\u59275\u3064\u306e\u30ea\u30fc\u30b8\u30e7\u30f3\u3092\u8a2d\u5b9a\u53ef\u80fd", + "rate_limit": "\u30ea\u30af\u30a8\u30b9\u30c8\u304c\u591a\u3059\u304e\u307e\u3059", + "timeout": "\u63a5\u7d9a\u78ba\u7acb\u6642\u306b\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "community": { + "data": { + "region": "[%key:component::ukraine_alarm::config::step::state::data::region%]" + }, + "description": "\u5dde\u3084\u5730\u533a\u3060\u3051\u3067\u306a\u304f\u3001\u7279\u5b9a\u306e\u5730\u57df\u3092\u76e3\u8996\u3057\u305f\u3044\u5834\u5408\u306f\u3001\u305d\u306e\u5730\u57df\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044" + }, + "district": { + "data": { + "region": "[%key:component::ukraine_alarm::config::step::state::data::region%]" + }, + "description": "\u5dde\u3060\u3051\u3067\u306a\u304f\u3001\u7279\u5b9a\u306e\u5730\u533a\u3092\u76e3\u8996\u3057\u305f\u3044\u5834\u5408\u306f\u3001\u305d\u306e\u5730\u533a\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044" + }, + "user": { + "data": { + "region": "\u30ea\u30fc\u30b8\u30e7\u30f3" + }, + "description": "\u30a6\u30af\u30e9\u30a4\u30ca\u306e\u8b66\u5831\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001 {api_url} \u306b\u79fb\u52d5\u3057\u3066\u304f\u3060\u3055\u3044" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/ko.json b/homeassistant/components/ukraine_alarm/translations/ko.json new file mode 100644 index 00000000000..e9da77f866b --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/ko.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "max_regions": "\ucd5c\ub300 5\uac1c \uc9c0\uc5ed\uc744 \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "rate_limit": "\uc694\uccad\uc774 \ub108\ubb34 \ub9ce\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "community": { + "data": { + "region": "\uc9c0\uc5ed" + }, + "description": "\uc8fc \ubc0f \uc9c0\uc5ed\ubfd0\ub9cc \uc544\ub2c8\ub77c \ud574\ub2f9 \uc9c0\uc5ed\uc758 \ud2b9\uc815 \ucee4\ubba4\ub2c8\ud2f0\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud558\ub824\uba74 \ud574\ub2f9 \ucee4\ubba4\ub2c8\ud2f0\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624" + }, + "district": { + "data": { + "region": "\uc9c0\uc5ed" + }, + "description": "\uc8fc\ubfd0\ub9cc \uc544\ub2c8\ub77c \ud2b9\uc815 \uc9c0\uc5ed\uc744 \ubaa8\ub2c8\ud130\ub9c1\ud558\ub824\uba74 \ud574\ub2f9 \uc9c0\uc5ed\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624" + }, + "user": { + "data": { + "region": "\uc9c0\uc5ed" + }, + "description": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \uc8fc \uc120\ud0dd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/nl.json b/homeassistant/components/ukraine_alarm/translations/nl.json new file mode 100644 index 00000000000..d9caa461588 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/nl.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Locatie is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", + "max_regions": "Er kunnen maximaal 5 regio's worden geconfigureerd", + "rate_limit": "Te veel verzoeken", + "timeout": "Time-out bij het maken van verbinding", + "unknown": "Onverwachte fout" + }, + "step": { + "community": { + "data": { + "region": "Regio" + }, + "description": "Als u niet alleen de staat en het district wilt volgen, kiest u de specifieke gemeenschap ervan" + }, + "district": { + "data": { + "region": "Regio" + }, + "description": "Als u niet alleen de staat wilt controleren, kies dan het specifieke district" + }, + "user": { + "data": { + "region": "Regio" + }, + "description": "De status kiezen om te monitoren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/no.json b/homeassistant/components/ukraine_alarm/translations/no.json new file mode 100644 index 00000000000..de5e4284cb0 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/no.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Plasseringen er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes", + "max_regions": "Maks 5 regioner kan konfigureres", + "rate_limit": "For mange foresp\u00f8rsler", + "timeout": "Tidsavbrudd oppretter forbindelse", + "unknown": "Uventet feil" + }, + "step": { + "community": { + "data": { + "region": "Region" + }, + "description": "Hvis du ikke bare vil overv\u00e5ke stat og distrikt, velg dets spesifikke fellesskap" + }, + "district": { + "data": { + "region": "Region" + }, + "description": "Hvis du ikke bare vil overv\u00e5ke staten, velg dens spesifikke distrikt" + }, + "user": { + "data": { + "region": "Region" + }, + "description": "Velg tilstand \u00e5 overv\u00e5ke" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/pl.json b/homeassistant/components/ukraine_alarm/translations/pl.json new file mode 100644 index 00000000000..7d432900372 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/pl.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Lokalizacja jest ju\u017c skonfigurowana", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "max_regions": "Mo\u017cna skonfigurowa\u0107 maksymalnie 5 region\u00f3w", + "rate_limit": "Zbyt wiele \u017c\u0105da\u0144", + "timeout": "Limit czasu na nawi\u0105zanie po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "community": { + "data": { + "region": "Region" + }, + "description": "Je\u015bli nie chcesz monitorowa\u0107 tylko rejonu i dystryktu, wybierz jego konkretn\u0105 spo\u0142eczno\u015b\u0107" + }, + "district": { + "data": { + "region": "Region" + }, + "description": "Je\u015bli nie chcesz monitorowa\u0107 tylko rejonu, wybierz jego konkretny dystrykt" + }, + "user": { + "data": { + "region": "Region" + }, + "description": "Wybierz rejon do monitorowania" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/pt-BR.json b/homeassistant/components/ukraine_alarm/translations/pt-BR.json new file mode 100644 index 00000000000..64b371196e3 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/pt-BR.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "O local j\u00e1 est\u00e1 configurado", + "cannot_connect": "Falhou ao conectar", + "max_regions": "M\u00e1ximo de 5 regi\u00f5es podem ser configuradas", + "rate_limit": "Excesso de pedidos", + "timeout": "Tempo limite estabelecendo conex\u00e3o", + "unknown": "Erro inesperado" + }, + "step": { + "community": { + "data": { + "region": "Regi\u00e3o" + }, + "description": "Se voc\u00ea deseja monitorar n\u00e3o apenas estado e distrito, escolha sua comunidade espec\u00edfica" + }, + "district": { + "data": { + "region": "Regi\u00e3o" + }, + "description": "Se voc\u00ea deseja monitorar n\u00e3o apenas o estado, escolha seu distrito espec\u00edfico" + }, + "user": { + "data": { + "region": "Regi\u00e3o" + }, + "description": "Escolha o estado para monitorar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/ru.json b/homeassistant/components/ukraine_alarm/translations/ru.json index 89c9eb1670a..8944129497a 100644 --- a/homeassistant/components/ukraine_alarm/translations/ru.json +++ b/homeassistant/components/ukraine_alarm/translations/ru.json @@ -1,28 +1,32 @@ { "config": { + "abort": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0440\u0435\u0433\u0438\u043e\u043d\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "max_regions": "\u041c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c 5 \u0440\u0435\u0433\u0438\u043e\u043d\u043e\u0432.", + "rate_limit": "\u0421\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432.", + "timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, "step": { - "user": { - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f\u0020\u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438\u0020\u0441 Ukraine Alarm. \u0414\u043b\u044f\u0020\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f\u0020\u043a\u043b\u044e\u0447\u0430 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435\u0020\u043d\u0430 {api_url}.", - "title": "Ukraine Alarm" - }, - "state": { + "community": { "data": { - "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + "region": "\u0420\u0430\u0439\u043e\u043d" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435\u0020\u043e\u0431\u043b\u0430\u0441\u0442\u044c\u0020\u0434\u043b\u044f\u0020\u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430" + "description": "\u0415\u0441\u043b\u0438 \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e \u0438 \u0433\u0440\u043e\u043c\u0430\u0434\u043e\u0439, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0451 \u0440\u0430\u0439\u043e\u043d" }, "district": { "data": { - "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + "region": "\u0413\u0440\u043e\u043c\u0430\u0434\u0430" }, - "description": "\u0415\u0441\u043b\u0438\u0020\u0432\u044b\u0020\u0436\u0435\u043b\u0430\u0435\u0442\u0435\u0020\u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u0442\u044c\u0020\u043d\u0435\u0020\u0442\u043e\u043b\u044c\u043a\u043e\u0020\u043e\u0431\u043b\u0430\u0441\u0442\u044c\u002c\u0020\u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435\u0020\u0435\u0451\u0020\u0440\u0430\u0439\u043e\u043d" + "description": "\u0415\u0441\u043b\u0438 \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0451 \u0433\u0440\u043e\u043c\u0430\u0434\u0443" }, - "community": { + "user": { "data": { - "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + "region": "\u041e\u0431\u043b\u0430\u0441\u0442\u044c" }, - "description": "\u0415\u0441\u043b\u0438\u0020\u0432\u044b\u0020\u0436\u0435\u043b\u0430\u0435\u0442\u0435\u0020\u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u0442\u044c\u0020\u043d\u0435\u0020\u0442\u043e\u043b\u044c\u043a\u043e\u0020\u043e\u0431\u043b\u0430\u0441\u0442\u044c\u0020\u0438\u0020\u0440\u0430\u0439\u043e\u043d\u002c\u0020\u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435\u0020\u0435\u0451\u0020\u0433\u0440\u043e\u043c\u0430\u0434\u0443" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0434\u043b\u044f \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f" } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/tr.json b/homeassistant/components/ukraine_alarm/translations/tr.json new file mode 100644 index 00000000000..2b36f5b73f0 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/tr.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "max_regions": "En fazla 5 b\u00f6lge yap\u0131land\u0131r\u0131labilir", + "rate_limit": "\u00c7ok fazla istek", + "timeout": "Ba\u011flant\u0131 kurulurken zaman a\u015f\u0131m\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "community": { + "data": { + "region": "B\u00f6lge" + }, + "description": "Yaln\u0131zca eyalet ve il\u00e7eyi izlemek istemiyorsan\u0131z, belirli toplulu\u011funu se\u00e7in" + }, + "district": { + "data": { + "region": "B\u00f6lge" + }, + "description": "Sadece eyaleti izlemek istemiyorsan\u0131z, belirli bir b\u00f6lgeyi se\u00e7in" + }, + "user": { + "data": { + "region": "B\u00f6lge" + }, + "description": "\u0130zlenecek durumu se\u00e7in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/uk.json b/homeassistant/components/ukraine_alarm/translations/uk.json index 2eed983f34f..930f9ab31de 100644 --- a/homeassistant/components/ukraine_alarm/translations/uk.json +++ b/homeassistant/components/ukraine_alarm/translations/uk.json @@ -1,28 +1,32 @@ { "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u0440\u0435\u0433\u0456\u043e\u043d\u0443 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043e", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "max_regions": "\u041c\u043e\u0436\u043d\u0430 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c 5 \u0440\u0435\u0433\u0456\u043e\u043d\u0456\u0432", + "rate_limit": "\u0417\u0430\u0431\u0430\u0433\u0430\u0442\u043e \u0437\u0430\u043f\u0438\u0442\u0456\u0432", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0437\u2019\u0454\u0434\u043d\u0430\u043d\u043d\u044f", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, "step": { - "user": { - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f\u0020\u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457\u0020\u0437 Ukraine Alarm. \u0414\u043b\u044f\u0020\u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f\u0020\u043a\u043b\u044e\u0447\u0430 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c\u0020\u043d\u0430 {api_url}.", - "title": "Ukraine Alarm" - }, - "state": { + "community": { "data": { - "region": "\u0420\u0435\u0433\u0456\u043e\u043d" + "region": "\u0420\u0430\u0439\u043e\u043d" }, - "description": "\u041e\u0431\u0435\u0440\u0456\u0442\u044c\u0020\u043e\u0431\u043b\u0430\u0441\u0442\u044c\u0020\u0434\u043b\u044f\u0020\u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443" + "description": "\u042f\u043a\u0449\u043e \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u0442\u0438 \u043d\u0435 \u0442\u0456\u043b\u044c\u043a\u0438 \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0442\u0430 \u0433\u0440\u043e\u043c\u0430\u0434\u0443, \u043e\u0431\u0435\u0440\u0456\u0442\u044c \u0457\u0457 \u0440\u0430\u0439\u043e\u043d" }, "district": { "data": { - "region": "\u0420\u0435\u0433\u0456\u043e\u043d" + "region": "\u0413\u0440\u043e\u043c\u0430\u0434\u0430" }, - "description": "\u042f\u043a\u0449\u043e\u0020\u0432\u0438\u0020\u0431\u0430\u0436\u0430\u0454\u0442\u0435\u0020\u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u0442\u0438\u0020\u043d\u0435\u0020\u043b\u0438\u0448\u0435\u0020\u043e\u0431\u043b\u0430\u0441\u0442\u044c\u002c\u0020\u043e\u0431\u0435\u0440\u0456\u0442\u044c\u0020\u0457\u0457\u0020\u0440\u0430\u0439\u043e\u043d" + "description": "\u042f\u043a\u0449\u043e \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u0442\u0438 \u043d\u0435 \u0442\u0456\u043b\u044c\u043a\u0438 \u043e\u0431\u043b\u0430\u0441\u0442\u044c, \u043e\u0431\u0435\u0440\u0456\u0442\u044c \u0457\u0457 \u0433\u0440\u043e\u043c\u0430\u0434\u0443" }, - "community": { + "user": { "data": { - "region": "\u0420\u0435\u0433\u0456\u043e\u043d" + "region": "\u041e\u0431\u043b\u0430\u0441\u0442\u044c" }, - "description": "\u042f\u043a\u0449\u043e\u0020\u0432\u0438\u0020\u0431\u0430\u0436\u0430\u0454\u0442\u0435\u0020\u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u0442\u0438\u0020\u043d\u0435\u0020\u0442\u0456\u043b\u044c\u043a\u0438\u0020\u043e\u0431\u043b\u0430\u0441\u0442\u044c\u0020\u0442\u0430\u0020\u0440\u0430\u0439\u043e\u043d\u002c\u0020\u043e\u0431\u0435\u0440\u0456\u0442\u044c\u0020\u0457\u0457\u0020\u0433\u0440\u043e\u043c\u0430\u0434\u0443" + "description": "\u041e\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0434\u043b\u044f \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f" } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/zh-Hans.json b/homeassistant/components/ukraine_alarm/translations/zh-Hans.json new file mode 100644 index 00000000000..e4f7da73890 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/zh-Hans.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "max_regions": "\u6700\u591a\u53ef\u914d\u7f6e 5 \u4e2a\u533a\u57df", + "rate_limit": "\u8bf7\u6c42\u8fc7\u591a", + "timeout": "\u5efa\u7acb\u8fde\u63a5\u8d85\u65f6", + "unknown": "\u610f\u5916\u7684\u9519\u8bef" + }, + "step": { + "user": { + "data": { + "region": "\u5730\u533a" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ukraine_alarm/translations/zh-Hant.json b/homeassistant/components/ukraine_alarm/translations/zh-Hant.json new file mode 100644 index 00000000000..73b5ff2cce3 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/translations/zh-Hant.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u5ea7\u6a19\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "max_regions": "\u6700\u9ad8\u53ef\u8a2d\u5b9a 5 \u500b\u5340\u57df", + "rate_limit": "\u8acb\u6c42\u6b21\u6578\u904e\u591a", + "timeout": "\u5efa\u7acb\u9023\u7dda\u903e\u6642", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "community": { + "data": { + "region": "\u5340\u57df" + }, + "description": "\u5047\u5982\u4f60\u4e0d\u53ea\u8981\u76e3\u770b\u72c0\u614b\u8207\u5340\u57df\u3001\u8acb\u9078\u64c7\u7279\u5b9a\u793e\u5340" + }, + "district": { + "data": { + "region": "\u5340\u57df" + }, + "description": "\u5047\u5982\u4f60\u4e0d\u53ea\u8981\u76e3\u770b\u72c0\u614b\u3001\u8acb\u9078\u64c7\u7279\u5b9a\u5340\u57df" + }, + "user": { + "data": { + "region": "\u5340\u57df" + }, + "description": "\u9078\u64c7\u6240\u8981\u76e3\u8996\u7684\u72c0\u614b" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index a396f8ce38f..7a874aff993 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -4,6 +4,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType from .const import ( @@ -106,7 +107,7 @@ class UnifiWirelessClients: """Set up client storage.""" self.hass = hass self.data = {} - self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY) async def async_load(self): """Load data from file.""" diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index d02f3f49a5e..50e578b1dae 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -5,7 +5,11 @@ Discovery of UniFi Network instances hosted on UDM and UDM Pro devices through SSDP. Reauthentication when issue with credentials are reported. Configuration of options through options flow. """ +from __future__ import annotations + +from collections.abc import Mapping import socket +from typing import Any from urllib.parse import urlparse import voluptuous as vol @@ -19,7 +23,7 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import format_mac @@ -63,11 +67,13 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry): + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> UnifiOptionsFlowHandler: """Get the options flow for this handler.""" return UnifiOptionsFlowHandler(config_entry) - def __init__(self): + def __init__(self) -> None: """Initialize the UniFi Network flow.""" self.config = {} self.site_ids = {} @@ -75,7 +81,9 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): self.reauth_config_entry = None self.reauth_schema = {} - 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.""" errors = {} @@ -123,7 +131,7 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): return await self.async_step_site() - if not (host := self.config.get(CONF_HOST, "")) and await async_discover_unifi( + if not (host := self.config.get(CONF_HOST, "")) and await _async_discover_unifi( self.hass ): host = "unifi" @@ -144,7 +152,9 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): errors=errors, ) - async def async_step_site(self, user_input=None): + async def async_step_site( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Select site to control.""" errors = {} @@ -192,7 +202,7 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): errors=errors, ) - async def async_step_reauth(self, data: dict): + async def async_step_reauth(self, data: Mapping[str, Any]) -> FlowResult: """Trigger a reauthentication flow.""" config_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -248,13 +258,15 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): class UnifiOptionsFlowHandler(config_entries.OptionsFlow): """Handle Unifi Network options.""" - def __init__(self, config_entry): + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize UniFi Network options flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) self.controller = None - 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 UniFi Network options.""" if self.config_entry.entry_id not in self.hass.data[UNIFI_DOMAIN]: return self.async_abort(reason="integration_not_setup") @@ -266,7 +278,9 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): return await self.async_step_simple_options() - async def async_step_simple_options(self, user_input=None): + async def async_step_simple_options( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """For users without advanced settings enabled.""" if user_input is not None: self.options.update(user_input) @@ -299,7 +313,9 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): last_step=True, ) - async def async_step_device_tracker(self, user_input=None): + async def async_step_device_tracker( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Manage the device tracker options.""" if user_input is not None: self.options.update(user_input) @@ -359,7 +375,9 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): last_step=False, ) - async def async_step_client_control(self, user_input=None): + async def async_step_client_control( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Manage configuration of network access controlled clients.""" errors = {} @@ -403,7 +421,9 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): last_step=False, ) - async def async_step_statistics_sensors(self, user_input=None): + async def async_step_statistics_sensors( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Manage the statistics sensors options.""" if user_input is not None: self.options.update(user_input) @@ -426,12 +446,12 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): last_step=True, ) - async def _update_options(self): + async def _update_options(self) -> FlowResult: """Update config entry options.""" return self.async_create_entry(title="", data=self.options) -async def async_discover_unifi(hass): +async def _async_discover_unifi(hass: HomeAssistant) -> str | None: """Discover UniFi Network address.""" try: return await hass.async_add_executor_job(socket.gethostbyname, "unifi") diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index be59a25f69f..fa92568b477 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -74,7 +74,7 @@ from .switch import BLOCK_SWITCH, POE_SWITCH RETRY_TIMER = 15 CHECK_HEARTBEAT_INTERVAL = timedelta(seconds=1) -PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH] +PLATFORMS = [Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH, Platform.UPDATE] CLIENT_CONNECTED = ( WIRED_CLIENT_CONNECTED, diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 2ae8eb2e32b..b2070362d02 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -3,7 +3,7 @@ from datetime import timedelta import logging -from aiounifi.api import SOURCE_DATA +from aiounifi.api import SOURCE_DATA, SOURCE_EVENT from aiounifi.events import ( ACCESS_POINT_UPGRADED, GATEWAY_UPGRADED, @@ -156,6 +156,8 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): self._controller_connection_state_changed = False + self._only_listen_to_data_source = False + last_seen = client.last_seen or 0 self.schedule_update = self._is_connected = ( self.is_wired == client.is_wired @@ -224,6 +226,23 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): ): self._is_connected = True self.schedule_update = True + self._only_listen_to_data_source = True + + elif ( + self.client.last_updated == SOURCE_EVENT + and not self._only_listen_to_data_source + ): + + if (self.is_wired and self.client.event.event in WIRED_CONNECTION) or ( + not self.is_wired and self.client.event.event in WIRELESS_CONNECTION + ): + self._is_connected = True + self.schedule_update = False + self.controller.async_heartbeat(self.unique_id) + super().async_update_callback() + + else: + self.schedule_update = True self._async_log_debug_data("update_callback") @@ -429,6 +448,16 @@ class UniFiDeviceTracker(UniFiBase, ScannerEntity): return attributes + @property + def ip_address(self) -> str: + """Return the primary ip address of the device.""" + return self.device.ip + + @property + def mac_address(self) -> str: + """Return the mac address of the device.""" + return self.device.mac + async def options_updated(self) -> None: """Config entry options are updated, remove entity if option is disabled.""" if not self.controller.option_track_devices: diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 9151c81543a..67c4d5c4544 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -20,6 +20,7 @@ from homeassistant.components.switch import DOMAIN, SwitchDeviceClass, SwitchEnt from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_NAME from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.device_registry import ( CONNECTION_NETWORK_MAC, DeviceEntryType, @@ -27,7 +28,6 @@ from homeassistant.helpers.device_registry import ( from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.entity_registry import async_entries_for_config_entry from homeassistant.helpers.restore_state import RestoreEntity from .const import ATTR_MANUFACTURER, DOMAIN as UNIFI_DOMAIN @@ -65,8 +65,10 @@ async def async_setup_entry( # Store previously known POE control entities in case their POE are turned off. known_poe_clients = [] - entity_registry = await hass.helpers.entity_registry.async_get_registry() - for entry in async_entries_for_config_entry(entity_registry, config_entry.entry_id): + entity_registry = er.async_get(hass) + for entry in er.async_entries_for_config_entry( + entity_registry, config_entry.entry_id + ): if not entry.unique_id.startswith(POE_SWITCH): continue diff --git a/homeassistant/components/unifi/translations/bg.json b/homeassistant/components/unifi/translations/bg.json index afe2db0cc5b..c1bf205abfe 100644 --- a/homeassistant/components/unifi/translations/bg.json +++ b/homeassistant/components/unifi/translations/bg.json @@ -22,6 +22,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u043d\u0435 \u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430" + }, "step": { "device_tracker": { "data": { diff --git a/homeassistant/components/unifi/translations/ca.json b/homeassistant/components/unifi/translations/ca.json index 4bb01d82ef6..74129c3c2af 100644 --- a/homeassistant/components/unifi/translations/ca.json +++ b/homeassistant/components/unifi/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El lloc web d'UniFi Network ja est\u00e0 configurat", - "configuration_updated": "S'ha actualitzat la configuraci\u00f3.", + "configuration_updated": "S'ha actualitzat la configuraci\u00f3", "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "La integraci\u00f3 UniFi no est\u00e0 configurada" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/de.json b/homeassistant/components/unifi/translations/de.json index ce4047ced42..419bb3e8eba 100644 --- a/homeassistant/components/unifi/translations/de.json +++ b/homeassistant/components/unifi/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "UniFi-Netzwerkstandort ist bereits konfiguriert", - "configuration_updated": "Konfiguration aktualisiert.", + "configuration_updated": "Konfiguration aktualisiert", "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi-Integration ist nicht eingerichtet" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/el.json b/homeassistant/components/unifi/translations/el.json index b017910c279..6c150fe8133 100644 --- a/homeassistant/components/unifi/translations/el.json +++ b/homeassistant/components/unifi/translations/el.json @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 UniFi \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/en.json b/homeassistant/components/unifi/translations/en.json index 2a1c17cb6e3..6e36e517e16 100644 --- a/homeassistant/components/unifi/translations/en.json +++ b/homeassistant/components/unifi/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "UniFi Network site is already configured", - "configuration_updated": "Configuration updated.", + "configuration_updated": "Configuration updated", "reauth_successful": "Re-authentication was successful" }, "error": { diff --git a/homeassistant/components/unifi/translations/es.json b/homeassistant/components/unifi/translations/es.json index a5963d7019e..ebaf1d3c127 100644 --- a/homeassistant/components/unifi/translations/es.json +++ b/homeassistant/components/unifi/translations/es.json @@ -21,11 +21,14 @@ "username": "Usuario", "verify_ssl": "Controlador usando el certificado adecuado" }, - "title": "Configurar el controlador UniFi" + "title": "Configuraci\u00f3n de UniFi Network" } } }, "options": { + "abort": { + "integration_not_setup": "La integraci\u00f3n UniFi no est\u00e1 configurada" + }, "step": { "client_control": { "data": { @@ -39,7 +42,7 @@ "device_tracker": { "data": { "detection_time": "Tiempo en segundos desde la \u00faltima vez que se vio hasta considerarlo desconectado", - "ignore_wired_bug": "Desactivar la l\u00f3gica para el bug cableado de UniFi", + "ignore_wired_bug": "Desactiva la l\u00f3gica de errores de UniFi Network", "ssid_filter": "Seleccione los SSIDs para realizar seguimiento de clientes inal\u00e1mbricos", "track_clients": "Seguimiento de los clientes de red", "track_devices": "Rastree dispositivos de red (dispositivos Ubiquiti)", @@ -54,7 +57,7 @@ "track_clients": "Rastree clientes de red", "track_devices": "Rastree dispositivos de red (dispositivos Ubiquiti)" }, - "description": "Configurar la integraci\u00f3n de UniFi" + "description": "Configura la integraci\u00f3n UniFi Network" }, "statistics_sensors": { "data": { @@ -62,7 +65,7 @@ "allow_uptime_sensors": "Sensores de tiempo de actividad para clientes de la red" }, "description": "Configurar estad\u00edsticas de los sensores", - "title": "Opciones UniFi 3/3" + "title": "Opciones de UniFi Network 3/3" } } } diff --git a/homeassistant/components/unifi/translations/et.json b/homeassistant/components/unifi/translations/et.json index 76d93c95d97..6db9276562e 100644 --- a/homeassistant/components/unifi/translations/et.json +++ b/homeassistant/components/unifi/translations/et.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "UniFi Network on juba seadistatud", - "configuration_updated": "Seaded on v\u00e4rskendatud.", + "configuration_updated": "Seaded on v\u00e4rskendatud", "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi sidumine pole seadistatud" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/fr.json b/homeassistant/components/unifi/translations/fr.json index d8f1bc0a84c..3da202d1cec 100644 --- a/homeassistant/components/unifi/translations/fr.json +++ b/homeassistant/components/unifi/translations/fr.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Le contr\u00f4leur est d\u00e9j\u00e0 configur\u00e9", - "configuration_updated": "Configuration mise \u00e0 jour.", + "configuration_updated": "Configuration mise \u00e0 jour", "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "L'int\u00e9gration UniFi n'est pas configur\u00e9e" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/hu.json b/homeassistant/components/unifi/translations/hu.json index ea4c7484d44..be817737b5a 100644 --- a/homeassistant/components/unifi/translations/hu.json +++ b/homeassistant/components/unifi/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az UniFi Network webhely m\u00e1r konfigur\u00e1lva van", - "configuration_updated": "A konfigur\u00e1ci\u00f3 friss\u00edtve.", + "configuration_updated": "Konfigur\u00e1ci\u00f3 friss\u00edtve", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "Az UniFi integr\u00e1ci\u00f3 nincs be\u00e1ll\u00edtva" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/id.json b/homeassistant/components/unifi/translations/id.json index 4c78c96155c..f3618a31c02 100644 --- a/homeassistant/components/unifi/translations/id.json +++ b/homeassistant/components/unifi/translations/id.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Situs Jaringan UniFi sudah dikonfigurasi", - "configuration_updated": "Konfigurasi diperbarui.", + "configuration_updated": "Konfigurasi diperbarui", "reauth_successful": "Autentikasi ulang berhasil" }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "Integrasi UniFi tidak disiapkan" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/it.json b/homeassistant/components/unifi/translations/it.json index 7ddc0fde4fe..1ac0ff05798 100644 --- a/homeassistant/components/unifi/translations/it.json +++ b/homeassistant/components/unifi/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Il sito della rete UniFi \u00e8 gi\u00e0 configurato", - "configuration_updated": "Configurazione aggiornata.", + "configuration_updated": "Configurazione aggiornata", "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "L'integrazione UniFi non \u00e8 configurata" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/ja.json b/homeassistant/components/unifi/translations/ja.json index 1f77f15d519..fba092385c1 100644 --- a/homeassistant/components/unifi/translations/ja.json +++ b/homeassistant/components/unifi/translations/ja.json @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u304c\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3055\u308c\u3066\u3044\u307e\u305b\u3093" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/ko.json b/homeassistant/components/unifi/translations/ko.json index 3a13b420097..30d7559e4f3 100644 --- a/homeassistant/components/unifi/translations/ko.json +++ b/homeassistant/components/unifi/translations/ko.json @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi \ud1b5\ud569\uad6c\uc131\uc694\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4." + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/nl.json b/homeassistant/components/unifi/translations/nl.json index 5cadc27117c..6e9b0ec7ede 100644 --- a/homeassistant/components/unifi/translations/nl.json +++ b/homeassistant/components/unifi/translations/nl.json @@ -2,8 +2,8 @@ "config": { "abort": { "already_configured": "Unifi Network site is al geconfigureerd", - "configuration_updated": "Configuratie bijgewerkt.", - "reauth_successful": "Herauthenticatie was succesvol" + "configuration_updated": "Configuratie bijgewerkt", + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "faulty_credentials": "Ongeldige authenticatie", @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi-integratie is niet ingesteld" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/no.json b/homeassistant/components/unifi/translations/no.json index ec43a8f295b..2253bbd5023 100644 --- a/homeassistant/components/unifi/translations/no.json +++ b/homeassistant/components/unifi/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "UniFi Network-nettstedet er allerede konfigurert", - "configuration_updated": "Konfigurasjonen er oppdatert.", + "configuration_updated": "Konfigurasjonen er oppdatert", "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi-integrasjon er ikke konfigurert" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/pl.json b/homeassistant/components/unifi/translations/pl.json index a52660ac262..40bbc9e47a4 100644 --- a/homeassistant/components/unifi/translations/pl.json +++ b/homeassistant/components/unifi/translations/pl.json @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "Integracja UniFi nie jest skonfigurowana" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/pt-BR.json b/homeassistant/components/unifi/translations/pt-BR.json index 0e5ba9af217..00dd0b08165 100644 --- a/homeassistant/components/unifi/translations/pt-BR.json +++ b/homeassistant/components/unifi/translations/pt-BR.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "A conta j\u00e1 foi configurada", - "configuration_updated": "Configura\u00e7\u00e3o atualizada.", + "configuration_updated": "Configura\u00e7\u00e3o atualizada", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "A integra\u00e7\u00e3o UniFi n\u00e3o est\u00e1 configurada" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/ru.json b/homeassistant/components/unifi/translations/ru.json index 4d9726ab751..68247ef09e1 100644 --- a/homeassistant/components/unifi/translations/ru.json +++ b/homeassistant/components/unifi/translations/ru.json @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f UniFi \u043d\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430." + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/sv.json b/homeassistant/components/unifi/translations/sv.json index 2e4851e70ed..a84558a6b58 100644 --- a/homeassistant/components/unifi/translations/sv.json +++ b/homeassistant/components/unifi/translations/sv.json @@ -23,6 +23,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi-integration \u00e4r inte konfigurerad" + }, "step": { "client_control": { "title": "UniFi-inst\u00e4llningar 2/3" diff --git a/homeassistant/components/unifi/translations/tr.json b/homeassistant/components/unifi/translations/tr.json index 9b53e712fa5..9e9ff5034d8 100644 --- a/homeassistant/components/unifi/translations/tr.json +++ b/homeassistant/components/unifi/translations/tr.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "UniFi Network sitesi zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", - "configuration_updated": "Yap\u0131land\u0131rma g\u00fcncellendi.", + "configuration_updated": "Yap\u0131land\u0131rma g\u00fcncellendi", "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi entegrasyonu kurulmad\u0131" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/zh-Hant.json b/homeassistant/components/unifi/translations/zh-Hant.json index 1b394bae317..c6cb4724697 100644 --- a/homeassistant/components/unifi/translations/zh-Hant.json +++ b/homeassistant/components/unifi/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "UniFi \u7db2\u8def\u5df2\u7d93\u8a2d\u5b9a", - "configuration_updated": "\u8a2d\u5b9a\u5df2\u66f4\u65b0\u3002", + "configuration_updated": "\u8a2d\u5b9a\u5df2\u66f4\u65b0", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi \u6574\u5408\u5c1a\u672a\u8a2d\u5b9a" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/update.py b/homeassistant/components/unifi/update.py new file mode 100644 index 00000000000..09720f15f84 --- /dev/null +++ b/homeassistant/components/unifi/update.py @@ -0,0 +1,128 @@ +"""Update entities for Ubiquiti network devices.""" +from __future__ import annotations + +import logging + +from homeassistant.components.update import ( + DOMAIN, + UpdateDeviceClass, + UpdateEntity, + UpdateEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_NAME +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ATTR_MANUFACTURER, DOMAIN as UNIFI_DOMAIN +from .unifi_entity_base import UniFiBase + +LOGGER = logging.getLogger(__name__) + +DEVICE_UPDATE = "device_update" + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up update entities for UniFi Network integration.""" + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + controller.entities[DOMAIN] = {DEVICE_UPDATE: set()} + + @callback + def items_added( + clients: set = controller.api.clients, devices: set = controller.api.devices + ) -> None: + """Add device update entities.""" + add_device_update_entities(controller, async_add_entities, devices) + + for signal in (controller.signal_update, controller.signal_options_update): + config_entry.async_on_unload( + async_dispatcher_connect(hass, signal, items_added) + ) + + items_added() + + +@callback +def add_device_update_entities(controller, async_add_entities, devices): + """Add new device update entities from the controller.""" + entities = [] + + for mac in devices: + if mac in controller.entities[DOMAIN][UniFiDeviceUpdateEntity.TYPE]: + continue + + device = controller.api.devices[mac] + entities.append(UniFiDeviceUpdateEntity(device, controller)) + + if entities: + async_add_entities(entities) + + +class UniFiDeviceUpdateEntity(UniFiBase, UpdateEntity): + """Update entity for a UniFi network infrastructure device.""" + + DOMAIN = DOMAIN + TYPE = DEVICE_UPDATE + _attr_device_class = UpdateDeviceClass.FIRMWARE + _attr_supported_features = UpdateEntityFeature.PROGRESS + + def __init__(self, device, controller): + """Set up device update entity.""" + super().__init__(device, controller) + + self.device = self._item + + @property + def name(self) -> str: + """Return the name of the device.""" + return self.device.name or self.device.model + + @property + def unique_id(self) -> str: + """Return a unique identifier for this device.""" + return f"{self.TYPE}-{self.device.mac}" + + @property + def available(self) -> bool: + """Return if controller is available.""" + return not self.device.disabled and self.controller.available + + @property + def in_progress(self) -> bool: + """Update installation in progress.""" + return self.device.state == 4 + + @property + def installed_version(self) -> str | None: + """Version currently in use.""" + return self.device.version + + @property + def latest_version(self) -> str | None: + """Latest version available for install.""" + return self.device.upgrade_to_firmware or self.device.version + + @property + def device_info(self) -> DeviceInfo: + """Return a device description for device registry.""" + info = DeviceInfo( + connections={(CONNECTION_NETWORK_MAC, self.device.mac)}, + manufacturer=ATTR_MANUFACTURER, + model=self.device.model, + sw_version=self.device.version, + ) + + if self.device.name: + info[ATTR_NAME] = self.device.name + + return info + + async def options_updated(self) -> None: + """No action needed.""" diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index 97fdc6eac20..c28f2639e00 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -17,9 +17,11 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP, + Platform, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.aiohttp_client import async_create_clientsession from .const import ( @@ -27,6 +29,7 @@ from .const import ( CONF_OVERRIDE_CHOST, DEFAULT_SCAN_INTERVAL, DEVICES_FOR_SUBSCRIBE, + DEVICES_THAT_ADOPT, DOMAIN, MIN_REQUIRED_PROTECT_V, OUTDATED_LOG_MESSAGE, @@ -41,6 +44,60 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=DEFAULT_SCAN_INTERVAL) +async def _async_migrate_data( + hass: HomeAssistant, entry: ConfigEntry, protect: ProtectApiClient +) -> None: + + registry = er.async_get(hass) + to_migrate = [] + for entity in er.async_entries_for_config_entry(registry, entry.entry_id): + if entity.domain == Platform.BUTTON and "_" not in entity.unique_id: + _LOGGER.debug("Button %s needs migration", entity.entity_id) + to_migrate.append(entity) + + if len(to_migrate) == 0: + _LOGGER.debug("No entities need migration") + return + + _LOGGER.info("Migrating %s reboot button entities ", len(to_migrate)) + bootstrap = await protect.get_bootstrap() + count = 0 + for button in to_migrate: + device = None + for model in DEVICES_THAT_ADOPT: + attr = f"{model.value}s" + device = getattr(bootstrap, attr).get(button.unique_id) + if device is not None: + break + + if device is None: + continue + + new_unique_id = f"{device.id}_reboot" + _LOGGER.debug( + "Migrating entity %s (old unique_id: %s, new unique_id: %s)", + button.entity_id, + button.unique_id, + new_unique_id, + ) + try: + registry.async_update_entity(button.entity_id, new_unique_id=new_unique_id) + except ValueError: + _LOGGER.warning( + "Could not migrate entity %s (old unique_id: %s, new unique_id: %s)", + button.entity_id, + button.unique_id, + new_unique_id, + ) + else: + count += 1 + + if count < len(to_migrate): + _LOGGER.warning("Failed to migate %s reboot buttons", len(to_migrate) - count) + else: + _LOGGER.info("Migrated %s reboot button entities", count) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the UniFi Protect config entries.""" @@ -75,6 +132,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) return False + await _async_migrate_data(hass, entry, protect) if entry.unique_id is None: hass.config_entries.async_update_entry(entry, unique_id=nvr_info.mac) diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index 3940c85d21a..3728b6b4224 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -1,20 +1,63 @@ """Support for Ubiquiti's UniFi Protect NVR.""" from __future__ import annotations -import logging +from dataclasses import dataclass +from typing import Final -from pyunifiprotect.data.base import ProtectAdoptableDeviceModel +from pyunifiprotect.data import ProtectAdoptableDeviceModel -from homeassistant.components.button import ButtonDeviceClass, ButtonEntity +from homeassistant.components.button import ( + ButtonDeviceClass, + ButtonEntity, + ButtonEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DEVICES_THAT_ADOPT, DOMAIN +from .const import DOMAIN from .data import ProtectData -from .entity import ProtectDeviceEntity +from .entity import ProtectDeviceEntity, async_all_device_entities +from .models import ProtectSetableKeysMixin, T -_LOGGER = logging.getLogger(__name__) + +@dataclass +class ProtectButtonEntityDescription( + ProtectSetableKeysMixin[T], ButtonEntityDescription +): + """Describes UniFi Protect Button entity.""" + + ufp_press: str | None = None + + +DEVICE_CLASS_CHIME_BUTTON: Final = "unifiprotect__chime_button" + + +ALL_DEVICE_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = ( + ProtectButtonEntityDescription( + key="reboot", + entity_registry_enabled_default=False, + device_class=ButtonDeviceClass.RESTART, + name="Reboot Device", + ufp_press="reboot", + ), +) + +CHIME_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = ( + ProtectButtonEntityDescription( + key="play", + name="Play Chime", + device_class=DEVICE_CLASS_CHIME_BUTTON, + icon="mdi:play", + ufp_press="play", + ), + ProtectButtonEntityDescription( + key="play_buzzer", + name="Play Buzzer", + icon="mdi:play", + ufp_press="play_buzzer", + ), +) async def async_setup_entry( @@ -25,34 +68,30 @@ async def async_setup_entry( """Discover devices on a UniFi Protect NVR.""" data: ProtectData = hass.data[DOMAIN][entry.entry_id] - async_add_entities( - [ - ProtectButton( - data, - device, - ) - for device in data.get_by_types(DEVICES_THAT_ADOPT) - ] + entities: list[ProtectDeviceEntity] = async_all_device_entities( + data, ProtectButton, all_descs=ALL_DEVICE_BUTTONS, chime_descs=CHIME_BUTTONS ) + async_add_entities(entities) + class ProtectButton(ProtectDeviceEntity, ButtonEntity): """A Ubiquiti UniFi Protect Reboot button.""" - _attr_entity_registry_enabled_default = False - _attr_device_class = ButtonDeviceClass.RESTART + entity_description: ProtectButtonEntityDescription def __init__( self, data: ProtectData, device: ProtectAdoptableDeviceModel, + description: ProtectButtonEntityDescription, ) -> None: """Initialize an UniFi camera.""" - super().__init__(data, device) - self._attr_name = f"{self.device.name} Reboot Device" + super().__init__(data, device, description) + self._attr_name = f"{self.device.name} {self.entity_description.name}" async def async_press(self) -> None: """Press the button.""" - _LOGGER.debug("Rebooting %s with id %s", self.device.model, self.device.id) - await self.device.reboot() + if self.entity_description.ufp_press is not None: + await getattr(self.device, self.entity_description.ufp_press)() diff --git a/homeassistant/components/unifiprotect/camera.py b/homeassistant/components/unifiprotect/camera.py index 928d82f317b..ca076e490a2 100644 --- a/homeassistant/components/unifiprotect/camera.py +++ b/homeassistant/components/unifiprotect/camera.py @@ -5,8 +5,7 @@ from collections.abc import Generator import logging from pyunifiprotect.api import ProtectApiClient -from pyunifiprotect.data import Camera as UFPCamera, StateType -from pyunifiprotect.data.devices import CameraChannel +from pyunifiprotect.data import Camera as UFPCamera, CameraChannel, StateType from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index 27108d24eaa..daaae214df9 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -6,7 +6,7 @@ from typing import Any from aiohttp import CookieJar from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient -from pyunifiprotect.data.nvr import NVR +from pyunifiprotect.data import NVR import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/unifiprotect/const.py b/homeassistant/components/unifiprotect/const.py index 7d2842977b6..3ba22e6b85b 100644 --- a/homeassistant/components/unifiprotect/const.py +++ b/homeassistant/components/unifiprotect/const.py @@ -1,6 +1,6 @@ """Constant definitions for UniFi Protect Integration.""" -from pyunifiprotect.data.types import ModelType, Version +from pyunifiprotect.data import ModelType, Version from homeassistant.const import Platform @@ -38,6 +38,7 @@ DEVICES_THAT_ADOPT = { ModelType.VIEWPORT, ModelType.SENSOR, ModelType.DOORLOCK, + ModelType.CHIME, } DEVICES_WITH_ENTITIES = DEVICES_THAT_ADOPT | {ModelType.NVR} DEVICES_FOR_SUBSCRIBE = DEVICES_WITH_ENTITIES | {ModelType.EVENT} diff --git a/homeassistant/components/unifiprotect/diagnostics.py b/homeassistant/components/unifiprotect/diagnostics.py new file mode 100644 index 00000000000..b76c9eba1e7 --- /dev/null +++ b/homeassistant/components/unifiprotect/diagnostics.py @@ -0,0 +1,21 @@ +"""Diagnostics support for UniFi Network.""" +from __future__ import annotations + +from typing import Any, cast + +from pyunifiprotect.test_util.anonymize import anonymize_data + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .data import ProtectData + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + + data: ProtectData = hass.data[DOMAIN][config_entry.entry_id] + return cast(dict[str, Any], anonymize_data(data.api.bootstrap.unifi_dict())) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 045a9c4fd6d..f8ceaeec9e6 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -6,7 +6,9 @@ import logging from typing import Any from pyunifiprotect.data import ( + NVR, Camera, + Chime, Doorlock, Event, Light, @@ -16,7 +18,6 @@ from pyunifiprotect.data import ( StateType, Viewer, ) -from pyunifiprotect.data.nvr import NVR from homeassistant.core import callback import homeassistant.helpers.device_registry as dr @@ -42,7 +43,7 @@ def _async_device_entities( entities: list[ProtectDeviceEntity] = [] for device in data.get_by_types({model_type}): - assert isinstance(device, (Camera, Light, Sensor, Viewer, Doorlock)) + assert isinstance(device, (Camera, Light, Sensor, Viewer, Doorlock, Chime)) for description in descs: if description.ufp_required_field: required_field = get_nested_attr(device, description.ufp_required_field) @@ -75,6 +76,7 @@ def async_all_device_entities( sense_descs: Sequence[ProtectRequiredKeysMixin] | None = None, viewer_descs: Sequence[ProtectRequiredKeysMixin] | None = None, lock_descs: Sequence[ProtectRequiredKeysMixin] | None = None, + chime_descs: Sequence[ProtectRequiredKeysMixin] | None = None, all_descs: Sequence[ProtectRequiredKeysMixin] | None = None, ) -> list[ProtectDeviceEntity]: """Generate a list of all the device entities.""" @@ -84,6 +86,7 @@ def async_all_device_entities( sense_descs = list(sense_descs or []) + all_descs viewer_descs = list(viewer_descs or []) + all_descs lock_descs = list(lock_descs or []) + all_descs + chime_descs = list(chime_descs or []) + all_descs return ( _async_device_entities(data, klass, ModelType.CAMERA, camera_descs) @@ -91,6 +94,7 @@ def async_all_device_entities( + _async_device_entities(data, klass, ModelType.SENSOR, sense_descs) + _async_device_entities(data, klass, ModelType.VIEWPORT, viewer_descs) + _async_device_entities(data, klass, ModelType.DOORLOCK, lock_descs) + + _async_device_entities(data, klass, ModelType.CHIME, chime_descs) ) diff --git a/homeassistant/components/unifiprotect/lock.py b/homeassistant/components/unifiprotect/lock.py index 6fb70a2523f..c4d56dd1e71 100644 --- a/homeassistant/components/unifiprotect/lock.py +++ b/homeassistant/components/unifiprotect/lock.py @@ -4,8 +4,7 @@ from __future__ import annotations import logging from typing import Any -from pyunifiprotect.data import Doorlock -from pyunifiprotect.data.types import LockStatusType +from pyunifiprotect.data import Doorlock, LockStatusType from homeassistant.components.lock import LockEntity, LockEntityDescription from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 3efad51db31..898abd73a6f 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -3,7 +3,7 @@ "name": "UniFi Protect", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/unifiprotect", - "requirements": ["pyunifiprotect==3.5.1", "unifi-discovery==1.1.2"], + "requirements": ["pyunifiprotect==3.6.0", "unifi-discovery==1.1.3"], "dependencies": ["http"], "codeowners": ["@briis", "@AngellusMortis", "@bdraco"], "quality_scale": "platinum", diff --git a/homeassistant/components/unifiprotect/media_player.py b/homeassistant/components/unifiprotect/media_player.py index 0b7c2a2f60d..1acd14be130 100644 --- a/homeassistant/components/unifiprotect/media_player.py +++ b/homeassistant/components/unifiprotect/media_player.py @@ -118,7 +118,9 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity): """Play a piece of media.""" if media_source.is_media_source_id(media_id): media_type = MEDIA_TYPE_MUSIC - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = async_process_play_media_url(self.hass, play_item.url) if media_type != MEDIA_TYPE_MUSIC: diff --git a/homeassistant/components/unifiprotect/number.py b/homeassistant/components/unifiprotect/number.py index d6dfba0c38f..4ebdd17f5c9 100644 --- a/homeassistant/components/unifiprotect/number.py +++ b/homeassistant/components/unifiprotect/number.py @@ -4,7 +4,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import timedelta -from pyunifiprotect.data.devices import Camera, Doorlock, Light +from pyunifiprotect.data import Camera, Doorlock, Light from homeassistant.components.number import NumberEntity, NumberEntityDescription from homeassistant.config_entries import ConfigEntry @@ -149,6 +149,20 @@ DOORLOCK_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = ( ), ) +CHIME_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = ( + ProtectNumberEntityDescription( + key="volume", + name="Volume", + icon="mdi:speaker", + entity_category=EntityCategory.CONFIG, + ufp_min=0, + ufp_max=100, + ufp_step=1, + ufp_value="volume", + ufp_set_method="set_volume", + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -164,6 +178,7 @@ async def async_setup_entry( light_descs=LIGHT_NUMBERS, sense_descs=SENSE_NUMBERS, lock_descs=DOORLOCK_NUMBERS, + chime_descs=CHIME_NUMBERS, ) async_add_entities(entities) diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index f0500ea54e5..2c6c5fa4cc6 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -11,17 +11,18 @@ from typing import Any, Final from pyunifiprotect.api import ProtectApiClient from pyunifiprotect.data import ( Camera, + ChimeType, DoorbellMessageType, Doorlock, IRLEDMode, Light, LightModeEnableType, LightModeType, + MountType, RecordingMode, Sensor, Viewer, ) -from pyunifiprotect.data.types import ChimeType, MountType import voluptuous as vol from homeassistant.components.select import SelectEntity, SelectEntityDescription diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index c3b49fbdf9d..c30cc7fb80f 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -450,6 +450,16 @@ MOTION_TRIP_SENSORS: tuple[ProtectSensorEntityDescription, ...] = ( ), ) +CHIME_SENSORS: tuple[ProtectSensorEntityDescription, ...] = ( + ProtectSensorEntityDescription( + key="last_ring", + name="Last Ring", + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:bell", + ufp_value="last_ring", + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -466,6 +476,7 @@ async def async_setup_entry( sense_descs=SENSE_SENSORS, light_descs=LIGHT_SENSORS, lock_descs=DOORLOCK_SENSORS, + chime_descs=CHIME_SENSORS, ) entities += _async_motion_entities(data) entities += _async_nvr_entities(data) diff --git a/homeassistant/components/unifiprotect/services.py b/homeassistant/components/unifiprotect/services.py index a149f516d2f..f8aa446f857 100644 --- a/homeassistant/components/unifiprotect/services.py +++ b/homeassistant/components/unifiprotect/services.py @@ -10,25 +10,32 @@ from pyunifiprotect.api import ProtectApiClient from pyunifiprotect.exceptions import BadRequest import voluptuous as vol +from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import ATTR_DEVICE_ID +from homeassistant.const import ATTR_DEVICE_ID, Platform from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv, device_registry as dr +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + entity_registry as er, +) from homeassistant.helpers.service import async_extract_referenced_entity_ids +from homeassistant.util.read_only_dict import ReadOnlyDict from .const import ATTR_MESSAGE, DOMAIN from .data import ProtectData -from .utils import _async_unifi_mac_from_hass SERVICE_ADD_DOORBELL_TEXT = "add_doorbell_text" SERVICE_REMOVE_DOORBELL_TEXT = "remove_doorbell_text" SERVICE_SET_DEFAULT_DOORBELL_TEXT = "set_default_doorbell_text" +SERVICE_SET_CHIME_PAIRED = "set_chime_paired_doorbells" ALL_GLOBAL_SERIVCES = [ SERVICE_ADD_DOORBELL_TEXT, SERVICE_REMOVE_DOORBELL_TEXT, SERVICE_SET_DEFAULT_DOORBELL_TEXT, + SERVICE_SET_CHIME_PAIRED, ] DOORBELL_TEXT_SCHEMA = vol.All( @@ -41,70 +48,68 @@ DOORBELL_TEXT_SCHEMA = vol.All( cv.has_at_least_one_key(ATTR_DEVICE_ID), ) +CHIME_PAIRED_SCHEMA = vol.All( + vol.Schema( + { + **cv.ENTITY_SERVICE_FIELDS, + "doorbells": cv.TARGET_SERVICE_FIELDS, + }, + ), + cv.has_at_least_one_key(ATTR_DEVICE_ID), +) -def _async_all_ufp_instances(hass: HomeAssistant) -> list[ProtectApiClient]: - """All active UFP instances.""" - return [ - data.api for data in hass.data[DOMAIN].values() if isinstance(data, ProtectData) - ] + +def _async_ufp_instance_for_config_entry_ids( + hass: HomeAssistant, config_entry_ids: set[str] +) -> ProtectApiClient | None: + """Find the UFP instance for the config entry ids.""" + domain_data = hass.data[DOMAIN] + for config_entry_id in config_entry_ids: + if config_entry_id in domain_data: + protect_data: ProtectData = domain_data[config_entry_id] + return protect_data.api + return None @callback -def _async_get_macs_for_device(device_entry: dr.DeviceEntry) -> list[str]: - return [ - _async_unifi_mac_from_hass(cval) - for ctype, cval in device_entry.connections - if ctype == dr.CONNECTION_NETWORK_MAC - ] - - -@callback -def _async_get_ufp_instances( - hass: HomeAssistant, device_id: str -) -> tuple[dr.DeviceEntry, ProtectApiClient]: +def _async_get_ufp_instance(hass: HomeAssistant, device_id: str) -> ProtectApiClient: device_registry = dr.async_get(hass) if not (device_entry := device_registry.async_get(device_id)): raise HomeAssistantError(f"No device found for device id: {device_id}") if device_entry.via_device_id is not None: - return _async_get_ufp_instances(hass, device_entry.via_device_id) + return _async_get_ufp_instance(hass, device_entry.via_device_id) - macs = _async_get_macs_for_device(device_entry) - ufp_instances = [ - i for i in _async_all_ufp_instances(hass) if i.bootstrap.nvr.mac in macs - ] + config_entry_ids = device_entry.config_entries + if ufp_instance := _async_ufp_instance_for_config_entry_ids(hass, config_entry_ids): + return ufp_instance - if not ufp_instances: - # should not be possible unless user manually enters a bad device ID - raise HomeAssistantError( # pragma: no cover - f"No UniFi Protect NVR found for device ID: {device_id}" - ) - - return device_entry, ufp_instances[0] + raise HomeAssistantError(f"No device found for device id: {device_id}") @callback def _async_get_protect_from_call( hass: HomeAssistant, call: ServiceCall -) -> list[tuple[dr.DeviceEntry, ProtectApiClient]]: - referenced = async_extract_referenced_entity_ids(hass, call) - - instances: list[tuple[dr.DeviceEntry, ProtectApiClient]] = [] - for device_id in referenced.referenced_devices: - instances.append(_async_get_ufp_instances(hass, device_id)) - - return instances +) -> set[ProtectApiClient]: + return { + _async_get_ufp_instance(hass, device_id) + for device_id in async_extract_referenced_entity_ids( + hass, call + ).referenced_devices + } -async def _async_call_nvr( - instances: list[tuple[dr.DeviceEntry, ProtectApiClient]], +async def _async_service_call_nvr( + hass: HomeAssistant, + call: ServiceCall, method: str, *args: Any, **kwargs: Any, ) -> None: + instances = _async_get_protect_from_call(hass, call) try: await asyncio.gather( - *(getattr(i.bootstrap.nvr, method)(*args, **kwargs) for _, i in instances) + *(getattr(i.bootstrap.nvr, method)(*args, **kwargs) for i in instances) ) except (BadRequest, ValidationError) as err: raise HomeAssistantError(str(err)) from err @@ -113,22 +118,61 @@ async def _async_call_nvr( async def add_doorbell_text(hass: HomeAssistant, call: ServiceCall) -> None: """Add a custom doorbell text message.""" message: str = call.data[ATTR_MESSAGE] - instances = _async_get_protect_from_call(hass, call) - await _async_call_nvr(instances, "add_custom_doorbell_message", message) + await _async_service_call_nvr(hass, call, "add_custom_doorbell_message", message) async def remove_doorbell_text(hass: HomeAssistant, call: ServiceCall) -> None: """Remove a custom doorbell text message.""" message: str = call.data[ATTR_MESSAGE] - instances = _async_get_protect_from_call(hass, call) - await _async_call_nvr(instances, "remove_custom_doorbell_message", message) + await _async_service_call_nvr(hass, call, "remove_custom_doorbell_message", message) async def set_default_doorbell_text(hass: HomeAssistant, call: ServiceCall) -> None: """Set the default doorbell text message.""" message: str = call.data[ATTR_MESSAGE] - instances = _async_get_protect_from_call(hass, call) - await _async_call_nvr(instances, "set_default_doorbell_message", message) + await _async_service_call_nvr(hass, call, "set_default_doorbell_message", message) + + +@callback +def _async_unique_id_to_ufp_device_id(unique_id: str) -> str: + """Extract the UFP device id from the registry entry unique id.""" + return unique_id.split("_")[0] + + +async def set_chime_paired_doorbells(hass: HomeAssistant, call: ServiceCall) -> None: + """Set paired doorbells on chime.""" + ref = async_extract_referenced_entity_ids(hass, call) + entity_registry = er.async_get(hass) + + entity_id = ref.indirectly_referenced.pop() + chime_button = entity_registry.async_get(entity_id) + assert chime_button is not None + assert chime_button.device_id is not None + chime_ufp_device_id = _async_unique_id_to_ufp_device_id(chime_button.unique_id) + + instance = _async_get_ufp_instance(hass, chime_button.device_id) + chime = instance.bootstrap.chimes[chime_ufp_device_id] + + call.data = ReadOnlyDict(call.data.get("doorbells") or {}) + doorbell_refs = async_extract_referenced_entity_ids(hass, call) + doorbell_ids: set[str] = set() + for camera_id in doorbell_refs.referenced | doorbell_refs.indirectly_referenced: + doorbell_sensor = entity_registry.async_get(camera_id) + assert doorbell_sensor is not None + if ( + doorbell_sensor.platform != DOMAIN + or doorbell_sensor.domain != Platform.BINARY_SENSOR + or doorbell_sensor.original_device_class + != BinarySensorDeviceClass.OCCUPANCY + ): + continue + doorbell_ufp_device_id = _async_unique_id_to_ufp_device_id( + doorbell_sensor.unique_id + ) + camera = instance.bootstrap.cameras[doorbell_ufp_device_id] + doorbell_ids.add(camera.id) + chime.camera_ids = sorted(doorbell_ids) + await chime.save_device() def async_setup_services(hass: HomeAssistant) -> None: @@ -149,6 +193,11 @@ def async_setup_services(hass: HomeAssistant) -> None: functools.partial(set_default_doorbell_text, hass), DOORBELL_TEXT_SCHEMA, ), + ( + SERVICE_SET_CHIME_PAIRED, + functools.partial(set_chime_paired_doorbells, hass), + CHIME_PAIRED_SCHEMA, + ), ] for name, method, schema in services: if hass.services.has_service(DOMAIN, name): diff --git a/homeassistant/components/unifiprotect/services.yaml b/homeassistant/components/unifiprotect/services.yaml index 410dcae4699..037c10627ad 100644 --- a/homeassistant/components/unifiprotect/services.yaml +++ b/homeassistant/components/unifiprotect/services.yaml @@ -84,3 +84,28 @@ set_doorbell_message: step: 1 mode: slider unit_of_measurement: minutes +set_chime_paired_doorbells: + name: Set Chime Paired Doorbells + description: > + Use to set the paired doorbell(s) with a smart chime. + fields: + device_id: + name: Chime + description: The Chimes to link to the doorbells to + required: true + selector: + device: + integration: unifiprotect + entity: + device_class: unifiprotect__chime_button + doorbells: + name: Doorbells + description: The Doorbells to link to the chime + example: "binary_sensor.front_doorbell_doorbell" + required: false + selector: + target: + entity: + integration: unifiprotect + domain: binary_sensor + device_class: occupancy diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index 85a089994f8..971c637a8c2 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -5,8 +5,12 @@ from dataclasses import dataclass import logging from typing import Any -from pyunifiprotect.data import Camera, RecordingMode, VideoMode -from pyunifiprotect.data.base import ProtectAdoptableDeviceModel +from pyunifiprotect.data import ( + Camera, + ProtectAdoptableDeviceModel, + RecordingMode, + VideoMode, +) from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry @@ -43,7 +47,7 @@ async def _set_highfps(obj: Camera, value: bool) -> None: await obj.set_video_mode(VideoMode.DEFAULT) -ALL_DEVICES_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( +CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( ProtectSwitchEntityDescription( key="ssh", name="SSH Enabled", @@ -53,9 +57,6 @@ ALL_DEVICES_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( ufp_value="is_ssh_enabled", ufp_set_method="set_ssh", ), -) - -CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( ProtectSwitchEntityDescription( key="status_light", name="Status Light On", @@ -222,6 +223,15 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( + ProtectSwitchEntityDescription( + key="ssh", + name="SSH Enabled", + icon="mdi:lock", + entity_registry_enabled_default=False, + entity_category=EntityCategory.CONFIG, + ufp_value="is_ssh_enabled", + ufp_set_method="set_ssh", + ), ProtectSwitchEntityDescription( key="status_light", name="Status Light On", @@ -243,6 +253,18 @@ DOORLOCK_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( ), ) +VIEWER_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( + ProtectSwitchEntityDescription( + key="ssh", + name="SSH Enabled", + icon="mdi:lock", + entity_registry_enabled_default=False, + entity_category=EntityCategory.CONFIG, + ufp_value="is_ssh_enabled", + ufp_set_method="set_ssh", + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -254,11 +276,11 @@ async def async_setup_entry( entities: list[ProtectDeviceEntity] = async_all_device_entities( data, ProtectSwitch, - all_descs=ALL_DEVICES_SWITCHES, camera_descs=CAMERA_SWITCHES, light_descs=LIGHT_SWITCHES, sense_descs=SENSE_SWITCHES, lock_descs=DOORLOCK_SWITCHES, + viewer_descs=VIEWER_SWITCHES, ) async_add_entities(entities) diff --git a/homeassistant/components/unifiprotect/translations/bg.json b/homeassistant/components/unifiprotect/translations/bg.json index adc8d7ef699..a3987858b93 100644 --- a/homeassistant/components/unifiprotect/translations/bg.json +++ b/homeassistant/components/unifiprotect/translations/bg.json @@ -5,8 +5,7 @@ }, "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" + "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" }, "flow_title": "{name} ({ip_address})", "step": { diff --git a/homeassistant/components/unifiprotect/translations/ca.json b/homeassistant/components/unifiprotect/translations/ca.json index 2227011d113..a3d873ead5b 100644 --- a/homeassistant/components/unifiprotect/translations/ca.json +++ b/homeassistant/components/unifiprotect/translations/ca.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", - "protect_version": "La versi\u00f3 m\u00ednima necess\u00e0ria \u00e9s la v1.20.0. Actualitza UniFi Protect i torna-ho a provar.", - "unknown": "Error inesperat" + "protect_version": "La versi\u00f3 m\u00ednima necess\u00e0ria \u00e9s la v1.20.0. Actualitza UniFi Protect i torna-ho a provar." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Contrasenya", - "username": "Nom d'usuari", - "verify_ssl": "Verifica el certificat SSL" + "username": "Nom d'usuari" }, "description": "Vols configurar {name} ({ip_address})? Necessites un usuari local per iniciar sessi\u00f3, creat mitjan\u00e7ant la consola d'UniFi OS. Els usuaris d'Ubiquiti Cloud no funcionen. Per a m\u00e9s informaci\u00f3: {local_user_documentation_url}", "title": "UniFi Protect descobert" diff --git a/homeassistant/components/unifiprotect/translations/cs.json b/homeassistant/components/unifiprotect/translations/cs.json index 3668652797e..855cc6da6da 100644 --- a/homeassistant/components/unifiprotect/translations/cs.json +++ b/homeassistant/components/unifiprotect/translations/cs.json @@ -5,16 +5,14 @@ }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", - "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Heslo", - "username": "U\u017eivatelsk\u00e9 jm\u00e9no", - "verify_ssl": "Ov\u011b\u0159it certifik\u00e1t SSL" + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" } }, "reauth_confirm": { diff --git a/homeassistant/components/unifiprotect/translations/de.json b/homeassistant/components/unifiprotect/translations/de.json index 668a8c7b749..0f6ffd77793 100644 --- a/homeassistant/components/unifiprotect/translations/de.json +++ b/homeassistant/components/unifiprotect/translations/de.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", - "protect_version": "Die erforderliche Mindestversion ist v1.20.0. Bitte aktualisiere UniFi Protect und versuche es dann erneut.", - "unknown": "Unerwarteter Fehler" + "protect_version": "Die erforderliche Mindestversion ist v1.20.0. Bitte aktualisiere UniFi Protect und versuche es dann erneut." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Passwort", - "username": "Benutzername", - "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" + "username": "Benutzername" }, "description": "M\u00f6chtest du {name} ({ip_address}) einrichten? Du ben\u00f6tigst einen lokalen Benutzer, den du in deiner UniFi OS-Konsole angelegt hast, um sich damit anzumelden. Ubiquiti Cloud-Benutzer funktionieren nicht. F\u00fcr weitere Informationen: {local_user_documentation_url}", "title": "UniFi Protect erkannt" diff --git a/homeassistant/components/unifiprotect/translations/el.json b/homeassistant/components/unifiprotect/translations/el.json index c2f5d57d36d..8cede72d32b 100644 --- a/homeassistant/components/unifiprotect/translations/el.json +++ b/homeassistant/components/unifiprotect/translations/el.json @@ -7,16 +7,14 @@ "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", - "protect_version": "\u0397 \u03b5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 v1.20.0. \u0391\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf UniFi Protect \u03ba\u03b1\u03b9 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", - "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + "protect_version": "\u0397 \u03b5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03b7 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 v1.20.0. \u0391\u03bd\u03b1\u03b2\u03b1\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf UniFi Protect \u03ba\u03b1\u03b9 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." }, "flow_title": "{name} ( {ip_address} )", "step": { "discovery_confirm": { "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", - "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" + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({ip_address}); \u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03c4\u03bf\u03c0\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u039a\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 UniFi OS \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5. \u039f\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2 \u03c4\u03bf\u03c5 Ubiquiti Cloud \u03b4\u03b5\u03bd \u03b8\u03b1 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03bf\u03c5\u03bd. \u0393\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: {local_user_documentation_url}", "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf UniFi Protect" diff --git a/homeassistant/components/unifiprotect/translations/en.json b/homeassistant/components/unifiprotect/translations/en.json index 45e8fb8ea2e..b9d787b382e 100644 --- a/homeassistant/components/unifiprotect/translations/en.json +++ b/homeassistant/components/unifiprotect/translations/en.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", - "protect_version": "Minimum required version is v1.20.0. Please upgrade UniFi Protect and then retry.", - "unknown": "Unexpected error" + "protect_version": "Minimum required version is v1.20.0. Please upgrade UniFi Protect and then retry." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Password", - "username": "Username", - "verify_ssl": "Verify SSL certificate" + "username": "Username" }, "description": "Do you want to setup {name} ({ip_address})? You will need a local user created in your UniFi OS Console to log in with. Ubiquiti Cloud Users will not work. For more information: {local_user_documentation_url}", "title": "UniFi Protect Discovered" diff --git a/homeassistant/components/unifiprotect/translations/es.json b/homeassistant/components/unifiprotect/translations/es.json index bcd7efe6cdf..9ca1b56cf01 100644 --- a/homeassistant/components/unifiprotect/translations/es.json +++ b/homeassistant/components/unifiprotect/translations/es.json @@ -1,15 +1,20 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado" + "already_configured": "El dispositivo ya est\u00e1 configurado", + "discovery_started": "Se ha iniciado el descubrimiento" }, "error": { "cannot_connect": "No se pudo conectar", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", - "protect_version": "La versi\u00f3n m\u00ednima requerida es v1.20.0. Actualice UniFi Protect y vuelva a intentarlo.", - "unknown": "Error inesperado" + "protect_version": "La versi\u00f3n m\u00ednima requerida es v1.20.0. Actualice UniFi Protect y vuelva a intentarlo." }, "step": { + "discovery_confirm": { + "data": { + "username": "Usuario" + } + }, "reauth_confirm": { "data": { "host": "IP/Host del servidor UniFi Protect", diff --git a/homeassistant/components/unifiprotect/translations/et.json b/homeassistant/components/unifiprotect/translations/et.json index 607edbe52bc..9abd63559ee 100644 --- a/homeassistant/components/unifiprotect/translations/et.json +++ b/homeassistant/components/unifiprotect/translations/et.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "\u00dchendamine nurjus", "invalid_auth": "Tuvastamine nurjus", - "protect_version": "Minimaalne n\u00f5utav versioon on v1.20.0. Uuenda UniFi Protecti ja proovi seej\u00e4rel uuesti.", - "unknown": "Ootamatu t\u00f5rge" + "protect_version": "Minimaalne n\u00f5utav versioon on v1.20.0. Uuenda UniFi Protecti ja proovi seej\u00e4rel uuesti." }, "flow_title": "{name} ( {ip_address} )", "step": { "discovery_confirm": { "data": { "password": "Salas\u00f5na", - "username": "Kasutajanimi", - "verify_ssl": "Kontrolli SSL \u00fchendust" + "username": "Kasutajanimi" }, "description": "Kas soovid seadistada kasutaja {name} ( {ip_address} )? Sisselogimiseks on vaja UniFi OS-i konsoolis loodud kohalikku kasutajat. Ubiquiti pilve kasutajad ei t\u00f6\u00f6ta. Lisateabe saamiseks: {local_user_documentation_url}", "title": "Avastati UniFi Protect" diff --git a/homeassistant/components/unifiprotect/translations/fr.json b/homeassistant/components/unifiprotect/translations/fr.json index 592a18427b7..e1f189a55d5 100644 --- a/homeassistant/components/unifiprotect/translations/fr.json +++ b/homeassistant/components/unifiprotect/translations/fr.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "\u00c9chec de connexion", "invalid_auth": "Authentification non valide", - "protect_version": "La version minimale requise est la v1.20.0. Veuillez mettre \u00e0 jour UniFi Protect, puis r\u00e9essayer.", - "unknown": "Erreur inattendue" + "protect_version": "La version minimale requise est la v1.20.0. Veuillez mettre \u00e0 jour UniFi Protect, puis r\u00e9essayer." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Mot de passe", - "username": "Nom d'utilisateur", - "verify_ssl": "V\u00e9rifier le certificat SSL" + "username": "Nom d'utilisateur" }, "description": "Voulez-vous configurer {name} ({ip_address})\u00a0? Vous aurez besoin d'un utilisateur local cr\u00e9\u00e9 dans votre console UniFi OS pour vous connecter. Les utilisateurs Ubiquiti Cloud ne fonctionneront pas. Pour plus d'informations\u00a0: {local_user_documentation_url}", "title": "UniFi Protect d\u00e9couvert" diff --git a/homeassistant/components/unifiprotect/translations/he.json b/homeassistant/components/unifiprotect/translations/he.json index 7940beb3986..63974aff3ec 100644 --- a/homeassistant/components/unifiprotect/translations/he.json +++ b/homeassistant/components/unifiprotect/translations/he.json @@ -5,16 +5,14 @@ }, "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" + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "\u05e1\u05d9\u05e1\u05de\u05d4", - "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9", - "verify_ssl": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8 SSL" + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" } }, "reauth_confirm": { diff --git a/homeassistant/components/unifiprotect/translations/hu.json b/homeassistant/components/unifiprotect/translations/hu.json index c3cd3560a9d..11e0151f165 100644 --- a/homeassistant/components/unifiprotect/translations/hu.json +++ b/homeassistant/components/unifiprotect/translations/hu.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", - "protect_version": "A minim\u00e1lisan sz\u00fcks\u00e9ges verzi\u00f3 a v1.20.0. Friss\u00edtse az UniFi Protect-et, majd pr\u00f3b\u00e1lkozzon \u00fajra.", - "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + "protect_version": "A minim\u00e1lisan sz\u00fcks\u00e9ges verzi\u00f3 a v1.20.0. Friss\u00edtse az UniFi Protect-et, majd pr\u00f3b\u00e1lkozzon \u00fajra." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Jelsz\u00f3", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v", - "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name} ({ipaddress})? Egy helyi felhaszn\u00e1l\u00f3t kell l\u00e9trehoznia Unifi OS-ben.", "title": "UniFi Protect felfedezve" diff --git a/homeassistant/components/unifiprotect/translations/id.json b/homeassistant/components/unifiprotect/translations/id.json index b2f8e2541fe..a0a3b9751d3 100644 --- a/homeassistant/components/unifiprotect/translations/id.json +++ b/homeassistant/components/unifiprotect/translations/id.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Gagal terhubung", "invalid_auth": "Autentikasi tidak valid", - "protect_version": "Versi minimum yang diperlukan adalah v1.20.0. Tingkatkan UniFi Protect lalu coba lagi.", - "unknown": "Kesalahan yang tidak diharapkan" + "protect_version": "Versi minimum yang diperlukan adalah v1.20.0. Tingkatkan UniFi Protect lalu coba lagi." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Kata Sandi", - "username": "Nama Pengguna", - "verify_ssl": "Verifikasi sertifikat SSL" + "username": "Nama Pengguna" }, "description": "Ingin menyiapkan {name} ({ip_address})? Anda akan memerlukan pengguna lokal yang dibuat di Konsol OS UniFi Anda untuk masuk. Pengguna Ubiquiti Cloud tidak akan berfungsi. Untuk informasi lebih lanjut: {local_user_documentation_url}", "title": "UniFi Protect Ditemukan" diff --git a/homeassistant/components/unifiprotect/translations/it.json b/homeassistant/components/unifiprotect/translations/it.json index 747647b3d26..1d0fff95c3a 100644 --- a/homeassistant/components/unifiprotect/translations/it.json +++ b/homeassistant/components/unifiprotect/translations/it.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", - "protect_version": "La versione minima richiesta \u00e8 v1.20.0. Aggiorna UniFi Protect e riprova.", - "unknown": "Errore imprevisto" + "protect_version": "La versione minima richiesta \u00e8 v1.20.0. Aggiorna UniFi Protect e riprova." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Password", - "username": "Nome utente", - "verify_ssl": "Verifica il certificato SSL" + "username": "Nome utente" }, "description": "Vuoi configurare {name} ({ip_address})? Avrai bisogno di un utente locale creato nella tua console UniFi OS con cui accedere. Gli utenti Ubiquiti Cloud non funzioneranno. Per ulteriori informazioni: {local_user_documentation_url}", "title": "Rilevato UniFi Protect" diff --git a/homeassistant/components/unifiprotect/translations/ja.json b/homeassistant/components/unifiprotect/translations/ja.json index e4ad1b3f231..12275699260 100644 --- a/homeassistant/components/unifiprotect/translations/ja.json +++ b/homeassistant/components/unifiprotect/translations/ja.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", - "protect_version": "\u6700\u4f4e\u9650\u5fc5\u8981\u306a\u30d0\u30fc\u30b8\u30e7\u30f3\u306fv1.20.0\u3067\u3059\u3002UniFi Protect\u3092\u30a2\u30c3\u30d7\u30b0\u30ec\u30fc\u30c9\u3057\u3066\u304b\u3089\u518d\u5ea6\u8a66\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + "protect_version": "\u6700\u4f4e\u9650\u5fc5\u8981\u306a\u30d0\u30fc\u30b8\u30e7\u30f3\u306fv1.20.0\u3067\u3059\u3002UniFi Protect\u3092\u30a2\u30c3\u30d7\u30b0\u30ec\u30fc\u30c9\u3057\u3066\u304b\u3089\u518d\u5ea6\u8a66\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", - "username": "\u30e6\u30fc\u30b6\u30fc\u540d", - "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" + "username": "\u30e6\u30fc\u30b6\u30fc\u540d" }, "description": "{name} ({ip_address}) \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f", "title": "UniFi Protect\u304c\u691c\u51fa\u3055\u308c\u307e\u3057\u305f" diff --git a/homeassistant/components/unifiprotect/translations/ko.json b/homeassistant/components/unifiprotect/translations/ko.json new file mode 100644 index 00000000000..916dcc8a7e9 --- /dev/null +++ b/homeassistant/components/unifiprotect/translations/ko.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "discovery_started": "\uc7a5\uce58\uac80\uc0c9 \uc2dc\uc791" + }, + "step": { + "discovery_confirm": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "{name} ( {ip_address} )\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? ", + "title": "UniFi Protect \ubc1c\uacac" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifiprotect/translations/nl.json b/homeassistant/components/unifiprotect/translations/nl.json index 090624f2009..a5b834fc4d4 100644 --- a/homeassistant/components/unifiprotect/translations/nl.json +++ b/homeassistant/components/unifiprotect/translations/nl.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", - "protect_version": "Minimaal vereiste versie is v1.20.0. Upgrade UniFi Protect en probeer het opnieuw.", - "unknown": "Onverwachte fout" + "protect_version": "Minimaal vereiste versie is v1.20.0. Upgrade UniFi Protect en probeer het opnieuw." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Wachtwoord", - "username": "Gebruikersnaam", - "verify_ssl": "SSL-certificaat verifi\u00ebren" + "username": "Gebruikersnaam" }, "description": "Wilt u {name} ({ip_address}) instellen? U heeft een lokale gebruiker nodig die is aangemaakt in uw UniFi OS Console om mee in te loggen. Ubiquiti Cloud gebruikers zullen niet werken. Voor meer informatie: {local_user_documentation_url}", "title": "UniFi Protect ontdekt" diff --git a/homeassistant/components/unifiprotect/translations/no.json b/homeassistant/components/unifiprotect/translations/no.json index 9b45080b8f1..e11ed432313 100644 --- a/homeassistant/components/unifiprotect/translations/no.json +++ b/homeassistant/components/unifiprotect/translations/no.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Tilkobling mislyktes", "invalid_auth": "Ugyldig godkjenning", - "protect_version": "Minimum p\u00e5krevd versjon er v1.20.0. Vennligst oppgrader UniFi Protect og pr\u00f8v deretter p\u00e5 nytt.", - "unknown": "Uventet feil" + "protect_version": "Minimum p\u00e5krevd versjon er v1.20.0. Vennligst oppgrader UniFi Protect og pr\u00f8v deretter p\u00e5 nytt." }, "flow_title": "{name} ( {ip_address} )", "step": { "discovery_confirm": { "data": { "password": "Passord", - "username": "Brukernavn", - "verify_ssl": "Verifisere SSL-sertifikat" + "username": "Brukernavn" }, "description": "Vil du konfigurere {name} ( {ip_address} )? Du trenger en lokal bruker opprettet i UniFi OS-konsollen for \u00e5 logge p\u00e5. Ubiquiti Cloud-brukere vil ikke fungere. For mer informasjon: {local_user_documentation_url}", "title": "UniFi Protect oppdaget" diff --git a/homeassistant/components/unifiprotect/translations/pl.json b/homeassistant/components/unifiprotect/translations/pl.json index ef879805546..82aa3c91ee3 100644 --- a/homeassistant/components/unifiprotect/translations/pl.json +++ b/homeassistant/components/unifiprotect/translations/pl.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_auth": "Niepoprawne uwierzytelnienie", - "protect_version": "Minimalna wymagana wersja to v1.20.0. Zaktualizuj UniFi Protect, a nast\u0119pnie spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d" + "protect_version": "Minimalna wymagana wersja to v1.20.0. Zaktualizuj UniFi Protect, a nast\u0119pnie spr\u00f3buj ponownie." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Has\u0142o", - "username": "Nazwa u\u017cytkownika", - "verify_ssl": "Weryfikacja certyfikatu SSL" + "username": "Nazwa u\u017cytkownika" }, "description": "Czy chcesz skonfigurowa\u0107 {name} ({ip_address})? Aby si\u0119 zalogowa\u0107, b\u0119dziesz potrzebowa\u0107 lokalnego u\u017cytkownika utworzonego w konsoli UniFi OS. U\u017cytkownicy Ubiquiti Cloud nie b\u0119d\u0105 dzia\u0142a\u0107. Wi\u0119cej informacji: {local_user_documentation_url}", "title": "Wykryto UniFi Protect" diff --git a/homeassistant/components/unifiprotect/translations/pt-BR.json b/homeassistant/components/unifiprotect/translations/pt-BR.json index 1f26b952998..2c0c1270add 100644 --- a/homeassistant/components/unifiprotect/translations/pt-BR.json +++ b/homeassistant/components/unifiprotect/translations/pt-BR.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", - "protect_version": "A vers\u00e3o m\u00ednima exigida \u00e9 v1.20.0. Atualize o UniFi Protect e tente novamente.", - "unknown": "Erro inesperado" + "protect_version": "A vers\u00e3o m\u00ednima exigida \u00e9 v1.20.0. Atualize o UniFi Protect e tente novamente." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Senha", - "username": "Usu\u00e1rio", - "verify_ssl": "Verifique o certificado SSL" + "username": "Usu\u00e1rio" }, "description": "Deseja configurar {name} ({ip_address})?\nVoc\u00ea precisar\u00e1 de um usu\u00e1rio local criado no console do sistema operacional UniFi para fazer login. Usu\u00e1rios da Ubiquiti Cloud n\u00e3o funcionar\u00e3o. Para mais informa\u00e7\u00f5es: {local_user_documentation_url}", "title": "Descoberta UniFi Protect" diff --git a/homeassistant/components/unifiprotect/translations/ru.json b/homeassistant/components/unifiprotect/translations/ru.json index ea9810962f8..2e06e887cf4 100644 --- a/homeassistant/components/unifiprotect/translations/ru.json +++ b/homeassistant/components/unifiprotect/translations/ru.json @@ -7,16 +7,14 @@ "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.", - "protect_version": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0432\u0435\u0440\u0441\u0438\u044f 1.20.0 \u0438\u043b\u0438 \u0432\u044b\u0448\u0435. \u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 UniFi Protect \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.", - "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + "protect_version": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0432\u0435\u0440\u0441\u0438\u044f 1.20.0 \u0438\u043b\u0438 \u0432\u044b\u0448\u0435. \u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 UniFi Protect \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "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" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({ip_address})? \u0414\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 UniFi OS \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 Ubiquiti Cloud \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u0414\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: {local_user_documentation_url}", "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e UniFi Protect" diff --git a/homeassistant/components/unifiprotect/translations/tr.json b/homeassistant/components/unifiprotect/translations/tr.json index d7b87b13f31..869c13608ce 100644 --- a/homeassistant/components/unifiprotect/translations/tr.json +++ b/homeassistant/components/unifiprotect/translations/tr.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", - "protect_version": "Minimum gerekli s\u00fcr\u00fcm v1.20.0'd\u0131r. L\u00fctfen UniFi Protect'i y\u00fckseltin ve ard\u0131ndan yeniden deneyin.", - "unknown": "Beklenmeyen hata" + "protect_version": "Minimum gerekli s\u00fcr\u00fcm v1.20.0'd\u0131r. L\u00fctfen UniFi Protect'i y\u00fckseltin ve ard\u0131ndan yeniden deneyin." }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "Parola", - "username": "Kullan\u0131c\u0131 Ad\u0131", - "verify_ssl": "SSL sertifikas\u0131n\u0131 do\u011frulay\u0131n" + "username": "Kullan\u0131c\u0131 Ad\u0131" }, "description": "{name} ( {ip_address} ) kurulumu yapmak istiyor musunuz? Oturum a\u00e7mak i\u00e7in UniFi OS Konsolunuzda olu\u015fturulmu\u015f yerel bir kullan\u0131c\u0131ya ihtiyac\u0131n\u0131z olacak. Ubiquiti Bulut Kullan\u0131c\u0131lar\u0131 \u00e7al\u0131\u015fmayacakt\u0131r. Daha fazla bilgi i\u00e7in: {local_user_documentation_url}", "title": "UniFi Protect Ke\u015ffedildi" diff --git a/homeassistant/components/unifiprotect/translations/zh-Hant.json b/homeassistant/components/unifiprotect/translations/zh-Hant.json index 33a447e9f4c..d0c23849e12 100644 --- a/homeassistant/components/unifiprotect/translations/zh-Hant.json +++ b/homeassistant/components/unifiprotect/translations/zh-Hant.json @@ -7,16 +7,14 @@ "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", - "protect_version": "\u6240\u9700\u6700\u4f4e\u7248\u672c\u70ba V1.20.0\u3002\u8acb\u66f4\u65b0 UniFi \u76e3\u63a7\u5f8c\u518d\u91cd\u8a66\u4e00\u6b21\u3002", - "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + "protect_version": "\u6240\u9700\u6700\u4f4e\u7248\u672c\u70ba V1.20.0\u3002\u8acb\u66f4\u65b0 UniFi \u76e3\u63a7\u5f8c\u518d\u91cd\u8a66\u4e00\u6b21\u3002" }, "flow_title": "{name} ({ip_address})", "step": { "discovery_confirm": { "data": { "password": "\u5bc6\u78bc", - "username": "\u4f7f\u7528\u8005\u540d\u7a31", - "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" + "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} ({ip_address})\uff1f\u9700\u8981\u65bc UniFi OS Console \u65b0\u589e\u672c\u5730\u4f7f\u7528\u8005\u4ee5\u9032\u884c\u767b\u5165\u3001Ubiquiti \u96f2\u7aef\u4f7f\u7528\u8005\u7121\u6cd5\u4f7f\u7528\u3002\u66f4\u591a\u8a73\u7d30\u8cc7\u8a0a\uff1a{local_user_documentation_url}", "title": "\u767c\u73fe UniFi \u76e3\u63a7" diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index e29a18f285f..7ffd8b9d13d 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -79,7 +79,11 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.event import TrackTemplate, async_track_template_result +from homeassistant.helpers.event import ( + TrackTemplate, + async_track_state_change_event, + async_track_template_result, +) from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.service import async_call_from_config from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -202,8 +206,8 @@ class UniversalMediaPlayer(MediaPlayerEntity): depend.append(entity[0]) self.async_on_remove( - self.hass.helpers.event.async_track_state_change_event( - list(set(depend)), _async_on_dependency_update + async_track_state_change_event( + self.hass, list(set(depend)), _async_on_dependency_update ) ) diff --git a/homeassistant/components/upb/translations/nl.json b/homeassistant/components/upb/translations/nl.json index 56ba7eae754..b3284eed134 100644 --- a/homeassistant/components/upb/translations/nl.json +++ b/homeassistant/components/upb/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "invalid_upb_file": "Ontbrekend of ongeldig UPB UPStart-exportbestand, controleer de naam en het pad van het bestand.", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/upb/translations/sk.json b/homeassistant/components/upb/translations/sk.json new file mode 100644 index 00000000000..3f20d345b26 --- /dev/null +++ b/homeassistant/components/upb/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dak\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/update/device_trigger.py b/homeassistant/components/update/device_trigger.py index 690e67cce56..ac8113d5708 100644 --- a/homeassistant/components/update/device_trigger.py +++ b/homeassistant/components/update/device_trigger.py @@ -1,8 +1,6 @@ """Provides device triggers for update entities.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -36,7 +34,7 @@ async def async_attach_trigger( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/update/translations/es.json b/homeassistant/components/update/translations/es.json index 79993c92b20..ee92c01c938 100644 --- a/homeassistant/components/update/translations/es.json +++ b/homeassistant/components/update/translations/es.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "La disponibilidad de la actualizaci\u00f3n de {entity_name} cambie", + "turned_off": "{entity_name} se actualice", + "turned_on": "{entity_name} tenga una actualizaci\u00f3n disponible" + } + }, "title": "Actualizar" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/sv.json b/homeassistant/components/update/translations/sv.json new file mode 100644 index 00000000000..53ecdb21377 --- /dev/null +++ b/homeassistant/components/update/translations/sv.json @@ -0,0 +1,9 @@ +{ + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} uppdateringstillg\u00e4nglighet \u00e4ndrad", + "turned_off": "{entity_name} blev uppdaterad", + "turned_on": "{entity_name} har en uppdatering tillg\u00e4nglig" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index b571a2b447f..07560f7413f 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -39,7 +39,7 @@ from .const import ( DOMAIN, LOGGER, ) -from .device import Device, async_get_mac_address_from_host +from .device import Device, async_create_device, async_get_mac_address_from_host NOTIFICATION_ID = "upnp_notification" NOTIFICATION_TITLE = "UPnP/IGD Setup" @@ -113,8 +113,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: await asyncio.wait_for(device_discovered_event.wait(), timeout=10) except asyncio.TimeoutError as err: - LOGGER.debug("Device not discovered: %s", usn) - raise ConfigEntryNotReady from err + raise ConfigEntryNotReady(f"Device not discovered: {usn}") from err finally: cancel_discovered_callback() @@ -123,12 +122,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: assert discovery_info.ssdp_location is not None location = discovery_info.ssdp_location try: - device = await Device.async_create_device(hass, location) + device = await async_create_device(hass, location) except UpnpConnectionError as err: - LOGGER.debug( - "Error connecting to device at location: %s, err: %s", location, err - ) - raise ConfigEntryNotReady from err + raise ConfigEntryNotReady( + f"Error connecting to device at location: {location}, err: {err}" + ) from err # Track the original UDN such that existing sensors do not change their unique_id. if CONFIG_ENTRY_ORIGINAL_UDN not in entry.data: @@ -255,21 +253,15 @@ class UpnpDataUpdateCoordinator(DataUpdateCoordinator): LOGGER, name=device.name, update_interval=update_interval, - update_method=self._async_fetch_data, ) - async def _async_fetch_data(self) -> Mapping[str, Any]: + async def _async_update_data(self) -> Mapping[str, Any]: """Update data.""" try: update_values = await asyncio.gather( self.device.async_get_traffic_data(), self.device.async_get_status(), ) - - return { - **update_values[0], - **update_values[1], - } except UpnpCommunicationError as exception: LOGGER.debug( "Caught exception when updating device: %s, exception: %s", @@ -280,6 +272,11 @@ class UpnpDataUpdateCoordinator(DataUpdateCoordinator): f"Unable to communicate with IGD at: {self.device.device_url}" ) from exception + return { + **update_values[0], + **update_values[1], + } + class UpnpEntity(CoordinatorEntity[UpnpDataUpdateCoordinator]): """Base class for UPnP/IGD entities.""" diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index b7208e6e6e2..b54098b6566 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -54,7 +54,7 @@ async def _async_wait_for_discoveries(hass: HomeAssistant) -> bool: async def device_discovered(info: SsdpServiceInfo, change: SsdpChange) -> None: if change != SsdpChange.BYEBYE: - LOGGER.info( + LOGGER.debug( "Device discovered: %s, at: %s", info.ssdp_usn, info.ssdp_location, diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index 0e7c7902bd9..334d870939f 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -9,7 +9,6 @@ from typing import Any from urllib.parse import urlparse from async_upnp_client.aiohttp import AiohttpSessionRequester -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 @@ -47,15 +46,19 @@ async def async_get_mac_address_from_host(hass: HomeAssistant, host: str) -> str return mac_address -async def async_create_upnp_device( - hass: HomeAssistant, ssdp_location: str -) -> UpnpDevice: - """Create UPnP device.""" +async def async_create_device(hass: HomeAssistant, ssdp_location: str) -> Device: + """Create UPnP/IGD device.""" session = async_get_clientsession(hass) requester = AiohttpSessionRequester(session, with_sleep=True, timeout=20) factory = UpnpFactory(requester, disable_state_variable_validation=True) - return await factory.async_create_device(ssdp_location) + upnp_device = await factory.async_create_device(ssdp_location) + + # Create profile wrapper. + igd_device = IgdDevice(upnp_device, None) + device = Device(hass, igd_device) + + return device class Device: @@ -66,40 +69,8 @@ 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( - cls, hass: HomeAssistant, ssdp_location: str - ) -> Device: - """Create UPnP/IGD device.""" - upnp_device = await async_create_upnp_device(hass, ssdp_location) - - # Create profile wrapper. - igd_device = IgdDevice(upnp_device, None) - device = cls(hass, igd_device) - - return device - - @property - def mac_address(self) -> str | None: - """Get the mac address.""" - return self._mac_address - - @mac_address.setter - def mac_address(self, mac_address: str) -> None: - """Set the mac address.""" - self._mac_address = mac_address - - @property - def original_udn(self) -> str | None: - """Get the mac address.""" - return self._original_udn - - @original_udn.setter - def original_udn(self, original_udn: str) -> None: - """Set the original UDN.""" - self._original_udn = original_udn + self.mac_address: str | None = None + self.original_udn: str | None = None @property def udn(self) -> str: diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index 94d9c8ab058..2e76dac4adb 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.29.0", "getmac==0.8.2"], + "requirements": ["async-upnp-client==0.30.1", "getmac==0.8.2"], "dependencies": ["network", "ssdp"], "codeowners": ["@StevenLooman", "@ehendrix23"], "ssdp": [ diff --git a/homeassistant/components/upnp/translations/bg.json b/homeassistant/components/upnp/translations/bg.json index 82632bc19b5..8bc4e9b740a 100644 --- a/homeassistant/components/upnp/translations/bg.json +++ b/homeassistant/components/upnp/translations/bg.json @@ -15,8 +15,7 @@ }, "user": { "data": { - "unique_id": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e", - "usn": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + "unique_id": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" } } } diff --git a/homeassistant/components/upnp/translations/ca.json b/homeassistant/components/upnp/translations/ca.json index 0fc68f8a9b7..94ffd69cbf4 100644 --- a/homeassistant/components/upnp/translations/ca.json +++ b/homeassistant/components/upnp/translations/ca.json @@ -16,9 +16,7 @@ }, "user": { "data": { - "scan_interval": "Interval d'actualitzaci\u00f3 (en segons, m\u00ednim 30)", - "unique_id": "Dispositiu", - "usn": "Dispositiu" + "unique_id": "Dispositiu" } } } diff --git a/homeassistant/components/upnp/translations/cs.json b/homeassistant/components/upnp/translations/cs.json index 319dd28c11e..c684856b52b 100644 --- a/homeassistant/components/upnp/translations/cs.json +++ b/homeassistant/components/upnp/translations/cs.json @@ -9,12 +9,6 @@ "step": { "ssdp_confirm": { "description": "Chcete nastavit toto za\u0159\u00edzen\u00ed UPnP/IGD?" - }, - "user": { - "data": { - "scan_interval": "Interval aktualizace (v sekund\u00e1ch, minim\u00e1ln\u011b 30)", - "usn": "Za\u0159\u00edzen\u00ed" - } } } } diff --git a/homeassistant/components/upnp/translations/de.json b/homeassistant/components/upnp/translations/de.json index b63d17947ae..6c99c42fd8e 100644 --- a/homeassistant/components/upnp/translations/de.json +++ b/homeassistant/components/upnp/translations/de.json @@ -16,9 +16,7 @@ }, "user": { "data": { - "scan_interval": "Aktualisierungsintervall (Sekunden, mindestens 30)", - "unique_id": "Ger\u00e4t", - "usn": "Ger\u00e4t" + "unique_id": "Ger\u00e4t" } } } diff --git a/homeassistant/components/upnp/translations/el.json b/homeassistant/components/upnp/translations/el.json index 9a84ba81ced..8e98495d4b7 100644 --- a/homeassistant/components/upnp/translations/el.json +++ b/homeassistant/components/upnp/translations/el.json @@ -12,9 +12,7 @@ }, "user": { "data": { - "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1, \u03b5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf 30)", - "unique_id": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", - "usn": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + "unique_id": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" } } } diff --git a/homeassistant/components/upnp/translations/en.json b/homeassistant/components/upnp/translations/en.json index c62ebfd2a87..aa22348e308 100644 --- a/homeassistant/components/upnp/translations/en.json +++ b/homeassistant/components/upnp/translations/en.json @@ -12,9 +12,7 @@ }, "user": { "data": { - "scan_interval": "Update interval (seconds, minimal 30)", - "unique_id": "Device", - "usn": "Device" + "unique_id": "Device" } } } diff --git a/homeassistant/components/upnp/translations/es.json b/homeassistant/components/upnp/translations/es.json index 356376e2e07..9a8a9e0160b 100644 --- a/homeassistant/components/upnp/translations/es.json +++ b/homeassistant/components/upnp/translations/es.json @@ -9,16 +9,14 @@ "one": "UNO", "other": "OTRO" }, - "flow_title": "UPnP / IGD: {name}", + "flow_title": "{name}", "step": { "ssdp_confirm": { "description": "\u00bfQuieres configurar este dispositivo UPnP/IGD?" }, "user": { "data": { - "scan_interval": "Intervalo de actualizaci\u00f3n (segundos, m\u00ednimo 30)", - "unique_id": "Dispositivo", - "usn": "Dispositivo" + "unique_id": "Dispositivo" } } } diff --git a/homeassistant/components/upnp/translations/et.json b/homeassistant/components/upnp/translations/et.json index 2ac4884c9ea..f090cebb31a 100644 --- a/homeassistant/components/upnp/translations/et.json +++ b/homeassistant/components/upnp/translations/et.json @@ -20,9 +20,7 @@ }, "user": { "data": { - "scan_interval": "P\u00e4ringute intervall (sekundites, v\u00e4hemalt 30)", - "unique_id": "Seade", - "usn": "Seade" + "unique_id": "Seade" } } } diff --git a/homeassistant/components/upnp/translations/fi.json b/homeassistant/components/upnp/translations/fi.json index aaf44e6c730..479b3be83de 100644 --- a/homeassistant/components/upnp/translations/fi.json +++ b/homeassistant/components/upnp/translations/fi.json @@ -2,14 +2,6 @@ "config": { "abort": { "already_configured": "Laite on jo m\u00e4\u00e4ritetty" - }, - "step": { - "user": { - "data": { - "scan_interval": "P\u00e4ivitysv\u00e4li (sekuntia, v\u00e4hint\u00e4\u00e4n 30)", - "usn": "Laite" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/fr.json b/homeassistant/components/upnp/translations/fr.json index 1c5a1c61309..b1f7ffb03fd 100644 --- a/homeassistant/components/upnp/translations/fr.json +++ b/homeassistant/components/upnp/translations/fr.json @@ -20,9 +20,7 @@ }, "user": { "data": { - "scan_interval": "Intervalle de mise \u00e0 jour (secondes, minimum 30)", - "unique_id": "Appareil", - "usn": "Appareil" + "unique_id": "Appareil" } } } diff --git a/homeassistant/components/upnp/translations/he.json b/homeassistant/components/upnp/translations/he.json index 6395b5f029f..a8501a7de41 100644 --- a/homeassistant/components/upnp/translations/he.json +++ b/homeassistant/components/upnp/translations/he.json @@ -8,8 +8,7 @@ "step": { "user": { "data": { - "unique_id": "\u05d4\u05ea\u05e7\u05df", - "usn": "\u05de\u05db\u05e9\u05d9\u05e8" + "unique_id": "\u05d4\u05ea\u05e7\u05df" } } } diff --git a/homeassistant/components/upnp/translations/hu.json b/homeassistant/components/upnp/translations/hu.json index 46c6bd2de1f..141d5ae8d8d 100644 --- a/homeassistant/components/upnp/translations/hu.json +++ b/homeassistant/components/upnp/translations/hu.json @@ -20,9 +20,7 @@ }, "user": { "data": { - "scan_interval": "Friss\u00edt\u00e9si intervallum (m\u00e1sodperc, minimum 30)", - "unique_id": "Eszk\u00f6z", - "usn": "Eszk\u00f6z" + "unique_id": "Eszk\u00f6z" } } } diff --git a/homeassistant/components/upnp/translations/id.json b/homeassistant/components/upnp/translations/id.json index f70fca145e8..2f2c195759e 100644 --- a/homeassistant/components/upnp/translations/id.json +++ b/homeassistant/components/upnp/translations/id.json @@ -12,9 +12,7 @@ }, "user": { "data": { - "scan_interval": "Interval pembaruan (dalam detik, minimal 30)", - "unique_id": "Perangkat", - "usn": "Perangkat" + "unique_id": "Perangkat" } } } diff --git a/homeassistant/components/upnp/translations/it.json b/homeassistant/components/upnp/translations/it.json index a57429ac78a..7b6a22de5c8 100644 --- a/homeassistant/components/upnp/translations/it.json +++ b/homeassistant/components/upnp/translations/it.json @@ -20,9 +20,7 @@ }, "user": { "data": { - "scan_interval": "Intervallo di aggiornamento (secondi, minimo 30)", - "unique_id": "Dispositivo", - "usn": "Dispositivo" + "unique_id": "Dispositivo" } } } diff --git a/homeassistant/components/upnp/translations/ja.json b/homeassistant/components/upnp/translations/ja.json index 2148b0d8a16..8c4d8d4695e 100644 --- a/homeassistant/components/upnp/translations/ja.json +++ b/homeassistant/components/upnp/translations/ja.json @@ -12,9 +12,7 @@ }, "user": { "data": { - "scan_interval": "\u66f4\u65b0\u9593\u9694(\u79d2\u3001\u6700\u5c0f30)", - "unique_id": "\u30c7\u30d0\u30a4\u30b9", - "usn": "\u30c7\u30d0\u30a4\u30b9" + "unique_id": "\u30c7\u30d0\u30a4\u30b9" } } } diff --git a/homeassistant/components/upnp/translations/ko.json b/homeassistant/components/upnp/translations/ko.json index ab80ceb9caa..8782bc180dd 100644 --- a/homeassistant/components/upnp/translations/ko.json +++ b/homeassistant/components/upnp/translations/ko.json @@ -9,12 +9,6 @@ "step": { "ssdp_confirm": { "description": "\uc774 UPnP/IGD \uae30\uae30\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" - }, - "user": { - "data": { - "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc19f\uac12 30)", - "usn": "\uae30\uae30" - } } } } diff --git a/homeassistant/components/upnp/translations/lb.json b/homeassistant/components/upnp/translations/lb.json index ac1eecebf99..a1f8cf85c3b 100644 --- a/homeassistant/components/upnp/translations/lb.json +++ b/homeassistant/components/upnp/translations/lb.json @@ -13,12 +13,6 @@ "step": { "ssdp_confirm": { "description": "Soll d\u00ebsen UPnP/IGD Apparat konfigur\u00e9iert ginn?" - }, - "user": { - "data": { - "scan_interval": "Update Intervall (Sekonnen, minimum 30)", - "usn": "Apparat" - } } } } diff --git a/homeassistant/components/upnp/translations/nl.json b/homeassistant/components/upnp/translations/nl.json index b4d690ca58c..5b176c4b22f 100644 --- a/homeassistant/components/upnp/translations/nl.json +++ b/homeassistant/components/upnp/translations/nl.json @@ -20,9 +20,7 @@ }, "user": { "data": { - "scan_interval": "Update-interval (seconden, minimaal 30)", - "unique_id": "Apparaat", - "usn": "Apparaat" + "unique_id": "Apparaat" } } } diff --git a/homeassistant/components/upnp/translations/no.json b/homeassistant/components/upnp/translations/no.json index c92144bf40d..8eb74395fa2 100644 --- a/homeassistant/components/upnp/translations/no.json +++ b/homeassistant/components/upnp/translations/no.json @@ -20,9 +20,7 @@ }, "user": { "data": { - "scan_interval": "Oppdateringsintervall (sekunder, minimum 30)", - "unique_id": "Enhet", - "usn": "Enhet" + "unique_id": "Enhet" } } } diff --git a/homeassistant/components/upnp/translations/pl.json b/homeassistant/components/upnp/translations/pl.json index 30213436d27..e8c8d72b74c 100644 --- a/homeassistant/components/upnp/translations/pl.json +++ b/homeassistant/components/upnp/translations/pl.json @@ -24,9 +24,7 @@ }, "user": { "data": { - "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (sekundy, minimum 30)", - "unique_id": "Urz\u0105dzenie", - "usn": "Urz\u0105dzenie" + "unique_id": "Urz\u0105dzenie" } } } diff --git a/homeassistant/components/upnp/translations/pt-BR.json b/homeassistant/components/upnp/translations/pt-BR.json index a1544981ea9..3070be8a1d0 100644 --- a/homeassistant/components/upnp/translations/pt-BR.json +++ b/homeassistant/components/upnp/translations/pt-BR.json @@ -12,9 +12,7 @@ }, "user": { "data": { - "scan_interval": "Intervalo de atualiza\u00e7\u00e3o (segundos, m\u00ednimo 30)", - "unique_id": "Dispositivo", - "usn": "Dispositivo" + "unique_id": "Dispositivo" } } } diff --git a/homeassistant/components/upnp/translations/pt.json b/homeassistant/components/upnp/translations/pt.json index 8985d608033..022d1c823c1 100644 --- a/homeassistant/components/upnp/translations/pt.json +++ b/homeassistant/components/upnp/translations/pt.json @@ -12,11 +12,6 @@ "step": { "ssdp_confirm": { "description": "Deseja configurar este dispositivo UPnP/IGD?" - }, - "user": { - "data": { - "usn": "Dispositivo" - } } } } diff --git a/homeassistant/components/upnp/translations/ru.json b/homeassistant/components/upnp/translations/ru.json index 20c652fa1e0..40a8585ac95 100644 --- a/homeassistant/components/upnp/translations/ru.json +++ b/homeassistant/components/upnp/translations/ru.json @@ -18,9 +18,7 @@ }, "user": { "data": { - "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445, \u043c\u0438\u043d\u0438\u043c\u0443\u043c 30)", - "unique_id": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e", - "usn": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" + "unique_id": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" } } } diff --git a/homeassistant/components/upnp/translations/sl.json b/homeassistant/components/upnp/translations/sl.json index ee25689fd51..c6258ec4774 100644 --- a/homeassistant/components/upnp/translations/sl.json +++ b/homeassistant/components/upnp/translations/sl.json @@ -14,11 +14,6 @@ "step": { "ssdp_confirm": { "description": "Ali \u017eelite nastaviti to UPnP/IGD napravo?" - }, - "user": { - "data": { - "usn": "Naprava" - } } } } diff --git a/homeassistant/components/upnp/translations/sv.json b/homeassistant/components/upnp/translations/sv.json index b6702d976d0..5ffe4a62f26 100644 --- a/homeassistant/components/upnp/translations/sv.json +++ b/homeassistant/components/upnp/translations/sv.json @@ -7,13 +7,6 @@ "error": { "one": "En", "other": "Andra" - }, - "step": { - "user": { - "data": { - "usn": "Enheten" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/tr.json b/homeassistant/components/upnp/translations/tr.json index 8176c0541c7..4742549eaff 100644 --- a/homeassistant/components/upnp/translations/tr.json +++ b/homeassistant/components/upnp/translations/tr.json @@ -20,9 +20,7 @@ }, "user": { "data": { - "scan_interval": "G\u00fcncelleme aral\u0131\u011f\u0131 (saniye, minimum 30)", - "unique_id": "Cihaz", - "usn": "Cihaz" + "unique_id": "Cihaz" } } } diff --git a/homeassistant/components/upnp/translations/uk.json b/homeassistant/components/upnp/translations/uk.json index 905958eeca9..870c3d38ffd 100644 --- a/homeassistant/components/upnp/translations/uk.json +++ b/homeassistant/components/upnp/translations/uk.json @@ -9,12 +9,6 @@ "step": { "ssdp_confirm": { "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0446\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 UPnP / IGD?" - }, - "user": { - "data": { - "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445, \u043c\u0456\u043d\u0456\u043c\u0443\u043c 30)", - "usn": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" - } } } } diff --git a/homeassistant/components/upnp/translations/zh-Hans.json b/homeassistant/components/upnp/translations/zh-Hans.json index 09cf281418b..73ee0d983c1 100644 --- a/homeassistant/components/upnp/translations/zh-Hans.json +++ b/homeassistant/components/upnp/translations/zh-Hans.json @@ -11,8 +11,7 @@ }, "user": { "data": { - "unique_id": "\u8bbe\u5907", - "usn": "\u8bbe\u5907" + "unique_id": "\u8bbe\u5907" } } } diff --git a/homeassistant/components/upnp/translations/zh-Hant.json b/homeassistant/components/upnp/translations/zh-Hant.json index 80c92662d22..df91540c0a0 100644 --- a/homeassistant/components/upnp/translations/zh-Hant.json +++ b/homeassistant/components/upnp/translations/zh-Hant.json @@ -12,9 +12,7 @@ }, "user": { "data": { - "scan_interval": "\u66f4\u65b0\u9593\u9694\uff08\u79d2\u3001\u6700\u5c11 30 \u79d2\uff09", - "unique_id": "\u88dd\u7f6e", - "usn": "\u88dd\u7f6e" + "unique_id": "\u88dd\u7f6e" } } } diff --git a/homeassistant/components/uptime/translations/ko.json b/homeassistant/components/uptime/translations/ko.json new file mode 100644 index 00000000000..758f3336cd4 --- /dev/null +++ b/homeassistant/components/uptime/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptime/translations/nl.json b/homeassistant/components/uptime/translations/nl.json index 786fec37f6b..b4ed0a1db36 100644 --- a/homeassistant/components/uptime/translations/nl.json +++ b/homeassistant/components/uptime/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "user": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } }, diff --git a/homeassistant/components/uptimerobot/__init__.py b/homeassistant/components/uptimerobot/__init__.py index 6d9be1b2364..a4c975ff58e 100644 --- a/homeassistant/components/uptimerobot/__init__.py +++ b/homeassistant/components/uptimerobot/__init__.py @@ -12,12 +12,8 @@ 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 import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.device_registry import ( - DeviceRegistry, - async_entries_for_config_entry, - async_get_registry, -) from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import API_ATTR_OK, COORDINATOR_UPDATE_INTERVAL, DOMAIN, LOGGER, PLATFORMS @@ -32,7 +28,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: "Wrong API key type detected, use the 'main' API key" ) uptime_robot_api = UptimeRobot(key, async_get_clientsession(hass)) - dev_reg = await async_get_registry(hass) + dev_reg = dr.async_get(hass) hass.data[DOMAIN][entry.entry_id] = coordinator = UptimeRobotDataUpdateCoordinator( hass, @@ -67,7 +63,7 @@ class UptimeRobotDataUpdateCoordinator(DataUpdateCoordinator): self, hass: HomeAssistant, config_entry_id: str, - dev_reg: DeviceRegistry, + dev_reg: dr.DeviceRegistry, api: UptimeRobot, ) -> None: """Initialize coordinator.""" @@ -97,7 +93,7 @@ class UptimeRobotDataUpdateCoordinator(DataUpdateCoordinator): current_monitors = { list(device.identifiers)[0][1] - for device in async_entries_for_config_entry( + for device in dr.async_entries_for_config_entry( self._device_registry, self._config_entry_id ) } diff --git a/homeassistant/components/uptimerobot/translations/es.json b/homeassistant/components/uptimerobot/translations/es.json index 455c96cd644..e78a90b4176 100644 --- a/homeassistant/components/uptimerobot/translations/es.json +++ b/homeassistant/components/uptimerobot/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_failed_existing": "No se pudo actualizar la entrada de configuraci\u00f3n, elimine la integraci\u00f3n y config\u00farela nuevamente.", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente", "unknown": "Error inesperado" @@ -9,6 +9,7 @@ "error": { "cannot_connect": "No se pudo conectar", "invalid_api_key": "Clave API no v\u00e1lida", + "not_main_key": "Se ha detectado un tipo de clave API incorrecta, utiliza la clave API 'principal'", "reauth_failed_matching_account": "La clave de API que has proporcionado no coincide con el ID de cuenta para la configuraci\u00f3n existente.", "unknown": "Error inesperado" }, diff --git a/homeassistant/components/uptimerobot/translations/nl.json b/homeassistant/components/uptimerobot/translations/nl.json index d3ee1f7515a..96db2ba1f23 100644 --- a/homeassistant/components/uptimerobot/translations/nl.json +++ b/homeassistant/components/uptimerobot/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "reauth_failed_existing": "Kon de config entry niet updaten, gelieve de integratie te verwijderen en het opnieuw op te zetten.", - "reauth_successful": "Herauthenticatie was succesvol", + "reauth_successful": "Herauthenticatie geslaagd", "unknown": "Onverwachte fout" }, "error": { @@ -19,7 +19,7 @@ "api_key": "API-sleutel" }, "description": "U moet een nieuwe 'hoofd'-API-sleutel van UptimeRobot opgeven", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { diff --git a/homeassistant/components/uptimerobot/translations/sensor.es.json b/homeassistant/components/uptimerobot/translations/sensor.es.json new file mode 100644 index 00000000000..1f037738b42 --- /dev/null +++ b/homeassistant/components/uptimerobot/translations/sensor.es.json @@ -0,0 +1,9 @@ +{ + "state": { + "uptimerobot__monitor_status": { + "not_checked_yet": "No comprobado", + "pause": "En pausa", + "up": "Funcionante" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/usb/__init__.py b/homeassistant/components/usb/__init__.py index 5ac390ad168..7a56a659d07 100644 --- a/homeassistant/components/usb/__init__.py +++ b/homeassistant/components/usb/__init__.py @@ -6,7 +6,7 @@ import fnmatch import logging import os import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from serial.tools.list_ports import comports from serial.tools.list_ports_common import ListPortInfo @@ -20,7 +20,6 @@ from homeassistant.core import Event, HomeAssistant, callback from homeassistant.data_entry_flow import BaseServiceInfo from homeassistant.helpers import discovery_flow, system_info from homeassistant.helpers.debounce import Debouncer -from homeassistant.helpers.frame import report from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_usb @@ -47,20 +46,6 @@ class UsbServiceInfo(BaseServiceInfo): manufacturer: str | None description: str | None - def __getitem__(self, name: str) -> Any: - """ - Allow property access by name for compatibility reason. - - Deprecated, and will be removed in version 2022.6. - """ - report( - f"accessed discovery_info['{name}'] instead of discovery_info.{name}; " - "this will fail in version 2022.6", - exclude_integrations={DOMAIN}, - error_if_core=False, - ) - return getattr(self, name) - def human_readable_device_name( device: str, diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index 8df86b3e5a8..d2a2d2ba8ca 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -99,6 +99,13 @@ PAUSED = "paused" COLLECTING = "collecting" +def validate_is_number(value): + """Validate value is a number.""" + if is_number(value): + return value + raise vol.Invalid("Value is not a number") + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -167,7 +174,7 @@ async def async_setup_entry( platform.async_register_entity_service( SERVICE_CALIBRATE_METER, - {vol.Required(ATTR_VALUE): vol.Coerce(Decimal)}, + {vol.Required(ATTR_VALUE): validate_is_number}, "async_calibrate", ) @@ -244,7 +251,7 @@ async def async_setup_platform( platform.async_register_entity_service( SERVICE_CALIBRATE_METER, - {vol.Required(ATTR_VALUE): vol.Coerce(Decimal)}, + {vol.Required(ATTR_VALUE): validate_is_number}, "async_calibrate", ) @@ -446,8 +453,8 @@ class UtilityMeterSensor(RestoreSensor): async def async_calibrate(self, value): """Calibrate the Utility Meter with a given value.""" - _LOGGER.debug("Calibrate %s = %s", self._name, value) - self._state = value + _LOGGER.debug("Calibrate %s = %s type(%s)", self._name, value, type(value)) + self._state = Decimal(str(value)) self.async_write_ha_state() async def async_added_to_hass(self): diff --git a/homeassistant/components/utility_meter/translations/es.json b/homeassistant/components/utility_meter/translations/es.json new file mode 100644 index 00000000000..bea05df125d --- /dev/null +++ b/homeassistant/components/utility_meter/translations/es.json @@ -0,0 +1,29 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Ciclo de reinicio del contador", + "delta_values": "Valores delta", + "source": "Sensor de entrada", + "tariffs": "Tarifas soportadas" + }, + "data_description": { + "net_consumption": "Act\u00edvalo si es un contador limpio, es decir, puede aumentar y disminuir.", + "offset": "Desplaza el d\u00eda de restablecimiento mensual del contador.", + "tariffs": "Lista de tarifas admitidas, d\u00e9jala en blanco si utilizas una \u00fanica tarifa." + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Sensor de entrada" + } + } + } + }, + "title": "Contador" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/ja.json b/homeassistant/components/utility_meter/translations/ja.json index 90c175f16ec..ec12f2310be 100644 --- a/homeassistant/components/utility_meter/translations/ja.json +++ b/homeassistant/components/utility_meter/translations/ja.json @@ -15,7 +15,7 @@ "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" + "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\u767d\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" diff --git a/homeassistant/components/utility_meter/translations/ko.json b/homeassistant/components/utility_meter/translations/ko.json new file mode 100644 index 00000000000..00df29f1e0b --- /dev/null +++ b/homeassistant/components/utility_meter/translations/ko.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "\ubbf8\ud130\uae30 \ub9ac\uc14b \uc8fc\uae30", + "delta_values": "\ub378\ud0c0 \uac12", + "name": "\uc774\ub984", + "net_consumption": "\uc21c \uc18c\ube44\ub7c9", + "offset": "\ubbf8\ud130 \ub9ac\uc14b \uc624\ud504\uc14b", + "source": "\uc785\ub825 \uc13c\uc11c", + "tariffs": "\uc9c0\uc6d0\ub418\ub294 \uc694\uae08\uccb4\uacc4" + }, + "data_description": { + "delta_values": "\uc18c\uc2a4 \uac12\uc774 \uc808\ub300\uac12\uc774 \uc544\ub2cc \ub9c8\uc9c0\ub9c9 \uac12 \uc774\ud6c4\uc758 \ub378\ud0c0 \uac12\uc778 \uacbd\uc6b0 \ud65c\uc131\ud654\ud569\ub2c8\ub2e4.", + "net_consumption": "\uc18c\uc2a4\uac00 \ub124\ud2b8 \ubbf8\ud130\uc778 \uacbd\uc6b0 \ud65c\uc131\ud654\ud569\ub2c8\ub2e4. \uc989, \uc99d\uac00\ud558\uac70\ub098 \uac10\uc18c\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "offset": "\uc6d4\ubcc4 \ubbf8\ud130\uae30 \ub9ac\uc14b \ub0a0\uc9dc\ub97c \uc624\ud504\uc14b\ud569\ub2c8\ub2e4.", + "tariffs": "\uc9c0\uc6d0\ub418\ub294 \uc694\uae08\uccb4\uacc4, \ub2e8\uc77c \uc694\uae08\uccb4\uacc4\ub9cc \ud544\uc694\ud55c \uacbd\uc6b0 \ube44\uc6cc \ub461\ub2c8\ub2e4." + }, + "description": "\uae30\uac04 \ub3d9\uc548 \uc18c\ube44\ub41c \uc790\uc6d0\ub7c9(\uc608: \uc5d0\ub108\uc9c0, \uac00\uc2a4, \ubb3c, \ub09c\ubc29)\uc744 \ucd94\uc801\ud558\ub294 \uc13c\uc11c\ub97c \ub9cc\ub4ed\ub2c8\ub2e4. \uc720\ud2f8\ub9ac\ud2f0 \ubbf8\ud130 \uc13c\uc11c\ub294 \uc694\uae08\uccb4\uacc4\uc5d0 \ub530\ub978 \ubd84\ub958\ub97c \uc9c0\uc6d0\ud569\ub2c8\ub2e4. \uc774 \uacbd\uc6b0 \uac01 \uc694\uae08\uccb4\uacc4\uc5d0 \ub300\ud574 \uac01\uac01\uc758 \uc13c\uc11c\uac00 \uc0dd\uc131\ub418\uace0 \ud574\ub2f9 \uc694\uae08\uccb4\uacc4\ub97c \uc0ac\uc6a9\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "\uc720\ud2f8\ub9ac\ud2f0 \ubbf8\ud130 \ucd94\uac00" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "\uc785\ub825 \uc13c\uc11c" + } + } + } + }, + "title": "\uc720\ud2f8\ub9ac\ud2f0 \ubbf8\ud130" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/sv.json b/homeassistant/components/utility_meter/translations/sv.json new file mode 100644 index 00000000000..38d433e7b42 --- /dev/null +++ b/homeassistant/components/utility_meter/translations/sv.json @@ -0,0 +1,3 @@ +{ + "title": "Tj\u00e4nsten \u00e4r redan konfigurerad" +} \ No newline at end of file diff --git a/homeassistant/components/vacuum/device_action.py b/homeassistant/components/vacuum/device_action.py index 702f3fe7439..e8fe53b08ae 100644 --- a/homeassistant/components/vacuum/device_action.py +++ b/homeassistant/components/vacuum/device_action.py @@ -13,6 +13,7 @@ from homeassistant.const import ( from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from . import DOMAIN, SERVICE_RETURN_TO_BASE, SERVICE_START @@ -30,7 +31,7 @@ async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Vacuum devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) actions = [] # Get all the integrations entities for this device @@ -51,7 +52,10 @@ async def async_get_actions( async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/vacuum/device_condition.py b/homeassistant/components/vacuum/device_condition.py index 7a973c93694..fa76dd800ec 100644 --- a/homeassistant/components/vacuum/device_condition.py +++ b/homeassistant/components/vacuum/device_condition.py @@ -32,7 +32,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Vacuum devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) conditions = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/vacuum/device_trigger.py b/homeassistant/components/vacuum/device_trigger.py index 25a874a1e69..4b8ec2fc08d 100644 --- a/homeassistant/components/vacuum/device_trigger.py +++ b/homeassistant/components/vacuum/device_trigger.py @@ -1,8 +1,6 @@ """Provides device automations for Vacuum.""" from __future__ import annotations -from typing import Any - import voluptuous as vol from homeassistant.components.automation import ( @@ -38,9 +36,9 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """List device triggers for Vacuum devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers = [] # Get all the integrations entities for this device diff --git a/homeassistant/components/vallox/__init__.py b/homeassistant/components/vallox/__init__.py index 23e5cb53f97..a69542596c8 100644 --- a/homeassistant/components/vallox/__init__.py +++ b/homeassistant/components/vallox/__init__.py @@ -5,14 +5,16 @@ from dataclasses import dataclass, field from datetime import date import ipaddress import logging -from typing import Any, NamedTuple +from typing import Any, NamedTuple, cast from uuid import UUID from vallox_websocket_api import PROFILE as VALLOX_PROFILE, Vallox from vallox_websocket_api.exceptions import ValloxApiException from vallox_websocket_api.vallox import ( - get_next_filter_change_date as calculate_next_filter_change_date, - get_uuid as calculate_uuid, + get_model as _api_get_model, + get_next_filter_change_date as _api_get_next_filter_change_date, + get_sw_version as _api_get_sw_version, + get_uuid as _api_get_uuid, ) import voluptuous as vol @@ -20,8 +22,13 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_NAME, Platform from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.typing import ConfigType, StateType -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) from .const import ( DEFAULT_FAN_SPEED_AWAY, @@ -114,16 +121,32 @@ class ValloxState: return value - def get_uuid(self) -> UUID | None: + @property + def model(self) -> str | None: + """Return the model, if any.""" + model = cast(str, _api_get_model(self.metric_cache)) + + if model == "Unknown": + return None + + return model + + @property + def sw_version(self) -> str: + """Return the SW version.""" + return cast(str, _api_get_sw_version(self.metric_cache)) + + @property + def uuid(self) -> UUID | None: """Return cached UUID value.""" - uuid = calculate_uuid(self.metric_cache) + uuid = _api_get_uuid(self.metric_cache) if not isinstance(uuid, UUID): raise ValueError return uuid def get_next_filter_change_date(self) -> date | None: """Return the next filter change date.""" - next_filter_change_date = calculate_next_filter_change_date(self.metric_cache) + next_filter_change_date = _api_get_next_filter_change_date(self.metric_cache) if not isinstance(next_filter_change_date, date): return None @@ -291,3 +314,22 @@ class ValloxServiceHandler: # be observed by all parties involved. if result: await self._coordinator.async_request_refresh() + + +class ValloxEntity(CoordinatorEntity[ValloxDataUpdateCoordinator]): + """Representation of a Vallox entity.""" + + def __init__(self, name: str, coordinator: ValloxDataUpdateCoordinator) -> None: + """Initialize a Vallox entity.""" + super().__init__(coordinator) + + self._device_uuid = self.coordinator.data.uuid + assert self.coordinator.config_entry is not None + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, str(self._device_uuid))}, + manufacturer=DEFAULT_NAME, + model=self.coordinator.data.model, + name=name, + sw_version=self.coordinator.data.sw_version, + configuration_url=f"http://{self.coordinator.config_entry.data[CONF_HOST]}", + ) diff --git a/homeassistant/components/vallox/binary_sensor.py b/homeassistant/components/vallox/binary_sensor.py index 348bad97158..762b63c0c1d 100644 --- a/homeassistant/components/vallox/binary_sensor.py +++ b/homeassistant/components/vallox/binary_sensor.py @@ -9,19 +9,18 @@ from homeassistant.components.binary_sensor import ( ) 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 homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import ValloxDataUpdateCoordinator +from . import ValloxDataUpdateCoordinator, ValloxEntity from .const import DOMAIN -class ValloxBinarySensor( - CoordinatorEntity[ValloxDataUpdateCoordinator], BinarySensorEntity -): +class ValloxBinarySensor(ValloxEntity, BinarySensorEntity): """Representation of a Vallox binary sensor.""" entity_description: ValloxBinarySensorEntityDescription + _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( self, @@ -30,14 +29,12 @@ class ValloxBinarySensor( description: ValloxBinarySensorEntityDescription, ) -> None: """Initialize the Vallox binary sensor.""" - super().__init__(coordinator) + super().__init__(name, coordinator) self.entity_description = description self._attr_name = f"{name} {description.name}" - - uuid = self.coordinator.data.get_uuid() - self._attr_unique_id = f"{uuid}-{description.key}" + self._attr_unique_id = f"{self._device_uuid}-{description.key}" @property def is_on(self) -> bool | None: diff --git a/homeassistant/components/vallox/fan.py b/homeassistant/components/vallox/fan.py index 85b3f501c85..6872acbb5b7 100644 --- a/homeassistant/components/vallox/fan.py +++ b/homeassistant/components/vallox/fan.py @@ -17,9 +17,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import ValloxDataUpdateCoordinator +from . import ValloxDataUpdateCoordinator, ValloxEntity from .const import ( DOMAIN, METRIC_KEY_MODE, @@ -80,7 +79,7 @@ async def async_setup_entry( async_add_entities([device]) -class ValloxFan(CoordinatorEntity[ValloxDataUpdateCoordinator], FanEntity): +class ValloxFan(ValloxEntity, FanEntity): """Representation of the fan.""" _attr_supported_features = FanEntityFeature.PRESET_MODE @@ -92,13 +91,12 @@ class ValloxFan(CoordinatorEntity[ValloxDataUpdateCoordinator], FanEntity): coordinator: ValloxDataUpdateCoordinator, ) -> None: """Initialize the fan.""" - super().__init__(coordinator) + super().__init__(name, coordinator) self._client = client self._attr_name = name - - self._attr_unique_id = str(self.coordinator.data.get_uuid()) + self._attr_unique_id = str(self._device_uuid) @property def preset_modes(self) -> list[str]: diff --git a/homeassistant/components/vallox/sensor.py b/homeassistant/components/vallox/sensor.py index 92f0bc32e76..54a010c3e3d 100644 --- a/homeassistant/components/vallox/sensor.py +++ b/homeassistant/components/vallox/sensor.py @@ -17,12 +17,12 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType -from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import dt -from . import ValloxDataUpdateCoordinator +from . import ValloxDataUpdateCoordinator, ValloxEntity from .const import ( DOMAIN, METRIC_KEY_MODE, @@ -32,10 +32,11 @@ from .const import ( ) -class ValloxSensor(CoordinatorEntity[ValloxDataUpdateCoordinator], SensorEntity): +class ValloxSensor(ValloxEntity, SensorEntity): """Representation of a Vallox sensor.""" entity_description: ValloxSensorEntityDescription + _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( self, @@ -44,14 +45,12 @@ class ValloxSensor(CoordinatorEntity[ValloxDataUpdateCoordinator], SensorEntity) description: ValloxSensorEntityDescription, ) -> None: """Initialize the Vallox sensor.""" - super().__init__(coordinator) + super().__init__(name, coordinator) self.entity_description = description self._attr_name = f"{name} {description.name}" - - uuid = self.coordinator.data.get_uuid() - self._attr_unique_id = f"{uuid}-{description.key}" + self._attr_unique_id = f"{self._device_uuid}-{description.key}" @property def native_value(self) -> StateType | datetime: diff --git a/homeassistant/components/vallox/translations/nl.json b/homeassistant/components/vallox/translations/nl.json index b8b5b6bdc57..867efc3ddc9 100644 --- a/homeassistant/components/vallox/translations/nl.json +++ b/homeassistant/components/vallox/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd", - "cannot_connect": "Kon niet verbinden", + "already_configured": "Dienst is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", "invalid_host": "Ongeldige hostnaam of IP-adres", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/vasttrafik/sensor.py b/homeassistant/components/vasttrafik/sensor.py index 5e446106312..bec7959f9c8 100644 --- a/homeassistant/components/vasttrafik/sensor.py +++ b/homeassistant/components/vasttrafik/sensor.py @@ -139,7 +139,7 @@ class VasttrafikDepartureSensor(SensorEntity): if not self._departureboard: _LOGGER.debug( - "No departures from departure station %s " "to destination station %s", + "No departures from departure station %s to destination station %s", self._departure["station_name"], self._heading["station_name"] if self._heading else "ANY", ) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index ae50609baf1..f759eea0a34 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2022.2.4"], + "requirements": ["velbus-aio==2022.5.1"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "dependencies": ["usb"], diff --git a/homeassistant/components/velbus/translations/es.json b/homeassistant/components/velbus/translations/es.json index cb600d577e7..ed2585a03e9 100644 --- a/homeassistant/components/velbus/translations/es.json +++ b/homeassistant/components/velbus/translations/es.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "name": "El nombre de esta conexi\u00f3n velbus", + "name": "Nombre de la conexi\u00f3n Velbus", "port": "Cadena de conexi\u00f3n" }, - "title": "Definir el tipo de conexi\u00f3n velbus" + "title": "Tipo de conexi\u00f3n Velbus" } } } diff --git a/homeassistant/components/venstar/sensor.py b/homeassistant/components/venstar/sensor.py index 824774f3e31..723b26d1956 100644 --- a/homeassistant/components/venstar/sensor.py +++ b/homeassistant/components/venstar/sensor.py @@ -12,7 +12,13 @@ from homeassistant.components.sensor import ( SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT, TIME_MINUTES +from homeassistant.const import ( + CONCENTRATION_PARTS_PER_MILLION, + PERCENTAGE, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + TIME_MINUTES, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -58,7 +64,7 @@ class VenstarSensorTypeMixin: value_fn: Callable[[Any, Any], Any] name_fn: Callable[[Any, Any], str] - uom_fn: Callable[[Any], str] + uom_fn: Callable[[Any], str | None] @dataclass @@ -140,7 +146,7 @@ class VenstarSensor(VenstarEntity, SensorEntity): return self.entity_description.value_fn(self.coordinator, self.sensor_name) @property - def native_unit_of_measurement(self) -> str: + def native_unit_of_measurement(self) -> str | None: """Return unit of measurement the value is expressed in.""" return self.entity_description.uom_fn(self.coordinator) @@ -150,7 +156,7 @@ SENSOR_ENTITIES: tuple[VenstarSensorEntityDescription, ...] = ( key="hum", device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, - uom_fn=lambda coordinator: PERCENTAGE, + uom_fn=lambda _: PERCENTAGE, value_fn=lambda coordinator, sensor_name: coordinator.client.get_sensor( sensor_name, "hum" ), @@ -166,11 +172,31 @@ SENSOR_ENTITIES: tuple[VenstarSensorEntityDescription, ...] = ( ), name_fn=lambda coordinator, sensor_name: f"{coordinator.client.name} {sensor_name.replace(' Temp', '')} Temperature", ), + VenstarSensorEntityDescription( + key="co2", + device_class=SensorDeviceClass.CO2, + state_class=SensorStateClass.MEASUREMENT, + uom_fn=lambda _: CONCENTRATION_PARTS_PER_MILLION, + value_fn=lambda coordinator, sensor_name: coordinator.client.get_sensor( + sensor_name, "co2" + ), + name_fn=lambda coordinator, sensor_name: f"{coordinator.client.name} {sensor_name} CO2", + ), + VenstarSensorEntityDescription( + key="iaq", + device_class=SensorDeviceClass.AQI, + state_class=SensorStateClass.MEASUREMENT, + uom_fn=lambda _: None, + value_fn=lambda coordinator, sensor_name: coordinator.client.get_sensor( + sensor_name, "iaq" + ), + name_fn=lambda coordinator, sensor_name: f"{coordinator.client.name} {sensor_name} IAQ", + ), VenstarSensorEntityDescription( key="battery", device_class=SensorDeviceClass.BATTERY, state_class=SensorStateClass.MEASUREMENT, - uom_fn=lambda coordinator: PERCENTAGE, + uom_fn=lambda _: PERCENTAGE, value_fn=lambda coordinator, sensor_name: coordinator.client.get_sensor( sensor_name, "battery" ), @@ -181,7 +207,7 @@ SENSOR_ENTITIES: tuple[VenstarSensorEntityDescription, ...] = ( RUNTIME_ENTITY = VenstarSensorEntityDescription( key="runtime", state_class=SensorStateClass.MEASUREMENT, - uom_fn=lambda coordinator: TIME_MINUTES, + uom_fn=lambda _: TIME_MINUTES, value_fn=lambda coordinator, sensor_name: coordinator.runtimes[-1][sensor_name], name_fn=lambda coordinator, sensor_name: f"{coordinator.client.name} {RUNTIME_ATTRIBUTES[sensor_name]} Runtime", ) diff --git a/homeassistant/components/venstar/translations/nl.json b/homeassistant/components/venstar/translations/nl.json index 3c8a61faf20..b39a0f356e1 100644 --- a/homeassistant/components/venstar/translations/nl.json +++ b/homeassistant/components/venstar/translations/nl.json @@ -12,8 +12,8 @@ "data": { "host": "Host", "password": "Wachtwoord", - "pin": "PIN-code", - "ssl": "Gebruik een SSL-certificaat", + "pin": "Pincode", + "ssl": "Maakt gebruik van een SSL-certificaat", "username": "Gebruikersnaam" }, "title": "Maak verbinding met de Venstar-thermostaat" diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index d923861112b..2dda8fdb7c4 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -46,15 +46,18 @@ _LOGGER = logging.getLogger(__name__) VERA_ID_LIST_SCHEMA = vol.Schema([int]) CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_CONTROLLER): cv.url, - vol.Optional(CONF_EXCLUDE, default=[]): VERA_ID_LIST_SCHEMA, - vol.Optional(CONF_LIGHTS, default=[]): VERA_ID_LIST_SCHEMA, - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CONTROLLER): cv.url, + vol.Optional(CONF_EXCLUDE, default=[]): VERA_ID_LIST_SCHEMA, + vol.Optional(CONF_LIGHTS, default=[]): VERA_ID_LIST_SCHEMA, + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index b2c4fe5e5db..319dcd031d0 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -14,7 +14,7 @@ from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE from homeassistant.core import callback -from homeassistant.helpers.entity_registry import EntityRegistry +from homeassistant.helpers import entity_registry as er from .const import CONF_CONTROLLER, CONF_LEGACY_UNIQUE_ID, DOMAIN @@ -119,9 +119,7 @@ class VeraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # If there are entities with the legacy unique_id, then this imported config # should also use the legacy unique_id for entity creation. - entity_registry: EntityRegistry = ( - await self.hass.helpers.entity_registry.async_get_registry() - ) + entity_registry = er.async_get(self.hass) use_legacy_unique_id = ( len( [ diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py index f54cfb8ddcc..e91492a934f 100644 --- a/homeassistant/components/vera/light.py +++ b/homeassistant/components/vera/light.py @@ -9,8 +9,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, ENTITY_ID_FORMAT, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -63,11 +62,18 @@ class VeraLight(VeraDevice[veraApi.VeraDimmer], LightEntity): return self._color @property - def supported_features(self) -> int: - """Flag supported features.""" - if self._color: - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR - return SUPPORT_BRIGHTNESS + def color_mode(self) -> ColorMode: + """Return the color mode of the light.""" + if self.vera_device.is_dimmable: + if self._color: + return ColorMode.HS + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF + + @property + def supported_color_modes(self) -> set[ColorMode]: + """Flag supported color modes.""" + return {self.color_mode} def turn_on(self, **kwargs: Any) -> None: """Turn the light on.""" diff --git a/homeassistant/components/vera/manifest.json b/homeassistant/components/vera/manifest.json index 5a87ae29483..517d19a89d9 100644 --- a/homeassistant/components/vera/manifest.json +++ b/homeassistant/components/vera/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/vera", "requirements": ["pyvera==0.3.13"], - "codeowners": ["@pavoni"], + "codeowners": [], "iot_class": "local_polling", "loggers": ["pyvera"] } diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 0e28298b2e8..8f9556643f8 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -99,6 +99,11 @@ class VerisureDoorlock(CoordinatorEntity[VerisureDataUpdateCoordinator], LockEnt """Last change triggered by.""" return self.coordinator.data["locks"][self.serial_number].get("userString") + @property + def changed_method(self) -> str: + """Last change method.""" + return self.coordinator.data["locks"][self.serial_number]["method"] + @property def code_format(self) -> str: """Return the required six digit code.""" @@ -112,6 +117,11 @@ class VerisureDoorlock(CoordinatorEntity[VerisureDataUpdateCoordinator], LockEnt == "LOCKED" ) + @property + def extra_state_attributes(self): + """Return the state attributes.""" + return {"method": self.changed_method} + async def async_unlock(self, **kwargs) -> None: """Send unlock command.""" code = kwargs.get( diff --git a/homeassistant/components/verisure/translations/es.json b/homeassistant/components/verisure/translations/es.json index 7ec2812d964..19517bb7ed0 100644 --- a/homeassistant/components/verisure/translations/es.json +++ b/homeassistant/components/verisure/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { diff --git a/homeassistant/components/verisure/translations/nl.json b/homeassistant/components/verisure/translations/nl.json index d0519e584fd..1a23da57319 100644 --- a/homeassistant/components/verisure/translations/nl.json +++ b/homeassistant/components/verisure/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/version/__init__.py b/homeassistant/components/version/__init__.py index 75545adb8db..22dbad36d7b 100644 --- a/homeassistant/components/version/__init__.py +++ b/homeassistant/components/version/__init__.py @@ -1,6 +1,8 @@ """The Version integration.""" from __future__ import annotations +import logging + from pyhaversion import HaVersion from homeassistant.config_entries import ConfigEntry @@ -18,16 +20,29 @@ from .const import ( ) from .coordinator import VersionDataUpdateCoordinator +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the version integration from a config entry.""" + + board = entry.data[CONF_BOARD] + + if board not in BOARD_MAP: + _LOGGER.error( + 'Board "%s" is (no longer) valid. Please remove the integration "%s"', + board, + entry.title, + ) + return False + coordinator = VersionDataUpdateCoordinator( hass=hass, api=HaVersion( session=async_get_clientsession(hass), source=entry.data[CONF_SOURCE], image=entry.data[CONF_IMAGE], - board=BOARD_MAP[entry.data[CONF_BOARD]], + board=BOARD_MAP[board], channel=entry.data[CONF_CHANNEL].lower(), ), ) diff --git a/homeassistant/components/version/const.py b/homeassistant/components/version/const.py index 419e49d7240..1693f79ec64 100644 --- a/homeassistant/components/version/const.py +++ b/homeassistant/components/version/const.py @@ -61,8 +61,6 @@ HA_VERSION_SOURCES: Final[list[str]] = [source.value for source in HaVersionSour BOARD_MAP: Final[dict[str, str]] = { "OVA": "ova", - "RaspberryPi": "rpi", - "RaspberryPi Zero-W": "rpi0-w", "RaspberryPi 2": "rpi2", "RaspberryPi 3": "rpi3", "RaspberryPi 3 64bit": "rpi3-64", @@ -73,8 +71,10 @@ BOARD_MAP: Final[dict[str, str]] = { "ODROID C4": "odroid-c4", "ODROID N2": "odroid-n2", "ODROID XU4": "odroid-xu4", + "Generic AArch64": "generic-aarch64", "Generic x86-64": "generic-x86-64", - "Intel NUC": "intel-nuc", + "Home Assistant Yellow": "yellow", + "Khadas VIM3": "khadas-vim3", } VALID_BOARDS: Final[list[str]] = list(BOARD_MAP) diff --git a/homeassistant/components/vesync/translations/nl.json b/homeassistant/components/vesync/translations/nl.json index ab330237afd..36e661eca21 100644 --- a/homeassistant/components/vesync/translations/nl.json +++ b/homeassistant/components/vesync/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slecht \u00e9\u00e9n configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "invalid_auth": "Ongeldige authenticatie" diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index 4af36835ed2..221f3a4374a 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -7,19 +7,14 @@ import logging from PyViCare.PyViCare import PyViCare from PyViCare.PyViCareDevice import Device -import voluptuous as vol -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.storage import STORAGE_DIR -from homeassistant.helpers.typing import ConfigType from .const import ( - CONF_CIRCUIT, CONF_HEATING_TYPE, - DEFAULT_HEATING_TYPE, DEFAULT_SCAN_INTERVAL, DOMAIN, HEATING_TYPE_TO_CREATOR_METHOD, @@ -39,50 +34,6 @@ class ViCareRequiredKeysMixin: value_getter: Callable[[Device], bool] -CONFIG_SCHEMA = vol.Schema( - vol.All( - cv.deprecated(DOMAIN), - { - DOMAIN: vol.All( - cv.deprecated(CONF_CIRCUIT), - cv.deprecated(DOMAIN), - vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Optional( - CONF_CIRCUIT - ): int, # Ignored: All circuits are now supported. Will be removed when switching to Setup via UI. - vol.Optional( - CONF_HEATING_TYPE, default=DEFAULT_HEATING_TYPE.value - ): vol.In([e.value for e in HeatingType]), - } - ), - ) - }, - ), - extra=vol.ALLOW_EXTRA, -) - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the ViCare component from yaml.""" - if DOMAIN not in config: - # Setup via UI. No need to continue yaml-based setup - return True - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=config[DOMAIN], - ) - ) - - return True - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up from config entry.""" _LOGGER.debug("Setting up ViCare component") diff --git a/homeassistant/components/vicare/config_flow.py b/homeassistant/components/vicare/config_flow.py index 0f0e2a85b3c..a0feb8f38ea 100644 --- a/homeassistant/components/vicare/config_flow.py +++ b/homeassistant/components/vicare/config_flow.py @@ -16,7 +16,6 @@ from homeassistant.helpers.device_registry import format_mac from . import vicare_login from .const import ( - CONF_CIRCUIT, CONF_HEATING_TYPE, DEFAULT_HEATING_TYPE, DOMAIN, @@ -32,7 +31,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_user(self, user_input: dict[str, Any] | None = None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Invoke when a user initiates a flow via the user interface.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -75,17 +76,3 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="single_instance_allowed") return await self.async_step_user() - - async def async_step_import(self, import_info): - """Handle a flow initiated by a YAML config import.""" - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - - # Remove now unsupported config parameters - import_info.pop(CONF_CIRCUIT, None) - - # CONF_HEATING_TYPE is now required but was optional in yaml config. Add if missing. - if import_info.get(CONF_HEATING_TYPE) is None: - import_info[CONF_HEATING_TYPE] = DEFAULT_HEATING_TYPE.value - - return await self.async_step_user(import_info) diff --git a/homeassistant/components/vicare/manifest.json b/homeassistant/components/vicare/manifest.json index 3b3058058b6..575ca35729b 100644 --- a/homeassistant/components/vicare/manifest.json +++ b/homeassistant/components/vicare/manifest.json @@ -3,7 +3,7 @@ "name": "Viessmann ViCare", "documentation": "https://www.home-assistant.io/integrations/vicare", "codeowners": ["@oischinger"], - "requirements": ["PyViCare==2.16.1"], + "requirements": ["PyViCare==2.16.2"], "iot_class": "cloud_polling", "config_flow": true, "dhcp": [ diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index 249cadaee86..60a39b454a2 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -27,6 +27,7 @@ from homeassistant.const import ( POWER_WATT, TEMP_CELSIUS, TIME_HOURS, + VOLUME_CUBIC_METERS, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -132,6 +133,126 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( unit_getter=lambda api: api.getGasConsumptionHeatingUnit(), state_class=SensorStateClass.TOTAL_INCREASING, ), + ViCareSensorEntityDescription( + key="gas_summary_consumption_heating_currentday", + name="Heating gas consumption current day", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + value_getter=lambda api: api.getGasSummaryConsumptionHeatingCurrentDay(), + unit_getter=lambda api: api.getGasSummaryConsumptionHeatingUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="gas_summary_consumption_heating_currentmonth", + name="Heating gas consumption current month", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + value_getter=lambda api: api.getGasSummaryConsumptionHeatingCurrentMonth(), + unit_getter=lambda api: api.getGasSummaryConsumptionHeatingUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="gas_summary_consumption_heating_currentyear", + name="Heating gas consumption current year", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + value_getter=lambda api: api.getGasSummaryConsumptionHeatingCurrentYear(), + unit_getter=lambda api: api.getGasSummaryConsumptionHeatingUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="hotwater_gas_summary_consumption_heating_currentday", + name="Hot water gas consumption current day", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + value_getter=lambda api: api.getGasSummaryConsumptionDomesticHotWaterCurrentDay(), + unit_getter=lambda api: api.getGasSummaryConsumptionDomesticHotWaterUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="hotwater_gas_summary_consumption_heating_currentmonth", + name="Hot water gas consumption current month", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + value_getter=lambda api: api.getGasSummaryConsumptionDomesticHotWaterCurrentMonth(), + unit_getter=lambda api: api.getGasSummaryConsumptionDomesticHotWaterUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="hotwater_gas_summary_consumption_heating_currentyear", + name="Hot water gas consumption current year", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + value_getter=lambda api: api.getGasSummaryConsumptionDomesticHotWaterCurrentYear(), + unit_getter=lambda api: api.getGasSummaryConsumptionDomesticHotWaterUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="hotwater_gas_summary_consumption_heating_lastsevendays", + name="Hot water gas consumption last seven days", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + value_getter=lambda api: api.getGasSummaryConsumptionDomesticHotWaterLastSevenDays(), + unit_getter=lambda api: api.getGasSummaryConsumptionDomesticHotWaterUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="energy_summary_consumption_heating_currentday", + name="Energy consumption of gas heating current day", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerSummaryConsumptionHeatingCurrentDay(), + unit_getter=lambda api: api.getPowerSummaryConsumptionHeatingUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="energy_summary_consumption_heating_currentmonth", + name="Energy consumption of gas heating current month", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerSummaryConsumptionHeatingCurrentMonth(), + unit_getter=lambda api: api.getPowerSummaryConsumptionHeatingUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="energy_summary_consumption_heating_currentyear", + name="Energy consumption of gas heating current year", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerSummaryConsumptionHeatingCurrentYear(), + unit_getter=lambda api: api.getPowerSummaryConsumptionHeatingUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="energy_summary_consumption_heating_lastsevendays", + name="Energy consumption of gas heating last seven days", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerSummaryConsumptionHeatingLastSevenDays(), + unit_getter=lambda api: api.getPowerSummaryConsumptionHeatingUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="energy_dhw_summary_consumption_heating_currentday", + name="Energy consumption of hot water gas heating current day", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerSummaryConsumptionDomesticHotWaterCurrentDay(), + unit_getter=lambda api: api.getPowerSummaryConsumptionDomesticHotWaterUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="energy_dhw_summary_consumption_heating_currentmonth", + name="Energy consumption of hot water gas heating current month", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerSummaryConsumptionDomesticHotWaterCurrentMonth(), + unit_getter=lambda api: api.getPowerSummaryConsumptionDomesticHotWaterUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="energy_dhw_summary_consumption_heating_currentyear", + name="Energy consumption of hot water gas heating current year", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerSummaryConsumptionDomesticHotWaterCurrentYear(), + unit_getter=lambda api: api.getPowerSummaryConsumptionDomesticHotWaterUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), + ViCareSensorEntityDescription( + key="energy_summary_dhw_consumption_heating_lastsevendays", + name="Energy consumption of hot water gas heating last seven days", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + value_getter=lambda api: api.getPowerSummaryConsumptionDomesticHotWaterLastSevenDays(), + unit_getter=lambda api: api.getPowerSummaryConsumptionDomesticHotWaterUnit(), + state_class=SensorStateClass.TOTAL_INCREASING, + ), ViCareSensorEntityDescription( key="power_production_current", name="Power production current", @@ -142,7 +263,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="power_production_today", - name="Power production today", + name="Energy production today", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getPowerProductionToday(), device_class=SensorDeviceClass.ENERGY, @@ -158,7 +279,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="power_production_this_month", - name="Power production this month", + name="Energy production this month", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getPowerProductionThisMonth(), device_class=SensorDeviceClass.ENERGY, @@ -166,7 +287,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="power_production_this_year", - name="Power production this year", + name="Energy production this year", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getPowerProductionThisYear(), device_class=SensorDeviceClass.ENERGY, @@ -190,7 +311,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="solar power production today", - name="Solar power production today", + name="Solar energy production today", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getSolarPowerProductionToday(), unit_getter=lambda api: api.getSolarPowerProductionUnit(), @@ -199,7 +320,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="solar power production this week", - name="Solar power production this week", + name="Solar energy production this week", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getSolarPowerProductionThisWeek(), unit_getter=lambda api: api.getSolarPowerProductionUnit(), @@ -208,7 +329,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="solar power production this month", - name="Solar power production this month", + name="Solar energy production this month", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getSolarPowerProductionThisMonth(), unit_getter=lambda api: api.getSolarPowerProductionUnit(), @@ -217,7 +338,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="solar power production this year", - name="Solar power production this year", + name="Solar energy production this year", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getSolarPowerProductionThisYear(), unit_getter=lambda api: api.getSolarPowerProductionUnit(), @@ -226,7 +347,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="power consumption today", - name="Power consumption today", + name="Energy consumption today", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getPowerConsumptionToday(), unit_getter=lambda api: api.getPowerConsumptionUnit(), @@ -244,7 +365,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="power consumption this month", - name="Power consumption this month", + name="Energy consumption this month", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getPowerConsumptionThisMonth(), unit_getter=lambda api: api.getPowerConsumptionUnit(), @@ -253,7 +374,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="power consumption this year", - name="Power consumption this year", + name="Energy consumption this year", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value_getter=lambda api: api.getPowerConsumptionThisYear(), unit_getter=lambda api: api.getPowerConsumptionUnit(), diff --git a/homeassistant/components/vicare/translations/af.json b/homeassistant/components/vicare/translations/af.json index 92321a6be86..74536dab793 100644 --- a/homeassistant/components/vicare/translations/af.json +++ b/homeassistant/components/vicare/translations/af.json @@ -4,7 +4,6 @@ "user": { "data": { "password": "\u5bc6\u7801", - "scan_interval": "\u626b\u63cf\u95f4\u9694\u65f6\u95f4(\u79d2)", "username": "\u7535\u5b50\u90ae\u7bb1" } } diff --git a/homeassistant/components/vicare/translations/bg.json b/homeassistant/components/vicare/translations/bg.json index c3178df6b87..242339c3815 100644 --- a/homeassistant/components/vicare/translations/bg.json +++ b/homeassistant/components/vicare/translations/bg.json @@ -12,12 +12,9 @@ "user": { "data": { "client_id": "API \u043a\u043b\u044e\u0447", - "name": "\u0418\u043c\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u0430", - "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043d\u0430 \u0441\u043a\u0430\u043d\u0438\u0440\u0430\u043d\u0435 (\u0441\u0435\u043a\u0443\u043d\u0434\u0438)", "username": "Email" - }, - "title": "{name}" + } } } } diff --git a/homeassistant/components/vicare/translations/ca.json b/homeassistant/components/vicare/translations/ca.json index a850cdf27fa..0fef2889375 100644 --- a/homeassistant/components/vicare/translations/ca.json +++ b/homeassistant/components/vicare/translations/ca.json @@ -13,13 +13,10 @@ "data": { "client_id": "Clau API", "heating_type": "Tipus d'escalfador", - "name": "Nom", "password": "Contrasenya", - "scan_interval": "Interval d'escaneig (segons)", "username": "Correu electr\u00f2nic" }, - "description": "Configura la integraci\u00f3 ViCare. Per generar la clau API, v\u00e9s a https://developer.viessmann.com", - "title": "{name}" + "description": "Configura la integraci\u00f3 ViCare. Per generar la clau API, v\u00e9s a https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/cs.json b/homeassistant/components/vicare/translations/cs.json index 5f29c738f07..332b6206db2 100644 --- a/homeassistant/components/vicare/translations/cs.json +++ b/homeassistant/components/vicare/translations/cs.json @@ -12,7 +12,6 @@ "user": { "data": { "client_id": "Kl\u00ed\u010d API", - "name": "Jm\u00e9no", "password": "Heslo", "username": "E-mail" } diff --git a/homeassistant/components/vicare/translations/de.json b/homeassistant/components/vicare/translations/de.json index 854c1cadead..1ef07bd2fc4 100644 --- a/homeassistant/components/vicare/translations/de.json +++ b/homeassistant/components/vicare/translations/de.json @@ -13,13 +13,10 @@ "data": { "client_id": "API-Schl\u00fcssel", "heating_type": "Art der Heizung", - "name": "Name", "password": "Passwort", - "scan_interval": "Scanintervall (Sekunden)", "username": "E-Mail" }, - "description": "Richte die ViCare-Integration ein. Um einen API-Schl\u00fcssel zu generieren, gehe auf https://developer.viessmann.com", - "title": "{name}" + "description": "Richte die ViCare-Integration ein. Um einen API-Schl\u00fcssel zu generieren, gehe auf https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/el.json b/homeassistant/components/vicare/translations/el.json index 4c4385ec88b..61ac6bdbb8f 100644 --- a/homeassistant/components/vicare/translations/el.json +++ b/homeassistant/components/vicare/translations/el.json @@ -13,13 +13,10 @@ "data": { "client_id": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", "heating_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)", "username": "Email" }, - "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 ViCare. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://developer.viessmann.com", - "title": "{name}" + "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 ViCare. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/en.json b/homeassistant/components/vicare/translations/en.json index ef7235f86f8..89fc4caeeaf 100644 --- a/homeassistant/components/vicare/translations/en.json +++ b/homeassistant/components/vicare/translations/en.json @@ -13,13 +13,10 @@ "data": { "client_id": "API Key", "heating_type": "Heating type", - "name": "Name", "password": "Password", - "scan_interval": "Scan Interval (seconds)", "username": "Email" }, - "description": "Set up ViCare integration. To generate API key go to https://developer.viessmann.com", - "title": "{name}" + "description": "Set up ViCare integration. To generate API key go to https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/es.json b/homeassistant/components/vicare/translations/es.json index a921723e893..6653fa3198e 100644 --- a/homeassistant/components/vicare/translations/es.json +++ b/homeassistant/components/vicare/translations/es.json @@ -13,13 +13,10 @@ "data": { "client_id": "Clave API", "heating_type": "Tipo de calefacci\u00f3n", - "name": "Nombre", "password": "Contrase\u00f1a", - "scan_interval": "Intervalo de exploraci\u00f3n (segundos)", "username": "Email" }, - "description": "Configure la integraci\u00f3n de ViCare. Para generar la clave API, vaya a https://developer.viessmann.com", - "title": "{name}" + "description": "Configure la integraci\u00f3n de ViCare. Para generar la clave API, vaya a https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/et.json b/homeassistant/components/vicare/translations/et.json index 9214a527580..453b9b691bb 100644 --- a/homeassistant/components/vicare/translations/et.json +++ b/homeassistant/components/vicare/translations/et.json @@ -13,13 +13,10 @@ "data": { "client_id": "API v\u00f5ti", "heating_type": "K\u00fcttere\u017eiim", - "name": "Nimi", "password": "Salas\u00f5na", - "scan_interval": "P\u00e4ringute intervall (sekundites)", "username": "E-posti aadress" }, - "description": "Seadista ViCare sidumine. API-v\u00f5tme loomiseks mine aadressile https://developer.viessmann.com", - "title": "{name}" + "description": "Seadista ViCare sidumine. API-v\u00f5tme loomiseks mine aadressile https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/fr.json b/homeassistant/components/vicare/translations/fr.json index b3dce5ec826..bdf85ff8007 100644 --- a/homeassistant/components/vicare/translations/fr.json +++ b/homeassistant/components/vicare/translations/fr.json @@ -13,13 +13,10 @@ "data": { "client_id": "Cl\u00e9 d'API", "heating_type": "Type de chauffage", - "name": "Nom", "password": "Mot de passe", - "scan_interval": "Intervalle de balayage (secondes)", "username": "Courriel" }, - "description": "Configurer l'int\u00e9gration ViCare. Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://developer.viessmann.com", - "title": "{name}" + "description": "Configurer l'int\u00e9gration ViCare. Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/he.json b/homeassistant/components/vicare/translations/he.json index db5a392f540..c64540395cd 100644 --- a/homeassistant/components/vicare/translations/he.json +++ b/homeassistant/components/vicare/translations/he.json @@ -12,11 +12,9 @@ "user": { "data": { "client_id": "\u05de\u05e4\u05ea\u05d7 API", - "name": "\u05e9\u05dd", "password": "\u05e1\u05d9\u05e1\u05de\u05d4", "username": "\u05d3\u05d5\u05d0\"\u05dc" - }, - "title": "{name}" + } } } } diff --git a/homeassistant/components/vicare/translations/hu.json b/homeassistant/components/vicare/translations/hu.json index 0e69a395a90..cc94705aad8 100644 --- a/homeassistant/components/vicare/translations/hu.json +++ b/homeassistant/components/vicare/translations/hu.json @@ -13,13 +13,10 @@ "data": { "client_id": "API kulcs", "heating_type": "F\u0171t\u00e9s t\u00edpusa", - "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", - "scan_interval": "Beolvas\u00e1si id\u0151k\u00f6z (m\u00e1sodperc)", "username": "E-mail" }, - "description": "A ViCare integr\u00e1ci\u00f3 be\u00e1ll\u00edt\u00e1sa. Az API-kulcs gener\u00e1l\u00e1s\u00e1hoz keresse fel a https://developer.viessmann.com webhelyet.", - "title": "{name}" + "description": "A ViCare integr\u00e1ci\u00f3 be\u00e1ll\u00edt\u00e1sa. Az API-kulcs gener\u00e1l\u00e1s\u00e1hoz keresse fel a https://developer.viessmann.com webhelyet." } } } diff --git a/homeassistant/components/vicare/translations/id.json b/homeassistant/components/vicare/translations/id.json index 7c69ec28d85..32d0813b672 100644 --- a/homeassistant/components/vicare/translations/id.json +++ b/homeassistant/components/vicare/translations/id.json @@ -13,13 +13,10 @@ "data": { "client_id": "Kunci API", "heating_type": "Jenis pemanasan", - "name": "Nama", "password": "Kata Sandi", - "scan_interval": "Interval Pindai (detik)", "username": "Email" }, - "description": "Siapkan integrasi ViCare. Untuk menghasilkan kunci API, buka https://developer.viessmann.com", - "title": "{name}" + "description": "Siapkan integrasi ViCare. Untuk menghasilkan kunci API, buka https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/it.json b/homeassistant/components/vicare/translations/it.json index 151e0c84509..c6917117a1f 100644 --- a/homeassistant/components/vicare/translations/it.json +++ b/homeassistant/components/vicare/translations/it.json @@ -13,13 +13,10 @@ "data": { "client_id": "Chiave API", "heating_type": "Modalit\u00e0 di riscaldamento", - "name": "Nome", "password": "Password", - "scan_interval": "Intervallo di scansione (secondi)", "username": "Email" }, - "description": "Configura l'integrazione ViCare. Per generare la chiave API vai su https://developer.viessmann.com", - "title": "{name}" + "description": "Configura l'integrazione ViCare. Per generare la chiave API vai su https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/ja.json b/homeassistant/components/vicare/translations/ja.json index f88edf8f359..aa7e159778c 100644 --- a/homeassistant/components/vicare/translations/ja.json +++ b/homeassistant/components/vicare/translations/ja.json @@ -13,13 +13,10 @@ "data": { "client_id": "API\u30ad\u30fc", "heating_type": "\u6696\u623f(\u52a0\u71b1)\u30bf\u30a4\u30d7", - "name": "\u540d\u524d", "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", - "scan_interval": "\u30b9\u30ad\u30e3\u30f3\u30a4\u30f3\u30bf\u30fc\u30d0\u30eb(\u79d2)", "username": "E\u30e1\u30fc\u30eb" }, - "description": "ViCare\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.viessmann.com \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "{name}" + "description": "ViCare\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.viessmann.com \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044" } } } diff --git a/homeassistant/components/vicare/translations/nl.json b/homeassistant/components/vicare/translations/nl.json index d16758be360..a3efaf0661e 100644 --- a/homeassistant/components/vicare/translations/nl.json +++ b/homeassistant/components/vicare/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "unknown": "Onverwachte fout" }, "error": { @@ -13,13 +13,10 @@ "data": { "client_id": "API-sleutel", "heating_type": "Verwarmingstype", - "name": "Naam", "password": "Wachtwoord", - "scan_interval": "Scaninterval (seconden)", "username": "E-mail" }, - "description": "ViCare integratie instellen. Ga naar https://developer.viessmann.com om een API-sleutel te genereren.", - "title": "{name}" + "description": "ViCare integratie instellen. Ga naar https://developer.viessmann.com om een API-sleutel te genereren." } } } diff --git a/homeassistant/components/vicare/translations/no.json b/homeassistant/components/vicare/translations/no.json index 6f2cde73484..dda5a48434b 100644 --- a/homeassistant/components/vicare/translations/no.json +++ b/homeassistant/components/vicare/translations/no.json @@ -13,13 +13,10 @@ "data": { "client_id": "API-n\u00f8kkel", "heating_type": "Oppvarmingstype", - "name": "Navn", "password": "Passord", - "scan_interval": "Skanneintervall (sekunder)", "username": "E-post" }, - "description": "Sett opp ViCare-integrasjon. Hvis du vil generere API-n\u00f8kkel, g\u00e5r du til https://developer.viessmann.com", - "title": "" + "description": "Sett opp ViCare-integrasjon. Hvis du vil generere API-n\u00f8kkel, g\u00e5r du til https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/pl.json b/homeassistant/components/vicare/translations/pl.json index e8374d7cc46..ae7bf6e5672 100644 --- a/homeassistant/components/vicare/translations/pl.json +++ b/homeassistant/components/vicare/translations/pl.json @@ -13,13 +13,10 @@ "data": { "client_id": "Klucz API", "heating_type": "Typ ogrzewania", - "name": "Nazwa", "password": "Has\u0142o", - "scan_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania (w sekundach)", "username": "Adres e-mail" }, - "description": "Skonfiguruj integracj\u0119 ViCare. Aby wygenerowa\u0107 klucz API wejd\u017a na https://developer.viessmann.com", - "title": "{name}" + "description": "Skonfiguruj integracj\u0119 ViCare. Aby wygenerowa\u0107 klucz API wejd\u017a na https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/pt-BR.json b/homeassistant/components/vicare/translations/pt-BR.json index 01504272dac..1fe4ffe5848 100644 --- a/homeassistant/components/vicare/translations/pt-BR.json +++ b/homeassistant/components/vicare/translations/pt-BR.json @@ -13,13 +13,10 @@ "data": { "client_id": "Chave da API", "heating_type": "Tipo de aquecimento", - "name": "Nome", "password": "Senha", - "scan_interval": "Intervalo de varredura (segundos)", "username": "Email" }, - "description": "Configure a integra\u00e7\u00e3o do ViCare. Para gerar a chave de API, acesse https://developer.viessmann.com", - "title": "{name}" + "description": "Configure a integra\u00e7\u00e3o do ViCare. Para gerar a chave de API, acesse https://developer.viessmann.com" } } } diff --git a/homeassistant/components/vicare/translations/ru.json b/homeassistant/components/vicare/translations/ru.json index 9ce8c37f956..434daeb8e4b 100644 --- a/homeassistant/components/vicare/translations/ru.json +++ b/homeassistant/components/vicare/translations/ru.json @@ -13,13 +13,10 @@ "data": { "client_id": "\u041a\u043b\u044e\u0447 API", "heating_type": "\u0422\u0438\u043f \u043e\u0442\u043e\u043f\u043b\u0435\u043d\u0438\u044f", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", "username": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b" }, - "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 ViCare. \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://developer.viessmann.com.", - "title": "{name}" + "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 ViCare. \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://developer.viessmann.com." } } } diff --git a/homeassistant/components/vicare/translations/sk.json b/homeassistant/components/vicare/translations/sk.json index d68d88fd2cd..1a2c2a47260 100644 --- a/homeassistant/components/vicare/translations/sk.json +++ b/homeassistant/components/vicare/translations/sk.json @@ -7,7 +7,6 @@ "user": { "data": { "client_id": "API k\u013e\u00fa\u010d", - "name": "N\u00e1zov", "username": "Email" } } diff --git a/homeassistant/components/vicare/translations/tr.json b/homeassistant/components/vicare/translations/tr.json index 0e3bd45bdf7..87cc440733d 100644 --- a/homeassistant/components/vicare/translations/tr.json +++ b/homeassistant/components/vicare/translations/tr.json @@ -13,13 +13,10 @@ "data": { "client_id": "API Anahtar\u0131", "heating_type": "Is\u0131tma tipi", - "name": "Ad", "password": "Parola", - "scan_interval": "Tarama Aral\u0131\u011f\u0131 (saniye)", "username": "E-posta" }, - "description": "ViCare entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://developer.viessmann.com adresine gidin.", - "title": "{name}" + "description": "ViCare entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://developer.viessmann.com adresine gidin." } } } diff --git a/homeassistant/components/vicare/translations/zh-Hant.json b/homeassistant/components/vicare/translations/zh-Hant.json index c3b8fc02c4f..4d18a4f11df 100644 --- a/homeassistant/components/vicare/translations/zh-Hant.json +++ b/homeassistant/components/vicare/translations/zh-Hant.json @@ -13,13 +13,10 @@ "data": { "client_id": "API \u91d1\u9470", "heating_type": "\u6696\u6c23\u985e\u5225", - "name": "\u540d\u7a31", "password": "\u5bc6\u78bc", - "scan_interval": "\u6383\u63cf\u9593\u8ddd\uff08\u79d2\uff09", "username": "\u96fb\u5b50\u90f5\u4ef6" }, - "description": "\u6b32\u8a2d\u5b9a ViCare \u6574\u5408\u3002\u8acb\u81f3 https://developer.viessmann.com \u7522\u751f API \u91d1\u9470", - "title": "{name}" + "description": "\u6b32\u8a2d\u5b9a ViCare \u6574\u5408\u3002\u8acb\u81f3 https://developer.viessmann.com \u7522\u751f API \u91d1\u9470" } } } diff --git a/homeassistant/components/vilfo/translations/ca.json b/homeassistant/components/vilfo/translations/ca.json index 827545f5b54..b068144df09 100644 --- a/homeassistant/components/vilfo/translations/ca.json +++ b/homeassistant/components/vilfo/translations/ca.json @@ -13,9 +13,7 @@ "data": { "access_token": "Token d'acc\u00e9s", "host": "Amfitri\u00f3" - }, - "description": "Configura la integraci\u00f3 de l'encaminador Vilfo. Necessites la seva IP o nom d'amfitri\u00f3 i el token d'acc\u00e9s de l'API. Per a m\u00e9s informaci\u00f3, visita: https://www.home-assistant.io/integrations/vilfo", - "title": "Connexi\u00f3 amb l'encaminador Vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/cs.json b/homeassistant/components/vilfo/translations/cs.json index b6735bd64cb..77566bbc59e 100644 --- a/homeassistant/components/vilfo/translations/cs.json +++ b/homeassistant/components/vilfo/translations/cs.json @@ -13,9 +13,7 @@ "data": { "access_token": "P\u0159\u00edstupov\u00fd token", "host": "Hostitel" - }, - "description": "Nastaven\u00ed integrace routeru Vilfo. Pot\u0159ebujete n\u00e1zev hostitele/IP adresu routeru Vilfo a p\u0159\u00edstupov\u00fd API token. Dal\u0161\u00ed informace o t\u00e9to integraci a o tom, jak tyto podrobnosti z\u00edskat, najdete na adrese: https://www.home-assistant.io/integrations/vilfo", - "title": "P\u0159ipojen\u00ed k routeru Vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/da.json b/homeassistant/components/vilfo/translations/da.json index 0ef9c619c91..014c8aee190 100644 --- a/homeassistant/components/vilfo/translations/da.json +++ b/homeassistant/components/vilfo/translations/da.json @@ -13,9 +13,7 @@ "data": { "access_token": "Adgangstoken til Vilfo-router-API", "host": "Router-v\u00e6rtsnavn eller IP" - }, - "description": "Indstil Vilfo-routerintegration. Du har brug for dit Vilfo-routerv\u00e6rtsnavn/IP og et API-adgangstoken. For yderligere information om denne integration og hvordan du f\u00e5r disse detaljer, kan du bes\u00f8ge: https://www.home-assistant.io/integrations/vilfo", - "title": "Opret forbindelse til Vilfo-router" + } } } } diff --git a/homeassistant/components/vilfo/translations/de.json b/homeassistant/components/vilfo/translations/de.json index 798410c56e3..6e20a86a64a 100644 --- a/homeassistant/components/vilfo/translations/de.json +++ b/homeassistant/components/vilfo/translations/de.json @@ -13,9 +13,7 @@ "data": { "access_token": "Zugangstoken", "host": "Host" - }, - "description": "Richte die Vilfo Router-Integration ein. Du ben\u00f6tigst deinen Vilfo Router-Hostnamen / deine IP-Adresse und ein API-Zugriffstoken. Weitere Informationen zu dieser Integration und wie du diese Details erh\u00e4ltst, findest du unter: https://www.home-assistant.io/integrations/vilfo", - "title": "Stelle eine Verbindung zum Vilfo Router her" + } } } } diff --git a/homeassistant/components/vilfo/translations/el.json b/homeassistant/components/vilfo/translations/el.json index 7af0b69e97e..dad6b11a492 100644 --- a/homeassistant/components/vilfo/translations/el.json +++ b/homeassistant/components/vilfo/translations/el.json @@ -13,9 +13,7 @@ "data": { "access_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "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\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Vilfo. \u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03c4\u03bf hostname/IP \u03c4\u03bf\u03c5 Vilfo Router \u03ba\u03b1\u03b9 \u03ad\u03bd\u03b1 \u03ba\u03bf\u03c5\u03c0\u03cc\u03bd\u03b9 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 API. \u0393\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 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c0\u03ce\u03c2 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c4\u03b9\u03c2 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2, \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5: https://www.home-assistant.io/integrations/vilfo", - "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae Vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/en.json b/homeassistant/components/vilfo/translations/en.json index 03f9a9bd23a..f26c34aa917 100644 --- a/homeassistant/components/vilfo/translations/en.json +++ b/homeassistant/components/vilfo/translations/en.json @@ -13,9 +13,7 @@ "data": { "access_token": "Access Token", "host": "Host" - }, - "description": "Set up the Vilfo Router integration. You need your Vilfo Router hostname/IP and an API access token. For additional information on this integration and how to get those details, visit: https://www.home-assistant.io/integrations/vilfo", - "title": "Connect to the Vilfo Router" + } } } } diff --git a/homeassistant/components/vilfo/translations/es-419.json b/homeassistant/components/vilfo/translations/es-419.json index 9c62c327235..03bd5f05446 100644 --- a/homeassistant/components/vilfo/translations/es-419.json +++ b/homeassistant/components/vilfo/translations/es-419.json @@ -13,9 +13,7 @@ "data": { "access_token": "Token de acceso para la API del enrutador Vilfo", "host": "Nombre de host o IP del enrutador" - }, - "description": "Configure la integraci\u00f3n del enrutador Vilfo. Necesita su nombre de host/IP de Vilfo Router y un token de acceso API. Para obtener informaci\u00f3n adicional sobre esta integraci\u00f3n y c\u00f3mo obtener esos detalles, visite: https://www.home-assistant.io/integrations/vilfo", - "title": "Conectar con el Router Vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/es.json b/homeassistant/components/vilfo/translations/es.json index 863e0cfb6ce..eb5219b01a1 100644 --- a/homeassistant/components/vilfo/translations/es.json +++ b/homeassistant/components/vilfo/translations/es.json @@ -13,9 +13,7 @@ "data": { "access_token": "Token de acceso", "host": "Host" - }, - "description": "Configure la integraci\u00f3n del Router Vilfo. Necesita su nombre de host/IP del Router Vilfo y un token de acceso a la API. Para obtener informaci\u00f3n adicional sobre esta integraci\u00f3n y c\u00f3mo obtener esos detalles, visite: https://www.home-assistant.io/integrations/vilfo", - "title": "Conectar con el Router Vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/et.json b/homeassistant/components/vilfo/translations/et.json index 8ed6aa9e14c..2274221a6bb 100644 --- a/homeassistant/components/vilfo/translations/et.json +++ b/homeassistant/components/vilfo/translations/et.json @@ -13,9 +13,7 @@ "data": { "access_token": "Juurdep\u00e4\u00e4sut\u00f5end", "host": "" - }, - "description": "Seadista Vilfo ruuteri sidumine. Vaja on oma Vilfo ruuteri hosti nime / IP-d ja API juurdep\u00e4\u00e4suluba. Lisateavet selle integreerimise ja selle kohta, kuidas neid \u00fcksikasju saada, leiad aadressilt https://www.home-assistant.io/integrations/vilfo", - "title": "\u00dchenda Vilfo ruuteriga" + } } } } diff --git a/homeassistant/components/vilfo/translations/fr.json b/homeassistant/components/vilfo/translations/fr.json index 10289500383..55658b0f1ca 100644 --- a/homeassistant/components/vilfo/translations/fr.json +++ b/homeassistant/components/vilfo/translations/fr.json @@ -13,9 +13,7 @@ "data": { "access_token": "Jeton d'acc\u00e8s", "host": "H\u00f4te" - }, - "description": "Configurez l'int\u00e9gration du routeur Vilfo. Vous avez besoin du nom d'h\u00f4te / IP de votre routeur Vilfo et d'un jeton d'acc\u00e8s API. Pour plus d'informations sur cette int\u00e9gration et comment obtenir ces d\u00e9tails, visitez: https://www.home-assistant.io/integrations/vilfo", - "title": "Connectez-vous au routeur Vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/he.json b/homeassistant/components/vilfo/translations/he.json index 642f482d14e..87a65e1af93 100644 --- a/homeassistant/components/vilfo/translations/he.json +++ b/homeassistant/components/vilfo/translations/he.json @@ -13,8 +13,7 @@ "data": { "access_token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d2\u05d9\u05e9\u05d4", "host": "\u05de\u05d0\u05e8\u05d7" - }, - "description": "\u05d4\u05d2\u05d3\u05e8 \u05d0\u05ea \u05e9\u05d9\u05dc\u05d5\u05d1 \u05d4\u05e0\u05ea\u05d1 \u05e9\u05dc Vilfo. \u05d0\u05ea\u05d4 \u05e6\u05e8\u05d9\u05da \u05d0\u05ea \u05e9\u05dd \u05d4\u05de\u05d0\u05e8\u05d7/IP \u05e9\u05dc \u05e0\u05ea\u05d1 Vilfo \u05e9\u05dc\u05da \u05d5\u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d2\u05d9\u05e9\u05ea API. \u05dc\u05de\u05d9\u05d3\u05e2 \u05e0\u05d5\u05e1\u05e3 \u05e2\u05dc \u05e9\u05d9\u05dc\u05d5\u05d1 \u05d6\u05d4 \u05d5\u05db\u05d9\u05e6\u05d3 \u05dc\u05e7\u05d1\u05dc \u05e4\u05e8\u05d8\u05d9\u05dd \u05d0\u05dc\u05d4, \u05d1\u05e7\u05e8 \u05d1\u05db\u05ea\u05d5\u05d1\u05ea: https://www.home-assistant.io/integrations/vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/hu.json b/homeassistant/components/vilfo/translations/hu.json index 66e0b4d7d77..f2b23836336 100644 --- a/homeassistant/components/vilfo/translations/hu.json +++ b/homeassistant/components/vilfo/translations/hu.json @@ -13,9 +13,7 @@ "data": { "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", "host": "C\u00edm" - }, - "description": "\u00c1ll\u00edtsa be a Vilfo Router integr\u00e1ci\u00f3t. Sz\u00fcks\u00e9ge van a Vilfo Router g\u00e9pnev\u00e9re/IP -c\u00edm\u00e9re \u00e9s egy API hozz\u00e1f\u00e9r\u00e9si jogkivonatra. Ha tov\u00e1bbi inform\u00e1ci\u00f3ra van sz\u00fcks\u00e9ge az integr\u00e1ci\u00f3r\u00f3l \u00e9s a r\u00e9szletekr\u0151l, l\u00e1togasson el a k\u00f6vetkez\u0151 webhelyre: https://www.home-assistant.io/integrations/vilfo", - "title": "Csatlakoz\u00e1s a Vilfo routerhez" + } } } } diff --git a/homeassistant/components/vilfo/translations/id.json b/homeassistant/components/vilfo/translations/id.json index 3871670a1cd..9f8adda9737 100644 --- a/homeassistant/components/vilfo/translations/id.json +++ b/homeassistant/components/vilfo/translations/id.json @@ -13,9 +13,7 @@ "data": { "access_token": "Token Akses", "host": "Host" - }, - "description": "Siapkan integrasi Router Vilfo. Anda memerlukan nama host/IP Router Vilfo dan token akses API. Untuk informasi lebih lanjut tentang integrasi ini dan cara mendapatkan data tersebut, kunjungi: https://www.home-assistant.io/integrations/vilfo", - "title": "Hubungkan ke Router Vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/it.json b/homeassistant/components/vilfo/translations/it.json index 7d30b0017fb..8c813c64226 100644 --- a/homeassistant/components/vilfo/translations/it.json +++ b/homeassistant/components/vilfo/translations/it.json @@ -13,9 +13,7 @@ "data": { "access_token": "Token di accesso", "host": "Host" - }, - "description": "Configura l'integrazione del Vilfo Router. Hai bisogno del tuo nome host/IP del Vilfo Router e un token di accesso API. Per ulteriori informazioni su questa integrazione e su come ottenere tali dettagli, visita il sito: https://www.home-assistant.io/integrations/vilfo", - "title": "Collegamento al Vilfo Router" + } } } } diff --git a/homeassistant/components/vilfo/translations/ja.json b/homeassistant/components/vilfo/translations/ja.json index 6f98d264f24..6a41c1168cb 100644 --- a/homeassistant/components/vilfo/translations/ja.json +++ b/homeassistant/components/vilfo/translations/ja.json @@ -13,9 +13,7 @@ "data": { "access_token": "\u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3", "host": "\u30db\u30b9\u30c8" - }, - "description": "Vilfo Router\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002Vilfo Router\u306e\u30db\u30b9\u30c8\u540d/IP\u3068API\u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3\u304c\u5fc5\u8981\u3067\u3059\u3002\u3053\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306b\u95a2\u3059\u308b\u8a73\u7d30\u3068\u305d\u308c\u3089\u306e\u53d6\u5f97\u65b9\u6cd5\u306b\u3064\u3044\u3066\u306f\u3001https://www.home-assistant.io/integrations/vilfo \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "Vilfo\u30eb\u30fc\u30bf\u30fc\u306b\u63a5\u7d9a" + } } } } diff --git a/homeassistant/components/vilfo/translations/ko.json b/homeassistant/components/vilfo/translations/ko.json index 980ee36d7a5..d2db1a7fb7a 100644 --- a/homeassistant/components/vilfo/translations/ko.json +++ b/homeassistant/components/vilfo/translations/ko.json @@ -13,9 +13,7 @@ "data": { "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070", "host": "\ud638\uc2a4\ud2b8" - }, - "description": "Vilfo \ub77c\uc6b0\ud130 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. Vilfo \ub77c\uc6b0\ud130 \ud638\uc2a4\ud2b8 \uc774\ub984/IP \uc640 API \uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ucd94\uac00 \uc815\ubcf4\uc640 \uc138\ubd80 \uc0ac\ud56d\uc740 https://www.home-assistant.io/integrations/vilfo \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", - "title": "Vilfo \ub77c\uc6b0\ud130\uc5d0 \uc5f0\uacb0\ud558\uae30" + } } } } diff --git a/homeassistant/components/vilfo/translations/lb.json b/homeassistant/components/vilfo/translations/lb.json index cdf16438469..ebbe55e48eb 100644 --- a/homeassistant/components/vilfo/translations/lb.json +++ b/homeassistant/components/vilfo/translations/lb.json @@ -13,9 +13,7 @@ "data": { "access_token": "Acc\u00e8s Jeton", "host": "Host" - }, - "description": "Vilfo Router Integratioun ariichten. Dir braucht \u00e4re Vilfo Router Numm/IP an een API Acc\u00e8s Jeton. Fir weider Informatiounen zu d\u00ebser Integratioun a w\u00e9i een zu d\u00ebsen n\u00e9idegen Informatioune k\u00ebnnt, gitt op: https://www.home-assistant.io/integrations/vilfo", - "title": "Mam Vilfo Router verbannen" + } } } } diff --git a/homeassistant/components/vilfo/translations/nl.json b/homeassistant/components/vilfo/translations/nl.json index 304871cc145..fc49ddbf7b8 100644 --- a/homeassistant/components/vilfo/translations/nl.json +++ b/homeassistant/components/vilfo/translations/nl.json @@ -13,9 +13,7 @@ "data": { "access_token": "Toegangstoken", "host": "Host" - }, - "description": "Stel de Vilfo Router-integratie in. U heeft de hostnaam/IP van uw Vilfo Router en een API-toegangstoken nodig. Voor meer informatie over deze integratie en hoe u die details kunt verkrijgen, gaat u naar: https://www.home-assistant.io/integrations/vilfo", - "title": "Maak verbinding met de Vilfo Router" + } } } } diff --git a/homeassistant/components/vilfo/translations/no.json b/homeassistant/components/vilfo/translations/no.json index b1f1cd8950f..6550778a853 100644 --- a/homeassistant/components/vilfo/translations/no.json +++ b/homeassistant/components/vilfo/translations/no.json @@ -13,9 +13,7 @@ "data": { "access_token": "Tilgangstoken", "host": "Vert" - }, - "description": "Sett opp Vilfo Router-integrasjonen. Du trenger ditt Vilfo Router vertsnavn/IP og et API-tilgangstoken. For ytterligere informasjon om denne integrasjonen og hvordan du f\u00e5r disse detaljene, bes\u00f8k [https://www.home-assistant.io/integrations/vilfo](https://www.home-assistant.io/integrations/vilfo)", - "title": "Koble til Vilfo Ruteren" + } } } } diff --git a/homeassistant/components/vilfo/translations/pl.json b/homeassistant/components/vilfo/translations/pl.json index 43b94ffd103..a5016c989f5 100644 --- a/homeassistant/components/vilfo/translations/pl.json +++ b/homeassistant/components/vilfo/translations/pl.json @@ -13,9 +13,7 @@ "data": { "access_token": "Token dost\u0119pu", "host": "Nazwa hosta lub adres IP" - }, - "description": "Skonfiguruj integracj\u0119 routera Vilfo. Potrzebujesz nazwy hosta/adresu IP routera Vilfo i tokena dost\u0119pu do interfejsu API. Aby uzyska\u0107 dodatkowe informacje na temat tej integracji i sposobu uzyskania niezb\u0119dnych danych do konfiguracji, odwied\u017a: https://www.home-assistant.io/integrations/vilfo", - "title": "Po\u0142\u0105czenie z routerem Vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/pt-BR.json b/homeassistant/components/vilfo/translations/pt-BR.json index 8766261955f..f772ec37472 100644 --- a/homeassistant/components/vilfo/translations/pt-BR.json +++ b/homeassistant/components/vilfo/translations/pt-BR.json @@ -13,9 +13,7 @@ "data": { "access_token": "Token de acesso", "host": "Nome do host" - }, - "description": "Configure a integra\u00e7\u00e3o do roteador Vilfo. Voc\u00ea precisa do seu nome de host/IP do roteador Vilfo e um token de acesso \u00e0 API. Para obter informa\u00e7\u00f5es adicionais sobre essa integra\u00e7\u00e3o e como obter esses detalhes, visite: https://www.home-assistant.io/integrations/vilfo", - "title": "Conecte-se ao roteador Vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/ru.json b/homeassistant/components/vilfo/translations/ru.json index 62ec2fe5dae..6a2a2163313 100644 --- a/homeassistant/components/vilfo/translations/ru.json +++ b/homeassistant/components/vilfo/translations/ru.json @@ -13,9 +13,7 @@ "data": { "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430", "host": "\u0425\u043e\u0441\u0442" - }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 Vilfo. \u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0438 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 API. \u0414\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 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u044d\u0442\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438, \u043f\u043e\u0441\u0435\u0442\u0438\u0442\u0435 \u0432\u0435\u0431-\u0441\u0430\u0439\u0442: https://www.home-assistant.io/integrations/vilfo.", - "title": "Vilfo Router" + } } } } diff --git a/homeassistant/components/vilfo/translations/sl.json b/homeassistant/components/vilfo/translations/sl.json index c815698099f..d31dc23a3f6 100644 --- a/homeassistant/components/vilfo/translations/sl.json +++ b/homeassistant/components/vilfo/translations/sl.json @@ -13,9 +13,7 @@ "data": { "access_token": "Dostopni \u017eeton za API Vilfo Router", "host": "Ime gostitelja usmerjevalnika ali IP" - }, - "description": "Nastavite integracijo Vilfo Router. Potrebujete ime gostitelja ali IP Vilfo usmerjevalnika in dostopni \u017eeton API. Za dodatne informacije o tej integraciji in kako do teh podrobnosti obi\u0161\u010dite: https://www.home-assistant.io/integrations/vilfo", - "title": "Pove\u017eite se z usmerjevalnikom Vilfo" + } } } } diff --git a/homeassistant/components/vilfo/translations/sv.json b/homeassistant/components/vilfo/translations/sv.json index 7192f91ee1a..2594b350d1c 100644 --- a/homeassistant/components/vilfo/translations/sv.json +++ b/homeassistant/components/vilfo/translations/sv.json @@ -13,9 +13,7 @@ "data": { "access_token": "\u00c5tkomstnyckel f\u00f6r Vilfo-routerns API", "host": "Routerns v\u00e4rdnamn eller IP-adress" - }, - "description": "St\u00e4ll in Vilfo Router-integrationen. Du beh\u00f6ver din Vilfo-routers v\u00e4rdnamn eller IP-adress och en \u00e5tkomstnyckel till dess API. F\u00f6r ytterligare information om den h\u00e4r integrationen och hur du f\u00e5r fram den n\u00f6dv\u00e4ndiga informationen, bes\u00f6k: https://www.home-assistant.io/integrations/vilfo", - "title": "Anslut till Vilfo-routern" + } } } } diff --git a/homeassistant/components/vilfo/translations/tr.json b/homeassistant/components/vilfo/translations/tr.json index 2e27132afb6..dc66041e35a 100644 --- a/homeassistant/components/vilfo/translations/tr.json +++ b/homeassistant/components/vilfo/translations/tr.json @@ -13,9 +13,7 @@ "data": { "access_token": "Eri\u015fim Belirteci", "host": "Ana Bilgisayar" - }, - "description": "Vilfo Router entegrasyonunu ayarlay\u0131n. Vilfo Router ana bilgisayar ad\u0131n\u0131za/IP'nize ve bir API eri\u015fim anahtar\u0131na ihtiyac\u0131n\u0131z var. Bu entegrasyon ve bu ayr\u0131nt\u0131lar\u0131n nas\u0131l al\u0131naca\u011f\u0131 hakk\u0131nda ek bilgi i\u00e7in \u015fu adresi ziyaret edin: https://www.home-assistant.io/integrations/vilfo", - "title": "Vilfo Router'a ba\u011flan\u0131n" + } } } } diff --git a/homeassistant/components/vilfo/translations/uk.json b/homeassistant/components/vilfo/translations/uk.json index 1a93176f290..4688b58e187 100644 --- a/homeassistant/components/vilfo/translations/uk.json +++ b/homeassistant/components/vilfo/translations/uk.json @@ -13,9 +13,7 @@ "data": { "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", "host": "\u0425\u043e\u0441\u0442" - }, - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 Vilfo. \u0412\u043a\u0430\u0436\u0456\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0456 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 API. \u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0431\u0456\u043b\u044c\u0448 \u0434\u043e\u043a\u043b\u0430\u0434\u043d\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457, \u0432\u0456\u0434\u0432\u0456\u0434\u0430\u0439\u0442\u0435 \u0432\u0435\u0431-\u0441\u0430\u0439\u0442: https://www.home-assistant.io/integrations/vilfo.", - "title": "Vilfo Router" + } } } } diff --git a/homeassistant/components/vilfo/translations/zh-Hans.json b/homeassistant/components/vilfo/translations/zh-Hans.json index f3fec441d4c..756c7e68c98 100644 --- a/homeassistant/components/vilfo/translations/zh-Hans.json +++ b/homeassistant/components/vilfo/translations/zh-Hans.json @@ -9,9 +9,7 @@ "data": { "access_token": "Vilfo \u8def\u7531\u5668 API \u5b58\u53d6\u5bc6\u94a5", "host": "\u8def\u7531\u5668\u4e3b\u673a\u540d\u6216 IP \u5730\u5740" - }, - "description": "\u8bbe\u7f6e Vilfo \u8def\u7531\u5668\u6574\u5408\u3002\u60a8\u9700\u8981\u8f93\u5165 Vilfo \u8def\u7531\u5668\u4e3b\u673a\u540d/IP \u5730\u5740\u3001API\u5b58\u53d6\u5bc6\u94a5\u3002\u5176\u4ed6\u6574\u5408\u7684\u76f8\u5173\u4fe1\u606f\uff0c\u8bf7\u8bbf\u95ee\uff1ahttps://www.home-assistant.io/integrations/vilfo", - "title": "\u8fde\u63a5\u5230 Vilfo \u8def\u7531\u5668" + } } } } diff --git a/homeassistant/components/vilfo/translations/zh-Hant.json b/homeassistant/components/vilfo/translations/zh-Hant.json index eb3dba826be..171fa5b16e8 100644 --- a/homeassistant/components/vilfo/translations/zh-Hant.json +++ b/homeassistant/components/vilfo/translations/zh-Hant.json @@ -13,9 +13,7 @@ "data": { "access_token": "\u5b58\u53d6\u6b0a\u6756", "host": "\u4e3b\u6a5f\u7aef" - }, - "description": "\u8a2d\u5b9a Vilfo \u8def\u7531\u5668\u6574\u5408\u3002\u9700\u8981\u8f38\u5165 Vilfo \u8def\u7531\u5668\u4e3b\u6a5f\u540d\u7a31/IP \u4f4d\u5740\u3001API \u5b58\u53d6\u6b0a\u6756\u3002\u5176\u4ed6\u6574\u5408\u76f8\u95dc\u8cc7\u8a0a\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/vilfo", - "title": "\u9023\u7dda\u81f3 Vilfo \u8def\u7531\u5668" + } } } } diff --git a/homeassistant/components/vizio/translations/nl.json b/homeassistant/components/vizio/translations/nl.json index f17e0470799..871aa4e3e69 100644 --- a/homeassistant/components/vizio/translations/nl.json +++ b/homeassistant/components/vizio/translations/nl.json @@ -1,19 +1,19 @@ { "config": { "abort": { - "already_configured_device": "Dit apparaat is al geconfigureerd", + "already_configured_device": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", "updated_entry": "Dit item is al ingesteld, maar de naam en/of opties die zijn gedefinieerd in de configuratie komen niet overeen met de eerder ge\u00efmporteerde configuratie, dus het configuratie-item is dienovereenkomstig bijgewerkt." }, "error": { - "cannot_connect": "Verbinding mislukt", + "cannot_connect": "Kan geen verbinding maken", "complete_pairing_failed": "Kan het koppelen niet voltooien. Zorg ervoor dat de door u opgegeven pincode correct is en dat de tv nog steeds van stroom wordt voorzien en is verbonden met het netwerk voordat u opnieuw verzendt.", "existing_config_entry_found": "Een bestaande VIZIO SmartCast-apparaat config entry met hetzelfde serienummer is reeds geconfigureerd. U moet de bestaande entry verwijderen om deze te kunnen configureren." }, "step": { "pair_tv": { "data": { - "pin": "PIN-code" + "pin": "Pincode" }, "description": "Uw TV zou een code moeten weergeven. Voer die code in het formulier in en ga dan door naar de volgende stap om de koppeling te voltooien.", "title": "Voltooi het koppelingsproces" diff --git a/homeassistant/components/vlc/media_player.py b/homeassistant/components/vlc/media_player.py index 10903295f95..88b663e09c6 100644 --- a/homeassistant/components/vlc/media_player.py +++ b/homeassistant/components/vlc/media_player.py @@ -6,11 +6,16 @@ import logging import vlc import voluptuous as vol +from homeassistant.components import media_source from homeassistant.components.media_player import ( PLATFORM_SCHEMA, + 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_MUSIC from homeassistant.const import CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant @@ -54,6 +59,7 @@ class VlcDevice(MediaPlayerEntity): | MediaPlayerEntityFeature.PLAY_MEDIA | MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.BROWSE_MEDIA ) def __init__(self, name, arguments): @@ -158,15 +164,38 @@ class VlcDevice(MediaPlayerEntity): self._vlc.stop() self._state = STATE_IDLE - def play_media(self, media_type, media_id, **kwargs): + async def async_play_media(self, media_type, media_id, **kwargs): """Play media from a URL or file.""" - if media_type != MEDIA_TYPE_MUSIC: + # Handle media_source + if media_source.is_media_source_id(media_id): + sourced_media = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) + media_id = sourced_media.url + + elif media_type != MEDIA_TYPE_MUSIC: _LOGGER.error( "Invalid media type %s. Only %s is supported", media_type, MEDIA_TYPE_MUSIC, ) return - self._vlc.set_media(self._instance.media_new(media_id)) - self._vlc.play() + + media_id = async_process_play_media_url(self.hass, media_id) + + def play(): + self._vlc.set_media(self._instance.media_new(media_id)) + self._vlc.play() + + await self.hass.async_add_executor_job(play) self._state = STATE_PLAYING + + 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/"), + ) diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index 9d4e0a5a7a8..75305acbb0c 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -31,7 +31,7 @@ from .const import DATA_AVAILABLE, DATA_VLC, DEFAULT_NAME, DOMAIN, LOGGER MAX_VOLUME = 500 -_T = TypeVar("_T", bound="VlcDevice") +_VlcDeviceT = TypeVar("_VlcDeviceT", bound="VlcDevice") _P = ParamSpec("_P") @@ -48,12 +48,12 @@ async def async_setup_entry( def catch_vlc_errors( - func: Callable[Concatenate[_T, _P], Awaitable[None]] # type: ignore[misc] -) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]: # type: ignore[misc] + func: Callable[Concatenate[_VlcDeviceT, _P], Awaitable[None]] +) -> Callable[Concatenate[_VlcDeviceT, _P], Coroutine[Any, Any, None]]: """Catch VLC errors.""" @wraps(func) - async def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: + async def wrapper(self: _VlcDeviceT, *args: _P.args, **kwargs: _P.kwargs) -> None: """Catch VLC errors and modify availability.""" try: await func(self, *args, **kwargs) @@ -162,8 +162,16 @@ class VlcDevice(MediaPlayerEntity): data = info.data LOGGER.debug("Info data: %s", data) - self._media_artist = data.get(0, {}).get("artist") - self._media_title = data.get(0, {}).get("title") + self._attr_media_album_name = data.get("data", {}).get("album") + self._media_artist = data.get("data", {}).get("artist") + self._media_title = data.get("data", {}).get("title") + now_playing = data.get("data", {}).get("now_playing") + + # Many radio streams put artist/title/album in now_playing and title is the station name. + if now_playing: + if not self._media_artist: + self._media_artist = self._media_title + self._media_title = now_playing if self._media_title: return @@ -288,7 +296,9 @@ class VlcDevice(MediaPlayerEntity): """Play media from a URL or file.""" # Handle media_source if media_source.is_media_source_id(media_id): - sourced_media = await media_source.async_resolve_media(self.hass, media_id) + sourced_media = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_type = sourced_media.mime_type media_id = sourced_media.url diff --git a/homeassistant/components/vlc_telnet/translations/nl.json b/homeassistant/components/vlc_telnet/translations/nl.json index 538421263c9..87ddaffab2c 100644 --- a/homeassistant/components/vlc_telnet/translations/nl.json +++ b/homeassistant/components/vlc_telnet/translations/nl.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "already_configured": "Deze service is al geconfigureerd", + "already_configured": "Dienst is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", - "reauth_successful": "Het opnieuw verifi\u00ebren is geslaagd", + "reauth_successful": "Herauthenticatie geslaagd", "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Verbinding tot stand brengen is mislukt", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/volkszaehler/manifest.json b/homeassistant/components/volkszaehler/manifest.json index 5212593da45..baa9e4a8f14 100644 --- a/homeassistant/components/volkszaehler/manifest.json +++ b/homeassistant/components/volkszaehler/manifest.json @@ -3,7 +3,7 @@ "name": "Volkszaehler", "documentation": "https://www.home-assistant.io/integrations/volkszaehler", "requirements": ["volkszaehler==0.3.2"], - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "local_polling", "loggers": ["volkszaehler"] } diff --git a/homeassistant/components/vulcan/__init__.py b/homeassistant/components/vulcan/__init__.py index 430819c8f4c..50f525d9a68 100644 --- a/homeassistant/components/vulcan/__init__.py +++ b/homeassistant/components/vulcan/__init__.py @@ -1,18 +1,15 @@ """The Vulcan component.""" -import logging from aiohttp import ClientConnectorError -from vulcan import Account, Keystore, Vulcan -from vulcan._utils import VulcanAPIException +from vulcan import Account, Keystore, UnauthorizedCertificateException, Vulcan from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN -_LOGGER = logging.getLogger(__name__) - PLATFORMS = ["calendar"] @@ -22,54 +19,29 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: keystore = Keystore.load(entry.data["keystore"]) account = Account.load(entry.data["account"]) - client = Vulcan(keystore, account) + client = Vulcan(keystore, account, async_get_clientsession(hass)) await client.select_student() students = await client.get_students() for student in students: if str(student.pupil.id) == str(entry.data["student_id"]): client.student = student break - except VulcanAPIException as err: - if str(err) == "The certificate is not authorized.": - _LOGGER.error( - "The certificate is not authorized, please authorize integration again" - ) - raise ConfigEntryAuthFailed from err - _LOGGER.error("Vulcan API error: %s", err) - return False + except UnauthorizedCertificateException as err: + raise ConfigEntryAuthFailed("The certificate is not authorized.") from err except ClientConnectorError as err: - if "connection_error" not in hass.data[DOMAIN]: - _LOGGER.error( - "Connection error - please check your internet connection: %s", err - ) - hass.data[DOMAIN]["connection_error"] = True - await client.close() - raise ConfigEntryNotReady from err - hass.data[DOMAIN]["students_number"] = len( - hass.config_entries.async_entries(DOMAIN) - ) + raise ConfigEntryNotReady( + f"Connection error - please check your internet connection: {err}" + ) from err hass.data[DOMAIN][entry.entry_id] = client - if not entry.update_listeners: - entry.add_update_listener(_async_update_options) - - for platform in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, platform) - ) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True 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: - await hass.config_entries.async_forward_entry_unload(entry, platform) + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) - return True - - -async def _async_update_options(hass, entry): - """Update options.""" - await hass.config_entries.async_reload(entry.entry_id) + return unload_ok diff --git a/homeassistant/components/vulcan/calendar.py b/homeassistant/components/vulcan/calendar.py index 2e9e79063f9..ac302940c72 100644 --- a/homeassistant/components/vulcan/calendar.py +++ b/homeassistant/components/vulcan/calendar.py @@ -1,24 +1,25 @@ """Support for Vulcan Calendar platform.""" -import copy +from __future__ import annotations + from datetime import date, datetime, timedelta import logging from aiohttp import ClientConnectorError -from vulcan._utils import VulcanAPIException +from vulcan import UnauthorizedCertificateException -from homeassistant.components.calendar import ENTITY_ID_FORMAT, CalendarEventDevice +from homeassistant.components.calendar import ( + ENTITY_ID_FORMAT, + CalendarEntity, + CalendarEvent, +) 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 from . import DOMAIN -from .const import DEFAULT_SCAN_INTERVAL from .fetch_data import get_lessons, get_student_info _LOGGER = logging.getLogger(__name__) @@ -29,20 +30,16 @@ async def async_setup_entry( 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) - ) + """Set up the calendar platform for entity.""" client = hass.data[DOMAIN][config_entry.entry_id] data = { "student_info": await get_student_info( client, config_entry.data.get("student_id") ), - "students_number": hass.data[DOMAIN]["students_number"], } async_add_entities( [ - VulcanCalendarEventDevice( + VulcanCalendarEntity( client, data, generate_entity_id( @@ -55,80 +52,33 @@ async def async_setup_entry( ) -class VulcanCalendarEventDevice(CalendarEventDevice): - """A calendar event device.""" +class VulcanCalendarEntity(CalendarEntity): + """A calendar entity.""" - def __init__(self, client, data, entity_id): - """Create the Calendar event device.""" + def __init__(self, client, data, entity_id) -> None: + """Create the Calendar entity.""" self.student_info = data["student_info"] - self.data = VulcanCalendarData( - client, - self.student_info, - self.hass, - ) - self._event = None + self._event: CalendarEvent | None = None + self.client = client self.entity_id = entity_id self._unique_id = f"vulcan_calendar_{self.student_info['id']}" - - if data["students_number"] == 1: - self._attr_name = "Vulcan calendar" - self.device_name = "Calendar" - else: - self._attr_name = f"Vulcan calendar - {self.student_info['full_name']}" - self.device_name = f"{self.student_info['full_name']}: Calendar" + self._attr_name = f"Vulcan calendar - {self.student_info['full_name']}" self._attr_unique_id = f"vulcan_calendar_{self.student_info['id']}" self._attr_device_info = { "identifiers": {(DOMAIN, f"calendar_{self.student_info['id']}")}, "entry_type": DeviceEntryType.SERVICE, - "name": self.device_name, + "name": f"{self.student_info['full_name']}: Calendar", "model": f"{self.student_info['full_name']} - {self.student_info['class']} {self.student_info['school']}", "manufacturer": "Uonet +", "configuration_url": f"https://uonetplus.vulcan.net.pl/{self.student_info['symbol']}", } @property - def event(self): + def event(self) -> CalendarEvent | None: """Return the next upcoming event.""" return self._event - async def async_get_events(self, hass, start_date, end_date): - """Get all events in a specific time frame.""" - return await self.data.async_get_events(hass, start_date, end_date) - - async def async_update(self): - """Update event data.""" - await self.data.async_update() - event = copy.deepcopy(self.data.event) - if event is None: - self._event = event - return - event["start"] = { - "dateTime": datetime.combine(event["date"], event["time"].from_) - .astimezone(dt.DEFAULT_TIME_ZONE) - .isoformat() - } - event["end"] = { - "dateTime": datetime.combine(event["date"], event["time"].to) - .astimezone(dt.DEFAULT_TIME_ZONE) - .isoformat() - } - self._event = event - - -class VulcanCalendarData: - """Class to utilize calendar service object to get next event.""" - - MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=DEFAULT_SCAN_INTERVAL) - - def __init__(self, client, student_info, hass): - """Set up how we are going to search the Vulcan calendar.""" - self.client = client - self.event = None - self.hass = hass - self.student_info = student_info - self._available = True - - async def async_get_events(self, hass, start_date, end_date): + async def async_get_events(self, hass, start_date, end_date) -> list[CalendarEvent]: """Get all events in a specific time frame.""" try: events = await get_lessons( @@ -136,16 +86,12 @@ class VulcanCalendarData: date_from=start_date, date_to=end_date, ) - except VulcanAPIException as err: - if str(err) == "The certificate is not authorized.": - _LOGGER.error( - "The certificate is not authorized, please authorize integration again" - ) - raise ConfigEntryAuthFailed from err - _LOGGER.error("An API error has occurred: %s", err) - events = [] + except UnauthorizedCertificateException as err: + raise ConfigEntryAuthFailed( + "The certificate is not authorized, please authorize integration again" + ) from err except ClientConnectorError as err: - if self._available: + if self.available: _LOGGER.warning( "Connection error - please check your internet connection: %s", err ) @@ -153,37 +99,27 @@ class VulcanCalendarData: event_list = [] for item in events: - event = { - "uid": item["id"], - "start": { - "dateTime": datetime.combine( - item["date"], item["time"].from_ - ).strftime(DATE_STR_FORMAT) - }, - "end": { - "dateTime": datetime.combine( - item["date"], item["time"].to - ).strftime(DATE_STR_FORMAT) - }, - "summary": item["lesson"], - "location": item["room"], - "description": item["teacher"], - } + event = CalendarEvent( + start=datetime.combine(item["date"], item["time"].from_), + end=datetime.combine(item["date"], item["time"].to), + summary=item["lesson"], + location=item["room"], + description=item["teacher"], + ) event_list.append(event) return event_list - @Throttle(MIN_TIME_BETWEEN_UPDATES) - async def async_update(self): + async def async_update(self) -> None: """Get the latest data.""" try: events = await get_lessons(self.client) - if not self._available: + if not self.available: _LOGGER.info("Restored connection with API") - self._available = True + self._attr_available = True if events == []: events = await get_lessons( @@ -191,22 +127,18 @@ class VulcanCalendarData: date_to=date.today() + timedelta(days=7), ) if events == []: - self.event = None + self._event = None return - except VulcanAPIException as err: - if str(err) == "The certificate is not authorized.": - _LOGGER.error( - "The certificate is not authorized, please authorize integration again" - ) - raise ConfigEntryAuthFailed from err - _LOGGER.error("An API error has occurred: %s", err) - return + except UnauthorizedCertificateException as err: + raise ConfigEntryAuthFailed( + "The certificate is not authorized, please authorize integration again" + ) from err except ClientConnectorError as err: - if self._available: + if self.available: _LOGGER.warning( "Connection error - please check your internet connection: %s", err ) - self._available = False + self._attr_available = False return new_event = min( @@ -216,11 +148,10 @@ class VulcanCalendarData: abs(datetime.combine(d["date"], d["time"].to) - datetime.now()), ), ) - self.event = { - "uid": new_event["id"], - "date": new_event["date"], - "time": new_event["time"], - "summary": new_event["lesson"], - "location": new_event["room"], - "description": new_event["teacher"], - } + self._event = CalendarEvent( + start=datetime.combine(new_event["date"], new_event["time"].from_), + end=datetime.combine(new_event["date"], new_event["time"].to), + summary=new_event["lesson"], + location=new_event["room"], + description=new_event["teacher"], + ) diff --git a/homeassistant/components/vulcan/config_flow.py b/homeassistant/components/vulcan/config_flow.py index ef700560d73..09acb13ea27 100644 --- a/homeassistant/components/vulcan/config_flow.py +++ b/homeassistant/components/vulcan/config_flow.py @@ -3,16 +3,22 @@ import logging from aiohttp import ClientConnectionError import voluptuous as vol -from vulcan import Account, Keystore, Vulcan -from vulcan._utils import VulcanAPIException +from vulcan import ( + Account, + ExpiredTokenException, + InvalidPINException, + InvalidSymbolException, + InvalidTokenException, + Keystore, + UnauthorizedCertificateException, + Vulcan, +) from homeassistant import config_entries -from homeassistant.const import CONF_PIN, CONF_REGION, CONF_SCAN_INTERVAL, CONF_TOKEN -from homeassistant.core import callback -import homeassistant.helpers.config_validation as cv +from homeassistant.const import CONF_PIN, CONF_REGION, CONF_TOKEN +from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import DOMAIN -from .const import DEFAULT_SCAN_INTERVAL from .register import register _LOGGER = logging.getLogger(__name__) @@ -29,11 +35,11 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - @staticmethod - @callback - def async_get_options_flow(config_entry): - """Get the options flow for this handler.""" - return VulcanOptionsFlowHandler(config_entry) + def __init__(self): + """Initialize config flow.""" + self.account = None + self.keystore = None + self.students = None async def async_step_user(self, user_input=None): """Handle config flow.""" @@ -53,22 +59,14 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): user_input[CONF_REGION], user_input[CONF_PIN], ) - except VulcanAPIException as err: - if str(err) == "Invalid token!" or str(err) == "Invalid token.": - errors = {"base": "invalid_token"} - elif str(err) == "Expired token.": - errors = {"base": "expired_token"} - elif str(err) == "Invalid PIN.": - errors = {"base": "invalid_pin"} - else: - errors = {"base": "unknown"} - _LOGGER.error(err) - except RuntimeError as err: - if str(err) == "Internal Server Error (ArgumentException)": - errors = {"base": "invalid_symbol"} - else: - errors = {"base": "unknown"} - _LOGGER.error(err) + except InvalidSymbolException: + errors = {"base": "invalid_symbol"} + except InvalidTokenException: + errors = {"base": "invalid_token"} + except InvalidPINException: + errors = {"base": "invalid_pin"} + except ExpiredTokenException: + errors = {"base": "expired_token"} except ClientConnectionError as err: errors = {"base": "cannot_connect"} _LOGGER.error("Connection error: %s", err) @@ -78,12 +76,10 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if not errors: account = credentials["account"] keystore = credentials["keystore"] - client = Vulcan(keystore, account) + client = Vulcan(keystore, account, async_get_clientsession(self.hass)) students = await client.get_students() - await client.close() if len(students) > 1: - # pylint:disable=attribute-defined-outside-init self.account = account self.keystore = keystore self.students = students @@ -109,10 +105,10 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_select_student(self, user_input=None): """Allow user to select student.""" errors = {} - students_list = {} + students = {} if self.students is not None: for student in self.students: - students_list[ + students[ str(student.pupil.id) ] = f"{student.pupil.first_name} {student.pupil.last_name}" if user_input is not None: @@ -120,7 +116,7 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(str(student_id)) self._abort_if_unique_id_configured() return self.async_create_entry( - title=students_list[student_id], + title=students[student_id], data={ "student_id": str(student_id), "keystore": self.keystore.as_dict, @@ -128,37 +124,30 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): }, ) - data_schema = { - vol.Required( - "student", - ): vol.In(students_list), - } return self.async_show_form( step_id="select_student", - data_schema=vol.Schema(data_schema), + data_schema=vol.Schema({vol.Required("student"): vol.In(students)}), errors=errors, ) async def async_step_select_saved_credentials(self, user_input=None, errors=None): """Allow user to select saved credentials.""" - credentials_list = {} + + credentials = {} for entry in self.hass.config_entries.async_entries(DOMAIN): - credentials_list[entry.entry_id] = entry.data["account"]["UserName"] + credentials[entry.entry_id] = entry.data["account"]["UserName"] if user_input is not None: entry = self.hass.config_entries.async_get_entry(user_input["credentials"]) keystore = Keystore.load(entry.data["keystore"]) account = Account.load(entry.data["account"]) - client = Vulcan(keystore, account) + client = Vulcan(keystore, account, async_get_clientsession(self.hass)) try: students = await client.get_students() - except VulcanAPIException as err: - if str(err) == "The certificate is not authorized.": - return await self.async_step_auth( - errors={"base": "expired_credentials"} - ) - _LOGGER.error(err) - return await self.async_step_auth(errors={"base": "unknown"}) + except UnauthorizedCertificateException: + return await self.async_step_auth( + errors={"base": "expired_credentials"} + ) except ClientConnectionError as err: _LOGGER.error("Connection error: %s", err) return await self.async_step_select_saved_credentials( @@ -167,8 +156,6 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") return await self.async_step_auth(errors={"base": "unknown"}) - finally: - await client.close() if len(students) == 1: student = students[0] await self.async_set_unique_id(str(student.pupil.id)) @@ -181,7 +168,6 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): "account": account.as_dict, }, ) - # pylint:disable=attribute-defined-outside-init self.account = account self.keystore = keystore self.students = students @@ -190,7 +176,7 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): data_schema = { vol.Required( "credentials", - ): vol.In(credentials_list), + ): vol.In(credentials), } return self.async_show_form( step_id="select_saved_credentials", @@ -200,46 +186,46 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_add_next_config_entry(self, user_input=None): """Flow initialized when user is adding next entry of that integration.""" + existing_entries = [] for entry in self.hass.config_entries.async_entries(DOMAIN): existing_entries.append(entry) errors = {} + if user_input is not None: - if user_input["use_saved_credentials"]: - if len(existing_entries) == 1: - keystore = Keystore.load(existing_entries[0].data["keystore"]) - account = Account.load(existing_entries[0].data["account"]) - client = Vulcan(keystore, account) - students = await client.get_students() - await client.close() - new_students = [] - existing_entry_ids = [] - for entry in self.hass.config_entries.async_entries(DOMAIN): - existing_entry_ids.append(entry.data["student_id"]) - for student in students: - if str(student.pupil.id) not in existing_entry_ids: - new_students.append(student) - if not new_students: - return self.async_abort(reason="all_student_already_configured") - if len(new_students) == 1: - await self.async_set_unique_id(str(new_students[0].pupil.id)) - self._abort_if_unique_id_configured() - return self.async_create_entry( - title=f"{new_students[0].pupil.first_name} {new_students[0].pupil.last_name}", - data={ - "student_id": str(new_students[0].pupil.id), - "keystore": keystore.as_dict, - "account": account.as_dict, - }, - ) - # pylint:disable=attribute-defined-outside-init - self.account = account - self.keystore = keystore - self.students = new_students - return await self.async_step_select_student() + if not user_input["use_saved_credentials"]: + return await self.async_step_auth() + if len(existing_entries) > 1: return await self.async_step_select_saved_credentials() - return await self.async_step_auth() + keystore = Keystore.load(existing_entries[0].data["keystore"]) + account = Account.load(existing_entries[0].data["account"]) + client = Vulcan(keystore, account, async_get_clientsession(self.hass)) + students = await client.get_students() + new_students = [] + existing_entry_ids = [] + for entry in self.hass.config_entries.async_entries(DOMAIN): + existing_entry_ids.append(entry.data["student_id"]) + for student in students: + if str(student.pupil.id) not in existing_entry_ids: + new_students.append(student) + if not new_students: + return self.async_abort(reason="all_student_already_configured") + if len(new_students) == 1: + await self.async_set_unique_id(str(new_students[0].pupil.id)) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=f"{new_students[0].pupil.first_name} {new_students[0].pupil.last_name}", + data={ + "student_id": str(new_students[0].pupil.id), + "keystore": keystore.as_dict, + "account": account.as_dict, + }, + ) + self.account = account + self.keystore = keystore + self.students = new_students + return await self.async_step_select_student() data_schema = { vol.Required("use_saved_credentials", default=True): bool, @@ -251,6 +237,10 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_reauth(self, user_input=None): + """Perform reauth upon an API authentication error.""" + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm(self, user_input=None): """Reauthorize integration.""" errors = {} if user_input is not None: @@ -261,22 +251,14 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): user_input[CONF_REGION], user_input[CONF_PIN], ) - except VulcanAPIException as err: - if str(err) == "Invalid token!" or str(err) == "Invalid token.": - errors["base"] = "invalid_token" - elif str(err) == "Expired token.": - errors["base"] = "expired_token" - elif str(err) == "Invalid PIN.": - errors["base"] = "invalid_pin" - else: - errors["base"] = "unknown" - _LOGGER.error(err) - except RuntimeError as err: - if str(err) == "Internal Server Error (ArgumentException)": - errors["base"] = "invalid_symbol" - else: - errors["base"] = "unknown" - _LOGGER.error(err) + except InvalidSymbolException: + errors = {"base": "invalid_symbol"} + except InvalidTokenException: + errors = {"base": "invalid_token"} + except InvalidPINException: + errors = {"base": "invalid_pin"} + except ExpiredTokenException: + errors = {"base": "expired_token"} except ClientConnectionError as err: errors["base"] = "cannot_connect" _LOGGER.error("Connection error: %s", err) @@ -286,12 +268,12 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if not errors: account = credentials["account"] keystore = credentials["keystore"] - client = Vulcan(keystore, account) + client = Vulcan(keystore, account, async_get_clientsession(self.hass)) students = await client.get_students() - await client.close() existing_entries = [] for entry in self.hass.config_entries.async_entries(DOMAIN): existing_entries.append(entry) + matching_entries = False for student in students: for entry in existing_entries: if str(student.pupil.id) == str(entry.data["student_id"]): @@ -305,38 +287,13 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): }, ) await self.hass.config_entries.async_reload(entry.entry_id) + matching_entries = True + if not matching_entries: + return self.async_abort(reason="no_matching_entries") return self.async_abort(reason="reauth_successful") return self.async_show_form( - step_id="reauth", + step_id="reauth_confirm", data_schema=vol.Schema(LOGIN_SCHEMA), errors=errors, ) - - -class VulcanOptionsFlowHandler(config_entries.OptionsFlow): - """Config flow options for Uonet+ Vulcan.""" - - def __init__(self, config_entry): - """Initialize options flow.""" - self.config_entry = config_entry - - async def async_step_init(self, user_input=None): - """Manage the options.""" - errors = {} - - if user_input is not None: - return self.async_create_entry(title="", data=user_input) - - options = { - vol.Optional( - CONF_SCAN_INTERVAL, - default=self.config_entry.options.get( - CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL - ), - ): cv.positive_int, - } - - return self.async_show_form( - step_id="init", data_schema=vol.Schema(options), errors=errors - ) diff --git a/homeassistant/components/vulcan/const.py b/homeassistant/components/vulcan/const.py index 938cc4df8cd..4f17d43c342 100644 --- a/homeassistant/components/vulcan/const.py +++ b/homeassistant/components/vulcan/const.py @@ -1,4 +1,3 @@ """Constants for the Vulcan integration.""" DOMAIN = "vulcan" -DEFAULT_SCAN_INTERVAL = 5 diff --git a/homeassistant/components/vulcan/fetch_data.py b/homeassistant/components/vulcan/fetch_data.py index 04da8d125d7..c706bfa805f 100644 --- a/homeassistant/components/vulcan/fetch_data.py +++ b/homeassistant/components/vulcan/fetch_data.py @@ -94,4 +94,5 @@ async def get_student_info(client, student_id): student_info["class"] = student.class_ student_info["school"] = student.school.name student_info["symbol"] = student.symbol + break return student_info diff --git a/homeassistant/components/vulcan/manifest.json b/homeassistant/components/vulcan/manifest.json index 6449f07a3fb..b7f1f5fe4d6 100644 --- a/homeassistant/components/vulcan/manifest.json +++ b/homeassistant/components/vulcan/manifest.json @@ -3,9 +3,8 @@ "name": "Uonet+ Vulcan", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/vulcan", - "requirements": ["vulcan-api==2.0.3"], - "dependencies": [], + "requirements": ["vulcan-api==2.1.1"], "codeowners": ["@Antoni-Czaplicki"], "iot_class": "cloud_polling", - "quality_scale": "platinum" + "quality_scale": "silver" } diff --git a/homeassistant/components/vulcan/register.py b/homeassistant/components/vulcan/register.py index 802805d1db8..67cceb8d7b8 100644 --- a/homeassistant/components/vulcan/register.py +++ b/homeassistant/components/vulcan/register.py @@ -1,13 +1,10 @@ """Support for register Vulcan account.""" -from functools import partial from vulcan import Account, Keystore async def register(hass, token, symbol, pin): """Register integration and save credentials.""" - keystore = await hass.async_add_executor_job( - partial(Keystore.create, device_model="Home Assistant") - ) + keystore = await Keystore.create(device_model="Home Assistant") account = await Account.register(keystore, token, symbol, pin) return {"account": account, "keystore": keystore} diff --git a/homeassistant/components/vulcan/strings.json b/homeassistant/components/vulcan/strings.json index abb3dce7c7f..bb9e1d4d848 100644 --- a/homeassistant/components/vulcan/strings.json +++ b/homeassistant/components/vulcan/strings.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "That student has already been added.", "all_student_already_configured": "All students have already been added.", - "reauth_successful": "Reauth successful" + "reauth_successful": "Reauth successful", + "no_matching_entries": "No matching entries found, please use different account or remove integration with outdated student.." }, "error": { "unknown": "Unknown error occurred", @@ -23,7 +24,7 @@ "pin": "Pin" } }, - "reauth": { + "reauth_confirm": { "description": "Login to your Vulcan Account using mobile app registration page.", "data": { "token": "Token", @@ -50,20 +51,5 @@ } } } - }, - "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)" - } - } - } } } diff --git a/homeassistant/components/vulcan/translations/bg.json b/homeassistant/components/vulcan/translations/bg.json index 27893871c14..187ad8cff4b 100644 --- a/homeassistant/components/vulcan/translations/bg.json +++ b/homeassistant/components/vulcan/translations/bg.json @@ -13,18 +13,5 @@ } } } - }, - "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 index 505bcb1d326..2f7e454be6b 100644 --- a/homeassistant/components/vulcan/translations/ca.json +++ b/homeassistant/components/vulcan/translations/ca.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "Ja s'han afegit tots els alumnes.", "already_configured": "Ja s'ha afegit aquest alumne.", + "no_matching_entries": "No s'han trobat entrades coincidents, utilitza un compte diferent o elimina la integraci\u00f3 amb l'estudiant obsolet.", "reauth_successful": "Re-autenticaci\u00f3 exitosa" }, "error": { @@ -29,7 +30,7 @@ }, "description": "Inicia sessi\u00f3 al teu compte de Vulcan mitjan\u00e7ant la p\u00e0gina de registre de l'aplicaci\u00f3 m\u00f2bil." }, - "reauth": { + "reauth_confirm": { "data": { "pin": "PIN", "region": "S\u00edmbol", @@ -50,20 +51,5 @@ "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 index 23d5032f5fa..66de1c8ec75 100644 --- a/homeassistant/components/vulcan/translations/de.json +++ b/homeassistant/components/vulcan/translations/de.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "Alle Sch\u00fcler wurden bereits hinzugef\u00fcgt.", "already_configured": "Dieser Sch\u00fcler wurde bereits hinzugef\u00fcgt.", + "no_matching_entries": "Keine \u00fcbereinstimmenden Eintr\u00e4ge gefunden, bitte verwende ein anderes Konto oder entferne die Integration mit veraltetem Sch\u00fcler.", "reauth_successful": "Reauth erfolgreich" }, "error": { @@ -29,7 +30,7 @@ }, "description": "Melde dich bei deinem Vulcan-Konto \u00fcber die Registrierungsseite der mobilen App an." }, - "reauth": { + "reauth_confirm": { "data": { "pin": "PIN", "region": "Symbol", @@ -50,20 +51,5 @@ "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 index bb77c88f8e5..a041679919a 100644 --- a/homeassistant/components/vulcan/translations/el.json +++ b/homeassistant/components/vulcan/translations/el.json @@ -3,6 +3,7 @@ "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.", + "no_matching_entries": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c0\u03bf\u03c5 \u03bd\u03b1 \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03bf\u03c5\u03bd, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03ae \u03ba\u03b1\u03c4\u03b1\u03c1\u03b3\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03bc\u03b5 \u03c0\u03b1\u03bb\u03b9\u03cc \u03bc\u03b1\u03b8\u03b7\u03c4\u03ae..", "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": { @@ -29,7 +30,7 @@ }, "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": { + "reauth_confirm": { "data": { "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN", "region": "\u03a3\u03cd\u03bc\u03b2\u03bf\u03bb\u03bf", @@ -50,20 +51,5 @@ "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 48c054d2c15..65b9616e1e7 100644 --- a/homeassistant/components/vulcan/translations/en.json +++ b/homeassistant/components/vulcan/translations/en.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "All students have already been added.", "already_configured": "That student has already been added.", + "no_matching_entries": "No matching entries found, please use different account or remove integration with outdated student..", "reauth_successful": "Reauth successful" }, "error": { @@ -29,7 +30,7 @@ }, "description": "Login to your Vulcan Account using mobile app registration page." }, - "reauth": { + "reauth_confirm": { "data": { "pin": "Pin", "region": "Symbol", @@ -50,20 +51,5 @@ "description": "Select student, you can add more students by adding integration again." } } - }, - "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)" - } - } - } } } \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/es.json b/homeassistant/components/vulcan/translations/es.json new file mode 100644 index 00000000000..a92a8878730 --- /dev/null +++ b/homeassistant/components/vulcan/translations/es.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Ya se ha a\u00f1adido a este alumno.", + "reauth_successful": "Re-autenticaci\u00f3n exitosa" + }, + "error": { + "expired_token": "Token caducado, genera un nuevo token", + "invalid_symbol": "S\u00edmbolo inv\u00e1lido", + "invalid_token": "Token inv\u00e1lido" + }, + "step": { + "auth": { + "data": { + "pin": "PIN" + } + }, + "reauth_confirm": { + "data": { + "region": "S\u00edmbolo" + } + }, + "select_student": { + "data": { + "student_name": "Selecciona al alumno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/et.json b/homeassistant/components/vulcan/translations/et.json index ab3156cc676..f50fddae8f4 100644 --- a/homeassistant/components/vulcan/translations/et.json +++ b/homeassistant/components/vulcan/translations/et.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "K\u00f5ik \u00f5pilased on juba lisatud.", "already_configured": "See \u00f5pilane on juba lisatud.", + "no_matching_entries": "Sobivaid kirjeid ei leitud, kasuta teist kontot v\u00f5i eemalda muudetud \u00f5pilasega sidumine...", "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { @@ -29,7 +30,7 @@ }, "description": "Logi Vulcani kontole sisse mobiilirakenduse registreerimislehe kaudu." }, - "reauth": { + "reauth_confirm": { "data": { "pin": "PIN kood", "region": "S\u00fcmbol", @@ -50,20 +51,5 @@ "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 index 812c069223e..0a1f16b8406 100644 --- a/homeassistant/components/vulcan/translations/fr.json +++ b/homeassistant/components/vulcan/translations/fr.json @@ -3,6 +3,7 @@ "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.", + "no_matching_entries": "Aucune entr\u00e9e correspondante n'a \u00e9t\u00e9 trouv\u00e9e, veuillez utiliser un autre compte ou retirer l'int\u00e9gration incluant un \u00e9tudiant obsol\u00e8te.", "reauth_successful": "R\u00e9-authentification r\u00e9ussie" }, "error": { @@ -29,7 +30,7 @@ }, "description": "Connectez-vous \u00e0 votre compte Vulcan en utilisant la page d'inscription de l'application mobile." }, - "reauth": { + "reauth_confirm": { "data": { "pin": "PIN", "region": "Symbole", @@ -50,20 +51,5 @@ "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/he.json b/homeassistant/components/vulcan/translations/he.json new file mode 100644 index 00000000000..605830b683e --- /dev/null +++ b/homeassistant/components/vulcan/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "region": "\u05e1\u05de\u05dc", + "token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/hu.json b/homeassistant/components/vulcan/translations/hu.json index 9e138254ebe..65d90d6e24a 100644 --- a/homeassistant/components/vulcan/translations/hu.json +++ b/homeassistant/components/vulcan/translations/hu.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "M\u00e1r minden tanul\u00f3 hozz\u00e1adva.", "already_configured": "Ezt a tanul\u00f3t m\u00e1r hozz\u00e1adt\u00e1k.", + "no_matching_entries": "Nem tal\u00e1ltunk megfelel\u0151 bejegyz\u00e9st, k\u00e9rj\u00fck, haszn\u00e1ljon m\u00e1sik fi\u00f3kot, vagy t\u00e1vol\u00edtsa el az elavult di\u00e1kkal val\u00f3 integr\u00e1ci\u00f3t...", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres" }, "error": { @@ -29,7 +30,7 @@ }, "description": "Jelentkezzen be Vulcan fi\u00f3kj\u00e1ba a mobilalkalmaz\u00e1s regisztr\u00e1ci\u00f3s oldal\u00e1n." }, - "reauth": { + "reauth_confirm": { "data": { "pin": "PIN", "region": "Szimb\u00f3lum", @@ -50,20 +51,5 @@ "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 index a15ed1772d2..6aa1e0d7fa6 100644 --- a/homeassistant/components/vulcan/translations/id.json +++ b/homeassistant/components/vulcan/translations/id.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "Semua siswa telah ditambahkan.", "already_configured": "Siswa tersebut telah ditambahkan.", + "no_matching_entries": "Tidak ditemukan entri yang cocok, harap gunakan akun lain atau hapus integrasi dengan siswa yang ketinggalan zaman..", "reauth_successful": "Otorisasi ulang berhasil" }, "error": { @@ -29,7 +30,7 @@ }, "description": "Masuk ke Akun Vulcan Anda menggunakan halaman pendaftaran aplikasi seluler." }, - "reauth": { + "reauth_confirm": { "data": { "pin": "PIN", "region": "Simbol", @@ -50,20 +51,5 @@ "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 index 72bb696a909..affc791af72 100644 --- a/homeassistant/components/vulcan/translations/it.json +++ b/homeassistant/components/vulcan/translations/it.json @@ -3,13 +3,14 @@ "abort": { "all_student_already_configured": "Tutti gli studenti sono gi\u00e0 stati aggiunti.", "already_configured": "Quello studente \u00e8 gi\u00e0 stato aggiunto.", + "no_matching_entries": "Nessuna voce corrispondente trovata, utilizza un account diverso o rimuovi l'integrazione con lo studente obsoleto.", "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_pin": "PIN non valido", "invalid_symbol": "Simbolo non valido", "invalid_token": "Token non valido", "unknown": "Si \u00e8 verificato un errore sconosciuto" @@ -29,7 +30,7 @@ }, "description": "Accedi al tuo account Vulcan utilizzando la pagina di registrazione dell'applicazione mobile." }, - "reauth": { + "reauth_confirm": { "data": { "pin": "PIN", "region": "Simbolo", @@ -50,20 +51,5 @@ "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 index 98363f12f13..4f18d47b7ea 100644 --- a/homeassistant/components/vulcan/translations/ja.json +++ b/homeassistant/components/vulcan/translations/ja.json @@ -3,6 +3,7 @@ "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", + "no_matching_entries": "\u4e00\u81f4\u3059\u308b\u30a8\u30f3\u30c8\u30ea\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u5225\u306e\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u4f7f\u7528\u3059\u308b\u304b\u3001outdated student\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u524a\u9664\u3057\u3066\u304f\u3060\u3055\u3044..", "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f" }, "error": { @@ -29,7 +30,7 @@ }, "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": { + "reauth_confirm": { "data": { "pin": "\u30d4\u30f3", "region": "\u30b7\u30f3\u30dc\u30eb", @@ -50,20 +51,5 @@ "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/ko.json b/homeassistant/components/vulcan/translations/ko.json new file mode 100644 index 00000000000..f65f8ada294 --- /dev/null +++ b/homeassistant/components/vulcan/translations/ko.json @@ -0,0 +1,15 @@ +{ + "config": { + "step": { + "auth": { + "description": "\ubaa8\ubc14\uc77c \uc571 \ub4f1\ub85d \ud398\uc774\uc9c0\ub97c \uc0ac\uc6a9\ud558\uc5ec Vulcan \uacc4\uc815\uc5d0 \ub85c\uadf8\uc778\ud569\ub2c8\ub2e4." + }, + "reauth_confirm": { + "data": { + "token": "\ud1a0\ud070" + }, + "description": "\ubaa8\ubc14\uc77c \uc571\uc73c\ub85c Vulcan \uacc4\uc815\uc5d0 \ub85c\uadf8\uc778\ud569\ub2c8\ub2e4." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/nl.json b/homeassistant/components/vulcan/translations/nl.json index b05b32937c0..4b95ff00f0d 100644 --- a/homeassistant/components/vulcan/translations/nl.json +++ b/homeassistant/components/vulcan/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "Alle studenten zijn al toegevoegd.", "already_configured": "Die student is al toegevoegd.", + "no_matching_entries": "Geen overeenkomende vermeldingen gevonden, gebruik een ander account of verwijder integratie met verouderde student..", "reauth_successful": "Opnieuw verifi\u00ebren gelukt" }, "error": { @@ -29,13 +30,13 @@ }, "description": "Log in op uw Vulcan-account via de registratiepagina voor mobiele apps." }, - "reauth": { + "reauth_confirm": { "data": { - "pin": "Pin", + "pin": "Pincode", "region": "Symbool", "token": "Token" }, - "description": "Log in op uw Vulcan-account via de registratiepagina voor mobiele apps." + "description": "Log in op je Vulcan Account met behulp van de registratie pagina in de mobiele app." }, "select_saved_credentials": { "data": { @@ -50,20 +51,5 @@ "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 index 7cb8b68fa63..8dafd8b80ba 100644 --- a/homeassistant/components/vulcan/translations/no.json +++ b/homeassistant/components/vulcan/translations/no.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "Alle elever er allerede lagt til.", "already_configured": "Den studenten er allerede lagt til.", + "no_matching_entries": "Ingen samsvarende oppf\u00f8ringer funnet, vennligst bruk en annen konto eller fjern integrasjon med utdatert student..", "reauth_successful": "Reauth vellykket" }, "error": { @@ -29,7 +30,7 @@ }, "description": "Logg p\u00e5 Vulcan-kontoen din ved \u00e5 bruke registreringssiden for mobilappen." }, - "reauth": { + "reauth_confirm": { "data": { "pin": "Pin", "region": "Symbol", @@ -50,20 +51,5 @@ "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 index acbc51c6754..c8b1f4cf13a 100644 --- a/homeassistant/components/vulcan/translations/pl.json +++ b/homeassistant/components/vulcan/translations/pl.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "Wszyscy uczniowie zostali ju\u017c dodani.", "already_configured": "Ten ucze\u0144 zosta\u0142 ju\u017c dodany.", + "no_matching_entries": "Nie znaleziono pasuj\u0105cych wpis\u00f3w. U\u017cyj innego konta lub usu\u0144 integracj\u0119 z nieaktualnym uczniem.", "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { @@ -29,9 +30,9 @@ }, "description": "Zaloguj si\u0119 do swojego konta Vulcan za pomoc\u0105 strony rejestracji aplikacji mobilnej." }, - "reauth": { + "reauth_confirm": { "data": { - "pin": "Kod pin", + "pin": "Kod PIN", "region": "Symbol", "token": "Token" }, @@ -50,20 +51,5 @@ "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 index ecef34ff96b..1413cccffc9 100644 --- a/homeassistant/components/vulcan/translations/pt-BR.json +++ b/homeassistant/components/vulcan/translations/pt-BR.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "Todos os alunos j\u00e1 foram adicionados.", "already_configured": "Esse aluno j\u00e1 foi adicionado.", + "no_matching_entries": "Nenhuma entrada correspondente encontrada, use uma conta diferente ou remova a integra\u00e7\u00e3o com o aluno desatualizado.", "reauth_successful": "Autentica\u00e7\u00e3o bem-sucedida" }, "error": { @@ -29,13 +30,13 @@ }, "description": "Fa\u00e7a login na sua conta Vulcan usando a p\u00e1gina de registro do aplicativo m\u00f3vel." }, - "reauth": { + "reauth_confirm": { "data": { - "pin": "Pin", + "pin": "C\u00f3digo PIN", "region": "S\u00edmbolo", "token": "Token" }, - "description": "Fa\u00e7a login na sua conta Vulcan usando a p\u00e1gina de registro do aplicativo m\u00f3vel." + "description": "Fa\u00e7a login na sua conta Vulcan usando a p\u00e1gina de registro do aplicativo." }, "select_saved_credentials": { "data": { @@ -50,20 +51,5 @@ "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 index 88e64d9ca79..7692a97fc63 100644 --- a/homeassistant/components/vulcan/translations/ru.json +++ b/homeassistant/components/vulcan/translations/ru.json @@ -3,6 +3,7 @@ "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.", + "no_matching_entries": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0434\u0440\u0443\u0433\u0443\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0438\u0442\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0441 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0435\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u044c\u044e \u0441\u0442\u0443\u0434\u0435\u043d\u0442\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." }, "error": { @@ -29,7 +30,7 @@ }, "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": { + "reauth_confirm": { "data": { "pin": "PIN-\u043a\u043e\u0434", "region": "\u0421\u0438\u043c\u0432\u043e\u043b", @@ -50,20 +51,5 @@ "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/sk.json b/homeassistant/components/vulcan/translations/sk.json new file mode 100644 index 00000000000..c16ed208d24 --- /dev/null +++ b/homeassistant/components/vulcan/translations/sk.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "add_next_config_entry": { + "description": "Prida\u0165 \u010fal\u0161ieho \u0161tudenta." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/tr.json b/homeassistant/components/vulcan/translations/tr.json index 9f4324dfbcc..6d79e4d80a1 100644 --- a/homeassistant/components/vulcan/translations/tr.json +++ b/homeassistant/components/vulcan/translations/tr.json @@ -3,6 +3,7 @@ "abort": { "all_student_already_configured": "T\u00fcm \u00f6\u011frenciler zaten eklendi.", "already_configured": "Bu \u00f6\u011frenci zaten eklendi.", + "no_matching_entries": "E\u015fle\u015fen giri\u015f bulunamad\u0131, l\u00fctfen farkl\u0131 bir hesap kullan\u0131n veya eski \u00f6\u011frenci ile entegrasyonu kald\u0131r\u0131n..", "reauth_successful": "Yeniden yetkilendirme ba\u015far\u0131l\u0131" }, "error": { @@ -29,7 +30,7 @@ }, "description": "Mobil uygulama kay\u0131t sayfas\u0131n\u0131 kullanarak Vulcan Hesab\u0131n\u0131za giri\u015f yap\u0131n." }, - "reauth": { + "reauth_confirm": { "data": { "pin": "Pin", "region": "Sembol", @@ -50,20 +51,5 @@ "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 index 3a1c06ce5ad..24adfff6e02 100644 --- a/homeassistant/components/vulcan/translations/zh-Hant.json +++ b/homeassistant/components/vulcan/translations/zh-Hant.json @@ -3,6 +3,7 @@ "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", + "no_matching_entries": "\u627e\u4e0d\u5230\u76f8\u7b26\u7684\u5be6\u9ad4\uff0c\u8acb\u4f7f\u7528\u5176\u4ed6\u5e33\u865f\u6216\u79fb\u9664\u5305\u542b\u904e\u671f\u5b78\u751f\u4e4b\u6574\u5408..", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { @@ -29,7 +30,7 @@ }, "description": "\u4f7f\u7528\u884c\u52d5 App \u8a3b\u518a\u9801\u9762\u767b\u5165 Vulcan \u5e33\u865f\u3002" }, - "reauth": { + "reauth_confirm": { "data": { "pin": "Pin", "region": "\u7b26\u865f", @@ -50,20 +51,5 @@ "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/translations/ko.json b/homeassistant/components/wallbox/translations/ko.json new file mode 100644 index 00000000000..7c532be8890 --- /dev/null +++ b/homeassistant/components/wallbox/translations/ko.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "reauth_invalid": "\uc7ac\uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \uc2dc\ub9ac\uc5bc \ubc88\ud638\uac00 \uc6d0\ubcf8\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/nl.json b/homeassistant/components/wallbox/translations/nl.json index 5cde7830b85..bedf5851a36 100644 --- a/homeassistant/components/wallbox/translations/nl.json +++ b/homeassistant/components/wallbox/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/water_heater/device_action.py b/homeassistant/components/water_heater/device_action.py index dae9e4d579b..6bc7e1ca635 100644 --- a/homeassistant/components/water_heater/device_action.py +++ b/homeassistant/components/water_heater/device_action.py @@ -15,6 +15,7 @@ from homeassistant.const import ( from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from . import DOMAIN @@ -32,7 +33,7 @@ async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Water Heater devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) actions = [] for entry in entity_registry.async_entries_for_device(registry, device_id): @@ -52,7 +53,10 @@ async def async_get_actions( async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} diff --git a/homeassistant/components/water_heater/translations/nl.json b/homeassistant/components/water_heater/translations/nl.json index 4a2d1357188..05c8eaffc0e 100644 --- a/homeassistant/components/water_heater/translations/nl.json +++ b/homeassistant/components/water_heater/translations/nl.json @@ -11,9 +11,9 @@ "electric": "Elektriciteit", "gas": "Gas", "heat_pump": "Warmtepomp", - "high_demand": "Hoge vraag", + "high_demand": "Grote vraag", "off": "Uit", - "performance": "Prestaties" + "performance": "Prestatie" } } } \ No newline at end of file diff --git a/homeassistant/components/waterfurnace/__init__.py b/homeassistant/components/waterfurnace/__init__.py index 1da170f2b75..107ae7b9d67 100644 --- a/homeassistant/components/waterfurnace/__init__.py +++ b/homeassistant/components/waterfurnace/__init__.py @@ -16,6 +16,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv, discovery +from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -156,5 +157,5 @@ class WaterFurnaceData(threading.Thread): self._reconnect() else: - self.hass.helpers.dispatcher.dispatcher_send(UPDATE_TOPIC) + dispatcher_send(self.hass, UPDATE_TOPIC) time.sleep(SCAN_INTERVAL.total_seconds()) diff --git a/homeassistant/components/watttime/translations/nl.json b/homeassistant/components/watttime/translations/nl.json index 72758f4a0d7..09fbfe6307a 100644 --- a/homeassistant/components/watttime/translations/nl.json +++ b/homeassistant/components/watttime/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "invalid_auth": "Ongeldige authenticatie", @@ -28,7 +28,7 @@ "password": "Wachtwoord" }, "description": "Voer het wachtwoord voor {username} opnieuw in:", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "user": { "data": { diff --git a/homeassistant/components/waze_travel_time/translations/ja.json b/homeassistant/components/waze_travel_time/translations/ja.json index 5ed0741a67e..545c0cdb0d6 100644 --- a/homeassistant/components/waze_travel_time/translations/ja.json +++ b/homeassistant/components/waze_travel_time/translations/ja.json @@ -11,7 +11,7 @@ "data": { "destination": "\u76ee\u7684\u5730", "name": "\u540d\u524d", - "origin": "\u30aa\u30ea\u30b8\u30f3", + "origin": "\u539f\u70b9(Origin)", "region": "\u30ea\u30fc\u30b8\u30e7\u30f3" }, "description": "\u51fa\u767a\u5730\u3068\u76ee\u7684\u5730\u306b\u3001\u5834\u6240\u306e\u4f4f\u6240\u307e\u305f\u306fGPS\u5ea7\u6a19\u3092\u5165\u529b\u3057\u307e\u3059(GPS\u306e\u5ea7\u6a19\u306f\u30b3\u30f3\u30de\u3067\u533a\u5207\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059)\u3002\u3053\u306e\u60c5\u5831\u3092\u72b6\u614b(state)\u3067\u63d0\u4f9b\u3059\u308b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3ID\u3001\u7def\u5ea6\u3068\u7d4c\u5ea6\u306e\u5c5e\u6027\u3092\u6301\u3064\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3ID\u3001\u307e\u305f\u306f\u30be\u30fc\u30f3\u306e\u30d5\u30ec\u30f3\u30c9\u30ea\u30fc\u540d\u3092\u5165\u529b\u3059\u308b\u3053\u3068\u3082\u3067\u304d\u307e\u3059\u3002" diff --git a/homeassistant/components/waze_travel_time/translations/ko.json b/homeassistant/components/waze_travel_time/translations/ko.json index 3596754ca04..5b2dbd7ac46 100644 --- a/homeassistant/components/waze_travel_time/translations/ko.json +++ b/homeassistant/components/waze_travel_time/translations/ko.json @@ -16,5 +16,23 @@ "description": "\ucd9c\ubc1c\uc9c0 \ubc0f \ub3c4\ucc29\uc9c0\uc758 \uacbd\uc6b0 \uc704\uce58\uc758 \uc8fc\uc18c \ub610\ub294 GPS \uc88c\ud45c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. (GPS \uc88c\ud45c\ub294 \uc27c\ud45c\ub85c \uad6c\ubd84\ud574\uc57c \ud569\ub2c8\ub2e4) \ub610\ub294 \uc774\ub7ec\ud55c \uc815\ubcf4\ub97c \ud574\ub2f9 \uc0c1\ud0dc\ub85c \uc81c\uacf5\ud558\ub294 \uad6c\uc131\uc694\uc18c ID\ub098 \uc704\ub3c4 \ubc0f \uacbd\ub3c4 \uc18d\uc131\uc744 \uac00\uc9c4 \uad6c\uc131\uc694\uc18c ID \ub610\ub294 \uc9c0\uc5ed \uc774\ub984\uc744 \uc785\ub825\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4." } } - } + }, + "options": { + "step": { + "init": { + "data": { + "avoid_ferries": "\ud398\ub9ac\ub97c \ud53c\ud558\uaca0\uc5b4\uc694?", + "avoid_subscription_roads": "\uace0\uc18d\ub3c4\ub85c/\uc720\ub8cc\ub3c4\ub85c\ub97c \ud53c\ud558\uaca0\uc5b4\uc694?", + "avoid_toll_roads": "\uc720\ub8cc\ub3c4\ub85c\ub97c \ud53c\ud558\uaca0\uc5b4\uc694?", + "excl_filter": "\uc120\ud0dd\ud55c \uacbd\ub85c\uc5d0\uc11c \ud558\uc704\ubb38\uc790\uc5f4\uc744 \uc81c\uc678\ud569\ub2c8\ub2e4.", + "incl_filter": "\uc120\ud0dd\ud55c \uacbd\ub85c\uc5d0\uc11c \ud558\uc704\ubb38\uc790\uc5f4\uc744 \ud3ec\ud568\ud569\ub2c8\ub2e4.", + "realtime": "\uc2e4\uc2dc\uac04 \uc774\ub3d9 \uc2dc\uac04?", + "units": "\ub2e8\uc704", + "vehicle_type": "\ucc28\ub7c9 \uc720\ud615" + }, + "description": "'\ud558\uc704 \ubb38\uc790\uc5f4'\uc785\ub825\uc744 \uc0ac\uc6a9\ud558\uba74 \ud1b5\ud569\uad6c\uc131\uc694\uc18c\uac00 \ud2b9\uc815 \uacbd\ub85c\ub97c \uc0ac\uc6a9\ud558\uac70\ub098 \ud2b9\uc815 \uacbd\ub85c\ub97c \ud53c\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + } + } + }, + "title": "Waze Travel Time" } \ No newline at end of file diff --git a/homeassistant/components/waze_travel_time/translations/nl.json b/homeassistant/components/waze_travel_time/translations/nl.json index 223d69625d4..249b42f3063 100644 --- a/homeassistant/components/waze_travel_time/translations/nl.json +++ b/homeassistant/components/waze_travel_time/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken" diff --git a/homeassistant/components/weather/translations/es.json b/homeassistant/components/weather/translations/es.json index 2457f68cf92..a6a7336e612 100644 --- a/homeassistant/components/weather/translations/es.json +++ b/homeassistant/components/weather/translations/es.json @@ -9,7 +9,7 @@ "lightning": "Rel\u00e1mpagos", "lightning-rainy": "Rel\u00e1mpagos, lluvioso", "partlycloudy": "Parcialmente nublado", - "pouring": "Torrencial", + "pouring": "Lluvia", "rainy": "Lluvioso", "snowy": "Nevado", "snowy-rainy": "Nevado, lluvioso", diff --git a/homeassistant/components/weather/translations/nl.json b/homeassistant/components/weather/translations/nl.json index bab37782936..2fff10215ab 100644 --- a/homeassistant/components/weather/translations/nl.json +++ b/homeassistant/components/weather/translations/nl.json @@ -9,7 +9,7 @@ "lightning": "Bliksem", "lightning-rainy": "Bliksem, regenachtig", "partlycloudy": "Gedeeltelijk bewolkt", - "pouring": "Regen", + "pouring": "Gieten", "rainy": "Regenachtig", "snowy": "Sneeuwachtig", "snowy-rainy": "Sneeuw-, regenachtig", diff --git a/homeassistant/components/weather/translations/sk.json b/homeassistant/components/weather/translations/sk.json index 12c3e530e9e..b174efccf4f 100644 --- a/homeassistant/components/weather/translations/sk.json +++ b/homeassistant/components/weather/translations/sk.json @@ -11,7 +11,7 @@ "partlycloudy": "\u010ciasto\u010dne zamra\u010den\u00e9", "pouring": "Lej\u00faco", "rainy": "Da\u017edivo", - "snowy": "Zasne\u017eeno", + "snowy": "Zasne\u017een\u00fd", "snowy-rainy": "Zasne\u017eeno, da\u017edivo", "sunny": "slne\u010dno", "windy": "Veterno", diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 798f863b8ee..fb9927f1b37 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -108,7 +108,7 @@ async def async_handle_webhook( if webhook["local_only"]: try: - remote = ip_address(request.remote) + remote = ip_address(request.remote) # type: ignore[arg-type] except ValueError: _LOGGER.debug("Unable to parse remote ip %s", request.remote) return Response(status=HTTPStatus.OK) diff --git a/homeassistant/components/webhook/trigger.py b/homeassistant/components/webhook/trigger.py index 4eaf60595a5..3f790b1ec42 100644 --- a/homeassistant/components/webhook/trigger.py +++ b/homeassistant/components/webhook/trigger.py @@ -4,9 +4,14 @@ from functools import partial from aiohttp import hdrs import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import CONF_PLATFORM, CONF_WEBHOOK_ID -from homeassistant.core import HassJob, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType from . import async_register, async_unregister @@ -37,10 +42,15 @@ async def _handle_webhook(job, trigger_data, hass, webhook_id, request): hass.async_run_hass_job(job, {"trigger": result}) -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Trigger based on incoming webhooks.""" trigger_data = automation_info["trigger_data"] - webhook_id = config.get(CONF_WEBHOOK_ID) + webhook_id: str = config[CONF_WEBHOOK_ID] job = HassJob(action) async_register( hass, diff --git a/homeassistant/components/webostv/device_trigger.py b/homeassistant/components/webostv/device_trigger.py index feb5bae98fe..9ce49bbe79e 100644 --- a/homeassistant/components/webostv/device_trigger.py +++ b/homeassistant/components/webostv/device_trigger.py @@ -77,7 +77,7 @@ async def async_attach_trigger( config: ConfigType, action: AutomationActionType, automation_info: AutomationTriggerInfo, -) -> CALLBACK_TYPE | None: +) -> CALLBACK_TYPE: """Attach a trigger.""" if (trigger_type := config[CONF_TYPE]) == TURN_ON_PLATFORM_TYPE: trigger_config = { diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 49f9a29052b..3806ee6c2bb 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -87,8 +87,8 @@ _P = ParamSpec("_P") def cmd( - func: Callable[Concatenate[_T, _P], Awaitable[None]] # type: ignore[misc] -) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]: # type: ignore[misc] + func: Callable[Concatenate[_T, _P], Awaitable[None]] +) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]: """Catch command exceptions.""" @wraps(func) diff --git a/homeassistant/components/webostv/translations/es.json b/homeassistant/components/webostv/translations/es.json index d15f31b514c..712de905ddc 100644 --- a/homeassistant/components/webostv/translations/es.json +++ b/homeassistant/components/webostv/translations/es.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en curso", "error_pairing": "Conectado a LG webOS TV pero no emparejado" }, "error": { @@ -13,6 +15,9 @@ "title": "Emparejamiento de webOS TV" }, "user": { + "data": { + "name": "Nombre" + }, "description": "Encienda la televisi\u00f3n, rellene los siguientes campos y haga clic en enviar", "title": "Conectarse a webOS TV" } diff --git a/homeassistant/components/webostv/translations/ko.json b/homeassistant/components/webostv/translations/ko.json new file mode 100644 index 00000000000..e79ddbd7b3f --- /dev/null +++ b/homeassistant/components/webostv/translations/ko.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "error_pairing": "LG webOS TV\uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc9c0\ub9cc \ud398\uc5b4\ub9c1\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. TV\ub97c \ucf1c\uac70\ub098 IP \uc8fc\uc18c\ub97c \ud655\uc778\ud558\uc138\uc694." + }, + "flow_title": "LG webOS \uc2a4\ub9c8\ud2b8 TV", + "step": { + "pairing": { + "description": "\ud655\uc778\uc744 \ub204\ub974\uace0 TV\uc5d0\uc11c \ud398\uc5b4\ub9c1 \uc694\uccad\uc744 \uc218\ub77d\ud558\uc138\uc694.\n\n![Image](/static/images/config_webos.png)", + "title": "webOS TV \ud398\uc5b4\ub9c1" + }, + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "name": "\uc774\ub984" + }, + "description": "TV\ub97c \ucf1c\uace0 \ub2e4\uc74c \ud544\ub4dc\ub97c \ucc44\uc6b0\uace0 \uc81c\ucd9c\uc744 \ud074\ub9ad\ud558\uc138\uc694.", + "title": "webOS TV\uc5d0 \uc5f0\uacb0" + } + } + }, + "device_automation": { + "trigger_type": { + "webostv.turn_on": "\uae30\uae30\uac00 \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" + } + }, + "options": { + "error": { + "cannot_retrieve": "\uc18c\uc2a4 \ubaa9\ub85d\uc744 \uac80\uc0c9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc7a5\uce58\uac00 \ucf1c\uc838 \uc788\ub294\uc9c0 \ud655\uc778\ud558\uc138\uc694", + "script_not_found": "\uc2a4\ud06c\ub9bd\ud2b8\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc74c" + }, + "step": { + "init": { + "data": { + "sources": "\uc18c\uc2a4 \ubaa9\ub85d" + }, + "description": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c \uc18c\uc2a4 \uc120\ud0dd", + "title": "webOS \uc2a4\ub9c8\ud2b8 TV \uc635\uc158" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/webostv/translations/nl.json b/homeassistant/components/webostv/translations/nl.json index 2a9fb59e1c1..f914287ce16 100644 --- a/homeassistant/components/webostv/translations/nl.json +++ b/homeassistant/components/webostv/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "error_pairing": "Verbonden met LG webOS TV maar niet gekoppeld" }, "error": { diff --git a/homeassistant/components/websocket_api/auth.py b/homeassistant/components/websocket_api/auth.py index 794dae77153..9c074588a17 100644 --- a/homeassistant/components/websocket_api/auth.py +++ b/homeassistant/components/websocket_api/auth.py @@ -56,7 +56,7 @@ class AuthPhase: self, logger: WebSocketAdapter, hass: HomeAssistant, - send_message: Callable[[str | dict[str, Any]], None], + send_message: Callable[[str | dict[str, Any] | Callable[[], str]], None], cancel_ws: CALLBACK_TYPE, request: Request, ) -> None: diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 414913be002..61bcb8badf0 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -105,17 +105,21 @@ def handle_subscribe_events( ): return - connection.send_message(messages.cached_event_message(msg["id"], event)) + connection.send_message( + lambda: messages.cached_event_message(msg["id"], event) + ) else: @callback def forward_events(event: Event) -> None: """Forward events to websocket.""" - connection.send_message(messages.cached_event_message(msg["id"], event)) + connection.send_message( + lambda: messages.cached_event_message(msg["id"], event) + ) connection.subscriptions[msg["id"]] = hass.bus.async_listen( - event_type, forward_events + event_type, forward_events, run_immediately=True ) connection.send_result(msg["id"]) @@ -286,14 +290,16 @@ def handle_subscribe_entities( if entity_ids and event.data["entity_id"] not in entity_ids: return - connection.send_message(messages.cached_state_diff_message(msg["id"], event)) + connection.send_message( + lambda: messages.cached_state_diff_message(msg["id"], event) + ) # We must never await between sending the states and listening for # state changed events or we will introduce a race condition # where some states are missed states = _async_get_allowed_states(hass, connection) connection.subscriptions[msg["id"]] = hass.bus.async_listen( - "state_changed", forward_entity_changes + EVENT_STATE_CHANGED, forward_entity_changes, run_immediately=True ) connection.send_result(msg["id"]) data: dict[str, dict[str, dict]] = { @@ -353,15 +359,19 @@ def handle_get_config( connection.send_result(msg["id"], hass.config.as_dict()) -@decorators.websocket_command({vol.Required("type"): "manifest/list"}) +@decorators.websocket_command( + {vol.Required("type"): "manifest/list", vol.Optional("integrations"): [str]} +) @decorators.async_response async def handle_manifest_list( hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Handle integrations command.""" - loaded_integrations = async_get_loaded_integrations(hass) + wanted_integrations = msg.get("integrations") + if wanted_integrations is None: + wanted_integrations = async_get_loaded_integrations(hass) integrations = await asyncio.gather( - *(async_get_integration(hass, domain) for domain in loaded_integrations) + *(async_get_integration(hass, domain) for domain in wanted_integrations) ) connection.send_result( msg["id"], [integration.manifest for integration in integrations] diff --git a/homeassistant/components/websocket_api/connection.py b/homeassistant/components/websocket_api/connection.py index 9b53f358b85..0280863f83e 100644 --- a/homeassistant/components/websocket_api/connection.py +++ b/homeassistant/components/websocket_api/connection.py @@ -30,7 +30,7 @@ class ActiveConnection: self, logger: WebSocketAdapter, hass: HomeAssistant, - send_message: Callable[[str | dict[str, Any]], None], + send_message: Callable[[str | dict[str, Any] | Callable[[], str]], None], user: User, refresh_token: RefreshToken, ) -> None: @@ -93,7 +93,7 @@ class ActiveConnection: return if msg["type"] not in handlers: - self.logger.error("Received invalid command: {}".format(msg["type"])) + self.logger.info("Received unknown command: {}".format(msg["type"])) self.send_message( messages.error_message( cur_id, const.ERR_UNKNOWN_COMMAND, "Unknown command." diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py index 6c5615ad253..107cf6d0270 100644 --- a/homeassistant/components/websocket_api/const.py +++ b/homeassistant/components/websocket_api/const.py @@ -56,3 +56,9 @@ DATA_CONNECTIONS: Final = f"{DOMAIN}.connections" JSON_DUMP: Final = partial( json.dumps, cls=JSONEncoder, allow_nan=False, separators=(",", ":") ) + +COMPRESSED_STATE_STATE = "s" +COMPRESSED_STATE_ATTRIBUTES = "a" +COMPRESSED_STATE_CONTEXT = "c" +COMPRESSED_STATE_LAST_CHANGED = "lc" +COMPRESSED_STATE_LAST_UPDATED = "lu" diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index 44b2aa8579c..e8972a227c8 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -14,6 +14,7 @@ import async_timeout from homeassistant.components.http import HomeAssistantView from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later from .auth import AuthPhase, auth_required_message @@ -72,9 +73,13 @@ class WebSocketHandler: # Exceptions if Socket disconnected or cancelled by connection handler with suppress(RuntimeError, ConnectionResetError, *CANCELLATION_ERRORS): while not self.wsock.closed: - if (message := await self._to_write.get()) is None: + if (process := await self._to_write.get()) is None: break + if not isinstance(process, str): + message: str = process() + else: + message = process self._logger.debug("Sending %s", message) await self.wsock.send_str(message) @@ -84,14 +89,14 @@ class WebSocketHandler: self._peak_checker_unsub = None @callback - def _send_message(self, message: str | dict[str, Any]) -> None: + def _send_message(self, message: str | dict[str, Any] | Callable[[], str]) -> None: """Send a message to the client. Closes connection if the client is not reading the messages. Async friendly. """ - if not isinstance(message, str): + if isinstance(message, dict): message = message_to_json(message) try: @@ -199,9 +204,7 @@ class WebSocketHandler: self.hass.data[DATA_CONNECTIONS] = ( self.hass.data.get(DATA_CONNECTIONS, 0) + 1 ) - self.hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_WEBSOCKET_CONNECTED - ) + async_dispatcher_send(self.hass, SIGNAL_WEBSOCKET_CONNECTED) # Command phase while not wsock.closed: @@ -254,8 +257,6 @@ class WebSocketHandler: if connection is not None: self.hass.data[DATA_CONNECTIONS] -= 1 - self.hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_WEBSOCKET_DISCONNECTED - ) + async_dispatcher_send(self.hass, SIGNAL_WEBSOCKET_DISCONNECTED) return wsock diff --git a/homeassistant/components/websocket_api/messages.py b/homeassistant/components/websocket_api/messages.py index eac40c9510b..f546ba5eec6 100644 --- a/homeassistant/components/websocket_api/messages.py +++ b/homeassistant/components/websocket_api/messages.py @@ -16,6 +16,13 @@ from homeassistant.util.json import ( from homeassistant.util.yaml.loader import JSON_TYPE from . import const +from .const import ( + COMPRESSED_STATE_ATTRIBUTES, + COMPRESSED_STATE_CONTEXT, + COMPRESSED_STATE_LAST_CHANGED, + COMPRESSED_STATE_LAST_UPDATED, + COMPRESSED_STATE_STATE, +) _LOGGER: Final = logging.getLogger(__name__) @@ -31,12 +38,6 @@ BASE_COMMAND_MESSAGE_SCHEMA: Final = vol.Schema({vol.Required("id"): cv.positive IDEN_TEMPLATE: Final = "__IDEN__" IDEN_JSON_TEMPLATE: Final = '"__IDEN__"' -COMPRESSED_STATE_STATE = "s" -COMPRESSED_STATE_ATTRIBUTES = "a" -COMPRESSED_STATE_CONTEXT = "c" -COMPRESSED_STATE_LAST_CHANGED = "lc" -COMPRESSED_STATE_LAST_UPDATED = "lu" - STATE_DIFF_ADDITIONS = "+" STATE_DIFF_REMOVALS = "-" @@ -60,7 +61,7 @@ def error_message(iden: int | None, code: str, message: str) -> dict[str, Any]: } -def event_message(iden: JSON_TYPE, event: Any) -> dict[str, Any]: +def event_message(iden: JSON_TYPE | int, event: Any) -> dict[str, Any]: """Return an event message.""" return {"id": iden, "type": "event", "event": event} diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index 3b08a39f2ce..2727c913fee 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -32,7 +32,7 @@ WEMO_MODEL_DISPATCH = { "CoffeeMaker": [Platform.SWITCH], "Dimmer": [Platform.LIGHT], "Humidifier": [Platform.FAN], - "Insight": [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH], + "Insight": [Platform.BINARY_SENSOR, Platform.SWITCH], "LightSwitch": [Platform.SWITCH], "Maker": [Platform.BINARY_SENSOR, Platform.SWITCH], "Motion": [Platform.BINARY_SENSOR], @@ -141,7 +141,7 @@ class WemoDispatcher: """Initialize the WemoDispatcher.""" self._config_entry = config_entry self._added_serial_numbers: set[str] = set() - self._loaded_components: set[str] = set() + self._loaded_platforms: set[Platform] = set() async def async_add_unique_device( self, hass: HomeAssistant, wemo: pywemo.WeMoDevice @@ -151,28 +151,30 @@ class WemoDispatcher: return coordinator = await async_register_device(hass, self._config_entry, wemo) - for component in WEMO_MODEL_DISPATCH.get(wemo.model_name, [Platform.SWITCH]): + platforms = set(WEMO_MODEL_DISPATCH.get(wemo.model_name, [Platform.SWITCH])) + platforms.add(Platform.SENSOR) + for platform in platforms: # Three cases: - # - First time we see component, we need to load it and initialize the backlog - # - Component is being loaded, add to backlog - # - Component is loaded, backlog is gone, dispatch discovery + # - First time we see platform, we need to load it and initialize the backlog + # - Platform is being loaded, add to backlog + # - Platform is loaded, backlog is gone, dispatch discovery - if component not in self._loaded_components: - hass.data[DOMAIN]["pending"][component] = [coordinator] - self._loaded_components.add(component) + if platform not in self._loaded_platforms: + hass.data[DOMAIN]["pending"][platform] = [coordinator] + self._loaded_platforms.add(platform) hass.async_create_task( hass.config_entries.async_forward_entry_setup( - self._config_entry, component + self._config_entry, platform ) ) - elif component in hass.data[DOMAIN]["pending"]: - hass.data[DOMAIN]["pending"][component].append(coordinator) + elif platform in hass.data[DOMAIN]["pending"]: + hass.data[DOMAIN]["pending"][platform].append(coordinator) else: async_dispatcher_send( hass, - f"{DOMAIN}.{component}", + f"{DOMAIN}.{platform}", coordinator, ) diff --git a/homeassistant/components/wemo/binary_sensor.py b/homeassistant/components/wemo/binary_sensor.py index cde13d632fe..ce7dfc2fa11 100644 --- a/homeassistant/components/wemo/binary_sensor.py +++ b/homeassistant/components/wemo/binary_sensor.py @@ -1,8 +1,7 @@ """Support for WeMo binary sensors.""" import asyncio -from typing import cast -from pywemo import Insight, Maker +from pywemo import Insight, Maker, StandbyState from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry @@ -49,19 +48,21 @@ class MakerBinarySensor(WemoEntity, BinarySensorEntity): """Maker device's sensor port.""" _name_suffix = "Sensor" + wemo: Maker @property def is_on(self) -> bool: """Return true if the Maker's sensor is pulled low.""" - return cast(int, self.wemo.has_sensor) != 0 and self.wemo.sensor_state == 0 + return self.wemo.has_sensor != 0 and self.wemo.sensor_state == 0 class InsightBinarySensor(WemoBinarySensor): """Sensor representing the device connected to the Insight Switch.""" _name_suffix = "Device" + wemo: Insight @property def is_on(self) -> bool: """Return true device connected to the Insight Switch is on.""" - return super().is_on and self.wemo.insight_params["state"] == "1" + return super().is_on and self.wemo.standby_state == StandbyState.ON diff --git a/homeassistant/components/wemo/device_trigger.py b/homeassistant/components/wemo/device_trigger.py index 1cdc29fa995..973a3358b1b 100644 --- a/homeassistant/components/wemo/device_trigger.py +++ b/homeassistant/components/wemo/device_trigger.py @@ -1,8 +1,6 @@ """Triggers for WeMo devices.""" from __future__ import annotations -from typing import Any - from pywemo.subscribe import EVENT_TYPE_LONG_PRESS import voluptuous as vol @@ -30,7 +28,7 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, Any]]: +) -> list[dict[str, str]]: """Return a list of triggers.""" wemo_trigger = { diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index 6d94e203932..c8ed9cdde08 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -4,7 +4,6 @@ from __future__ import annotations from collections.abc import Generator import contextlib import logging -from typing import cast from pywemo.exceptions import ActionException @@ -89,4 +88,4 @@ class WemoBinaryStateEntity(WemoEntity): @property def is_on(self) -> bool: """Return true if the state is on.""" - return cast(int, self.wemo.get_state()) != 0 + return self.wemo.get_state() != 0 diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index d62a7a3b7e3..d24827bee96 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -6,7 +6,7 @@ from datetime import timedelta import math from typing import Any -from pywemo.ouimeaux_device.humidifier import DesiredHumidity, FanMode, Humidifier +from pywemo import DesiredHumidity, FanMode, Humidifier import voluptuous as vol from homeassistant.components.fan import FanEntity, FanEntityFeature diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py index b7c6aefa868..3ff0f115a04 100644 --- a/homeassistant/components/wemo/light.py +++ b/homeassistant/components/wemo/light.py @@ -2,20 +2,18 @@ from __future__ import annotations import asyncio -from typing import Any, Optional, cast +from typing import Any, cast -from pywemo.ouimeaux_device import bridge +from pywemo import Bridge, BridgeLight, Dimmer from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -29,10 +27,6 @@ from .const import DOMAIN as WEMO_DOMAIN from .entity import WemoBinaryStateEntity, WemoEntity from .wemo_device import DeviceCoordinator -SUPPORT_WEMO = ( - SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR | SUPPORT_TRANSITION -) - # The WEMO_ constants below come from pywemo itself WEMO_OFF = 0 @@ -46,7 +40,7 @@ async def async_setup_entry( async def _discovered_wemo(coordinator: DeviceCoordinator) -> None: """Handle a discovered Wemo device.""" - if isinstance(coordinator.wemo, bridge.Bridge): + if isinstance(coordinator.wemo, Bridge): async_setup_bridge(hass, config_entry, async_add_entities, coordinator) else: async_add_entities([WemoDimmer(coordinator)]) @@ -76,7 +70,8 @@ def async_setup_bridge( """Check to see if the bridge has any new lights.""" new_lights = [] - for light_id, light in coordinator.wemo.Lights.items(): + bridge = cast(Bridge, coordinator.wemo) + for light_id, light in bridge.Lights.items(): if light_id not in known_light_ids: known_light_ids.add(light_id) new_lights.append(WemoLight(coordinator, light)) @@ -91,7 +86,9 @@ def async_setup_bridge( class WemoLight(WemoEntity, LightEntity): """Representation of a WeMo light.""" - def __init__(self, coordinator: DeviceCoordinator, light: bridge.Light) -> None: + _attr_supported_features = LightEntityFeature.TRANSITION + + def __init__(self, coordinator: DeviceCoordinator, light: BridgeLight) -> None: """Initialize the WeMo light.""" super().__init__(coordinator) self.light = light @@ -101,17 +98,17 @@ class WemoLight(WemoEntity, LightEntity): @property def name(self) -> str: """Return the name of the device if any.""" - return cast(str, self.light.name) + return self.light.name @property def available(self) -> bool: """Return true if the device is available.""" - return super().available and self.light.state.get("available") + return super().available and self.light.state.get("available", False) @property def unique_id(self) -> str: """Return the ID of this light.""" - return cast(str, self.light.uniqueID) + return self.light.uniqueID @property def device_info(self) -> DeviceInfo: @@ -127,29 +124,50 @@ class WemoLight(WemoEntity, LightEntity): @property def brightness(self) -> int: """Return the brightness of this light between 0..255.""" - return cast(int, self.light.state.get("level", 255)) + return self.light.state.get("level", 255) @property - def hs_color(self) -> tuple[float, float] | None: - """Return the hs color values of this light.""" - if xy_color := self.light.state.get("color_xy"): - return color_util.color_xy_to_hs(*xy_color) - return None + def xy_color(self) -> tuple[float, float] | None: + """Return the xy color value [float, float].""" + return self.light.state.get("color_xy") @property def color_temp(self) -> int | None: """Return the color temperature of this light in mireds.""" - return cast(Optional[int], self.light.state.get("temperature_mireds")) + return self.light.state.get("temperature_mireds") + + @property + def color_mode(self) -> ColorMode: + """Return the color mode of the light.""" + if ( + "colorcontrol" in self.light.capabilities + and self.light.state.get("color_xy") is not None + ): + return ColorMode.XY + if "colortemperature" in self.light.capabilities: + return ColorMode.COLOR_TEMP + if "levelcontrol" in self.light.capabilities: + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF + + @property + def supported_color_modes(self) -> set[ColorMode]: + """Flag supported color modes.""" + modes: set[ColorMode] = set() + if "colorcontrol" in self.light.capabilities: + modes.add(ColorMode.XY) + if "colortemperature" in self.light.capabilities: + modes.add(ColorMode.COLOR_TEMP) + if "levelcontrol" in self.light.capabilities and not modes: + modes.add(ColorMode.BRIGHTNESS) + if not modes: + modes.add(ColorMode.ONOFF) + return modes @property def is_on(self) -> bool: """Return true if device is on.""" - return cast(int, self.light.state.get("onoff")) != WEMO_OFF - - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_WEMO + return self.light.state.get("onoff", WEMO_OFF) != WEMO_OFF def turn_on(self, **kwargs: Any) -> None: """Turn the light on.""" @@ -191,10 +209,9 @@ class WemoLight(WemoEntity, LightEntity): class WemoDimmer(WemoBinaryStateEntity, LightEntity): """Representation of a WeMo dimmer.""" - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_color_mode = ColorMode.BRIGHTNESS + wemo: Dimmer @property def brightness(self) -> int: diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index d048a59d38c..40bb8161d90 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -3,7 +3,7 @@ "name": "Belkin WeMo", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wemo", - "requirements": ["pywemo==0.7.0"], + "requirements": ["pywemo==0.8.1"], "ssdp": [ { "manufacturer": "Belkin International Inc." diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index d3c6264ab89..1f9f8bce01f 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -3,9 +3,9 @@ from __future__ import annotations import asyncio from datetime import datetime, timedelta -from typing import Any, cast +from typing import Any -from pywemo import CoffeeMaker, Insight, Maker +from pywemo import CoffeeMaker, Insight, Maker, StandbyState, Switch from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -13,7 +13,6 @@ from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOW from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.util import convert from .const import DOMAIN as WEMO_DOMAIN from .entity import WemoBinaryStateEntity @@ -22,19 +21,18 @@ from .wemo_device import DeviceCoordinator SCAN_INTERVAL = timedelta(seconds=10) PARALLEL_UPDATES = 0 -# The WEMO_ constants below come from pywemo itself +ATTR_COFFEMAKER_MODE = "coffeemaker_mode" +ATTR_CURRENT_STATE_DETAIL = "state_detail" +ATTR_ON_LATEST_TIME = "on_latest_time" +ATTR_ON_TODAY_TIME = "on_today_time" +ATTR_ON_TOTAL_TIME = "on_total_time" +ATTR_POWER_THRESHOLD = "power_threshold_w" ATTR_SENSOR_STATE = "sensor_state" ATTR_SWITCH_MODE = "switch_mode" -ATTR_CURRENT_STATE_DETAIL = "state_detail" -ATTR_COFFEMAKER_MODE = "coffeemaker_mode" MAKER_SWITCH_MOMENTARY = "momentary" MAKER_SWITCH_TOGGLE = "toggle" -WEMO_ON = 1 -WEMO_OFF = 0 -WEMO_STANDBY = 8 - async def async_setup_entry( hass: HomeAssistant, @@ -60,21 +58,24 @@ async def async_setup_entry( class WemoSwitch(WemoBinaryStateEntity, SwitchEntity): """Representation of a WeMo switch.""" + # All wemo devices used with WemoSwitch are subclasses of Switch. + wemo: Switch + @property def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the device.""" attr: dict[str, Any] = {} if isinstance(self.wemo, Maker): # Is the maker sensor on or off. - if self.wemo.maker_params["hassensor"]: + if self.wemo.has_sensor: # Note a state of 1 matches the WeMo app 'not triggered'! - if self.wemo.maker_params["sensorstate"]: + if self.wemo.sensor_state: attr[ATTR_SENSOR_STATE] = STATE_OFF else: attr[ATTR_SENSOR_STATE] = STATE_ON # Is the maker switch configured as toggle(0) or momentary (1). - if self.wemo.maker_params["switchmode"]: + if self.wemo.switch_mode: attr[ATTR_SWITCH_MODE] = MAKER_SWITCH_MOMENTARY else: attr[ATTR_SWITCH_MODE] = MAKER_SWITCH_TOGGLE @@ -83,20 +84,10 @@ class WemoSwitch(WemoBinaryStateEntity, SwitchEntity): attr[ATTR_CURRENT_STATE_DETAIL] = self.detail_state if isinstance(self.wemo, Insight): - attr["on_latest_time"] = WemoSwitch.as_uptime( - self.wemo.insight_params.get("onfor", 0) - ) - attr["on_today_time"] = WemoSwitch.as_uptime( - self.wemo.insight_params.get("ontoday", 0) - ) - attr["on_total_time"] = WemoSwitch.as_uptime( - self.wemo.insight_params.get("ontotal", 0) - ) - threshold = convert( - self.wemo.insight_params.get("powerthreshold"), float, 0.0 - ) - assert isinstance(threshold, float) - attr["power_threshold_w"] = threshold / 1000.0 + attr[ATTR_ON_LATEST_TIME] = self.as_uptime(self.wemo.on_for) + attr[ATTR_ON_TODAY_TIME] = self.as_uptime(self.wemo.today_on_time) + attr[ATTR_ON_TOTAL_TIME] = self.as_uptime(self.wemo.total_on_time) + attr[ATTR_POWER_THRESHOLD] = self.wemo.threshold_power_watts if isinstance(self.wemo, CoffeeMaker): attr[ATTR_COFFEMAKER_MODE] = self.wemo.mode @@ -115,14 +106,14 @@ class WemoSwitch(WemoBinaryStateEntity, SwitchEntity): def detail_state(self) -> str: """Return the state of the device.""" if isinstance(self.wemo, CoffeeMaker): - return cast(str, self.wemo.mode_string) + return self.wemo.mode_string if isinstance(self.wemo, Insight): - standby_state = int(self.wemo.insight_params.get("state", 0)) - if standby_state == WEMO_ON: + standby_state = self.wemo.standby_state + if standby_state == StandbyState.ON: return STATE_ON - if standby_state == WEMO_OFF: + if standby_state == StandbyState.OFF: return STATE_OFF - if standby_state == WEMO_STANDBY: + if standby_state == StandbyState.STANDBY: return STATE_STANDBY return STATE_UNKNOWN assert False # Unreachable code statement. diff --git a/homeassistant/components/wemo/translations/es.json b/homeassistant/components/wemo/translations/es.json index 4c176762d04..29dc3d8db46 100644 --- a/homeassistant/components/wemo/translations/es.json +++ b/homeassistant/components/wemo/translations/es.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No se encontraron dispositivos Wemo en la red.", - "single_instance_allowed": "Solo es posible una \u00fanica configuraci\u00f3n de Wemo." + "no_devices_found": "No se encontraron dispositivos en la red", + "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/translations/nl.json b/homeassistant/components/wemo/translations/nl.json index 7fde5a7ef42..78b3b5f5a05 100644 --- a/homeassistant/components/wemo/translations/nl.json +++ b/homeassistant/components/wemo/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/wemo_device.py b/homeassistant/components/wemo/wemo_device.py index 3ca47544fd7..8f5e6864059 100644 --- a/homeassistant/components/wemo/wemo_device.py +++ b/homeassistant/components/wemo/wemo_device.py @@ -3,7 +3,7 @@ import asyncio from datetime import timedelta import logging -from pywemo import Insight, WeMoDevice +from pywemo import Insight, LongPressMixin, WeMoDevice from pywemo.exceptions import ActionException from pywemo.subscribe import EVENT_TYPE_LONG_PRESS @@ -159,7 +159,7 @@ async def async_register_device( registry.on(wemo, None, device.subscription_callback) await hass.async_add_executor_job(registry.register, wemo) - if device.supports_long_press: + if isinstance(wemo, LongPressMixin): try: await hass.async_add_executor_job(wemo.ensure_long_press_virtual_device) # Temporarily handling all exceptions for #52996 & pywemo/pywemo/issues/276 diff --git a/homeassistant/components/whois/translations/es.json b/homeassistant/components/whois/translations/es.json index 712c85865cd..0da233e02ad 100644 --- a/homeassistant/components/whois/translations/es.json +++ b/homeassistant/components/whois/translations/es.json @@ -3,6 +3,10 @@ "abort": { "already_configured": "El servicio ya est\u00e1 configurado" }, + "error": { + "unknown_date_format": "Formato de fecha desconocido en la respuesta del servidor whois", + "whois_command_failed": "El comando whois ha fallado: no se pudo obtener la informaci\u00f3n whois" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/whois/translations/nl.json b/homeassistant/components/whois/translations/nl.json index a6ad64881ef..714b9c9ddd9 100644 --- a/homeassistant/components/whois/translations/nl.json +++ b/homeassistant/components/whois/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is al geconfigureerd" + "already_configured": "Dienst is al geconfigureerd" }, "error": { "unexpected_response": "Onverwacht antwoord van whois-server", diff --git a/homeassistant/components/whois/translations/sk.json b/homeassistant/components/whois/translations/sk.json new file mode 100644 index 00000000000..102f110d9ba --- /dev/null +++ b/homeassistant/components/whois/translations/sk.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "domain": "Dom\u00e9nov\u00e9 meno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/es.json b/homeassistant/components/wiffi/translations/es.json index 392392e0f31..dbcec6f2dc9 100644 --- a/homeassistant/components/wiffi/translations/es.json +++ b/homeassistant/components/wiffi/translations/es.json @@ -8,7 +8,7 @@ "step": { "user": { "data": { - "port": "Puerto del servidor" + "port": "Puerto" }, "title": "Configurar servidor TCP para dispositivos WIFFI" } diff --git a/homeassistant/components/wilight/translations/ar.json b/homeassistant/components/wilight/translations/ar.json index 033cc81d349..f6b22194fe1 100644 --- a/homeassistant/components/wilight/translations/ar.json +++ b/homeassistant/components/wilight/translations/ar.json @@ -7,8 +7,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "\u0647\u0644 \u062a\u0631\u064a\u062f \u0625\u0639\u062f\u0627\u062f WiLight {name} \u061f \n\n \u064a\u062f\u0639\u0645: {components}", - "title": "WiLight" + "description": "\u0647\u0644 \u062a\u0631\u064a\u062f \u0625\u0639\u062f\u0627\u062f WiLight {name} \u061f \n\n \u064a\u062f\u0639\u0645: {components}" } } } diff --git a/homeassistant/components/wilight/translations/ca.json b/homeassistant/components/wilight/translations/ca.json index 22044549b0c..e9acff0a91e 100644 --- a/homeassistant/components/wilight/translations/ca.json +++ b/homeassistant/components/wilight/translations/ca.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "S'admeten els seg\u00fcents components: {components}", - "title": "WiLight" + "description": "S'admeten els seg\u00fcents components: {components}" } } } diff --git a/homeassistant/components/wilight/translations/cs.json b/homeassistant/components/wilight/translations/cs.json index 0903e0e3a65..bd61d73667a 100644 --- a/homeassistant/components/wilight/translations/cs.json +++ b/homeassistant/components/wilight/translations/cs.json @@ -8,8 +8,7 @@ "flow_title": "WiLight: {name}", "step": { "confirm": { - "description": "Chcete nastavit WiLight {name} ? \n\n Podporuje: {components}", - "title": "WiLight" + "description": "Chcete nastavit WiLight {name} ? \n\n Podporuje: {components}" } } } diff --git a/homeassistant/components/wilight/translations/de.json b/homeassistant/components/wilight/translations/de.json index 27d48a90e58..84fcf14c2a4 100644 --- a/homeassistant/components/wilight/translations/de.json +++ b/homeassistant/components/wilight/translations/de.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Die folgenden Komponenten werden unterst\u00fctzt: {components}", - "title": "WiLight" + "description": "Die folgenden Komponenten werden unterst\u00fctzt: {components}" } } } diff --git a/homeassistant/components/wilight/translations/el.json b/homeassistant/components/wilight/translations/el.json index 5bb3130753a..79bb1d443c4 100644 --- a/homeassistant/components/wilight/translations/el.json +++ b/homeassistant/components/wilight/translations/el.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf WiLight {name} ;\n\n \u03a5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03b9: {components}", - "title": "WiLight" + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf WiLight {name} ;\n\n \u03a5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03b9: {components}" } } } diff --git a/homeassistant/components/wilight/translations/en.json b/homeassistant/components/wilight/translations/en.json index 1f0733d28f3..01f80fc6e85 100644 --- a/homeassistant/components/wilight/translations/en.json +++ b/homeassistant/components/wilight/translations/en.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "The following components are supported: {components}", - "title": "WiLight" + "description": "The following components are supported: {components}" } } } diff --git a/homeassistant/components/wilight/translations/es.json b/homeassistant/components/wilight/translations/es.json index b0104e6e44c..5836867c0e7 100644 --- a/homeassistant/components/wilight/translations/es.json +++ b/homeassistant/components/wilight/translations/es.json @@ -5,11 +5,10 @@ "not_supported_device": "Este WiLight no es compatible actualmente", "not_wilight_device": "Este dispositivo no es un Wilight" }, - "flow_title": "WiLight: {name}", + "flow_title": "{name}", "step": { "confirm": { - "description": "\u00bfQuieres configurar WiLight {name} ? \n\n Es compatible con: {components}", - "title": "WiLight" + "description": "Se admiten los siguientes componentes: {componentes}" } } } diff --git a/homeassistant/components/wilight/translations/et.json b/homeassistant/components/wilight/translations/et.json index dae085d1084..f21fb75e796 100644 --- a/homeassistant/components/wilight/translations/et.json +++ b/homeassistant/components/wilight/translations/et.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Toetatud on j\u00e4rgmised komponendid: {components}", - "title": "" + "description": "Toetatud on j\u00e4rgmised komponendid: {components}" } } } diff --git a/homeassistant/components/wilight/translations/fr.json b/homeassistant/components/wilight/translations/fr.json index ddd85a5cc04..2fc169c5891 100644 --- a/homeassistant/components/wilight/translations/fr.json +++ b/homeassistant/components/wilight/translations/fr.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Les composants suivants sont pris en charge\u00a0: {components}", - "title": "WiLight" + "description": "Les composants suivants sont pris en charge\u00a0: {components}" } } } diff --git a/homeassistant/components/wilight/translations/he.json b/homeassistant/components/wilight/translations/he.json index 977167ec765..48ae2e01df1 100644 --- a/homeassistant/components/wilight/translations/he.json +++ b/homeassistant/components/wilight/translations/he.json @@ -3,11 +3,6 @@ "abort": { "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" }, - "flow_title": "{name}", - "step": { - "confirm": { - "title": "WiLight" - } - } + "flow_title": "{name}" } } \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/hu.json b/homeassistant/components/wilight/translations/hu.json index 02db2f1b7df..bae04810d43 100644 --- a/homeassistant/components/wilight/translations/hu.json +++ b/homeassistant/components/wilight/translations/hu.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "A k\u00f6vetkez\u0151 komponensek t\u00e1mogatottak: {components}", - "title": "WiLight" + "description": "A k\u00f6vetkez\u0151 komponensek t\u00e1mogatottak: {components}" } } } diff --git a/homeassistant/components/wilight/translations/id.json b/homeassistant/components/wilight/translations/id.json index 0489a0e96bb..1597c4a2c72 100644 --- a/homeassistant/components/wilight/translations/id.json +++ b/homeassistant/components/wilight/translations/id.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Komponen berikut didukung: {components}", - "title": "WiLight" + "description": "Komponen berikut didukung: {components}" } } } diff --git a/homeassistant/components/wilight/translations/it.json b/homeassistant/components/wilight/translations/it.json index 7b560b7bfdb..3f425fafbb1 100644 --- a/homeassistant/components/wilight/translations/it.json +++ b/homeassistant/components/wilight/translations/it.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Sono supportati i seguenti componenti: {components}", - "title": "WiLight" + "description": "Sono supportati i seguenti componenti: {components}" } } } diff --git a/homeassistant/components/wilight/translations/ja.json b/homeassistant/components/wilight/translations/ja.json index 0a01f69d43a..1f09be66bf1 100644 --- a/homeassistant/components/wilight/translations/ja.json +++ b/homeassistant/components/wilight/translations/ja.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "WiLight {name} \u3092\u8a2d\u5b9a\u3057\u307e\u3059\u304b\uff1f\n\n \u5bfe\u5fdc\u3057\u3066\u3044\u308b: {components}", - "title": "WiLight" + "description": "WiLight {name} \u3092\u8a2d\u5b9a\u3057\u307e\u3059\u304b\uff1f\n\n \u5bfe\u5fdc\u3057\u3066\u3044\u308b: {components}" } } } diff --git a/homeassistant/components/wilight/translations/ko.json b/homeassistant/components/wilight/translations/ko.json index 1b53a1ba544..ef740d1ca2c 100644 --- a/homeassistant/components/wilight/translations/ko.json +++ b/homeassistant/components/wilight/translations/ko.json @@ -8,8 +8,7 @@ "flow_title": "WiLight: {name}", "step": { "confirm": { - "description": "WiLight {name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\uc9c0\uc6d0 \uae30\uae30: {components}", - "title": "WiLight" + "description": "WiLight {name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\uc9c0\uc6d0 \uae30\uae30: {components}" } } } diff --git a/homeassistant/components/wilight/translations/lb.json b/homeassistant/components/wilight/translations/lb.json index 2c0b831098e..002fb238482 100644 --- a/homeassistant/components/wilight/translations/lb.json +++ b/homeassistant/components/wilight/translations/lb.json @@ -8,8 +8,7 @@ "flow_title": "Wilight: {name}", "step": { "confirm": { - "description": "Soll WiLight {name} konfigur\u00e9iert ginn?\n\nEt \u00ebnnerst\u00ebtzt: {components}", - "title": "WiLight" + "description": "Soll WiLight {name} konfigur\u00e9iert ginn?\n\nEt \u00ebnnerst\u00ebtzt: {components}" } } } diff --git a/homeassistant/components/wilight/translations/nl.json b/homeassistant/components/wilight/translations/nl.json index 51cefe8ce28..c1afd57e34d 100644 --- a/homeassistant/components/wilight/translations/nl.json +++ b/homeassistant/components/wilight/translations/nl.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "De volgende componenten worden ondersteund: {components}", - "title": "WiLight" + "description": "De volgende componenten worden ondersteund: {components}" } } } diff --git a/homeassistant/components/wilight/translations/no.json b/homeassistant/components/wilight/translations/no.json index 582b2289a32..aacb91e5638 100644 --- a/homeassistant/components/wilight/translations/no.json +++ b/homeassistant/components/wilight/translations/no.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "F\u00f8lgende komponenter st\u00f8ttes: {components}", - "title": "" + "description": "F\u00f8lgende komponenter st\u00f8ttes: {components}" } } } diff --git a/homeassistant/components/wilight/translations/pl.json b/homeassistant/components/wilight/translations/pl.json index c1f636b9d80..3880698b61c 100644 --- a/homeassistant/components/wilight/translations/pl.json +++ b/homeassistant/components/wilight/translations/pl.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Obs\u0142ugiwane s\u0105 nast\u0119puj\u0105ce komponenty: {components}", - "title": "WiLight" + "description": "Obs\u0142ugiwane s\u0105 nast\u0119puj\u0105ce komponenty: {components}" } } } diff --git a/homeassistant/components/wilight/translations/pt-BR.json b/homeassistant/components/wilight/translations/pt-BR.json index e70a286e812..48678c2d850 100644 --- a/homeassistant/components/wilight/translations/pt-BR.json +++ b/homeassistant/components/wilight/translations/pt-BR.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Os seguintes componentes s\u00e3o compat\u00edveis: {components}", - "title": "WiLight" + "description": "Os seguintes componentes s\u00e3o compat\u00edveis: {components}" } } } diff --git a/homeassistant/components/wilight/translations/pt.json b/homeassistant/components/wilight/translations/pt.json index 5b1005a95f5..7604ea73337 100644 --- a/homeassistant/components/wilight/translations/pt.json +++ b/homeassistant/components/wilight/translations/pt.json @@ -8,8 +8,7 @@ "flow_title": "WiLight: {name}", "step": { "confirm": { - "description": "Deseja configurar o WiLight {name} ? \n\n Suporta: {components}", - "title": "WiLight" + "description": "Deseja configurar o WiLight {name} ? \n\n Suporta: {components}" } } } diff --git a/homeassistant/components/wilight/translations/ru.json b/homeassistant/components/wilight/translations/ru.json index 7873b8ca326..afe612cd77e 100644 --- a/homeassistant/components/wilight/translations/ru.json +++ b/homeassistant/components/wilight/translations/ru.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "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" + "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}." } } } diff --git a/homeassistant/components/wilight/translations/tr.json b/homeassistant/components/wilight/translations/tr.json index 4762a678254..012326dc9df 100644 --- a/homeassistant/components/wilight/translations/tr.json +++ b/homeassistant/components/wilight/translations/tr.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "A\u015fa\u011f\u0131daki bile\u015fenler desteklenir: {components}", - "title": "WiLight" + "description": "A\u015fa\u011f\u0131daki bile\u015fenler desteklenir: {components}" } } } diff --git a/homeassistant/components/wilight/translations/uk.json b/homeassistant/components/wilight/translations/uk.json index 7517538499e..f06c8b51d51 100644 --- a/homeassistant/components/wilight/translations/uk.json +++ b/homeassistant/components/wilight/translations/uk.json @@ -8,8 +8,7 @@ "flow_title": "WiLight: {name}", "step": { "confirm": { - "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 WiLight {name}? \n\n \u0426\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454: {components}", - "title": "WiLight" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 WiLight {name}? \n\n \u0426\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454: {components}" } } } diff --git a/homeassistant/components/wilight/translations/zh-Hant.json b/homeassistant/components/wilight/translations/zh-Hant.json index 70a9bff0550..f3f7d9439a9 100644 --- a/homeassistant/components/wilight/translations/zh-Hant.json +++ b/homeassistant/components/wilight/translations/zh-Hant.json @@ -8,8 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "\u652f\u63f4\u4ee5\u4e0b\u5143\u4ef6\uff1a{components}", - "title": "WiLight" + "description": "\u652f\u63f4\u4ee5\u4e0b\u5143\u4ef6\uff1a{components}" } } } diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index 701694e40e9..47702090cc0 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -9,10 +9,13 @@ import asyncio from aiohttp.web import Request, Response import voluptuous as vol -from withings_api import AbstractWithingsApi, WithingsAuth from withings_api.common import NotifyAppli from homeassistant.components import webhook +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.components.webhook import ( async_unregister as async_unregister_webhook, ) @@ -28,10 +31,9 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import async_call_later from homeassistant.helpers.typing import ConfigType -from . import config_flow, const +from . import const from .common import ( _LOGGER, - WithingsLocalOAuth2Implementation, async_get_data_manager, async_remove_data_manager, get_data_manager_by_webhook_id, @@ -45,10 +47,12 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( cv.deprecated(const.CONF_PROFILES), + cv.deprecated(CONF_CLIENT_ID), + cv.deprecated(CONF_CLIENT_SECRET), vol.Schema( { - vol.Required(CONF_CLIENT_ID): vol.All(cv.string, vol.Length(min=1)), - vol.Required(CONF_CLIENT_SECRET): vol.All( + vol.Optional(CONF_CLIENT_ID): vol.All(cv.string, vol.Length(min=1)), + vol.Optional(CONF_CLIENT_SECRET): vol.All( cv.string, vol.Length(min=1) ), vol.Optional(const.CONF_USE_WEBHOOK, default=False): cv.boolean, @@ -76,17 +80,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.data[DOMAIN] = {const.CONFIG: conf} # Setup the oauth2 config flow. - config_flow.WithingsFlowHandler.async_register_implementation( - hass, - WithingsLocalOAuth2Implementation( + if CONF_CLIENT_ID in conf: + await async_import_client_credential( hass, - const.DOMAIN, - conf[CONF_CLIENT_ID], - conf[CONF_CLIENT_SECRET], - f"{WithingsAuth.URL}/oauth2_user/authorize2", - f"{AbstractWithingsApi.URL}/v2/oauth2", - ), - ) + DOMAIN, + ClientCredential( + conf[CONF_CLIENT_ID], + conf[CONF_CLIENT_SECRET], + ), + ) + _LOGGER.warning( + "Configuration of Withings integration OAuth2 credentials in YAML " + "is deprecated and will be removed in a future release; Your " + "existing OAuth Application Credentials have been imported into " + "the UI automatically and can be safely removed from your " + "configuration.yaml file" + ) return True @@ -111,10 +120,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: const.CONF_USE_WEBHOOK ], CONF_WEBHOOK_ID: webhook_id, - const.CONF_WEBHOOK_URL: entry.data.get( - const.CONF_WEBHOOK_URL, - webhook.async_generate_url(hass, webhook_id), - ), }, } diff --git a/homeassistant/components/withings/application_credentials.py b/homeassistant/components/withings/application_credentials.py new file mode 100644 index 00000000000..e5c401d5e74 --- /dev/null +++ b/homeassistant/components/withings/application_credentials.py @@ -0,0 +1,28 @@ +"""application_credentials platform for Withings.""" + +from withings_api import AbstractWithingsApi, WithingsAuth + +from homeassistant.components.application_credentials import ( + AuthorizationServer, + ClientCredential, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow + +from .common import WithingsLocalOAuth2Implementation +from .const import DOMAIN + + +async def async_get_auth_implementation( + hass: HomeAssistant, auth_domain: str, credential: ClientCredential +) -> config_entry_oauth2_flow.AbstractOAuth2Implementation: + """Return auth implementation.""" + return WithingsLocalOAuth2Implementation( + hass, + DOMAIN, + credential, + authorization_server=AuthorizationServer( + authorize_url=f"{WithingsAuth.URL}/oauth2_user/authorize2", + token_url=f"{AbstractWithingsApi.URL}/v2/oauth2", + ), + ) diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index f2fe7bfb1ec..ad40378eafb 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -27,6 +27,8 @@ from withings_api.common import ( query_measure_groups, ) +from homeassistant.components import webhook +from homeassistant.components.application_credentials import AuthImplementation from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.http import HomeAssistantView from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN @@ -40,15 +42,13 @@ from homeassistant.const import ( ) from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_entry_oauth2_flow +from homeassistant.helpers import config_entry_oauth2_flow, entity_registry as er from homeassistant.helpers.config_entry_oauth2_flow import ( AUTH_CALLBACK_PATH, AbstractOAuth2Implementation, - LocalOAuth2Implementation, OAuth2Session, ) from homeassistant.helpers.entity import Entity -from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.helpers.network import get_url from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util import dt @@ -917,9 +917,7 @@ async def async_get_entity_id( hass: HomeAssistant, attribute: WithingsAttribute, user_id: int ) -> str | None: """Get an entity id for a user's attribute.""" - entity_registry: EntityRegistry = ( - await hass.helpers.entity_registry.async_get_registry() - ) + entity_registry = er.async_get(hass) unique_id = get_attribute_unique_id(attribute, user_id) entity_id = entity_registry.async_get_entity_id( @@ -1046,7 +1044,9 @@ async def async_get_data_manager( config_entry.data["token"]["userid"], WebhookConfig( id=config_entry.data[CONF_WEBHOOK_ID], - url=config_entry.data[const.CONF_WEBHOOK_URL], + url=webhook.async_generate_url( + hass, config_entry.data[CONF_WEBHOOK_ID] + ), enabled=config_entry.data[const.CONF_USE_WEBHOOK], ), ) @@ -1106,7 +1106,7 @@ def get_platform_attributes(platform: str) -> tuple[WithingsAttribute, ...]: ) -class WithingsLocalOAuth2Implementation(LocalOAuth2Implementation): +class WithingsLocalOAuth2Implementation(AuthImplementation): """Oauth2 implementation that only uses the external url.""" @property diff --git a/homeassistant/components/withings/const.py b/homeassistant/components/withings/const.py index dd744152bb1..a5a99c193ab 100644 --- a/homeassistant/components/withings/const.py +++ b/homeassistant/components/withings/const.py @@ -13,7 +13,6 @@ DOMAIN = "withings" LOG_NAMESPACE = "homeassistant.components.withings" PROFILE = "profile" PUSH_HANDLER = "push_handler" -CONF_WEBHOOK_URL = "webhook_url" class Measurement(Enum): diff --git a/homeassistant/components/withings/manifest.json b/homeassistant/components/withings/manifest.json index 2e089bdbcc9..1437b7f2198 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": ["auth", "http", "webhook"], + "dependencies": ["application_credentials", "http", "webhook"], "codeowners": ["@vangorra"], "iot_class": "cloud_polling", "loggers": ["withings_api"] diff --git a/homeassistant/components/withings/translations/es.json b/homeassistant/components/withings/translations/es.json index 7f83e45c8c9..d3a3f60b12f 100644 --- a/homeassistant/components/withings/translations/es.json +++ b/homeassistant/components/withings/translations/es.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Configuraci\u00f3n actualizada para el perfil.", - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n.", "no_url_available": "No hay URL disponible. Para obtener informaci\u00f3n sobre este error, [consulta la secci\u00f3n de ayuda]({docs_url})" }, @@ -10,16 +10,16 @@ "default": "Autenticado correctamente con Withings." }, "error": { - "already_configured": "La cuenta ya ha sido configurada" + "already_configured": "La cuenta ya est\u00e1 configurada" }, - "flow_title": "Withings: {profile}", + "flow_title": "{profile}", "step": { "pick_implementation": { "title": "Selecciona el m\u00e9todo de autenticaci\u00f3n" }, "profile": { "data": { - "profile": "Perfil" + "profile": "Nombre de perfil" }, "description": "\u00bfQu\u00e9 perfil seleccion\u00f3 en el sitio web de Withings? Es importante que los perfiles coincidan, de lo contrario los datos se etiquetar\u00e1n incorrectamente.", "title": "Perfil de usuario." diff --git a/homeassistant/components/withings/translations/nl.json b/homeassistant/components/withings/translations/nl.json index a7400e3a693..cca755effbb 100644 --- a/homeassistant/components/withings/translations/nl.json +++ b/homeassistant/components/withings/translations/nl.json @@ -2,9 +2,9 @@ "config": { "abort": { "already_configured": "Configuratie bijgewerkt voor profiel.", - "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})" + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})" }, "create_entry": { "default": "Succesvol geverifieerd met Withings voor het geselecteerde profiel." @@ -26,7 +26,7 @@ }, "reauth": { "description": "Het {profile} \" moet opnieuw worden geverifieerd om Withings-gegevens te blijven ontvangen.", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" } } } diff --git a/homeassistant/components/wiz/translations/bg.json b/homeassistant/components/wiz/translations/bg.json index eb0697ca13d..059a8fb9358 100644 --- a/homeassistant/components/wiz/translations/bg.json +++ b/homeassistant/components/wiz/translations/bg.json @@ -20,8 +20,7 @@ }, "user": { "data": { - "host": "\u0425\u043e\u0441\u0442", - "name": "\u0418\u043c\u0435" + "host": "\u0425\u043e\u0441\u0442" } } } diff --git a/homeassistant/components/wiz/translations/ca.json b/homeassistant/components/wiz/translations/ca.json index 60ca2a8a884..157f2e21dff 100644 --- a/homeassistant/components/wiz/translations/ca.json +++ b/homeassistant/components/wiz/translations/ca.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Vols comen\u00e7ar la configuraci\u00f3?" - }, "discovery_confirm": { "description": "Vols configurar {name} ({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "Adre\u00e7a IP", - "name": "Nom" + "host": "Adre\u00e7a IP" }, "description": "Si deixes l'adre\u00e7a IP buida, s'utilitzar\u00e0 el descobriment per cercar dispositius." } diff --git a/homeassistant/components/wiz/translations/cs.json b/homeassistant/components/wiz/translations/cs.json index 0656e47c8be..b6b916d5d3a 100644 --- a/homeassistant/components/wiz/translations/cs.json +++ b/homeassistant/components/wiz/translations/cs.json @@ -10,13 +10,9 @@ "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "step": { - "confirm": { - "description": "Chcete za\u010d\u00edt nastavovat?" - }, "user": { "data": { - "host": "IP adresa", - "name": "Jm\u00e9no" + "host": "IP adresa" } } } diff --git a/homeassistant/components/wiz/translations/de.json b/homeassistant/components/wiz/translations/de.json index 73b933fefcf..af503506c59 100644 --- a/homeassistant/components/wiz/translations/de.json +++ b/homeassistant/components/wiz/translations/de.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "M\u00f6chtest Du mit der Einrichtung beginnen?" - }, "discovery_confirm": { "description": "M\u00f6chtest du {name} ({host}) einrichten?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "IP-Adresse", - "name": "Name" + "host": "IP-Adresse" }, "description": "Wenn du die IP-Adresse leer l\u00e4sst, wird die Erkennung verwendet, um Ger\u00e4te zu finden." } diff --git a/homeassistant/components/wiz/translations/el.json b/homeassistant/components/wiz/translations/el.json index aa137e84fe0..c92d036d5f6 100644 --- a/homeassistant/components/wiz/translations/el.json +++ b/homeassistant/components/wiz/translations/el.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "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;" - }, "discovery_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} ({host});" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" }, "description": "\u0391\u03bd \u03b1\u03c6\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ba\u03b5\u03bd\u03ae, \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd." } diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index a612ea165a4..97bb7fc25ba 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Do you want to start set up?" - }, "discovery_confirm": { "description": "Do you want to setup {name} ({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "IP Address", - "name": "Name" + "host": "IP Address" }, "description": "If you leave the IP Address empty, discovery will be used to find devices." } diff --git a/homeassistant/components/wiz/translations/es.json b/homeassistant/components/wiz/translations/es.json index bc06bff8053..b86f60649a2 100644 --- a/homeassistant/components/wiz/translations/es.json +++ b/homeassistant/components/wiz/translations/es.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "\u00bfDesea iniciar la configuraci\u00f3n?" - }, "discovery_confirm": { "description": "\u00bfDesea configurar {name} ({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "Direcci\u00f3n IP", - "name": "Nombre" + "host": "Direcci\u00f3n IP" }, "description": "Si deja la direcci\u00f3n IP vac\u00eda, la detecci\u00f3n se utilizar\u00e1 para buscar dispositivos." } diff --git a/homeassistant/components/wiz/translations/et.json b/homeassistant/components/wiz/translations/et.json index 9cb84a0e7bd..e220b5beb9f 100644 --- a/homeassistant/components/wiz/translations/et.json +++ b/homeassistant/components/wiz/translations/et.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Kas alustan seadistamist?" - }, "discovery_confirm": { "description": "Kas soovid seadistada {name}({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "IP aadress", - "name": "Nimi" + "host": "IP aadress" }, "description": "Kui j\u00e4tad IP aadressi t\u00fchjaks kasutatakse seadmete leidmiseks avastamist." } diff --git a/homeassistant/components/wiz/translations/fr.json b/homeassistant/components/wiz/translations/fr.json index 290bda2405c..058e751242d 100644 --- a/homeassistant/components/wiz/translations/fr.json +++ b/homeassistant/components/wiz/translations/fr.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Voulez-vous commencer la configuration\u00a0?" - }, "discovery_confirm": { "description": "Voulez-vous configurer {name} ({host})\u00a0?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "Adresse IP", - "name": "Nom" + "host": "Adresse IP" }, "description": "Si vous laissez l'adresse IP vide, la d\u00e9couverte sera utilis\u00e9e pour trouver des appareils." } diff --git a/homeassistant/components/wiz/translations/he.json b/homeassistant/components/wiz/translations/he.json index 8d4e41401c8..81b954067f7 100644 --- a/homeassistant/components/wiz/translations/he.json +++ b/homeassistant/components/wiz/translations/he.json @@ -11,9 +11,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05d7\u05d9\u05dc \u05d1\u05d4\u05d2\u05d3\u05e8\u05d4?" - }, "discovery_confirm": { "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea {name} ({host})?" }, @@ -24,8 +21,7 @@ }, "user": { "data": { - "host": "\u05db\u05ea\u05d5\u05d1\u05ea IP", - "name": "\u05e9\u05dd" + "host": "\u05db\u05ea\u05d5\u05d1\u05ea IP" } } } diff --git a/homeassistant/components/wiz/translations/hu.json b/homeassistant/components/wiz/translations/hu.json index 63ae8479bb9..0c27ee730b3 100644 --- a/homeassistant/components/wiz/translations/hu.json +++ b/homeassistant/components/wiz/translations/hu.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "El szeretn\u00e9 kezdeni a be\u00e1ll\u00edt\u00e1st?" - }, "discovery_confirm": { "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name} ({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "IP c\u00edm", - "name": "Elnevez\u00e9s" + "host": "IP c\u00edm" }, "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/wiz/translations/id.json b/homeassistant/components/wiz/translations/id.json index 694973f8ffa..2455999d1fd 100644 --- a/homeassistant/components/wiz/translations/id.json +++ b/homeassistant/components/wiz/translations/id.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Ingin memulai penyiapan?" - }, "discovery_confirm": { "description": "Ingin menyiapkan {name} ({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "Alamat IP", - "name": "Nama" + "host": "Alamat IP" }, "description": "Jika Alamat IP dibiarkan kosong, proses penemuan akan digunakan untuk menemukan perangkat." } diff --git a/homeassistant/components/wiz/translations/it.json b/homeassistant/components/wiz/translations/it.json index ebd093c22f7..ae87f88a151 100644 --- a/homeassistant/components/wiz/translations/it.json +++ b/homeassistant/components/wiz/translations/it.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Vuoi iniziare la configurazione?" - }, "discovery_confirm": { "description": "Vuoi configurare {name} ({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "Indirizzo IP", - "name": "Nome" + "host": "Indirizzo IP" }, "description": "Se lasci vuoto l'indirizzo IP, il rilevamento sar\u00e0 utilizzato per trovare i dispositivi." } diff --git a/homeassistant/components/wiz/translations/ja.json b/homeassistant/components/wiz/translations/ja.json index 8d9cbd0c055..e062fbdb75b 100644 --- a/homeassistant/components/wiz/translations/ja.json +++ b/homeassistant/components/wiz/translations/ja.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u958b\u59cb\u3057\u307e\u3059\u304b\uff1f" - }, "discovery_confirm": { "description": "{name} ({host})\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "\u30db\u30b9\u30c8", - "name": "\u540d\u524d" + "host": "\u30db\u30b9\u30c8" }, "description": "\u65b0\u3057\u3044\u96fb\u7403(bulb)\u3092\u8ffd\u52a0\u3059\u308b\u306b\u306f\u3001\u30db\u30b9\u30c8\u540d\u307e\u305f\u306fIP\u30a2\u30c9\u30ec\u30b9\u3068\u540d\u524d\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044:" } diff --git a/homeassistant/components/wiz/translations/ko.json b/homeassistant/components/wiz/translations/ko.json new file mode 100644 index 00000000000..9c08b2d78b0 --- /dev/null +++ b/homeassistant/components/wiz/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "IP \uc8fc\uc18c\ub97c \ube44\uc6cc\ub450\uba74 \uc7a5\uce58\ub97c \uc790\ub3d9\uc73c\ub85c \ucc3e\uc2b5\ub2c8\ub2e4." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/nl.json b/homeassistant/components/wiz/translations/nl.json index 3b59dd6a672..3a8d7be6ec6 100644 --- a/homeassistant/components/wiz/translations/nl.json +++ b/homeassistant/components/wiz/translations/nl.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Wilt u beginnen met instellen?" - }, "discovery_confirm": { "description": "Wilt u {name} ({host}) instellen?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "IP-adres", - "name": "Naam" + "host": "IP-adres" }, "description": "Als u het IP-adres leeg laat, zal discovery worden gebruikt om apparaten te vinden." } diff --git a/homeassistant/components/wiz/translations/no.json b/homeassistant/components/wiz/translations/no.json index 7f2090b5b71..031b184ae84 100644 --- a/homeassistant/components/wiz/translations/no.json +++ b/homeassistant/components/wiz/translations/no.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Vil du starte oppsettet?" - }, "discovery_confirm": { "description": "Vil du konfigurere {name} ({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "IP adresse", - "name": "Navn" + "host": "IP adresse" }, "description": "Hvis du lar IP-adressen st\u00e5 tom, vil oppdagelse bli brukt til \u00e5 finne enheter." } diff --git a/homeassistant/components/wiz/translations/pl.json b/homeassistant/components/wiz/translations/pl.json index 30d1455c470..ea3ea48868c 100644 --- a/homeassistant/components/wiz/translations/pl.json +++ b/homeassistant/components/wiz/translations/pl.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" - }, "discovery_confirm": { "description": "Czy chcesz skonfigurowa\u0107 {name} ({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "Adres IP", - "name": "Nazwa" + "host": "Adres IP" }, "description": "Je\u015bli nie podasz adresu IP, zostanie u\u017cyte wykrywanie do odnalezienia urz\u0105dze\u0144." } diff --git a/homeassistant/components/wiz/translations/pt-BR.json b/homeassistant/components/wiz/translations/pt-BR.json index ce27ea82abc..0c2ee8b7c04 100644 --- a/homeassistant/components/wiz/translations/pt-BR.json +++ b/homeassistant/components/wiz/translations/pt-BR.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Deseja iniciar a configura\u00e7\u00e3o?" - }, "discovery_confirm": { "description": "Deseja configurar {name} ({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "Endere\u00e7o IP", - "name": "Nome" + "host": "Endere\u00e7o IP" }, "description": "Se voc\u00ea deixar o endere\u00e7o IP vazio, a descoberta ser\u00e1 usada para localizar dispositivos." } diff --git a/homeassistant/components/wiz/translations/ru.json b/homeassistant/components/wiz/translations/ru.json index dd422f29fea..c9fe68b3ce9 100644 --- a/homeassistant/components/wiz/translations/ru.json +++ b/homeassistant/components/wiz/translations/ru.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" - }, "discovery_confirm": { "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "IP-\u0430\u0434\u0440\u0435\u0441", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + "host": "IP-\u0430\u0434\u0440\u0435\u0441" }, "description": "\u0415\u0441\u043b\u0438 \u043d\u0435 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441, \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u0443\u0434\u0443\u0442 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438." } diff --git a/homeassistant/components/wiz/translations/sk.json b/homeassistant/components/wiz/translations/sk.json index af15f92c2f2..641bd9c13ee 100644 --- a/homeassistant/components/wiz/translations/sk.json +++ b/homeassistant/components/wiz/translations/sk.json @@ -1,11 +1,7 @@ { "config": { - "step": { - "user": { - "data": { - "name": "N\u00e1zov" - } - } + "abort": { + "cannot_connect": "Nepodarilo sa pripoji\u0165" } } } \ No newline at end of file diff --git a/homeassistant/components/wiz/translations/tr.json b/homeassistant/components/wiz/translations/tr.json index 3f6b1f68dc5..15b2b683a50 100644 --- a/homeassistant/components/wiz/translations/tr.json +++ b/homeassistant/components/wiz/translations/tr.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "Kuruluma ba\u015flamak ister misiniz?" - }, "discovery_confirm": { "description": "{name} ( {host} ) kurulumu yapmak istiyor musunuz?" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "IP Adresi", - "name": "Ad" + "host": "IP Adresi" }, "description": "IP Adresini bo\u015f b\u0131rak\u0131rsan\u0131z, cihazlar\u0131 bulmak i\u00e7in ke\u015fif kullan\u0131lacakt\u0131r." } diff --git a/homeassistant/components/wiz/translations/zh-Hant.json b/homeassistant/components/wiz/translations/zh-Hant.json index 9e716b4928e..3bce0700569 100644 --- a/homeassistant/components/wiz/translations/zh-Hant.json +++ b/homeassistant/components/wiz/translations/zh-Hant.json @@ -14,9 +14,6 @@ }, "flow_title": "{name} ({host})", "step": { - "confirm": { - "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" - }, "discovery_confirm": { "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} ({host})\uff1f" }, @@ -27,8 +24,7 @@ }, "user": { "data": { - "host": "IP \u4f4d\u5740", - "name": "\u540d\u7a31" + "host": "IP \u4f4d\u5740" }, "description": "\u5047\u5982 IP \u4f4d\u5740\u6b04\u4f4d\u70ba\u7a7a\u767d\uff0c\u5c07\u6703\u641c\u7d22\u6240\u6709\u53ef\u7528\u88dd\u7f6e\u3002" } diff --git a/homeassistant/components/wled/button.py b/homeassistant/components/wled/button.py index 2967067ef44..97877053163 100644 --- a/homeassistant/components/wled/button.py +++ b/homeassistant/components/wled/button.py @@ -7,7 +7,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, LOGGER +from .const import DOMAIN from .coordinator import WLEDDataUpdateCoordinator from .helpers import wled_exception_handler from .models import WLEDEntity @@ -20,12 +20,7 @@ async def async_setup_entry( ) -> None: """Set up WLED button based on a config entry.""" coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities( - [ - WLEDRestartButton(coordinator), - WLEDUpdateButton(coordinator), - ] - ) + async_add_entities([WLEDRestartButton(coordinator)]) class WLEDRestartButton(WLEDEntity, ButtonEntity): @@ -44,67 +39,3 @@ class WLEDRestartButton(WLEDEntity, ButtonEntity): async def async_press(self) -> None: """Send out a restart command.""" await self.coordinator.wled.reset() - - -class WLEDUpdateButton(WLEDEntity, ButtonEntity): - """Defines a WLED update button.""" - - _attr_device_class = ButtonDeviceClass.UPDATE - _attr_entity_category = EntityCategory.CONFIG - - # Disabled by default, as this entity is deprecated. - _attr_entity_registry_enabled_default = False - - def __init__(self, coordinator: WLEDDataUpdateCoordinator) -> None: - """Initialize the button entity.""" - super().__init__(coordinator=coordinator) - self._attr_name = f"{coordinator.data.info.name} Update" - self._attr_unique_id = f"{coordinator.data.info.mac_address}_update" - - @property - def available(self) -> bool: - """Return if the entity and an update is available.""" - current = self.coordinator.data.info.version - beta = self.coordinator.data.info.version_latest_beta - stable = self.coordinator.data.info.version_latest_stable - - # If we already run a pre-release, allow upgrading to a newer - # pre-release offer a normal upgrade otherwise. - return ( - super().available - and current is not None - and ( - (stable is not None and stable > current) - or ( - beta is not None - and (current.alpha or current.beta or current.release_candidate) - and beta > current - ) - ) - ) - - @wled_exception_handler - async def async_press(self) -> None: - """Send out a update command.""" - LOGGER.warning( - "The WLED update button '%s' is deprecated, please " - "use the new update entity as a replacement", - self.entity_id, - ) - current = self.coordinator.data.info.version - beta = self.coordinator.data.info.version_latest_beta - stable = self.coordinator.data.info.version_latest_stable - - # If we already run a pre-release, allow update to a newer - # pre-release or newer stable, otherwise, offer a normal stable updates. - version = stable - if ( - current is not None - and beta is not None - and (current.alpha or current.beta or current.release_candidate) - and beta > current - and beta > stable - ): - version = beta - - await self.coordinator.wled.upgrade(version=str(version)) diff --git a/homeassistant/components/wled/helpers.py b/homeassistant/components/wled/helpers.py index d5ca895390b..66cd8b13b42 100644 --- a/homeassistant/components/wled/helpers.py +++ b/homeassistant/components/wled/helpers.py @@ -2,7 +2,7 @@ from wled import WLEDConnectionError, WLEDError -from .const import LOGGER +from homeassistant.exceptions import HomeAssistantError def wled_exception_handler(func): @@ -18,11 +18,11 @@ def wled_exception_handler(func): self.coordinator.update_listeners() except WLEDConnectionError as error: - LOGGER.error("Error communicating with API: %s", error) self.coordinator.last_update_success = False self.coordinator.update_listeners() + raise HomeAssistantError("Error communicating with WLED API") from error except WLEDError as error: - LOGGER.error("Invalid response from API: %s", error) + raise HomeAssistantError("Invalid response from WLED API") from error return handler diff --git a/homeassistant/components/wled/translations/es.json b/homeassistant/components/wled/translations/es.json index d883ba6ff23..07ebd6c2e02 100644 --- a/homeassistant/components/wled/translations/es.json +++ b/homeassistant/components/wled/translations/es.json @@ -8,7 +8,7 @@ "error": { "cannot_connect": "No se pudo conectar" }, - "flow_title": "WLED: {name}", + "flow_title": "{name}", "step": { "user": { "data": { diff --git a/homeassistant/components/wled/translations/select.nl.json b/homeassistant/components/wled/translations/select.nl.json index f2ac9da54cb..e93c165520a 100644 --- a/homeassistant/components/wled/translations/select.nl.json +++ b/homeassistant/components/wled/translations/select.nl.json @@ -3,7 +3,7 @@ "wled__live_override": { "0": "Uit", "1": "Aan", - "2": "Totdat het apparaat opnieuw opstart" + "2": "Totdat het apparaat opnieuw wordt opgestart" } } } \ No newline at end of file diff --git a/homeassistant/components/ws66i/__init__.py b/homeassistant/components/ws66i/__init__.py new file mode 100644 index 00000000000..dea1b470b9e --- /dev/null +++ b/homeassistant/components/ws66i/__init__.py @@ -0,0 +1,125 @@ +"""The Soundavo WS66i 6-Zone Amplifier integration.""" +from __future__ import annotations + +import logging + +from pyws66i import WS66i, get_ws66i + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_IP_ADDRESS, EVENT_HOMEASSISTANT_STOP +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryNotReady + +from .const import CONF_SOURCES, DOMAIN +from .coordinator import Ws66iDataUpdateCoordinator +from .models import SourceRep, Ws66iData + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = ["media_player"] + + +@callback +def _get_sources_from_dict(data) -> SourceRep: + sources_config = data[CONF_SOURCES] + + # Dict index to custom name + source_id_name = {int(index): name for index, name in sources_config.items()} + + # Dict custom name to index + source_name_id = {v: k for k, v in source_id_name.items()} + + # List of custom names + source_names = sorted(source_name_id.keys(), key=lambda v: source_name_id[v]) + + return SourceRep(source_id_name, source_name_id, source_names) + + +def _find_zones(hass: HomeAssistant, ws66i: WS66i) -> list[int]: + """Generate zones list by searching for presence of zones.""" + # Zones 11 - 16 are the master amp + # Zones 21,31 - 26,36 are the daisy-chained amps + zone_list = [] + for amp_num in range(1, 4): + + if amp_num > 1: + # Don't add entities that aren't present + status = ws66i.zone_status(amp_num * 10 + 1) + if status is None: + break + + for zone_num in range(1, 7): + zone_id = (amp_num * 10) + zone_num + zone_list.append(zone_id) + + _LOGGER.info("Detected %d amp(s)", amp_num - 1) + return zone_list + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Soundavo WS66i 6-Zone Amplifier from a config entry.""" + # Get the source names from the options flow + options: dict[str, dict[str, str]] + options = {CONF_SOURCES: entry.options[CONF_SOURCES]} + # Get the WS66i object and open up a connection to it + ws66i = get_ws66i(entry.data[CONF_IP_ADDRESS]) + try: + await hass.async_add_executor_job(ws66i.open) + except ConnectionError as err: + # Amplifier is probably turned off + raise ConfigEntryNotReady("Could not connect to WS66i Amp. Is it off?") from err + + # Create the zone Representation dataclass + source_rep: SourceRep = _get_sources_from_dict(options) + + # Create a list of discovered zones + zones = await hass.async_add_executor_job(_find_zones, hass, ws66i) + + # Create the coordinator for the WS66i + coordinator: Ws66iDataUpdateCoordinator = Ws66iDataUpdateCoordinator( + hass, + ws66i, + zones, + ) + + # Fetch initial data, retry on failed poll + await coordinator.async_config_entry_first_refresh() + + # Create the Ws66iData data class save it to hass + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = Ws66iData( + host_ip=entry.data[CONF_IP_ADDRESS], + device=ws66i, + sources=source_rep, + coordinator=coordinator, + zones=zones, + ) + + @callback + def shutdown(event): + """Close the WS66i connection to the amplifier.""" + ws66i.close() + + entry.async_on_unload(entry.add_update_listener(_update_listener)) + entry.async_on_unload( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) + ) + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +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) + if unload_ok: + ws66i: WS66i = hass.data[DOMAIN][entry.entry_id].device + ws66i.close() + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/ws66i/config_flow.py b/homeassistant/components/ws66i/config_flow.py new file mode 100644 index 00000000000..b84872da036 --- /dev/null +++ b/homeassistant/components/ws66i/config_flow.py @@ -0,0 +1,160 @@ +"""Config flow for WS66i 6-Zone Amplifier integration.""" +import logging +from typing import Any + +from pyws66i import WS66i, get_ws66i +import voluptuous as vol + +from homeassistant import config_entries, core, exceptions +from homeassistant.const import CONF_IP_ADDRESS + +from .const import ( + CONF_SOURCE_1, + CONF_SOURCE_2, + CONF_SOURCE_3, + CONF_SOURCE_4, + CONF_SOURCE_5, + CONF_SOURCE_6, + CONF_SOURCES, + DOMAIN, + INIT_OPTIONS_DEFAULT, +) + +_LOGGER = logging.getLogger(__name__) + +SOURCES = [ + CONF_SOURCE_1, + CONF_SOURCE_2, + CONF_SOURCE_3, + CONF_SOURCE_4, + CONF_SOURCE_5, + CONF_SOURCE_6, +] + +OPTIONS_SCHEMA = {vol.Optional(source): str for source in SOURCES} + +DATA_SCHEMA = vol.Schema({vol.Required(CONF_IP_ADDRESS): str}) + +FIRST_ZONE = 11 + + +@core.callback +def _sources_from_config(data): + sources_config = { + str(idx + 1): data.get(source) for idx, source in enumerate(SOURCES) + } + + return { + index: name.strip() + for index, name in sources_config.items() + if (name is not None and name.strip() != "") + } + + +def _verify_connection(ws66i: WS66i) -> bool: + """Verify a connection can be made to the WS66i.""" + try: + ws66i.open() + except ConnectionError as err: + raise CannotConnect from err + + # Connection successful. Verify correct port was opened + # Test on FIRST_ZONE because this zone will always be valid + ret_val = ws66i.zone_status(FIRST_ZONE) + + ws66i.close() + + return bool(ret_val) + + +async def validate_input( + hass: core.HomeAssistant, input_data: dict[str, Any] +) -> dict[str, Any]: + """Validate the user input. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + ws66i: WS66i = get_ws66i(input_data[CONF_IP_ADDRESS]) + + is_valid: bool = await hass.async_add_executor_job(_verify_connection, ws66i) + if not is_valid: + raise CannotConnect("Not a valid WS66i connection") + + # Return info that you want to store in the config entry. + return {CONF_IP_ADDRESS: input_data[CONF_IP_ADDRESS]} + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for WS66i 6-Zone Amplifier.""" + + VERSION = 1 + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + if user_input is not None: + try: + info = await validate_input(self.hass, user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + # Data is valid. Create a config entry. + return self.async_create_entry( + title="WS66i Amp", + data=info, + options={CONF_SOURCES: INIT_OPTIONS_DEFAULT}, + ) + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + @staticmethod + @core.callback + def async_get_options_flow(config_entry): + """Define the config flow to handle options.""" + return Ws66iOptionsFlowHandler(config_entry) + + +@core.callback +def _key_for_source(index, source, previous_sources): + key = vol.Required( + source, description={"suggested_value": previous_sources[str(index)]} + ) + + return key + + +class Ws66iOptionsFlowHandler(config_entries.OptionsFlow): + """Handle a WS66i options flow.""" + + def __init__(self, config_entry): + """Initialize.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry( + title="Source Names", + data={CONF_SOURCES: _sources_from_config(user_input)}, + ) + + # Fill form with previous source names + previous_sources = self.config_entry.options[CONF_SOURCES] + options = { + _key_for_source(idx + 1, source, previous_sources): str + for idx, source in enumerate(SOURCES) + } + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema(options), + ) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/ws66i/const.py b/homeassistant/components/ws66i/const.py new file mode 100644 index 00000000000..f824d991c1d --- /dev/null +++ b/homeassistant/components/ws66i/const.py @@ -0,0 +1,26 @@ +"""Constants for the Soundavo WS66i 6-Zone Amplifier Media Player component.""" +from datetime import timedelta + +DOMAIN = "ws66i" + +CONF_SOURCES = "sources" + +CONF_SOURCE_1 = "source_1" +CONF_SOURCE_2 = "source_2" +CONF_SOURCE_3 = "source_3" +CONF_SOURCE_4 = "source_4" +CONF_SOURCE_5 = "source_5" +CONF_SOURCE_6 = "source_6" + +INIT_OPTIONS_DEFAULT = { + "1": "Source 1", + "2": "Source 2", + "3": "Source 3", + "4": "Source 4", + "5": "Source 5", + "6": "Source 6", +} + +POLL_INTERVAL = timedelta(seconds=30) + +MAX_VOL = 38 diff --git a/homeassistant/components/ws66i/coordinator.py b/homeassistant/components/ws66i/coordinator.py new file mode 100644 index 00000000000..be8ae3aad38 --- /dev/null +++ b/homeassistant/components/ws66i/coordinator.py @@ -0,0 +1,50 @@ +"""Coordinator for WS66i.""" +from __future__ import annotations + +import logging + +from pyws66i import WS66i, ZoneStatus + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import POLL_INTERVAL + +_LOGGER = logging.getLogger(__name__) + + +class Ws66iDataUpdateCoordinator(DataUpdateCoordinator[list[ZoneStatus]]): + """DataUpdateCoordinator to gather data for WS66i Zones.""" + + def __init__( + self, + hass: HomeAssistant, + my_api: WS66i, + zones: list[int], + ) -> None: + """Initialize DataUpdateCoordinator to gather data for specific zones.""" + super().__init__( + hass, + _LOGGER, + name="WS66i", + update_interval=POLL_INTERVAL, + ) + self._ws66i = my_api + self._zones = zones + + def _update_all_zones(self) -> list[ZoneStatus]: + """Fetch data for each of the zones.""" + data = [] + for zone_id in self._zones: + data_zone = self._ws66i.zone_status(zone_id) + if data_zone is None: + raise UpdateFailed(f"Failed to update zone {zone_id}") + + data.append(data_zone) + + return data + + async def _async_update_data(self) -> list[ZoneStatus]: + """Fetch data for each of the zones.""" + # The data that is returned here can be accessed through coordinator.data. + return await self.hass.async_add_executor_job(self._update_all_zones) diff --git a/homeassistant/components/ws66i/manifest.json b/homeassistant/components/ws66i/manifest.json new file mode 100644 index 00000000000..abd943eb26b --- /dev/null +++ b/homeassistant/components/ws66i/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "ws66i", + "name": "Soundavo WS66i 6-Zone Amplifier", + "documentation": "https://www.home-assistant.io/integrations/ws66i", + "requirements": ["pyws66i==1.1"], + "codeowners": ["@ssaenger"], + "config_flow": true, + "quality_scale": "silver", + "iot_class": "local_polling" +} diff --git a/homeassistant/components/ws66i/media_player.py b/homeassistant/components/ws66i/media_player.py new file mode 100644 index 00000000000..7cd897e9c1a --- /dev/null +++ b/homeassistant/components/ws66i/media_player.py @@ -0,0 +1,170 @@ +"""Support for interfacing with WS66i 6 zone home audio controller.""" +from pyws66i import WS66i, ZoneStatus + +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import STATE_OFF, STATE_ON +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 .const import DOMAIN, MAX_VOL +from .coordinator import Ws66iDataUpdateCoordinator +from .models import Ws66iData + +PARALLEL_UPDATES = 1 + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the WS66i 6-zone amplifier platform from a config entry.""" + ws66i_data: Ws66iData = hass.data[DOMAIN][config_entry.entry_id] + + # Build and add the entities from the data class + async_add_entities( + Ws66iZone( + device=ws66i_data.device, + ws66i_data=ws66i_data, + entry_id=config_entry.entry_id, + zone_id=zone_id, + data_idx=idx, + coordinator=ws66i_data.coordinator, + ) + for idx, zone_id in enumerate(ws66i_data.zones) + ) + + +class Ws66iZone(CoordinatorEntity[Ws66iDataUpdateCoordinator], MediaPlayerEntity): + """Representation of a WS66i amplifier zone.""" + + def __init__( + self, + device: WS66i, + ws66i_data: Ws66iData, + entry_id: str, + zone_id: int, + data_idx: int, + coordinator: Ws66iDataUpdateCoordinator, + ) -> None: + """Initialize a zone entity.""" + super().__init__(coordinator) + self._ws66i: WS66i = device + self._ws66i_data: Ws66iData = ws66i_data + self._zone_id: int = zone_id + self._zone_id_idx: int = data_idx + self._status: ZoneStatus = coordinator.data[data_idx] + self._attr_source_list = ws66i_data.sources.name_list + self._attr_unique_id = f"{entry_id}_{self._zone_id}" + self._attr_name = f"Zone {self._zone_id}" + self._attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, str(self.unique_id))}, + name=self.name, + manufacturer="Soundavo", + model="WS66i 6-Zone Amplifier", + ) + self._set_attrs_from_status() + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + # This will be called for each of the entities after the coordinator + # finishes executing _async_update_data() + + # Save a reference to the zone status that this entity represents + self._status = self.coordinator.data[self._zone_id_idx] + self._set_attrs_from_status() + + # Parent will notify HA of the update + super()._handle_coordinator_update() + + @callback + def _set_attrs_from_status(self) -> None: + status = self._status + sources = self._ws66i_data.sources.id_name + self._attr_state = STATE_ON if status.power else STATE_OFF + self._attr_volume_level = status.volume / float(MAX_VOL) + self._attr_is_volume_muted = status.mute + self._attr_source = self._attr_media_title = sources[status.source] + + @callback + def _async_update_attrs_write_ha_state(self) -> None: + self._set_attrs_from_status() + self.async_write_ha_state() + + async def async_select_source(self, source): + """Set input source.""" + idx = self._ws66i_data.sources.name_id[source] + await self.hass.async_add_executor_job( + self._ws66i.set_source, self._zone_id, idx + ) + self._status.source = idx + self._async_update_attrs_write_ha_state() + + async def async_turn_on(self): + """Turn the media player on.""" + await self.hass.async_add_executor_job( + self._ws66i.set_power, self._zone_id, True + ) + self._status.power = True + self._async_update_attrs_write_ha_state() + + async def async_turn_off(self): + """Turn the media player off.""" + await self.hass.async_add_executor_job( + self._ws66i.set_power, self._zone_id, False + ) + self._status.power = False + self._async_update_attrs_write_ha_state() + + async def async_mute_volume(self, mute): + """Mute (true) or unmute (false) media player.""" + await self.hass.async_add_executor_job( + self._ws66i.set_mute, self._zone_id, mute + ) + self._status.mute = bool(mute) + self._async_update_attrs_write_ha_state() + + async def async_set_volume_level(self, volume): + """Set volume level, range 0..1.""" + await self.hass.async_add_executor_job(self._set_volume, int(volume * MAX_VOL)) + self._async_update_attrs_write_ha_state() + + async def async_volume_up(self): + """Volume up the media player.""" + await self.hass.async_add_executor_job( + self._set_volume, min(self._status.volume + 1, MAX_VOL) + ) + self._async_update_attrs_write_ha_state() + + async def async_volume_down(self): + """Volume down media player.""" + await self.hass.async_add_executor_job( + self._set_volume, max(self._status.volume - 1, 0) + ) + self._async_update_attrs_write_ha_state() + + def _set_volume(self, volume: int) -> None: + """Set the volume of the media player.""" + # Can't set a new volume level when this zone is muted. + # Follow behavior of keypads, where zone is unmuted when volume changes. + if self._status.mute: + self._ws66i.set_mute(self._zone_id, False) + self._status.mute = False + + self._ws66i.set_volume(self._zone_id, volume) + self._status.volume = volume diff --git a/homeassistant/components/ws66i/models.py b/homeassistant/components/ws66i/models.py new file mode 100644 index 00000000000..84f481b9a4a --- /dev/null +++ b/homeassistant/components/ws66i/models.py @@ -0,0 +1,28 @@ +"""The ws66i integration models.""" +from __future__ import annotations + +from dataclasses import dataclass + +from pyws66i import WS66i + +from .coordinator import Ws66iDataUpdateCoordinator + + +@dataclass +class SourceRep: + """Different representations of the amp sources.""" + + id_name: dict[int, str] + name_id: dict[str, int] + name_list: list[str] + + +@dataclass +class Ws66iData: + """Data for the ws66i integration.""" + + host_ip: str + device: WS66i + sources: SourceRep + coordinator: Ws66iDataUpdateCoordinator + zones: list[int] diff --git a/homeassistant/components/ws66i/strings.json b/homeassistant/components/ws66i/strings.json new file mode 100644 index 00000000000..ec5bc621a89 --- /dev/null +++ b/homeassistant/components/ws66i/strings.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "title": "Connect to the device", + "data": { + "ip_address": "[%key:common::config_flow::data::ip%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "options": { + "step": { + "init": { + "title": "Configure sources", + "data": { + "source_1": "Name of source #1", + "source_2": "Name of source #2", + "source_3": "Name of source #3", + "source_4": "Name of source #4", + "source_5": "Name of source #5", + "source_6": "Name of source #6" + } + } + } + } +} diff --git a/homeassistant/components/ws66i/translations/bg.json b/homeassistant/components/ws66i/translations/bg.json new file mode 100644 index 00000000000..ba078ae5f11 --- /dev/null +++ b/homeassistant/components/ws66i/translations/bg.json @@ -0,0 +1,18 @@ +{ + "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", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "ip_address": "IP \u0430\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/ca.json b/homeassistant/components/ws66i/translations/ca.json new file mode 100644 index 00000000000..789edb86fb2 --- /dev/null +++ b/homeassistant/components/ws66i/translations/ca.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "ip_address": "Adre\u00e7a IP" + }, + "title": "Connexi\u00f3 amb el dispositiu" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nom de la font #1", + "source_2": "Nom de la font #2", + "source_3": "Nom de la font #3", + "source_4": "Nom de la font #4", + "source_5": "Nom de la font #5", + "source_6": "Nom de la font #6" + }, + "title": "Configuraci\u00f3 de les fonts" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/de.json b/homeassistant/components/ws66i/translations/de.json new file mode 100644 index 00000000000..cab3e062d8e --- /dev/null +++ b/homeassistant/components/ws66i/translations/de.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-Adresse" + }, + "title": "Verbinden mit dem Ger\u00e4t" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Name der Quelle #1", + "source_2": "Name der Quelle #2", + "source_3": "Name der Quelle #3", + "source_4": "Name der Quelle #4", + "source_5": "Name der Quelle #5", + "source_6": "Name der Quelle #6" + }, + "title": "Quellen konfigurieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/el.json b/homeassistant/components/ws66i/translations/el.json new file mode 100644 index 00000000000..4a1365c3a77 --- /dev/null +++ b/homeassistant/components/ws66i/translations/el.json @@ -0,0 +1,34 @@ +{ + "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", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #1", + "source_2": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #2", + "source_3": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #3", + "source_4": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #4", + "source_5": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #5", + "source_6": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b7\u03b3\u03ae\u03c2 #6" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c0\u03b7\u03b3\u03ce\u03bd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/en.json b/homeassistant/components/ws66i/translations/en.json new file mode 100644 index 00000000000..fd4b170b378 --- /dev/null +++ b/homeassistant/components/ws66i/translations/en.json @@ -0,0 +1,31 @@ +{ + "config": { + "error": { + "cannot_connect": "Failed to connect", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "ip_address": "IP Address" + }, + "title": "Connect to the device" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Name of source #1", + "source_2": "Name of source #2", + "source_3": "Name of source #3", + "source_4": "Name of source #4", + "source_5": "Name of source #5", + "source_6": "Name of source #6" + }, + "title": "Configure sources" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/es.json b/homeassistant/components/ws66i/translations/es.json new file mode 100644 index 00000000000..d19017fdb95 --- /dev/null +++ b/homeassistant/components/ws66i/translations/es.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Ha fallado la conexi\u00f3n", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "ip_address": "Direcci\u00f3n IP" + }, + "title": "Conexi\u00f3n con el dispositivo" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nombre de la fuente #1", + "source_2": "Nombre de la fuente #2", + "source_3": "Nombre de la fuente #3", + "source_4": "Nombre de la fuente #4", + "source_5": "Nombre de la fuente #5", + "source_6": "Nombre de la fuente #6" + }, + "title": "Configuraci\u00f3n de las fuentes" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/et.json b/homeassistant/components/ws66i/translations/et.json new file mode 100644 index 00000000000..83b238d74f5 --- /dev/null +++ b/homeassistant/components/ws66i/translations/et.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "ip_address": "IP aadress" + }, + "title": "\u00dchendu seadmega" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Sisendi nr 1 nimi", + "source_2": "Sisendi nr 2 nimi", + "source_3": "Sisendi nr 3 nimi", + "source_4": "Sisendi nr 4 nimi", + "source_5": "Sisendi nr 5 nimi", + "source_6": "Sisendi nr 6 nimi" + }, + "title": "Seadista sisendid" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/fr.json b/homeassistant/components/ws66i/translations/fr.json new file mode 100644 index 00000000000..d4d3f6e7350 --- /dev/null +++ b/homeassistant/components/ws66i/translations/fr.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "ip_address": "Adresse IP" + }, + "title": "Se connecter \u00e0 l'appareil" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nom de la source n\u00ba\u00a01", + "source_2": "Nom de la source n\u00ba\u00a02", + "source_3": "Nom de la source n\u00ba\u00a03", + "source_4": "Nom de la source n\u00ba\u00a04", + "source_5": "Nom de la source n\u00ba\u00a05", + "source_6": "Nom de la source n\u00ba\u00a06" + }, + "title": "Configurer les sources" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/he.json b/homeassistant/components/ws66i/translations/he.json new file mode 100644 index 00000000000..fa770b28bf4 --- /dev/null +++ b/homeassistant/components/ws66i/translations/he.json @@ -0,0 +1,18 @@ +{ + "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", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "ip_address": "\u05db\u05ea\u05d5\u05d1\u05ea IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/hu.json b/homeassistant/components/ws66i/translations/hu.json new file mode 100644 index 00000000000..9d6585e8f6b --- /dev/null +++ b/homeassistant/components/ws66i/translations/hu.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "ip_address": "IP c\u00edm" + }, + "title": "Csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Forr\u00e1s neve #1", + "source_2": "Forr\u00e1s neve #2", + "source_3": "Forr\u00e1s neve #3", + "source_4": "Forr\u00e1s neve #4", + "source_5": "Forr\u00e1s neve #5", + "source_6": "Forr\u00e1s neve #6" + }, + "title": "Forr\u00e1sok konfigur\u00e1l\u00e1sa" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/id.json b/homeassistant/components/ws66i/translations/id.json new file mode 100644 index 00000000000..54cb3043a11 --- /dev/null +++ b/homeassistant/components/ws66i/translations/id.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "ip_address": "Alamat IP" + }, + "title": "Hubungkan ke perangkat" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nama sumber #1", + "source_2": "Nama sumber #2", + "source_3": "Nama sumber #3", + "source_4": "Nama sumber #4", + "source_5": "Nama sumber #5", + "source_6": "Nama sumber #6" + }, + "title": "Konfigurasikan sumber" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/it.json b/homeassistant/components/ws66i/translations/it.json new file mode 100644 index 00000000000..c98714c98ad --- /dev/null +++ b/homeassistant/components/ws66i/translations/it.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "ip_address": "Indirizzo IP" + }, + "title": "Connettiti al dispositivo" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nome della fonte n. 1", + "source_2": "Nome della fonte n. 2", + "source_3": "Nome della fonte n. 3", + "source_4": "Nome della fonte n. 4", + "source_5": "Nome della fonte n. 5", + "source_6": "Nome della fonte n. 6" + }, + "title": "Configura le fonti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/ja.json b/homeassistant/components/ws66i/translations/ja.json new file mode 100644 index 00000000000..2ae21b3916c --- /dev/null +++ b/homeassistant/components/ws66i/translations/ja.json @@ -0,0 +1,34 @@ +{ + "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", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "user": { + "data": { + "ip_address": "IP\u30a2\u30c9\u30ec\u30b9" + }, + "title": "\u30c7\u30d0\u30a4\u30b9\u306b\u63a5\u7d9a" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "\u30bd\u30fc\u30b9#1\u306e\u540d\u524d", + "source_2": "\u30bd\u30fc\u30b9#2\u306e\u540d\u524d", + "source_3": "\u30bd\u30fc\u30b9#3\u306e\u540d\u524d", + "source_4": "\u30bd\u30fc\u30b9#4\u306e\u540d\u524d", + "source_5": "\u30bd\u30fc\u30b9#5\u306e\u540d\u524d", + "source_6": "\u30bd\u30fc\u30b9#6\u306e\u540d\u524d" + }, + "title": "\u30bd\u30fc\u30b9\u306e\u8a2d\u5b9a" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/ko.json b/homeassistant/components/ws66i/translations/ko.json new file mode 100644 index 00000000000..ba31d74c21a --- /dev/null +++ b/homeassistant/components/ws66i/translations/ko.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "ip_address": "IP \uc8fc\uc18c" + }, + "title": "\uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "\uc18c\uc2a4 \uc774\ub984 #1", + "source_2": "\uc18c\uc2a4 \uc774\ub984 #2", + "source_3": "\uc18c\uc2a4 \uc774\ub984 #3", + "source_4": "\uc18c\uc2a4 \uc774\ub984 #4", + "source_5": "\uc18c\uc2a4 \uc774\ub984 #5", + "source_6": "\uc18c\uc2a4 \uc774\ub984 #6" + }, + "title": "\uc785\ub825 \uc18c\uc2a4 \uad6c\uc131\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/nl.json b/homeassistant/components/ws66i/translations/nl.json new file mode 100644 index 00000000000..0ff24636b88 --- /dev/null +++ b/homeassistant/components/ws66i/translations/nl.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-adres" + }, + "title": "Verbinding maken met het apparaat" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Naam van bron #1", + "source_2": "Naam van bron #2", + "source_3": "Naam van bron #3", + "source_4": "Naam van bron #4", + "source_5": "Naam van bron #5", + "source_6": "Naam van bron #6" + }, + "title": "Configureer bronnen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/no.json b/homeassistant/components/ws66i/translations/no.json new file mode 100644 index 00000000000..fa132ab681a --- /dev/null +++ b/homeassistant/components/ws66i/translations/no.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "ip_address": "IP adresse" + }, + "title": "Koble til enheten" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Navn p\u00e5 kilde #1", + "source_2": "Navn p\u00e5 kilde #2", + "source_3": "Navn p\u00e5 kilde #3", + "source_4": "Navn p\u00e5 kilde #4", + "source_5": "Navn p\u00e5 kilde #5", + "source_6": "Navn p\u00e5 kilde #6" + }, + "title": "Konfigurer kilder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/pl.json b/homeassistant/components/ws66i/translations/pl.json new file mode 100644 index 00000000000..1fc8c8b1cc9 --- /dev/null +++ b/homeassistant/components/ws66i/translations/pl.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "ip_address": "Adres IP" + }, + "title": "Po\u0142\u0105czenie z urz\u0105dzeniem" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nazwa \u017ar\u00f3d\u0142a #1", + "source_2": "Nazwa \u017ar\u00f3d\u0142a #2", + "source_3": "Nazwa \u017ar\u00f3d\u0142a #3", + "source_4": "Nazwa \u017ar\u00f3d\u0142a #4", + "source_5": "Nazwa \u017ar\u00f3d\u0142a #5", + "source_6": "Nazwa \u017ar\u00f3d\u0142a #6" + }, + "title": "Konfiguracja \u017ar\u00f3de\u0142" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/pt-BR.json b/homeassistant/components/ws66i/translations/pt-BR.json new file mode 100644 index 00000000000..d440aab3aa4 --- /dev/null +++ b/homeassistant/components/ws66i/translations/pt-BR.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falhou ao conectar", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "ip_address": "Endere\u00e7o IP" + }, + "title": "Conecte-se ao dispositivo" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nome da fonte #1", + "source_2": "Nome da fonte #2", + "source_3": "Nome da fonte #3", + "source_4": "Nome da fonte #4", + "source_5": "Nome da fonte #5", + "source_6": "Nome da fonte #6" + }, + "title": "Configurar fontes" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/ru.json b/homeassistant/components/ws66i/translations/ru.json new file mode 100644 index 00000000000..b7e244cf2b0 --- /dev/null +++ b/homeassistant/components/ws66i/translations/ru.json @@ -0,0 +1,34 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441" + }, + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u21161", + "source_2": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u21162", + "source_3": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u21163", + "source_4": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u21164", + "source_5": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u21165", + "source_6": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u21166" + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/tr.json b/homeassistant/components/ws66i/translations/tr.json new file mode 100644 index 00000000000..5baea0cee9d --- /dev/null +++ b/homeassistant/components/ws66i/translations/tr.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "ip_address": "IP Adresi" + }, + "title": "Cihaza ba\u011flan\u0131n" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Kaynak #1 ad\u0131", + "source_2": "Kaynak #2 ad\u0131", + "source_3": "Kaynak #3 ad\u0131", + "source_4": "Kaynak #4 ad\u0131", + "source_5": "Kaynak #5 ad\u0131", + "source_6": "Kaynak #6 ad\u0131" + }, + "title": "Kaynaklar\u0131 yap\u0131land\u0131r" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ws66i/translations/zh-Hant.json b/homeassistant/components/ws66i/translations/zh-Hant.json new file mode 100644 index 00000000000..a583ac2217f --- /dev/null +++ b/homeassistant/components/ws66i/translations/zh-Hant.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "ip_address": "IP \u4f4d\u5740" + }, + "title": "\u9023\u7dda\u81f3\u88dd\u7f6e" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "\u4f86\u6e90 #1 \u540d\u7a31", + "source_2": "\u4f86\u6e90 #2 \u540d\u7a31", + "source_3": "\u4f86\u6e90 #3 \u540d\u7a31", + "source_4": "\u4f86\u6e90 #4 \u540d\u7a31", + "source_5": "\u4f86\u6e90 #5 \u540d\u7a31", + "source_6": "\u4f86\u6e90 #6 \u540d\u7a31" + }, + "title": "\u8a2d\u5b9a\u4f86\u6e90" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xbox/__init__.py b/homeassistant/components/xbox/__init__.py index 0466d0191cf..19bbec8bbf5 100644 --- a/homeassistant/components/xbox/__init__.py +++ b/homeassistant/components/xbox/__init__.py @@ -20,6 +20,7 @@ from xbox.webapi.api.provider.smartglass.models import ( SmartglassConsoleStatus, ) +from homeassistant.components import application_credentials from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, Platform from homeassistant.core import HomeAssistant @@ -31,20 +32,23 @@ from homeassistant.helpers import ( from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import api, config_flow -from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN +from . import api +from .const import DOMAIN _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, - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) @@ -63,17 +67,20 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if DOMAIN not in config: return True - config_flow.OAuth2FlowHandler.async_register_implementation( + await application_credentials.async_import_client_credential( hass, - config_entry_oauth2_flow.LocalOAuth2Implementation( - hass, - DOMAIN, - config[DOMAIN][CONF_CLIENT_ID], - config[DOMAIN][CONF_CLIENT_SECRET], - OAUTH2_AUTHORIZE, - OAUTH2_TOKEN, + DOMAIN, + application_credentials.ClientCredential( + config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET] ), ) + _LOGGER.warning( + "Configuration of Xbox integration in YAML is deprecated and " + "will be removed in a future release; Your existing configuration " + "(including OAuth Application Credentials) has been imported into " + "the UI automatically and can be safely removed from your " + "configuration.yaml file" + ) return True diff --git a/homeassistant/components/xbox/application_credentials.py b/homeassistant/components/xbox/application_credentials.py new file mode 100644 index 00000000000..2e3d7f8a6a0 --- /dev/null +++ b/homeassistant/components/xbox/application_credentials.py @@ -0,0 +1,14 @@ +"""Application credentials platform for xbox.""" + +from homeassistant.components.application_credentials import AuthorizationServer +from homeassistant.core import HomeAssistant + +from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN + + +async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer: + """Return authorization server.""" + return AuthorizationServer( + authorize_url=OAUTH2_AUTHORIZE, + token_url=OAUTH2_TOKEN, + ) diff --git a/homeassistant/components/xbox/binary_sensor.py b/homeassistant/components/xbox/binary_sensor.py index 592909f3aac..7cf7ca6a6a5 100644 --- a/homeassistant/components/xbox/binary_sensor.py +++ b/homeassistant/components/xbox/binary_sensor.py @@ -6,10 +6,8 @@ from functools import partial from homeassistant.components.binary_sensor import BinarySensorEntity 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.entity_registry import ( - async_get_registry as async_get_entity_registry, -) from . import XboxUpdateCoordinator from .base_sensor import XboxBaseSensorEntity @@ -80,7 +78,7 @@ async def async_remove_entities( current: dict[str, XboxBinarySensorEntity], ) -> None: """Remove friend sensors from Home Assistant.""" - registry = await async_get_entity_registry(coordinator.hass) + registry = er.async_get(coordinator.hass) entities = current[xuid] for entity in entities: if entity.entity_id in registry.entities: diff --git a/homeassistant/components/xbox/manifest.json b/homeassistant/components/xbox/manifest.json index 432b3e84100..5adfa54a901 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": ["auth"], + "dependencies": ["auth", "application_credentials"], "codeowners": ["@hunterjm"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/xbox/sensor.py b/homeassistant/components/xbox/sensor.py index edcc4a8c135..02b4f8b84a4 100644 --- a/homeassistant/components/xbox/sensor.py +++ b/homeassistant/components/xbox/sensor.py @@ -6,10 +6,8 @@ from functools import partial from homeassistant.components.sensor import SensorEntity 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.entity_registry import ( - async_get_registry as async_get_entity_registry, -) from . import XboxUpdateCoordinator from .base_sensor import XboxBaseSensorEntity @@ -82,7 +80,7 @@ async def async_remove_entities( current: dict[str, XboxSensorEntity], ) -> None: """Remove friend sensors from Home Assistant.""" - registry = await async_get_entity_registry(coordinator.hass) + registry = er.async_get(coordinator.hass) entities = current[xuid] for entity in entities: if entity.entity_id in registry.entities: diff --git a/homeassistant/components/xbox/translations/es.json b/homeassistant/components/xbox/translations/es.json index 52fb998dfd3..27cbeaec139 100644 --- a/homeassistant/components/xbox/translations/es.json +++ b/homeassistant/components/xbox/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado durante la generaci\u00f3n de la URL de autorizaci\u00f3n.", "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n.", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, diff --git a/homeassistant/components/xbox/translations/nl.json b/homeassistant/components/xbox/translations/nl.json index 1e567e954d2..9d68863cc27 100644 --- a/homeassistant/components/xbox/translations/nl.json +++ b/homeassistant/components/xbox/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "De component is niet geconfigureerd. Gelieve de documentatie volgen.", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "create_entry": { - "default": "Succesvol geauthenticeerd" + "default": "Authenticatie geslaagd" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/xiaomi_aqara/translations/ca.json b/homeassistant/components/xiaomi_aqara/translations/ca.json index 78f5affd556..fe0e0d7c096 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ca.json +++ b/homeassistant/components/xiaomi_aqara/translations/ca.json @@ -18,8 +18,7 @@ "data": { "select_ip": "Adre\u00e7a IP" }, - "description": "Selecciona la passarel\u00b7la Xiaomi Aqara a la qual connectar-te", - "title": "Selecciona la passarel\u00b7la Xiaomi Aqara a la qual connectar-te" + "description": "Selecciona la passarel\u00b7la Xiaomi Aqara a la qual connectar-te" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "Interf\u00edcie de xarxa a utilitzar", "mac": "Adre\u00e7a MAC (opcional)" }, - "description": "Si les adreces IP i MAC es deixen buides s'utilitzar\u00e0 el descobriment autom\u00e0tic", - "title": "Passarel\u00b7la Xiaomi Aqara" + "description": "Si les adreces IP i MAC es deixen buides s'utilitzar\u00e0 el descobriment autom\u00e0tic" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/cs.json b/homeassistant/components/xiaomi_aqara/translations/cs.json index b344be8c647..f2fd489987f 100644 --- a/homeassistant/components/xiaomi_aqara/translations/cs.json +++ b/homeassistant/components/xiaomi_aqara/translations/cs.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP adresa" }, - "description": "Chcete-li p\u0159ipojit dal\u0161\u00ed br\u00e1ny, spus\u0165te znovu instalaci", - "title": "Vyberte br\u00e1nu Xiaomi Aqara, kterou chcete p\u0159ipojit" + "description": "Chcete-li p\u0159ipojit dal\u0161\u00ed br\u00e1ny, spus\u0165te znovu instalaci" }, "settings": { "data": { @@ -34,8 +33,7 @@ "host": "IP adresa (voliteln\u011b)", "mac": "MAC adresa (voliteln\u00e1)" }, - "description": "P\u0159ipojte se k va\u0161\u00ed br\u00e1n\u011b Xiaomi Aqara - pokud z\u016fstanou pr\u00e1zdn\u00e9 adresy IP a MAC, pou\u017eije se automatick\u00e9 zji\u0161\u0165ov\u00e1n\u00ed", - "title": "Br\u00e1na Xiaomi Aqara" + "description": "P\u0159ipojte se k va\u0161\u00ed br\u00e1n\u011b Xiaomi Aqara - pokud z\u016fstanou pr\u00e1zdn\u00e9 adresy IP a MAC, pou\u017eije se automatick\u00e9 zji\u0161\u0165ov\u00e1n\u00ed" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/de.json b/homeassistant/components/xiaomi_aqara/translations/de.json index 170442fc856..f9e5ea39abe 100644 --- a/homeassistant/components/xiaomi_aqara/translations/de.json +++ b/homeassistant/components/xiaomi_aqara/translations/de.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP-Adresse" }, - "description": "W\u00e4hle das Xiaomi Aqara Gateway, das du verbinden m\u00f6chtest", - "title": "W\u00e4hle das Xiaomi Aqara Gateway, das du verbinden m\u00f6chtest" + "description": "W\u00e4hle das Xiaomi Aqara Gateway, das du verbinden m\u00f6chtest" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "Die zu verwendende Netzwerkschnittstelle", "mac": "MAC-Adresse (optional)" }, - "description": "Wenn die IP- und MAC-Adressen leer gelassen werden, wird die automatische Erkennung verwendet", - "title": "Xiaomi Aqara Gateway" + "description": "Wenn die IP- und MAC-Adressen leer gelassen werden, wird die automatische Erkennung verwendet" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/el.json b/homeassistant/components/xiaomi_aqara/translations/el.json index 83923b660c2..8a2f85be99d 100644 --- a/homeassistant/components/xiaomi_aqara/translations/el.json +++ b/homeassistant/components/xiaomi_aqara/translations/el.json @@ -18,8 +18,7 @@ "data": { "select_ip": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" }, - "description": "\u0395\u03ba\u03c4\u03b5\u03bb\u03ad\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03ac\u03bd \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03b5\u03c0\u03b9\u03c0\u03bb\u03ad\u03bf\u03bd \u03c0\u03cd\u03bb\u03b5\u03c2", - "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 Xiaomi Aqara \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5" + "description": "\u0395\u03ba\u03c4\u03b5\u03bb\u03ad\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03ac\u03bd \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03b5\u03c0\u03b9\u03c0\u03bb\u03ad\u03bf\u03bd \u03c0\u03cd\u03bb\u03b5\u03c2" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "\u0397 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7", "mac": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 Mac (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" }, - "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 Xiaomi Aqara Gateway, \u03b5\u03ac\u03bd \u03bf\u03b9 \u03b4\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 IP \u03ba\u03b1\u03b9 MAC \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03bd\u03bf\u03c5\u03bd \u03ba\u03b5\u03bd\u03ad\u03c2, \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": "\u03a0\u03cd\u03bb\u03b7 Xiaomi Aqara" + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c0\u03cd\u03bb\u03b7 Xiaomi Aqara Gateway, \u03b5\u03ac\u03bd \u03bf\u03b9 \u03b4\u03b9\u03b5\u03c5\u03b8\u03cd\u03bd\u03c3\u03b5\u03b9\u03c2 IP \u03ba\u03b1\u03b9 MAC \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03bd\u03bf\u03c5\u03bd \u03ba\u03b5\u03bd\u03ad\u03c2, \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." } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/en.json b/homeassistant/components/xiaomi_aqara/translations/en.json index 72949820bc0..ab6da1c7bfa 100644 --- a/homeassistant/components/xiaomi_aqara/translations/en.json +++ b/homeassistant/components/xiaomi_aqara/translations/en.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP Address" }, - "description": "Select the Xiaomi Aqara Gateway that you wish to connect", - "title": "Select the Xiaomi Aqara Gateway that you wish to connect" + "description": "Select the Xiaomi Aqara Gateway that you wish to connect" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "The network interface to use", "mac": "Mac Address (optional)" }, - "description": "If the IP and MAC addresses are left empty, auto-discovery is used", - "title": "Xiaomi Aqara Gateway" + "description": "If the IP and MAC addresses are left empty, auto-discovery is used" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/es.json b/homeassistant/components/xiaomi_aqara/translations/es.json index 04da1a8caf5..e06806bbd9a 100644 --- a/homeassistant/components/xiaomi_aqara/translations/es.json +++ b/homeassistant/components/xiaomi_aqara/translations/es.json @@ -16,10 +16,9 @@ "step": { "select": { "data": { - "select_ip": "IP del gateway" + "select_ip": "Direcci\u00f3n IP" }, - "description": "Ejecuta la configuraci\u00f3n de nuevo si deseas conectar gateways adicionales", - "title": "Selecciona el Xiaomi Aqara Gateway que quieres conectar" + "description": "Ejecuta la configuraci\u00f3n de nuevo si deseas conectar gateways adicionales" }, "settings": { "data": { @@ -27,7 +26,7 @@ "name": "Nombre del Gateway" }, "description": "La clave (contrase\u00f1a) se puede obtener con este tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Si no se proporciona la clave solo se podr\u00e1 acceder a los sensores", - "title": "Xiaomi Aqara Gateway, configuraciones opcionales" + "title": "Configuraciones opcionales" }, "user": { "data": { @@ -35,8 +34,7 @@ "interface": "La interfaz de la red a usar", "mac": "Direcci\u00f3n Mac (opcional)" }, - "description": "Con\u00e9ctate a tu Xiaomi Aqara Gateway, si las direcciones IP y mac se dejan vac\u00edas, se utiliza el auto-descubrimiento.", - "title": "Xiaomi Aqara Gateway" + "description": "Con\u00e9ctate a tu Xiaomi Aqara Gateway, si las direcciones IP y mac se dejan vac\u00edas, se utiliza el auto-descubrimiento." } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/et.json b/homeassistant/components/xiaomi_aqara/translations/et.json index 087c59dd2e2..90702af0a7c 100644 --- a/homeassistant/components/xiaomi_aqara/translations/et.json +++ b/homeassistant/components/xiaomi_aqara/translations/et.json @@ -18,8 +18,7 @@ "data": { "select_ip": "L\u00fc\u00fcsi IP aadress" }, - "description": "Vali Xiaomi Aqara Gateway mida soovid \u00fchendada.", - "title": "Vali Xiaomi Aqara l\u00fc\u00fcs mida soovid \u00fchendada" + "description": "Vali Xiaomi Aqara Gateway mida soovid \u00fchendada." }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "Kasutatav v\u00f5rguliides", "mac": "MAC aadress (valikuline)" }, - "description": "Kui IP- ja MAC-aadressid j\u00e4etakse t\u00fchjaks, kasutatakse automaatset avastamist", - "title": "" + "description": "Kui IP- ja MAC-aadressid j\u00e4etakse t\u00fchjaks, kasutatakse automaatset avastamist" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/fr.json b/homeassistant/components/xiaomi_aqara/translations/fr.json index ab042cb5f0e..3c5544cfff3 100644 --- a/homeassistant/components/xiaomi_aqara/translations/fr.json +++ b/homeassistant/components/xiaomi_aqara/translations/fr.json @@ -18,8 +18,7 @@ "data": { "select_ip": "Adresse IP" }, - "description": "S\u00e9lectionnez la passerelle Xiaomi Aqara que vous souhaitez connecter", - "title": "S\u00e9lectionnez la passerelle Xiaomi Aqara que vous souhaitez connecter" + "description": "S\u00e9lectionnez la passerelle Xiaomi Aqara que vous souhaitez connecter" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "Interface r\u00e9seau \u00e0 utiliser", "mac": "Adresse MAC (facultatif)" }, - "description": "Si les adresses IP et MAC sont laiss\u00e9es vides, la d\u00e9couverte automatique sera utilis\u00e9e", - "title": "Passerelle Xiaomi Aqara" + "description": "Si les adresses IP et MAC sont laiss\u00e9es vides, la d\u00e9couverte automatique sera utilis\u00e9e" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/he.json b/homeassistant/components/xiaomi_aqara/translations/he.json index ea6cd8e278b..8d123517560 100644 --- a/homeassistant/components/xiaomi_aqara/translations/he.json +++ b/homeassistant/components/xiaomi_aqara/translations/he.json @@ -18,8 +18,7 @@ "data": { "select_ip": "\u05db\u05ea\u05d5\u05d1\u05ea IP" }, - "description": "\u05d9\u05e9 \u05dc\u05d4\u05e4\u05e2\u05d9\u05dc \u05d0\u05ea \u05d4\u05d4\u05ea\u05e7\u05e0\u05d4 \u05e9\u05d5\u05d1 \u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d7\u05d1\u05e8 \u05e9\u05e2\u05e8\u05d9\u05dd \u05e0\u05d5\u05e1\u05e4\u05d9\u05dd", - "title": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05e9\u05e2\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9 \u05d0\u05e7\u05d0\u05e8\u05d4 \u05e9\u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d7\u05d1\u05e8" + "description": "\u05d9\u05e9 \u05dc\u05d4\u05e4\u05e2\u05d9\u05dc \u05d0\u05ea \u05d4\u05d4\u05ea\u05e7\u05e0\u05d4 \u05e9\u05d5\u05d1 \u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d7\u05d1\u05e8 \u05e9\u05e2\u05e8\u05d9\u05dd \u05e0\u05d5\u05e1\u05e4\u05d9\u05dd" }, "settings": { "data": { @@ -35,8 +34,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": "\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" + "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" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/hu.json b/homeassistant/components/xiaomi_aqara/translations/hu.json index f0a1b076750..e3319baa594 100644 --- a/homeassistant/components/xiaomi_aqara/translations/hu.json +++ b/homeassistant/components/xiaomi_aqara/translations/hu.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP c\u00edm" }, - "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" + "description": "V\u00e1lassza ki a csatlakoztatni k\u00edv\u00e1nt Xiaomi Aqara K\u00f6zponti egys\u00e9get" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "A haszn\u00e1lni k\u00edv\u00e1nt h\u00e1l\u00f3zati interf\u00e9sz", "mac": "Mac-c\u00edm (opcion\u00e1lis)" }, - "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" + "description": "Ha az IP- \u00e9s MAC-c\u00edm mez\u0151ket \u00fcresen hagyja, akkor az automatikus felder\u00edt\u00e9s ker\u00fcl alkalmaz\u00e1sra." } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/id.json b/homeassistant/components/xiaomi_aqara/translations/id.json index 2b33af8237f..746815075b4 100644 --- a/homeassistant/components/xiaomi_aqara/translations/id.json +++ b/homeassistant/components/xiaomi_aqara/translations/id.json @@ -18,8 +18,7 @@ "data": { "select_ip": "Alamat IP" }, - "description": "Pilih Gateway Xiaomi Aqara yang ingin disambungkan", - "title": "Pilih Gateway Xiaomi Aqara yang ingin dihubungkan" + "description": "Pilih Gateway Xiaomi Aqara yang ingin disambungkan" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "Antarmuka jaringan yang akan digunakan", "mac": "Alamat MAC (opsional)" }, - "description": "Jika alamat IP dan MAC dikosongkan, penemuan otomatis digunakan", - "title": "Xiaomi Aqara Gateway" + "description": "Jika alamat IP dan MAC dikosongkan, penemuan otomatis digunakan" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/it.json b/homeassistant/components/xiaomi_aqara/translations/it.json index ac5eff78190..2ea75af7f4a 100644 --- a/homeassistant/components/xiaomi_aqara/translations/it.json +++ b/homeassistant/components/xiaomi_aqara/translations/it.json @@ -18,8 +18,7 @@ "data": { "select_ip": "Indirizzo IP" }, - "description": "Seleziona lo Xiaomi Aqara Gateway che desideri connettere", - "title": "Seleziona il Gateway Xiaomi Aqara che si desidera collegare" + "description": "Seleziona lo Xiaomi Aqara Gateway che desideri connettere" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "L'interfaccia di rete da utilizzare", "mac": "Indirizzo Mac (opzionale)" }, - "description": "Se gli indirizzi IP e MAC vengono lasciati vuoti, viene utilizzato il rilevamento automatico", - "title": "Xiaomi Aqara Gateway" + "description": "Se gli indirizzi IP e MAC vengono lasciati vuoti, viene utilizzato il rilevamento automatico" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/ja.json b/homeassistant/components/xiaomi_aqara/translations/ja.json index 87e757a2c5d..becb37c00f0 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ja.json +++ b/homeassistant/components/xiaomi_aqara/translations/ja.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP\u30a2\u30c9\u30ec\u30b9" }, - "description": "\u8ffd\u52a0\u306e\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4\u3092\u63a5\u7d9a\u3059\u308b\u5834\u5408\u306f\u3001\u518d\u5ea6\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "\u63a5\u7d9a\u3057\u305f\u3044Xiaomi Aqara Gateway\u3092\u9078\u629e\u3057\u307e\u3059\u3002" + "description": "\u8ffd\u52a0\u306e\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4\u3092\u63a5\u7d9a\u3059\u308b\u5834\u5408\u306f\u3001\u518d\u5ea6\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "\u4f7f\u7528\u3059\u308b\u30cd\u30c3\u30c8\u30ef\u30fc\u30af \u30a4\u30f3\u30bf\u30fc\u30d5\u30a7\u30a4\u30b9", "mac": "Mac\u30a2\u30c9\u30ec\u30b9 (\u30aa\u30d7\u30b7\u30e7\u30f3)" }, - "description": "Xiaomi Aqara Gateway\u306b\u63a5\u7d9a\u3057\u307e\u3059\u3002IP\u30a2\u30c9\u30ec\u30b9\u3068MAC\u30a2\u30c9\u30ec\u30b9\u304c\u7a7a\u306e\u307e\u307e\u306e\u5834\u5408\u3001\u81ea\u52d5\u691c\u51fa\u304c\u4f7f\u7528\u3055\u308c\u307e\u3059", - "title": "Xiaomi Aqara Gateway" + "description": "Xiaomi Aqara Gateway\u306b\u63a5\u7d9a\u3057\u307e\u3059\u3002IP\u30a2\u30c9\u30ec\u30b9\u3068MAC\u30a2\u30c9\u30ec\u30b9\u304c\u7a7a\u767d\u306e\u307e\u307e\u306e\u5834\u5408\u3001\u81ea\u52d5\u691c\u51fa\u304c\u4f7f\u7528\u3055\u308c\u307e\u3059" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/ko.json b/homeassistant/components/xiaomi_aqara/translations/ko.json index dd8a9ae5ede..1538f2adcb5 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ko.json +++ b/homeassistant/components/xiaomi_aqara/translations/ko.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP \uc8fc\uc18c" }, - "description": "\uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ucd94\uac00 \uc5f0\uacb0\ud558\ub824\uba74 \uc124\uc815\uc744 \ub2e4\uc2dc \uc2e4\ud589\ud574\uc8fc\uc138\uc694", - "title": "\uc5f0\uacb0\ud560 Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774 \uc120\ud0dd\ud558\uae30" + "description": "\uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ucd94\uac00 \uc5f0\uacb0\ud558\ub824\uba74 \uc124\uc815\uc744 \ub2e4\uc2dc \uc2e4\ud589\ud574\uc8fc\uc138\uc694" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "\uc0ac\uc6a9\ud560 \ub124\ud2b8\uc6cc\ud06c \uc778\ud130\ud398\uc774\uc2a4", "mac": "Mac \uc8fc\uc18c (\uc120\ud0dd \uc0ac\ud56d)" }, - "description": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \uc8fc\uc18c \ubc0f MAC \uc8fc\uc18c\ub97c \ube44\uc6cc\ub450\uba74 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", - "title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774" + "description": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \uc8fc\uc18c \ubc0f MAC \uc8fc\uc18c\ub97c \ube44\uc6cc\ub450\uba74 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/lb.json b/homeassistant/components/xiaomi_aqara/translations/lb.json index 1d3c5f6ed76..3bb703ffb34 100644 --- a/homeassistant/components/xiaomi_aqara/translations/lb.json +++ b/homeassistant/components/xiaomi_aqara/translations/lb.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP Adresse" }, - "description": "Setup nach emol ausf\u00e9ieren fir eng zous\u00e4tzlech Gateway dob\u00e4i ze setzen", - "title": "Xiaomi Aqara Gateway auswielen mat der sech soll verbonne ginn" + "description": "Setup nach emol ausf\u00e9ieren fir eng zous\u00e4tzlech Gateway dob\u00e4i ze setzen" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "Netzwierk Interface dee soll benotzt ginn", "mac": "Mac Adress (Optionell)" }, - "description": "Mat denger Xiaomi Aqara Gateway verbannen, falls keng IP oder MAC Adress uginn ass g\u00ebtt auto-discovery benotzt", - "title": "Xiaomi Aqara Gateway" + "description": "Mat denger Xiaomi Aqara Gateway verbannen, falls keng IP oder MAC Adress uginn ass g\u00ebtt auto-discovery benotzt" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/nl.json b/homeassistant/components/xiaomi_aqara/translations/nl.json index c2042850fdb..bfdd88a30de 100644 --- a/homeassistant/components/xiaomi_aqara/translations/nl.json +++ b/homeassistant/components/xiaomi_aqara/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "not_xiaomi_aqara": "Geen Xiaomi Aqara Gateway, ontdekt apparaat kwam niet overeen met bekende gateways" }, "error": { @@ -18,8 +18,7 @@ "data": { "select_ip": "IP-adres" }, - "description": "Selecteer de Xiaomi Aqara Gateway die u wilt verbinden", - "title": "Selecteer de Xiaomi Aqara Gateway waarmee u verbinding wilt maken" + "description": "Selecteer de Xiaomi Aqara Gateway die u wilt verbinden" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "De netwerkinterface die moet worden gebruikt", "mac": "MAC-adres (optioneel)" }, - "description": "Als de IP- en MAC-adressen leeg worden gelaten, wordt auto-discovery gebruikt", - "title": "Xiaomi Aqara Gateway" + "description": "Als de IP- en MAC-adressen leeg worden gelaten, wordt auto-discovery gebruikt" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/no.json b/homeassistant/components/xiaomi_aqara/translations/no.json index c325886dd22..8cb351dc7e2 100644 --- a/homeassistant/components/xiaomi_aqara/translations/no.json +++ b/homeassistant/components/xiaomi_aqara/translations/no.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP adresse" }, - "description": "Velg Xiaomi Aqara Gateway som du \u00f8nsker \u00e5 koble til", - "title": "Velg Xiaomi Aqara Gateway som du \u00f8nsker \u00e5 koble til" + "description": "Velg Xiaomi Aqara Gateway som du \u00f8nsker \u00e5 koble til" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "Nettverksgrensesnittet som skal brukes", "mac": "MAC-adresse (valgfritt)" }, - "description": "Hvis IP- og MAC-adressene er tomme, brukes automatisk oppdagelse", - "title": "" + "description": "Hvis IP- og MAC-adressene er tomme, brukes automatisk oppdagelse" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/pl.json b/homeassistant/components/xiaomi_aqara/translations/pl.json index e680415320c..dc7391166f5 100644 --- a/homeassistant/components/xiaomi_aqara/translations/pl.json +++ b/homeassistant/components/xiaomi_aqara/translations/pl.json @@ -18,8 +18,7 @@ "data": { "select_ip": "Adres IP" }, - "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" + "description": "Wybierz bramk\u0119 Xiaomi Aqara, z kt\u00f3r\u0105 chcesz si\u0119 po\u0142\u0105czy\u0107" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "Interfejs sieciowy", "mac": "Adres MAC (opcjonalnie)" }, - "description": "Je\u015bli zostawisz Adres IP oraz MAC puste to bramka zostanie automatycznie wykryta.", - "title": "Bramka Xiaomi Aqara" + "description": "Je\u015bli zostawisz Adres IP oraz MAC puste to bramka zostanie automatycznie wykryta." } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/pt-BR.json b/homeassistant/components/xiaomi_aqara/translations/pt-BR.json index 62deb3ba97c..f83050acfc7 100644 --- a/homeassistant/components/xiaomi_aqara/translations/pt-BR.json +++ b/homeassistant/components/xiaomi_aqara/translations/pt-BR.json @@ -18,8 +18,7 @@ "data": { "select_ip": "Endere\u00e7o IP" }, - "description": "Selecione o Xiaomi Aqara Gateway que voc\u00ea deseja conectar", - "title": "Selecione o Xiaomi Aqara Gateway que voc\u00ea deseja conectar" + "description": "Selecione o Xiaomi Aqara Gateway que voc\u00ea deseja conectar" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "A interface de rede a ser usada", "mac": "Endere\u00e7o Mac (opcional)" }, - "description": "Se os endere\u00e7os IP e MAC forem deixados vazios, a descoberta autom\u00e1tica \u00e9 usada", - "title": "Gateway Xiaomi Aqara" + "description": "Se os endere\u00e7os IP e MAC forem deixados vazios, a descoberta autom\u00e1tica \u00e9 usada" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/ru.json b/homeassistant/components/xiaomi_aqara/translations/ru.json index 499837889df..3483bf2d3f8 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ru.json +++ b/homeassistant/components/xiaomi_aqara/translations/ru.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP-\u0430\u0434\u0440\u0435\u0441" }, - "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" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0448\u043b\u044e\u0437 Xiaomi Aqara." }, "settings": { "data": { @@ -35,8 +34,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": "\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" + "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." } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/tr.json b/homeassistant/components/xiaomi_aqara/translations/tr.json index 0c961a34a3c..0d8b78fc3a8 100644 --- a/homeassistant/components/xiaomi_aqara/translations/tr.json +++ b/homeassistant/components/xiaomi_aqara/translations/tr.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP Adresi" }, - "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" + "description": "Ba\u011flamak istedi\u011finiz Xiaomi Aqara A\u011f Ge\u00e7idini se\u00e7in" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "Kullan\u0131lacak a\u011f aray\u00fcz\u00fc", "mac": "Mac Adresi (iste\u011fe ba\u011fl\u0131)" }, - "description": "IP ve MAC adresleri bo\u015f b\u0131rak\u0131l\u0131rsa otomatik ke\u015fif kullan\u0131l\u0131r", - "title": "Xiaomi Aqara A\u011f Ge\u00e7idi" + "description": "IP ve MAC adresleri bo\u015f b\u0131rak\u0131l\u0131rsa otomatik ke\u015fif kullan\u0131l\u0131r" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/uk.json b/homeassistant/components/xiaomi_aqara/translations/uk.json index 1598e96b38e..7c598b29fc7 100644 --- a/homeassistant/components/xiaomi_aqara/translations/uk.json +++ b/homeassistant/components/xiaomi_aqara/translations/uk.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" }, - "description": "\u041f\u043e\u0447\u043d\u0456\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u044e\u0432\u0430\u043d\u043d\u044f \u0437\u043d\u043e\u0432\u0443, \u044f\u043a\u0449\u043e \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043e\u0434\u0430\u0442\u0438 \u0456\u043d\u0448\u0438\u0439 \u0448\u043b\u044e\u0437", - "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0448\u043b\u044e\u0437 Xiaomi Aqara" + "description": "\u041f\u043e\u0447\u043d\u0456\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u044e\u0432\u0430\u043d\u043d\u044f \u0437\u043d\u043e\u0432\u0443, \u044f\u043a\u0449\u043e \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043e\u0434\u0430\u0442\u0438 \u0456\u043d\u0448\u0438\u0439 \u0448\u043b\u044e\u0437" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "\u041c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0439 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441", "mac": "MAC-\u0430\u0434\u0440\u0435\u0441\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)" }, - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437\u0456 \u0448\u043b\u044e\u0437\u043e\u043c Xiaomi Aqara. \u0414\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u0448\u043b\u044e\u0437\u0443, \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u043b\u044f IP \u0456 MAC-\u0430\u0434\u0440\u0435\u0441\u0438 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c\u0438.", - "title": "\u0428\u043b\u044e\u0437 Xiaomi Aqara" + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437\u0456 \u0448\u043b\u044e\u0437\u043e\u043c Xiaomi Aqara. \u0414\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u0448\u043b\u044e\u0437\u0443, \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u043b\u044f IP \u0456 MAC-\u0430\u0434\u0440\u0435\u0441\u0438 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c\u0438." } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/zh-Hans.json b/homeassistant/components/xiaomi_aqara/translations/zh-Hans.json index a0d0159e71c..1532e2eb05f 100644 --- a/homeassistant/components/xiaomi_aqara/translations/zh-Hans.json +++ b/homeassistant/components/xiaomi_aqara/translations/zh-Hans.json @@ -18,8 +18,7 @@ "data": { "select_ip": "\u7f51\u5173 IP" }, - "description": "\u5982\u679c\u8981\u8fde\u63a5\u5176\u4ed6\u7f51\u5173\uff0c\u8bf7\u518d\u6b21\u8fd0\u884c\u914d\u7f6e\u7a0b\u5e8f", - "title": "\u9009\u62e9\u8981\u8fde\u63a5\u7684\u5c0f\u7c73 Aqara \u7f51\u5173" + "description": "\u5982\u679c\u8981\u8fde\u63a5\u5176\u4ed6\u7f51\u5173\uff0c\u8bf7\u518d\u6b21\u8fd0\u884c\u914d\u7f6e\u7a0b\u5e8f" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "\u8981\u4f7f\u7528\u7684\u7f51\u7edc\u63a5\u53e3", "mac": "MAC \u5730\u5740 (\u53ef\u9009)" }, - "description": "\u8fde\u63a5\u5230\u60a8\u7684\u5c0f\u7c73 Aqara \u7f51\u5173\uff0c\u5982\u679c IP \u548c MAC \u5730\u5740\u7559\u7a7a\uff0c\u5219\u4f7f\u7528\u81ea\u52a8\u53d1\u73b0", - "title": "\u5c0f\u7c73 Aqara \u7f51\u5173" + "description": "\u8fde\u63a5\u5230\u60a8\u7684\u5c0f\u7c73 Aqara \u7f51\u5173\uff0c\u5982\u679c IP \u548c MAC \u5730\u5740\u7559\u7a7a\uff0c\u5219\u4f7f\u7528\u81ea\u52a8\u53d1\u73b0" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json b/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json index 5ffe34d5d39..d70d060efa4 100644 --- a/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json @@ -18,8 +18,7 @@ "data": { "select_ip": "IP \u4f4d\u5740" }, - "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" + "description": "\u5982\u679c\u6240\u8981\u9023\u7dda\u7684\u5c0f\u7c73 Aqara \u7db2\u95dc" }, "settings": { "data": { @@ -35,8 +34,7 @@ "interface": "\u4f7f\u7528\u7684\u7db2\u8def\u4ecb\u9762", "mac": "Mac \u4f4d\u5740\uff08\u9078\u9805\uff09" }, - "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" + "description": "\u5047\u5982 IP \u6216 Mac \u4f4d\u5740\u70ba\u7a7a\u767d\u3001\u5c07\u9032\u884c\u81ea\u52d5\u641c\u7d22" } } } diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 68d3fe04653..e2dc36ff568 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -405,36 +405,35 @@ async def async_setup_gateway_entry(hass: HomeAssistant, entry: ConfigEntry) -> hw_version=gateway_info.hardware_version, ) - def update_data(): - """Fetch data from the subdevice.""" - data = {} - for sub_device in gateway.gateway_device.devices.values(): + def update_data_factory(sub_device): + """Create update function for a subdevice.""" + + async def async_update_data(): + """Fetch data from the subdevice.""" try: - sub_device.update() + await hass.async_add_executor_job(sub_device.update) except GatewayException as ex: _LOGGER.error("Got exception while fetching the state: %s", ex) - data[sub_device.sid] = {ATTR_AVAILABLE: False} - else: - data[sub_device.sid] = {ATTR_AVAILABLE: True} - return data + return {ATTR_AVAILABLE: False} + return {ATTR_AVAILABLE: True} - async def async_update_data(): - """Fetch data from the subdevice using async_add_executor_job.""" - return await hass.async_add_executor_job(update_data) + return async_update_data - # Create update coordinator - coordinator = DataUpdateCoordinator( - hass, - _LOGGER, - name=name, - update_method=async_update_data, - # Polling interval. Will only be polled if there are subscribers. - update_interval=UPDATE_INTERVAL, - ) + coordinator_dict = {} + for sub_device in gateway.gateway_device.devices.values(): + # Create update coordinator + coordinator_dict[sub_device.sid] = DataUpdateCoordinator( + hass, + _LOGGER, + name=name, + update_method=update_data_factory(sub_device), + # Polling interval. Will only be polled if there are subscribers. + update_interval=UPDATE_INTERVAL, + ) hass.data[DOMAIN][entry.entry_id] = { CONF_GATEWAY: gateway.gateway_device, - KEY_COORDINATOR: coordinator, + KEY_COORDINATOR: coordinator_dict, } for platform in GATEWAY_PLATFORMS: diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 1e525887c56..969093545f0 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -1041,7 +1041,7 @@ class XiaomiFanMiot(XiaomiGenericFan): self._preset_mode = self.coordinator.data.mode.name self._oscillating = self.coordinator.data.oscillate if self.coordinator.data.is_on: - self._percentage = self.coordinator.data.fan_speed + self._percentage = self.coordinator.data.speed else: self._percentage = 0 diff --git a/homeassistant/components/xiaomi_miio/gateway.py b/homeassistant/components/xiaomi_miio/gateway.py index ebfb37ab8fb..6d0984a9aeb 100644 --- a/homeassistant/components/xiaomi_miio/gateway.py +++ b/homeassistant/components/xiaomi_miio/gateway.py @@ -169,4 +169,4 @@ class XiaomiGatewayDevice(CoordinatorEntity, Entity): if self.coordinator.data is None: return False - return self.coordinator.data[self._sub_device.sid][ATTR_AVAILABLE] + return self.coordinator.data[ATTR_AVAILABLE] diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index acb726e7c05..e97c6e76503 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -132,9 +132,11 @@ async def async_setup_entry( ) # Gateway sub devices sub_devices = gateway.devices - coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] for sub_device in sub_devices.values(): if sub_device.device_type == "LightBulb": + coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR][ + sub_device.sid + ] entities.append( XiaomiGatewayBulb(coordinator, sub_device, config_entry) ) diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 0beac2c0041..df0c953d62a 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -681,8 +681,10 @@ async def async_setup_entry( ) # Gateway sub devices sub_devices = gateway.devices - coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] for sub_device in sub_devices.values(): + coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR][ + sub_device.sid + ] for sensor, description in SENSOR_TYPES.items(): if sensor not in sub_device.status: continue diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 968abceb57c..05d6543e93d 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -369,10 +369,12 @@ async def async_setup_other_entry(hass, config_entry, async_add_entities): gateway = hass.data[DOMAIN][config_entry.entry_id][CONF_GATEWAY] # Gateway sub devices sub_devices = gateway.devices - coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] for sub_device in sub_devices.values(): if sub_device.device_type != "Switch": continue + coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR][ + sub_device.sid + ] switch_variables = set(sub_device.status) & set(GATEWAY_SWITCH_VARS) if switch_variables: entities.extend( diff --git a/homeassistant/components/xiaomi_miio/translations/bg.json b/homeassistant/components/xiaomi_miio/translations/bg.json index a73df3c5609..2339d5bc830 100644 --- a/homeassistant/components/xiaomi_miio/translations/bg.json +++ b/homeassistant/components/xiaomi_miio/translations/bg.json @@ -5,8 +5,7 @@ "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\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", - "no_device_selected": "\u041d\u0435 \u0435 \u0438\u0437\u0431\u0440\u0430\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u043c\u043e\u043b\u044f, \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0435\u0434\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e." + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" }, "flow_title": "{name}", "step": { @@ -20,17 +19,6 @@ "model": "\u041c\u043e\u0434\u0435\u043b \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e" } }, - "device": { - "data": { - "host": "IP \u0430\u0434\u0440\u0435\u0441" - } - }, - "gateway": { - "data": { - "host": "IP \u0430\u0434\u0440\u0435\u0441", - "name": "\u0418\u043c\u0435 \u043d\u0430 \u0448\u043b\u044e\u0437\u0430" - } - }, "manual": { "data": { "host": "IP \u0430\u0434\u0440\u0435\u0441" diff --git a/homeassistant/components/xiaomi_miio/translations/ca.json b/homeassistant/components/xiaomi_miio/translations/ca.json index cb36fbbb2e0..614238a54b4 100644 --- a/homeassistant/components/xiaomi_miio/translations/ca.json +++ b/homeassistant/components/xiaomi_miio/translations/ca.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Credencials del n\u00favol incompletes, introdueix el nom d'usuari, la contrasenya i el pa\u00eds", "cloud_login_error": "No s'ha pogut iniciar sessi\u00f3 a Xiaomi Miio Cloud, comprova les credencials.", "cloud_no_devices": "No s'han trobat dispositius en aquest compte al n\u00favol de Xiaomi Miio.", - "no_device_selected": "No hi ha cap dispositiu seleccionat, selecciona'n un.", "unknown_device": "No es reconeix el model del dispositiu, no es pot configurar el dispositiu mitjan\u00e7ant el flux de configuraci\u00f3.", "wrong_token": "Error de verificaci\u00f3, token erroni" }, @@ -25,42 +24,19 @@ "cloud_username": "Nom d'usuari del n\u00favol", "manual": "Configuraci\u00f3 manual (no recomanada)" }, - "description": "Inicia sessi\u00f3 al n\u00favol Xiaomi Miio, consulta https://www.openhab.org/addons/bindings/miio/#country-servers per obtenir el servidor al n\u00favol.", - "title": "Connexi\u00f3 amb un dispositiu Xiaomi Miio o una passarel\u00b7la Xiaomi" + "description": "Inicia sessi\u00f3 al n\u00favol Xiaomi Miio, consulta https://www.openhab.org/addons/bindings/miio/#country-servers per obtenir el servidor al n\u00favol." }, "connect": { "data": { "model": "Model de dispositiu" - }, - "description": "Selecciona manualment el model de dispositiu entre els models compatibles.", - "title": "Connexi\u00f3 amb un dispositiu Xiaomi Miio o una passarel\u00b7la Xiaomi" - }, - "device": { - "data": { - "host": "Adre\u00e7a IP", - "model": "Model del dispositiu (opcional)", - "name": "Nom del dispositiu", - "token": "Token d'API" - }, - "description": "Necessitar\u00e0s el Token d'API de 32 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Tingues en compte que aquest Token d'API \u00e9s diferent a la clau utilitzada per la integraci\u00f3 Xiaomi Aqara.", - "title": "Connexi\u00f3 amb un dispositiu Xiaomi Miio o una passarel\u00b7la de Xiaomi" - }, - "gateway": { - "data": { - "host": "Adre\u00e7a IP", - "name": "Nom de la passarel\u00b7la", - "token": "Token d'API" - }, - "description": "Necessitar\u00e0s el Token d'API de 32 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. Tingues en compte que aquest Token d'API \u00e9s diferent a la clau utilitzada per la integraci\u00f3 Xiaomi Aqara.", - "title": "Connexi\u00f3 amb la passarel\u00b7la de Xiaomi" + } }, "manual": { "data": { "host": "Adre\u00e7a IP", "token": "Token d'API" }, - "description": "Necessitar\u00e0s el Token d'API de 32 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/xiaomi_miio/#retrieving-the-access-token. Tingues en compte que aquest Token d'API \u00e9s diferent a la clau utilitzada per la integraci\u00f3 Xiaomi Aqara.", - "title": "Connexi\u00f3 amb un dispositiu Xiaomi Miio o una passarel\u00b7la Xiaomi" + "description": "Necessitar\u00e0s el Token d'API de 32 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/xiaomi_miio/#retrieving-the-access-token. Tingues en compte que aquest Token d'API \u00e9s diferent a la clau utilitzada per la integraci\u00f3 Xiaomi Aqara." }, "reauth_confirm": { "description": "La integraci\u00f3 Xiaomi Miio ha de tornar a autenticar-se amb el teu compte per poder actualitzar els tokens o afegir credencials pel n\u00favol.", @@ -70,15 +46,7 @@ "data": { "select_device": "Dispositiu Miio" }, - "description": "Selecciona el dispositiu Xiaomi Miio a configurar.", - "title": "Connexi\u00f3 amb un dispositiu Xiaomi Miio o una passarel\u00b7la Xiaomi" - }, - "user": { - "data": { - "gateway": "Connexi\u00f3 amb la passarel\u00b7la de Xiaomi" - }, - "description": "Selecciona a quin dispositiu vols connectar-te.", - "title": "Xiaomi Miio" + "description": "Selecciona el dispositiu Xiaomi Miio a configurar." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Utilitza el n\u00favol per obtenir subdispositius connectats" - }, - "description": "Especifica par\u00e0metres opcionals", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/cs.json b/homeassistant/components/xiaomi_miio/translations/cs.json index 69b6cd221ee..858546d915f 100644 --- a/homeassistant/components/xiaomi_miio/translations/cs.json +++ b/homeassistant/components/xiaomi_miio/translations/cs.json @@ -9,7 +9,6 @@ }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "no_device_selected": "Nebylo vybr\u00e1no \u017e\u00e1dn\u00e9 za\u0159\u00edzen\u00ed, vyberte jedno za\u0159\u00edzen\u00ed.", "unknown_device": "Model za\u0159\u00edzen\u00ed nen\u00ed zn\u00e1m, nastaven\u00ed za\u0159\u00edzen\u00ed nen\u00ed mo\u017en\u00e9 dokon\u010dit.", "wrong_token": "Chyba kontroln\u00edho sou\u010dtu, \u0161patn\u00fd token" }, @@ -18,42 +17,19 @@ "cloud": { "data": { "manual": "Nastavit ru\u010dn\u011b (nedoporu\u010deno)" - }, - "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed Xiaomi Miio nebo k br\u00e1n\u011b Xiaomi" + } }, "connect": { "data": { "model": "Model za\u0159\u00edzen\u00ed" - }, - "description": "Ru\u010dn\u011b vyberte model za\u0159\u00edzen\u00ed ze seznamu podporovan\u00fdch za\u0159\u00edzen\u00ed.", - "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed Xiaomi Miio nebo k br\u00e1n\u011b Xiaomi" - }, - "device": { - "data": { - "host": "IP adresa", - "model": "Model za\u0159\u00edzen\u00ed (voliteln\u00e9)", - "name": "Jm\u00e9no za\u0159\u00edzen\u00ed", - "token": "API token" - }, - "description": "Budete pot\u0159ebovat 32 znakov\u00fd API token, pokyny naleznete na https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Upozor\u0148ujeme, \u017ee tento token se li\u0161\u00ed od kl\u00ed\u010de pou\u017e\u00edvan\u00e9ho v integraci Xiaomi Aqara.", - "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed Xiaomi Miio nebo k br\u00e1n\u011b Xiaomi" - }, - "gateway": { - "data": { - "host": "IP adresa", - "name": "N\u00e1zev br\u00e1ny", - "token": "API token" - }, - "description": "Budete pot\u0159ebovat 32 znakov\u00fd API token, pokyny naleznete na https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. Upozor\u0148ujeme, \u017ee tento token se li\u0161\u00ed od kl\u00ed\u010de pou\u017e\u00edvan\u00e9ho v integraci Xiaomi Aqara.", - "title": "P\u0159ipojen\u00ed k br\u00e1n\u011b Xiaomi" + } }, "manual": { "data": { "host": "IP adresa", "token": "API token" }, - "description": "Budete pot\u0159ebovat 32 znakov\u00fd API token, pokyny naleznete na https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Upozor\u0148ujeme, \u017ee tento token se li\u0161\u00ed od kl\u00ed\u010de pou\u017e\u00edvan\u00e9ho v integraci Xiaomi Aqara.", - "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed Xiaomi Miio nebo k br\u00e1n\u011b Xiaomi" + "description": "Budete pot\u0159ebovat 32 znakov\u00fd API token, pokyny naleznete na https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Upozor\u0148ujeme, \u017ee tento token se li\u0161\u00ed od kl\u00ed\u010de pou\u017e\u00edvan\u00e9ho v integraci Xiaomi Aqara." }, "reauth_confirm": { "title": "Znovu ov\u011b\u0159it integraci" @@ -62,15 +38,7 @@ "data": { "select_device": "Za\u0159\u00edzen\u00ed Miio" }, - "description": "Vyberte Xiaomi Miio za\u0159\u00edzen\u00ed, kter\u00e9 chcete nastavit.", - "title": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed Xiaomi Miio nebo k br\u00e1n\u011b Xiaomi" - }, - "user": { - "data": { - "gateway": "P\u0159ipojen\u00ed k br\u00e1n\u011b Xiaomi" - }, - "description": "Vyberte, ke kter\u00e9mu za\u0159\u00edzen\u00ed se chcete p\u0159ipojit.", - "title": "Xiaomi Miio" + "description": "Vyberte Xiaomi Miio za\u0159\u00edzen\u00ed, kter\u00e9 chcete nastavit." } } }, @@ -79,9 +47,7 @@ "init": { "data": { "cloud_subdevices": "Pou\u017e\u00edt cloud pro z\u00edsk\u00e1n\u00ed p\u0159ipojen\u00fdch podru\u017en\u00fdch za\u0159\u00edzen\u00ed" - }, - "description": "Zadejte voliteln\u00e9 nastaven\u00ed", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json index 291323f31bd..70a630f90f3 100644 --- a/homeassistant/components/xiaomi_miio/translations/de.json +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Cloud-Anmeldeinformationen unvollst\u00e4ndig, bitte Benutzernamen, Passwort und Land eingeben", "cloud_login_error": "Die Anmeldung bei Xiaomi Miio Cloud ist fehlgeschlagen, \u00fcberpr\u00fcfe die Anmeldedaten.", "cloud_no_devices": "Keine Ger\u00e4te in diesem Xiaomi Miio Cloud-Konto gefunden.", - "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hle ein Ger\u00e4t aus.", "unknown_device": "Das Ger\u00e4temodell ist nicht bekannt und das Ger\u00e4t kann nicht mithilfe des Assistenten eingerichtet werden.", "wrong_token": "Pr\u00fcfsummenfehler, falscher Token" }, @@ -25,42 +24,19 @@ "cloud_username": "Cloud-Benutzername", "manual": "Manuell konfigurieren (nicht empfohlen)" }, - "description": "Melde dich bei der Xiaomi Miio Cloud an, siehe https://www.openhab.org/addons/bindings/miio/#country-servers f\u00fcr den zu verwendenden Cloud-Server.", - "title": "Verbinden mit einem Xiaomi Miio Ger\u00e4t oder Xiaomi Gateway" + "description": "Melde dich bei der Xiaomi Miio Cloud an, siehe https://www.openhab.org/addons/bindings/miio/#country-servers f\u00fcr den zu verwendenden Cloud-Server." }, "connect": { "data": { "model": "Ger\u00e4temodell" - }, - "description": "W\u00e4hle das Ger\u00e4temodell manuell aus den unterst\u00fctzten Modellen aus.", - "title": "Verbinden mit einem Xiaomi Miio Ger\u00e4t oder Xiaomi Gateway" - }, - "device": { - "data": { - "host": "IP-Adresse", - "model": "Ger\u00e4temodell (optional)", - "name": "Name des Ger\u00e4ts", - "token": "API-Token" - }, - "description": "Du ben\u00f6tigst den 32 Zeichen langen API-Token, siehe https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token f\u00fcr eine Anleitung. Dieser unterscheidet sich vom API-Token, den die Xiaomi Aqara-Integration nutzt.", - "title": "Herstellen einer Verbindung mit einem Xiaomi Miio-Ger\u00e4t oder Xiaomi Gateway" - }, - "gateway": { - "data": { - "host": "IP-Adresse", - "name": "Name des Gateways", - "token": "API-Token" - }, - "description": "Du ben\u00f6tigst den 32 Zeichen langen API-Token. Anweisungen findest du unter https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. Bitte beachte, dass sich dieser API-Token von dem Schl\u00fcssel unterscheidet, der von der Xiaomi Aqara Integration verwendet wird.", - "title": "Stelle eine Verbindung zu einem Xiaomi Gateway her" + } }, "manual": { "data": { "host": "IP-Adresse", "token": "API-Token" }, - "description": "Du ben\u00f6tigst den 32 Zeichen langen API-Token, siehe https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token f\u00fcr Anweisungen. Bitte beachte, dass sich dieser API-Token von dem Schl\u00fcssel unterscheidet, der von der Xiaomi Aqara-Integration verwendet wird.", - "title": "Verbinden mit einem Xiaomi Miio Ger\u00e4t oder Xiaomi Gateway" + "description": "Du ben\u00f6tigst den 32 Zeichen langen API-Token, siehe https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token f\u00fcr Anweisungen. Bitte beachte, dass sich dieser API-Token von dem Schl\u00fcssel unterscheidet, der von der Xiaomi Aqara-Integration verwendet wird." }, "reauth_confirm": { "description": "Die Xiaomi Miio-Integration muss dein Konto neu authentifizieren, um die Token zu aktualisieren oder fehlende Cloud-Anmeldedaten hinzuzuf\u00fcgen.", @@ -70,15 +46,7 @@ "data": { "select_device": "Miio-Ger\u00e4t" }, - "description": "W\u00e4hle das einzurichtende Xiaomi Miio-Ger\u00e4t aus.", - "title": "Verbinden mit einem Xiaomi Miio Ger\u00e4t oder Xiaomi Gateway" - }, - "user": { - "data": { - "gateway": "Stelle eine Verbindung zu einem Xiaomi Gateway her" - }, - "description": "W\u00e4hle aus, mit welchem Ger\u00e4t du eine Verbindung herstellen m\u00f6chtest.", - "title": "Xiaomi Miio" + "description": "W\u00e4hle das einzurichtende Xiaomi Miio-Ger\u00e4t aus." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Cloud verwenden, um verbundene Subdevices zu erhalten" - }, - "description": "Optionale Einstellungen angeben", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/el.json b/homeassistant/components/xiaomi_miio/translations/el.json index abd099a404b..48f55af9246 100644 --- a/homeassistant/components/xiaomi_miio/translations/el.json +++ b/homeassistant/components/xiaomi_miio/translations/el.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 Cloud \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bb\u03bb\u03b9\u03c0\u03ae, \u03c3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7, \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03c7\u03ce\u03c1\u03b1", "cloud_login_error": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Xiaomi Miio Cloud, \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1.", "cloud_no_devices": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc Xiaomi Miio cloud.", - "no_device_selected": "\u0394\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03b5\u03af \u03ba\u03b1\u03bc\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae.", "unknown_device": "\u03a4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03bd\u03c9\u03c3\u03c4\u03cc, \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03bc\u03b5 \u03c4\u03b7 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c1\u03bf\u03ae\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd.", "wrong_token": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b1\u03b8\u03c1\u03bf\u03af\u03c3\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5, \u03bb\u03ac\u03b8\u03bf\u03c2 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc" }, @@ -25,42 +24,19 @@ "cloud_username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 Cloud", "manual": "\u039c\u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 (\u03b4\u03b5\u03bd \u03c3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9)" }, - "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf cloud Xiaomi Miio, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.openhab.org/addons/bindings/miio/#country-servers \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae cloud \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5.", - "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf cloud Xiaomi Miio, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.openhab.org/addons/bindings/miio/#country-servers \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae cloud \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5." }, "connect": { "data": { "model": "\u039c\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b1 \u03c4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b1 \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03b1.", - "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" - }, - "device": { - "data": { - "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", - "model": "\u039c\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", - "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API" - }, - "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03bd 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", - "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" - }, - "gateway": { - "data": { - "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c0\u03cd\u03bb\u03b7\u03c2", - "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API" - }, - "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03b5\u03c2 32 \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API , \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", - "title": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 Xiaomi" + } }, "manual": { "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API" }, - "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara.", - "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" + "description": "\u0398\u03b1 \u03c7\u03c1\u03b5\u03b9\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf 32 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03ae\u03c1\u03c9\u03bd \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u03b3\u03b9\u03b1 \u03bf\u03b4\u03b7\u03b3\u03af\u03b5\u03c2. \u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Xiaomi Aqara." }, "reauth_confirm": { "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 xiaomi Miio \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03b5\u03b9 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03ac \u03ae \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03b9 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 cloud \u03c0\u03bf\u03c5 \u03bb\u03b5\u03af\u03c0\u03bf\u03c5\u03bd.", @@ -70,15 +46,7 @@ "data": { "select_device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Miio" }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03b3\u03b9\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7.", - "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03ae \u03c0\u03cd\u03bb\u03b7 Xiaomi" - }, - "user": { - "data": { - "gateway": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 Xiaomi" - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03b5 \u03c0\u03bf\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5.", - "title": "Xiaomi Miio" + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Xiaomi Miio \u03b3\u03b9\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf cloud \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c5\u03c0\u03bf\u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2" - }, - "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03ce\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index 8bd35f3e7d9..c37be0a7f74 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Cloud credentials incomplete, please fill in username, password and country", "cloud_login_error": "Could not login to Xiaomi Miio Cloud, check the credentials.", "cloud_no_devices": "No devices found in this Xiaomi Miio cloud account.", - "no_device_selected": "No device selected, please select one device.", "unknown_device": "The device model is not known, not able to setup the device using config flow.", "wrong_token": "Checksum error, wrong token" }, @@ -25,42 +24,19 @@ "cloud_username": "Cloud username", "manual": "Configure manually (not recommended)" }, - "description": "Log in to the Xiaomi Miio cloud, see https://www.openhab.org/addons/bindings/miio/#country-servers for the cloud server to use.", - "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" + "description": "Log in to the Xiaomi Miio cloud, see https://www.openhab.org/addons/bindings/miio/#country-servers for the cloud server to use." }, "connect": { "data": { "model": "Device model" - }, - "description": "Manually select the device model from the supported models.", - "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" - }, - "device": { - "data": { - "host": "IP Address", - "model": "Device model (Optional)", - "name": "Name of the device", - "token": "API Token" - }, - "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", - "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" - }, - "gateway": { - "data": { - "host": "IP Address", - "name": "Name of the Gateway", - "token": "API Token" - }, - "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", - "title": "Connect to a Xiaomi Gateway" + } }, "manual": { "data": { "host": "IP Address", "token": "API Token" }, - "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", - "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" + "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration." }, "reauth_confirm": { "description": "The Xiaomi Miio integration needs to re-authenticate your account in order to update the tokens or add missing cloud credentials.", @@ -70,15 +46,7 @@ "data": { "select_device": "Miio device" }, - "description": "Select the Xiaomi Miio device to setup.", - "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" - }, - "user": { - "data": { - "gateway": "Connect to a Xiaomi Gateway" - }, - "description": "Select to which device you want to connect.", - "title": "Xiaomi Miio" + "description": "Select the Xiaomi Miio device to setup." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Use cloud to get connected subdevices" - }, - "description": "Specify optional settings", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/es-419.json b/homeassistant/components/xiaomi_miio/translations/es-419.json deleted file mode 100644 index b3bbc25ebba..00000000000 --- a/homeassistant/components/xiaomi_miio/translations/es-419.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "config": { - "error": { - "no_device_selected": "Ning\u00fan dispositivo seleccionado, seleccione un dispositivo." - }, - "step": { - "gateway": { - "data": { - "host": "Direcci\u00f3n IP", - "name": "Nombre de la puerta de enlace", - "token": "Token API" - }, - "description": "Necesitar\u00e1 el token API, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obtener instrucciones.", - "title": "Conectarse a una puerta de enlace Xiaomi" - }, - "user": { - "data": { - "gateway": "Conectarse a una puerta de enlace Xiaomi" - }, - "description": "Seleccione a qu\u00e9 dispositivo desea conectarse.", - "title": "Xiaomi Miio" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/es.json b/homeassistant/components/xiaomi_miio/translations/es.json index ae59404a793..c1a3eae784b 100644 --- a/homeassistant/components/xiaomi_miio/translations/es.json +++ b/homeassistant/components/xiaomi_miio/translations/es.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El dispositivo ya est\u00e1 configurado", - "already_in_progress": "El flujo de configuraci\u00f3n para este dispositivo Xiaomi Miio ya est\u00e1 en marcha.", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "incomplete_info": "Informaci\u00f3n incompleta para configurar el dispositivo, no se ha suministrado ning\u00fan host o token.", "not_xiaomi_miio": "El dispositivo no es (todav\u00eda) compatible con Xiaomi Miio.", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" @@ -10,13 +10,12 @@ "error": { "cannot_connect": "No se pudo conectar", "cloud_credentials_incomplete": "Las credenciales de la nube est\u00e1n incompletas, por favor, rellene el nombre de usuario, la contrase\u00f1a y el pa\u00eds", - "cloud_login_error": "No se ha podido iniciar sesi\u00f3n en Xioami Miio Cloud, comprueba las credenciales.", + "cloud_login_error": "No se ha podido iniciar sesi\u00f3n en Xiaomi Miio Cloud, comprueba las credenciales.", "cloud_no_devices": "No se han encontrado dispositivos en esta cuenta de Xiaomi Miio.", - "no_device_selected": "No se ha seleccionado ning\u00fan dispositivo, por favor, seleccione un dispositivo.", "unknown_device": "No se conoce el modelo del dispositivo, no se puede configurar el dispositivo mediante el flujo de configuraci\u00f3n.", "wrong_token": "Error de suma de comprobaci\u00f3n, token err\u00f3neo" }, - "flow_title": "Xiaomi Miio: {name}", + "flow_title": "{name}", "step": { "cloud": { "data": { @@ -25,42 +24,19 @@ "cloud_username": "Nombre de usuario de la nube", "manual": "Configurar manualmente (no recomendado)" }, - "description": "Inicie sesi\u00f3n en la nube de Xiaomi Miio, consulte https://www.openhab.org/addons/bindings/miio/#country-servers para conocer el servidor de la nube que debe utilizar.", - "title": "Con\u00e9ctese a un dispositivo Xiaomi Miio o una puerta de enlace Xiaomi" + "description": "Inicie sesi\u00f3n en la nube de Xiaomi Miio, consulte https://www.openhab.org/addons/bindings/miio/#country-servers para conocer el servidor de la nube que debe utilizar." }, "connect": { "data": { "model": "Modelo del dispositivo" - }, - "description": "Seleccione manualmente el modelo de dispositivo entre los modelos admitidos.", - "title": "Con\u00e9ctese a un dispositivo Xiaomi Miio o una puerta de enlace Xiaomi" - }, - "device": { - "data": { - "host": "Direcci\u00f3n IP", - "model": "Modelo de dispositivo (opcional)", - "name": "Nombre del dispositivo", - "token": "Token API" - }, - "description": "Necesitar\u00e1 la clave de 32 caracteres Token API, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obtener instrucciones. Tenga en cuenta que esta Token API es diferente de la clave utilizada por la integraci\u00f3n de Xiaomi Aqara.", - "title": "Con\u00e9ctese a un dispositivo Xiaomi Miio o Xiaomi Gateway" - }, - "gateway": { - "data": { - "host": "Direcci\u00f3n IP", - "name": "Nombre del Gateway", - "token": "Token API" - }, - "description": "Necesitar\u00e1s el token de la API de 32 caracteres, revisa https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para m\u00e1s instrucciones. Por favor, ten en cuenta que este token es diferente de la clave utilizada por la integraci\u00f3n de Xiaomi Aqara.", - "title": "Conectar con un Xiaomi Gateway" + } }, "manual": { "data": { "host": "Direcci\u00f3n IP", "token": "Token API" }, - "description": "Necesitar\u00e1s la clave de 32 caracteres Token API, consulta https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obtener instrucciones. Ten en cuenta que esta Token API es diferente de la clave utilizada por la integraci\u00f3n de Xiaomi Aqara.", - "title": "Con\u00e9ctate a un dispositivo Xiaomi Miio o una puerta de enlace Xiaomi" + "description": "Necesitar\u00e1s la clave de 32 caracteres Token API, consulta https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obtener instrucciones. Ten en cuenta que esta Token API es diferente de la clave utilizada por la integraci\u00f3n de Xiaomi Aqara." }, "reauth_confirm": { "description": "La integraci\u00f3n de Xiaomi Miio necesita volver a autenticar tu cuenta para actualizar los tokens o a\u00f1adir las credenciales de la nube que faltan.", @@ -70,15 +46,7 @@ "data": { "select_device": "Dispositivo Miio" }, - "description": "Selecciona el dispositivo Xiaomi Miio para configurarlo.", - "title": "Con\u00e9ctese a un dispositivo Xiaomi Miio o una puerta de enlace Xiaomi" - }, - "user": { - "data": { - "gateway": "Conectar con un Xiaomi Gateway" - }, - "description": "Selecciona a qu\u00e9 dispositivo quieres conectar.", - "title": "Xiaomi Miio" + "description": "Selecciona el dispositivo Xiaomi Miio para configurarlo." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Utiliza la nube para conectar subdispositivos" - }, - "description": "Especifica los ajustes opcionales", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/et.json b/homeassistant/components/xiaomi_miio/translations/et.json index 6fd3c4257ef..fef6a73622a 100644 --- a/homeassistant/components/xiaomi_miio/translations/et.json +++ b/homeassistant/components/xiaomi_miio/translations/et.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Pilve mandaat on poolik, palun t\u00e4ida kasutajanimi, salas\u00f5na ja riik", "cloud_login_error": "Xiaomi Miio Cloudi ei saanud sisse logida, kontrolli mandaati.", "cloud_no_devices": "Xiaomi Miio pilvekontolt ei leitud \u00fchtegi seadet.", - "no_device_selected": "Seadmeid pole valitud, vali \u00fcks seade.", "unknown_device": "Seadme mudel pole teada, seadet ei saa seadistamisvoo abil seadistada.", "wrong_token": "Kontrollsumma t\u00f5rge, vigane r\u00e4si" }, @@ -25,42 +24,19 @@ "cloud_username": "Pilve kasutajatunnus", "manual": "Seadista k\u00e4sitsi (pole soovitatav)" }, - "description": "Logi sisse Xiaomi Miio pilve, vaata https://www.openhab.org/addons/bindings/miio/#country-servers pilveserveri kasutamiseks.", - "title": "\u00dchenda Xiaomi Miio seade v\u00f5i Xiaomi Gateway" + "description": "Logi sisse Xiaomi Miio pilve, vaata https://www.openhab.org/addons/bindings/miio/#country-servers pilveserveri kasutamiseks." }, "connect": { "data": { "model": "Seadme mudel" - }, - "description": "Vali seadme mudel k\u00e4sitsi toetatud mudelite hulgast.", - "title": "\u00dchenda Xiaomi Miio seade v\u00f5i Xiaomi Gateway" - }, - "device": { - "data": { - "host": "IP-aadress", - "model": "Seadme mudel (valikuline)", - "name": "Seadme nimi", - "token": "API v\u00f5ti" - }, - "description": "Vaja on 32 t\u00e4hem\u00e4rgilist v\u00f5tit API v\u00f5ti , juhiste saamiseks vaata https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. Pane t\u00e4hele, et see v\u00f5ti API v\u00f5ti erineb Xiaomi Aqara sidumises kasutatavast v\u00f5tmest.", - "title": "\u00dchenda Xiaomi Miio seade v\u00f5i Xiaomi Gateway" - }, - "gateway": { - "data": { - "host": "IP aadress", - "name": "L\u00fc\u00fcsi nimi", - "token": "API string" - }, - "description": "On vaja 32-kohalist API-tokenti, juhiste saamiseks vaata lehte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. Pane t\u00e4hele, et see token erineb Xiaomi Aqara sidumisel kasutatavast v\u00f5tmest.", - "title": "Loo \u00fchendus Xiaomi l\u00fc\u00fcsiga" + } }, "manual": { "data": { "host": "IP aadress", "token": "API v\u00f5ti" }, - "description": "On vajalik 32 t\u00e4hem\u00e4rki API v\u00f5ti, vt. https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token juhiseid. Pane t\u00e4hele, et see API v\u00f5ti erineb Xiaomi Aqara sidumises kasutatavast v\u00f5tmest.", - "title": "\u00dchenda Xiaomi Miio seade v\u00f5i Xiaomi Gateway" + "description": "On vajalik 32 t\u00e4hem\u00e4rki API v\u00f5ti, vt. https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token juhiseid. Pane t\u00e4hele, et see API v\u00f5ti erineb Xiaomi Aqara sidumises kasutatavast v\u00f5tmest." }, "reauth_confirm": { "description": "Xiaomi Miio sidumine peab konto uuesti tuvastama, et v\u00e4rskendada p\u00e4\u00e4sulube v\u00f5i lisada puuduv pilvemandaat.", @@ -70,15 +46,7 @@ "data": { "select_device": "Miio seade" }, - "description": "Vali seadistamiseks Xiaomi Miio seade.", - "title": "\u00dchenda Xiaomi Miio seade v\u00f5i Xiaomi Gateway" - }, - "user": { - "data": { - "gateway": "Loo \u00fchendus Xiaomi l\u00fc\u00fcsiga" - }, - "description": "Vali seade millega soovid \u00fchenduse luua.", - "title": "Xiaomi Miio" + "description": "Vali seadistamiseks Xiaomi Miio seade." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "\u00dchendatud alamseadmete hankimiseks kasuta pilve" - }, - "description": "Valikuliste s\u00e4tete m\u00e4\u00e4ramine", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/fi.json b/homeassistant/components/xiaomi_miio/translations/fi.json deleted file mode 100644 index ab360a82a8f..00000000000 --- a/homeassistant/components/xiaomi_miio/translations/fi.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "config": { - "error": { - "no_device_selected": "Ei valittuja laitteita. Ole hyv\u00e4 ja valitse yksi." - }, - "step": { - "gateway": { - "data": { - "host": "IP-osoite", - "name": "Yhdysk\u00e4yt\u00e4v\u00e4n nimi", - "token": "API-tunnus" - }, - "title": "Yhdist\u00e4 Xiaomi Gatewayhin" - }, - "user": { - "data": { - "gateway": "Yhdist\u00e4 Xiaomi Gatewayhin" - }, - "description": "Valitse laite, johon haluat muodostaa yhteyden.", - "title": "Xiaomi Miio" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/fr.json b/homeassistant/components/xiaomi_miio/translations/fr.json index 2a9d8d65c77..91c6b745816 100644 --- a/homeassistant/components/xiaomi_miio/translations/fr.json +++ b/homeassistant/components/xiaomi_miio/translations/fr.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Identifiants cloud incomplets, veuillez renseigner le nom d'utilisateur, le mot de passe et le pays", "cloud_login_error": "Impossible de se connecter \u00e0 Xioami Miio Cloud, v\u00e9rifiez les informations d'identification.", "cloud_no_devices": "Aucun appareil trouv\u00e9 dans ce compte cloud Xiaomi Miio.", - "no_device_selected": "Aucun appareil s\u00e9lectionn\u00e9, veuillez s\u00e9lectionner un appareil.", "unknown_device": "Le mod\u00e8le d'appareil n'est pas connu, impossible de configurer l'appareil \u00e0 l'aide du flux de configuration.", "wrong_token": "Erreur de somme de contr\u00f4le, jeton incorrect" }, @@ -25,42 +24,19 @@ "cloud_username": "Nom d'utilisateur cloud", "manual": "Configurer manuellement (non recommand\u00e9)" }, - "description": "Connectez-vous au cloud Xiaomi Miio, voir https://www.openhab.org/addons/bindings/miio/#country-servers pour le serveur cloud \u00e0 utiliser.", - "title": "Se connecter \u00e0 un appareil Xiaomi Miio ou \u00e0 une passerelle Xiaomi" + "description": "Connectez-vous au cloud Xiaomi Miio, voir https://www.openhab.org/addons/bindings/miio/#country-servers pour le serveur cloud \u00e0 utiliser." }, "connect": { "data": { "model": "Mod\u00e8le d'appareil" - }, - "description": "S\u00e9lectionner manuellement le mod\u00e8le d'appareil parmi les mod\u00e8les pris en charge.", - "title": "Se connecter \u00e0 un appareil Xiaomi Miio ou \u00e0 une passerelle Xiaomi" - }, - "device": { - "data": { - "host": "Adresse IP", - "model": "Mod\u00e8le d'appareil (facultatif)", - "name": "Nom de l'appareil", - "token": "Jeton d'API" - }, - "description": "Vous aurez besoin des 32 caract\u00e8res Jeton d'API , voir https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token pour les instructions. Veuillez noter que cette Jeton d'API est diff\u00e9rente de la cl\u00e9 utilis\u00e9e par l'int\u00e9gration Xiaomi Aqara.", - "title": "Connectez-vous \u00e0 un appareil Xiaomi Miio ou \u00e0 une passerelle Xiaomi" - }, - "gateway": { - "data": { - "host": "Adresse IP", - "name": "Nom de la passerelle", - "token": "Jeton d'API" - }, - "description": "Vous aurez besoin du jeton API, voir https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token pour les instructions.", - "title": "Se connecter \u00e0 la passerelle Xiaomi" + } }, "manual": { "data": { "host": "Adresse IP", "token": "Jeton d'API" }, - "description": "Vous aurez besoin du Jeton d'API de 32 caract\u00e8res, voir https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token pour les instructions. Veuillez noter que ce Jeton d'API est diff\u00e9rent de la cl\u00e9 utilis\u00e9e par l'int\u00e9gration Xiaomi Aqara.", - "title": "Se connecter \u00e0 un appareil Xiaomi Miio ou \u00e0 une passerelle Xiaomi" + "description": "Vous aurez besoin du Jeton d'API de 32 caract\u00e8res, voir https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token pour les instructions. Veuillez noter que ce Jeton d'API est diff\u00e9rent de la cl\u00e9 utilis\u00e9e par l'int\u00e9gration Xiaomi Aqara." }, "reauth_confirm": { "description": "L'int\u00e9gration de Xiaomi Miio doit r\u00e9-authentifier votre compte afin de mettre \u00e0 jour les jetons ou d'ajouter les informations d'identification cloud manquantes.", @@ -70,15 +46,7 @@ "data": { "select_device": "Appareil Miio" }, - "description": "S\u00e9lectionner l'appareil Xiaomi Miio \u00e0 configurer.", - "title": "Se connecter \u00e0 un appareil Xiaomi Miio ou \u00e0 une passerelle Xiaomi" - }, - "user": { - "data": { - "gateway": "Se connecter \u00e0 la passerelle Xiaomi" - }, - "description": "S\u00e9lectionnez \u00e0 quel appareil vous souhaitez vous connecter.", - "title": "Xiaomi Miio" + "description": "S\u00e9lectionner l'appareil Xiaomi Miio \u00e0 configurer." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Utiliser le cloud pour connecter des sous-appareils" - }, - "description": "Sp\u00e9cifiez les param\u00e8tres optionnels", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/he.json b/homeassistant/components/xiaomi_miio/translations/he.json index db0cee16884..1a21bd9840a 100644 --- a/homeassistant/components/xiaomi_miio/translations/he.json +++ b/homeassistant/components/xiaomi_miio/translations/he.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "\u05d0\u05d9\u05e9\u05d5\u05e8\u05d9 \u05e2\u05e0\u05df \u05d0\u05d9\u05e0\u05dd \u05de\u05dc\u05d0\u05d9\u05dd, \u05e0\u05d0 \u05dc\u05de\u05dc\u05d0 \u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9, \u05e1\u05d9\u05e1\u05de\u05d4 \u05d5\u05de\u05d3\u05d9\u05e0\u05d4", "cloud_login_error": "\u05dc\u05d0 \u05d4\u05d9\u05ea\u05d4 \u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05e2\u05e0\u05df \u05e9\u05d9\u05d5\u05d0\u05de\u05d9 \u05de\u05d9\u05d5, \u05e0\u05d0 \u05dc\u05d1\u05d3\u05d5\u05e7 \u05d0\u05ea \u05d4\u05d0\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd.", "cloud_no_devices": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05d4\u05ea\u05e7\u05e0\u05d9\u05dd \u05d1\u05d7\u05e9\u05d1\u05d5\u05df \u05d4\u05e2\u05e0\u05df \u05d4\u05d6\u05d4 \u05e9\u05dc \u05e9\u05d9\u05d5\u05d0\u05de\u05d9 \u05de\u05d9\u05d5.", - "no_device_selected": "\u05dc\u05d0 \u05e0\u05d1\u05d7\u05e8 \u05d4\u05ea\u05e7\u05df, \u05e0\u05d0 \u05d1\u05d7\u05e8 \u05d4\u05ea\u05e7\u05df \u05d0\u05d7\u05d3.", "unknown_device": "\u05d3\u05d2\u05dd \u05d4\u05d4\u05ea\u05e7\u05df \u05d0\u05d9\u05e0\u05d5 \u05d9\u05d3\u05d5\u05e2, \u05d0\u05d9\u05df \u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea \u05d6\u05e8\u05d9\u05de\u05ea \u05ea\u05e6\u05d5\u05e8\u05d4.", "wrong_token": "\u05e9\u05d2\u05d9\u05d0\u05ea \u05d1\u05d3\u05d9\u05e7\u05ea \u05e1\u05d9\u05db\u05d5\u05dd, \u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05e9\u05d2\u05d5\u05d9" }, @@ -25,42 +24,19 @@ "cloud_username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9 \u05e2\u05e0\u05df", "manual": "\u05d4\u05d2\u05d3\u05e8\u05ea \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d9\u05d3\u05e0\u05d9\u05ea (\u05dc\u05d0 \u05de\u05d5\u05de\u05dc\u05e5)" }, - "description": "\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5 \u05dc\u05e2\u05e0\u05df \u05e9\u05d9\u05d5\u05d0\u05de\u05d9 \u05de\u05d9\u05d5, \u05e8\u05d0\u05d5 https://www.openhab.org/addons/bindings/miio/#country-servers \u05dc\u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05e9\u05e8\u05ea \u05d4\u05e2\u05e0\u05df.", - "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05de\u05db\u05e9\u05d9\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9 \u05de\u05d9\u05d5 \u05d0\u05d5 \u05dc\u05e9\u05e2\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9" + "description": "\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5 \u05dc\u05e2\u05e0\u05df \u05e9\u05d9\u05d5\u05d0\u05de\u05d9 \u05de\u05d9\u05d5, \u05e8\u05d0\u05d5 https://www.openhab.org/addons/bindings/miio/#country-servers \u05dc\u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05e9\u05e8\u05ea \u05d4\u05e2\u05e0\u05df." }, "connect": { "data": { "model": "\u05d3\u05d2\u05dd \u05d4\u05ea\u05e7\u05df" - }, - "description": "\u05d1\u05d7\u05e8 \u05d9\u05d3\u05e0\u05d9\u05ea \u05d0\u05ea \u05d3\u05d2\u05dd \u05d4\u05d4\u05ea\u05e7\u05df \u05de\u05d4\u05d3\u05d2\u05de\u05d9\u05dd \u05d4\u05e0\u05ea\u05de\u05db\u05d9\u05dd.", - "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05de\u05db\u05e9\u05d9\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9 \u05de\u05d9\u05d5 \u05d0\u05d5 \u05dc\u05e9\u05e2\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9" - }, - "device": { - "data": { - "host": "\u05db\u05ea\u05d5\u05d1\u05ea IP", - "model": "\u05d3\u05d2\u05dd \u05d4\u05ea\u05e7\u05df (\u05d0\u05d5\u05e4\u05e6\u05d9\u05d5\u05e0\u05dc\u05d9)", - "name": "\u05e9\u05dd \u05d4\u05d4\u05ea\u05e7\u05df", - "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/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\u05de\u05db\u05e9\u05d9\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9 \u05de\u05d9\u05d5 \u05d0\u05d5 \u05dc\u05e9\u05e2\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9" - }, - "gateway": { - "data": { - "host": "\u05db\u05ea\u05d5\u05d1\u05ea IP", - "name": "\u05e9\u05dd \u05d4\u05e9\u05e2\u05e8", - "token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df API" - }, - "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": { "data": { "host": "\u05db\u05ea\u05d5\u05d1\u05ea IP", "token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df API" }, - "description": "\u05ea\u05d6\u05d3\u05e7\u05e7 \u05dc-32 \u05ea\u05d5\u05d5\u05d9 \u05d4\u05d0\u05e1\u05d9\u05de\u05d5\u05df API, \u05e8\u05d0\u05d4 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u05dc\u05d4\u05d5\u05e8\u05d0\u05d5\u05ea. \u05e9\u05d9\u05dd \u05dc\u05d1, \u05d6\u05d4 \u05d0\u05e1\u05d9\u05de\u05d5\u05df API \u05e9\u05d5\u05e0\u05d4 \u05de\u05d4\u05de\u05e4\u05ea\u05d7 \u05d4\u05de\u05e9\u05de\u05e9 \u05dc\u05e9\u05d9\u05dc\u05d5\u05d1 Xiaomi Aqara.", - "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05de\u05db\u05e9\u05d9\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9 \u05de\u05d9\u05d5 \u05d0\u05d5 \u05dc\u05e9\u05e2\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9" + "description": "\u05ea\u05d6\u05d3\u05e7\u05e7 \u05dc-32 \u05ea\u05d5\u05d5\u05d9 \u05d4\u05d0\u05e1\u05d9\u05de\u05d5\u05df API, \u05e8\u05d0\u05d4 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u05dc\u05d4\u05d5\u05e8\u05d0\u05d5\u05ea. \u05e9\u05d9\u05dd \u05dc\u05d1, \u05d6\u05d4 \u05d0\u05e1\u05d9\u05de\u05d5\u05df API \u05e9\u05d5\u05e0\u05d4 \u05de\u05d4\u05de\u05e4\u05ea\u05d7 \u05d4\u05de\u05e9\u05de\u05e9 \u05dc\u05e9\u05d9\u05dc\u05d5\u05d1 Xiaomi Aqara." }, "reauth_confirm": { "description": "\u05d4\u05e9\u05d9\u05dc\u05d5\u05d1 \u05e9\u05dc \u05e9\u05d9\u05d5\u05d0\u05de\u05d9 \u05de\u05d9\u05d5 \u05e6\u05e8\u05d9\u05db\u05d4 \u05dc\u05d0\u05de\u05ea \u05de\u05d7\u05d3\u05e9 \u05d0\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05e9\u05dc\u05da \u05db\u05d3\u05d9 \u05dc\u05e2\u05d3\u05db\u05df \u05d0\u05ea \u05d4\u05d0\u05e1\u05d9\u05de\u05d5\u05e0\u05d9\u05dd \u05d0\u05d5 \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05d0\u05d9\u05e9\u05d5\u05e8\u05d9 \u05e2\u05e0\u05df \u05d7\u05e1\u05e8\u05d9\u05dd.", @@ -70,15 +46,7 @@ "data": { "select_device": "\u05d4\u05ea\u05e7\u05df \u05de\u05d9\u05d5" }, - "description": "\u05d1\u05d7\u05e8 \u05d0\u05ea \u05d4\u05ea\u05e7\u05df \u05e9\u05d9\u05d5\u05d0\u05de\u05d9 \u05de\u05d9\u05d5 \u05dc\u05d4\u05ea\u05e7\u05e0\u05d4.", - "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05de\u05db\u05e9\u05d9\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9 \u05de\u05d9\u05d5 \u05d0\u05d5 \u05dc\u05e9\u05e2\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9" - }, - "user": { - "data": { - "gateway": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05e9\u05e2\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9" - }, - "description": "\u05d1\u05d7\u05e8 \u05dc\u05d0\u05d9\u05d6\u05d4 \u05d4\u05ea\u05e7\u05df \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8.", - "title": "\u05e9\u05d9\u05d0\u05d5\u05de\u05d9 \u05de\u05d9\u05d5" + "description": "\u05d1\u05d7\u05e8 \u05d0\u05ea \u05d4\u05ea\u05e7\u05df \u05e9\u05d9\u05d5\u05d0\u05de\u05d9 \u05de\u05d9\u05d5 \u05dc\u05d4\u05ea\u05e7\u05e0\u05d4." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "\u05d4\u05e9\u05ea\u05de\u05e9 \u05d1\u05e2\u05e0\u05df \u05db\u05d3\u05d9 \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05ea\u05ea-\u05d4\u05ea\u05e7\u05e0\u05d9\u05dd \u05de\u05d7\u05d5\u05d1\u05e8\u05d9\u05dd" - }, - "description": "\u05e6\u05d9\u05d9\u05df \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05d0\u05d5\u05e4\u05e6\u05d9\u05d5\u05e0\u05dc\u05d9\u05d5\u05ea", - "title": "\u05e9\u05d9\u05d0\u05d5\u05de\u05d9 \u05de\u05d9\u05d5" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/hu.json b/homeassistant/components/xiaomi_miio/translations/hu.json index 189e9906e24..51f093b871c 100644 --- a/homeassistant/components/xiaomi_miio/translations/hu.json +++ b/homeassistant/components/xiaomi_miio/translations/hu.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "A felh\u0151alap\u00fa hiteles\u00edt\u0151 adatok hi\u00e1nyosak, k\u00e9rj\u00fck, adja meg a felhaszn\u00e1l\u00f3nevet, a jelsz\u00f3t \u00e9s az orsz\u00e1got", "cloud_login_error": "Nem siker\u00fclt bejelentkezni a Xioami Miio Cloud szolg\u00e1ltat\u00e1sba, ellen\u0151rizze a hiteles\u00edt\u0151 adatokat.", "cloud_no_devices": "Nincs eszk\u00f6z ebben a Xiaomi Miio felh\u0151fi\u00f3kban.", - "no_device_selected": "Nincs kiv\u00e1lasztva eszk\u00f6z, k\u00e9rj\u00fck, v\u00e1lasszon egyet.", "unknown_device": "Az eszk\u00f6z modell nem ismert, nem tudja be\u00e1ll\u00edtani az eszk\u00f6zt a konfigur\u00e1ci\u00f3s folyamat seg\u00edts\u00e9g\u00e9vel.", "wrong_token": "Ellen\u0151rz\u0151\u00f6sszeg-hiba, hib\u00e1s token" }, @@ -25,42 +24,19 @@ "cloud_username": "Felh\u0151 felhaszn\u00e1l\u00f3neve", "manual": "Konfigur\u00e1l\u00e1s manu\u00e1lisan (nem aj\u00e1nlott)" }, - "description": "Jelentkezzen be a Xiaomi Miio felh\u0151be, a felh\u0151szerver haszn\u00e1lat\u00e1hoz l\u00e1sd: https://www.openhab.org/addons/bindings/miio/#country-servers.", - "title": "Csatlakozzon egy Xiaomi Miio eszk\u00f6zh\u00f6z vagy a Xiaomi Gateway-hez" + "description": "Jelentkezzen be a Xiaomi Miio felh\u0151be, a felh\u0151szerver haszn\u00e1lat\u00e1hoz l\u00e1sd: https://www.openhab.org/addons/bindings/miio/#country-servers." }, "connect": { "data": { "model": "Eszk\u00f6z modell" - }, - "description": "V\u00e1lassza ki manu\u00e1lisan a modellt a t\u00e1mogatott modellek k\u00f6z\u00fcl.", - "title": "Csatlakozzon egy Xiaomi Miio eszk\u00f6zh\u00f6z vagy a Xiaomi Gateway-hez" - }, - "device": { - "data": { - "host": "IP c\u00edm", - "model": "Eszk\u00f6z modell (opcion\u00e1lis)", - "name": "Eszk\u00f6z neve", - "token": "API Token" - }, - "description": "Sz\u00fcks\u00e9ge lesz a 32 karakteres API Tokenre, k\u00f6vesse a https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token oldal instrukci\u00f3it. Vegye figyelembe, hogy ez az API Token k\u00fcl\u00f6nb\u00f6zik a Xiaomi Aqara integr\u00e1ci\u00f3 \u00e1ltal haszn\u00e1lt kulcst\u00f3l.", - "title": "Csatlakoz\u00e1s Xiaomi Miio eszk\u00f6zh\u00f6z vagy Xiaomi Gateway-hez" - }, - "gateway": { - "data": { - "host": "IP c\u00edm", - "name": "K\u00f6zponti egys\u00e9g neve", - "token": "API Token" - }, - "description": "Sz\u00fcks\u00e9ge lesz az API Tokenre, tov\u00e1bbi inforaciok: https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. K\u00e9rj\u00fck, vegye figyelembe, hogy ez az API Token k\u00fcl\u00f6nb\u00f6zik a Xiaomi Aqara integr\u00e1ci\u00f3 \u00e1ltal haszn\u00e1lt kulcst\u00f3l.", - "title": "Csatlakozzon egy Xiaomi K\u00f6zponti egys\u00e9ghez" + } }, "manual": { "data": { "host": "IP c\u00edm", "token": "API Token" }, - "description": "Sz\u00fcks\u00e9ge lesz a 32 karakteres API Token -re. Az utas\u00edt\u00e1sok\u00e9rt l\u00e1sd: https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Felh\u00edvjuk figyelm\u00e9t, hogy ez a API Token elt\u00e9r a Xiaomi Aqara integr\u00e1ci\u00f3 \u00e1ltal haszn\u00e1lt kulcst\u00f3l.", - "title": "Csatlakozzon egy Xiaomi Miio eszk\u00f6zh\u00f6z vagy a Xiaomi Gateway-hez" + "description": "Sz\u00fcks\u00e9ge lesz a 32 karakteres API Token -re. Az utas\u00edt\u00e1sok\u00e9rt l\u00e1sd: https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Felh\u00edvjuk figyelm\u00e9t, hogy ez a API Token elt\u00e9r a Xiaomi Aqara integr\u00e1ci\u00f3 \u00e1ltal haszn\u00e1lt kulcst\u00f3l." }, "reauth_confirm": { "description": "A tokenek friss\u00edt\u00e9s\u00e9hez vagy hi\u00e1nyz\u00f3 felh\u0151alap\u00fa hiteles\u00edt\u0151 adatok hozz\u00e1ad\u00e1s\u00e1hoz a Xiaomi Miio integr\u00e1ci\u00f3nak \u00fajra hiteles\u00edtenie kell a fi\u00f3kj\u00e1t.", @@ -70,15 +46,7 @@ "data": { "select_device": "Miio eszk\u00f6z" }, - "description": "V\u00e1lassza ki a be\u00e1ll\u00edtand\u00f3 Xiaomi Miio eszk\u00f6zt.", - "title": "Csatlakoz\u00e1s Xiaomi Miio eszk\u00f6zh\u00f6z vagy Xiaomi Gateway-hez" - }, - "user": { - "data": { - "gateway": "Csatlakozzon egy Xiaomi K\u00f6zponti egys\u00e9ghez" - }, - "description": "V\u00e1lassza ki, melyik k\u00e9sz\u00fcl\u00e9khez szeretne csatlakozni.", - "title": "Xiaomi Miio" + "description": "V\u00e1lassza ki a be\u00e1ll\u00edtand\u00f3 Xiaomi Miio eszk\u00f6zt." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Haszn\u00e1lja a felh\u0151t a csatlakoztatott alegys\u00e9gek megszerz\u00e9s\u00e9hez" - }, - "description": "Adja meg az opcion\u00e1lis be\u00e1ll\u00edt\u00e1sokat", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/id.json b/homeassistant/components/xiaomi_miio/translations/id.json index 4aae8d6396c..8d1d70642aa 100644 --- a/homeassistant/components/xiaomi_miio/translations/id.json +++ b/homeassistant/components/xiaomi_miio/translations/id.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Kredensial cloud tidak lengkap, isi nama pengguna, kata sandi, dan negara", "cloud_login_error": "Tidak dapat masuk ke Xiaomi Miio Cloud, periksa kredensialnya.", "cloud_no_devices": "Tidak ada perangkat yang ditemukan di akun cloud Xiaomi Miio ini.", - "no_device_selected": "Tidak ada perangkat yang dipilih, pilih satu perangkat.", "unknown_device": "Model perangkat tidak diketahui, tidak dapat menyiapkan perangkat menggunakan alur konfigurasi.", "wrong_token": "Kesalahan checksum, token salah" }, @@ -25,42 +24,19 @@ "cloud_username": "Nama pengguna cloud", "manual": "Konfigurasi secara manual (tidak disarankan)" }, - "description": "Masuk ke cloud Xiaomi Miio, lihat https://www.openhab.org/addons/bindings/miio/#country-servers untuk menemukan server cloud yang digunakan.", - "title": "Hubungkan ke Perangkat Xiaomi Miio atau Xiaomi Gateway" + "description": "Masuk ke cloud Xiaomi Miio, lihat https://www.openhab.org/addons/bindings/miio/#country-servers untuk menemukan server cloud yang digunakan." }, "connect": { "data": { "model": "Model perangkat" - }, - "description": "Pilih model perangkat secara manual dari model yang didukung.", - "title": "Hubungkan ke Perangkat Xiaomi Miio atau Xiaomi Gateway" - }, - "device": { - "data": { - "host": "Alamat IP", - "model": "Model perangkat (Opsional)", - "name": "Nama perangkat", - "token": "Token API" - }, - "description": "Anda akan membutuhkan Token API 32 karakter, lihat https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token untuk mendapatkan petunjuknya. Perhatikan bahwa Token API ini berbeda dari kunci yang digunakan oleh integrasi Xiaomi Aqara.", - "title": "Hubungkan ke Perangkat Xiaomi Miio atau Xiaomi Gateway" - }, - "gateway": { - "data": { - "host": "Alamat IP", - "name": "Nama Gateway", - "token": "Token API" - }, - "description": "Anda akan membutuhkan Token API 32 karakter, lihat https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token untuk mendapatkan petunjuknya. Perhatikan bahwa Token API ini berbeda dari kunci yang digunakan oleh integrasi Xiaomi Aqara.", - "title": "Hubungkan ke Xiaomi Gateway" + } }, "manual": { "data": { "host": "Alamat IP", "token": "Token API" }, - "description": "Anda akan membutuhkan Token API 32 karakter, baca petunjuknya di https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Perhatikan bahwa Token API ini berbeda dengan kunci yang digunakan untuk integrasi Xiaomi Aqara.", - "title": "Hubungkan ke Perangkat Xiaomi Miio atau Xiaomi Gateway" + "description": "Anda akan membutuhkan Token API 32 karakter, baca petunjuknya di https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Perhatikan bahwa Token API ini berbeda dengan kunci yang digunakan untuk integrasi Xiaomi Aqara." }, "reauth_confirm": { "description": "Integrasi Xiaomi Miio perlu mengautentikasi ulang akun Anda untuk memperbarui token atau menambahkan kredensial cloud yang hilang.", @@ -70,15 +46,7 @@ "data": { "select_device": "Perangkat Miio" }, - "description": "Pilih perangkat Xiaomi Miio untuk disiapkan.", - "title": "Hubungkan ke Perangkat Xiaomi Miio atau Xiaomi Gateway" - }, - "user": { - "data": { - "gateway": "Hubungkan ke Xiaomi Gateway" - }, - "description": "Pilih perangkat mana yang ingin disambungkan.", - "title": "Xiaomi Miio" + "description": "Pilih perangkat Xiaomi Miio untuk disiapkan." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Gunakan cloud untuk mendapatkan subperangkat yang tersambung" - }, - "description": "Tentukan pengaturan opsional", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/it.json b/homeassistant/components/xiaomi_miio/translations/it.json index b643f7df4de..51b5f108751 100644 --- a/homeassistant/components/xiaomi_miio/translations/it.json +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Credenziali cloud incomplete, inserisci nome utente, password e paese", "cloud_login_error": "Impossibile accedere a Xioami Miio Cloud, controlla le credenziali.", "cloud_no_devices": "Nessun dispositivo trovato in questo account cloud Xiaomi Miio.", - "no_device_selected": "Nessun dispositivo selezionato, seleziona un dispositivo.", "unknown_device": "Il modello del dispositivo non \u00e8 noto, non \u00e8 possibile configurare il dispositivo utilizzando il flusso di configurazione.", "wrong_token": "Errore del codice di controllo, token errato" }, @@ -25,42 +24,19 @@ "cloud_username": "Nome utente cloud", "manual": "Configura manualmente (non consigliato)" }, - "description": "Accedi al cloud Xiaomi Miio, vedi https://www.openhab.org/addons/bindings/miio/#country-servers per utilizzare il server cloud.", - "title": "Connettiti a un dispositivo Xiaomi Miio o Xiaomi Gateway" + "description": "Accedi al cloud Xiaomi Miio, vedi https://www.openhab.org/addons/bindings/miio/#country-servers per utilizzare il server cloud." }, "connect": { "data": { "model": "Modello del dispositivo" - }, - "description": "Seleziona manualmente il modello del dispositivo tra i modelli supportati.", - "title": "Connettiti a un dispositivo Xiaomi Miio o Xiaomi Gateway" - }, - "device": { - "data": { - "host": "Indirizzo IP", - "model": "Modello del dispositivo (opzionale)", - "name": "Nome del dispositivo", - "token": "Token API" - }, - "description": "Avrai bisogno dei 32 caratteri del Token API, vedi https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token per istruzioni. Tieni presente che questo Token API \u00e8 diverso dalla chiave utilizzata dall'integrazione Xiaomi Aqara.", - "title": "Connettiti a un dispositivo Xiaomi Miio o Xiaomi Gateway" - }, - "gateway": { - "data": { - "host": "Indirizzo IP", - "name": "Nome del Gateway", - "token": "Token API" - }, - "description": "\u00c8 necessario il Token API di 32 caratteri, vedi https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token per le istruzioni. Nota che questo Token API \u00e8 diverso dalla chiave usata dall'integrazione di Xiaomi Aqara.", - "title": "Connettiti a un Xiaomi Gateway " + } }, "manual": { "data": { "host": "Indirizzo IP", "token": "Token API" }, - "description": "Avrai bisogno dei 32 caratteri del Token API, vedi https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token per istruzioni. Tieni presente che questo Token API \u00e8 diverso dalla chiave utilizzata dall'integrazione Xiaomi Aqara.", - "title": "Connettiti a un dispositivo Xiaomi Miio o Xiaomi Gateway" + "description": "Avrai bisogno dei 32 caratteri del Token API, vedi https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token per istruzioni. Tieni presente che questo Token API \u00e8 diverso dalla chiave utilizzata dall'integrazione Xiaomi Aqara." }, "reauth_confirm": { "description": "L'integrazione di Xiaomi Miio deve autenticare nuovamente il tuo account per aggiornare i token o aggiungere credenziali cloud mancanti.", @@ -70,15 +46,7 @@ "data": { "select_device": "Dispositivo Miio" }, - "description": "Seleziona il dispositivo Xiaomi Miio da configurare.", - "title": "Connettiti a un dispositivo Xiaomi Miio o Xiaomi Gateway" - }, - "user": { - "data": { - "gateway": "Connettiti a un Xiaomi Gateway" - }, - "description": "Seleziona a quale dispositivo desideri collegarti.", - "title": "Xiaomi Miio" + "description": "Seleziona il dispositivo Xiaomi Miio da configurare." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Usa il cloud per connettere i sottodispositivi" - }, - "description": "Specifica le impostazioni opzionali", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/ja.json b/homeassistant/components/xiaomi_miio/translations/ja.json index 44c6f7f149e..0877850f754 100644 --- a/homeassistant/components/xiaomi_miio/translations/ja.json +++ b/homeassistant/components/xiaomi_miio/translations/ja.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "\u30af\u30e9\u30a6\u30c9\u8a8d\u8a3c\u304c\u4e0d\u5b8c\u5168\u3067\u3059\u3002\u30e6\u30fc\u30b6\u30fc\u540d\u3001\u30d1\u30b9\u30ef\u30fc\u30c9\u3001\u56fd\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "cloud_login_error": "Xiaomi Miio Cloud\u306b\u30ed\u30b0\u30a4\u30f3\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u8a8d\u8a3c\u60c5\u5831\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "cloud_no_devices": "\u3053\u306eXiaomi Miio cloud\u306e\u30a2\u30ab\u30a6\u30f3\u30c8\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002", - "no_device_selected": "\u30c7\u30d0\u30a4\u30b9\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30c7\u30d0\u30a4\u30b9\u30921\u3064\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "unknown_device": "\u30c7\u30d0\u30a4\u30b9\u30e2\u30c7\u30eb\u304c\u4e0d\u660e\u306a\u306e\u3067\u3001\u69cb\u6210\u30d5\u30ed\u30fc\u3092\u4f7f\u7528\u3057\u3066\u30c7\u30d0\u30a4\u30b9\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3067\u304d\u307e\u305b\u3093\u3002", "wrong_token": "\u30c1\u30a7\u30c3\u30af\u30b5\u30e0\u30a8\u30e9\u30fc\u3001\u9593\u9055\u3063\u305f\u30c8\u30fc\u30af\u30f3" }, @@ -25,42 +24,19 @@ "cloud_username": "\u30af\u30e9\u30a6\u30c9\u306e\u30e6\u30fc\u30b6\u30fc\u540d", "manual": "\u624b\u52d5\u3067\u8a2d\u5b9a\u3059\u308b(\u975e\u63a8\u5968)" }, - "description": "Xiaomi Miio cloud\u306b\u30ed\u30b0\u30a4\u30f3\u3057\u307e\u3059\u3002\u4f7f\u7528\u3059\u308b\u30af\u30e9\u30a6\u30c9\u30b5\u30fc\u30d0\u30fc\u306b\u3064\u3044\u3066\u306f\u3001https://www.openhab.org/addons/bindings/miio/#country-servers \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "Xiaomi Miio\u30c7\u30d0\u30a4\u30b9\u307e\u305f\u306f\u3001Xiaomi Gateway\u306b\u63a5\u7d9a" + "description": "Xiaomi Miio cloud\u306b\u30ed\u30b0\u30a4\u30f3\u3057\u307e\u3059\u3002\u4f7f\u7528\u3059\u308b\u30af\u30e9\u30a6\u30c9\u30b5\u30fc\u30d0\u30fc\u306b\u3064\u3044\u3066\u306f\u3001https://www.openhab.org/addons/bindings/miio/#country-servers \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, "connect": { "data": { "model": "\u30c7\u30d0\u30a4\u30b9\u30e2\u30c7\u30eb" - }, - "description": "\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u308b\u30e2\u30c7\u30eb\u304b\u3089\u30c7\u30d0\u30a4\u30b9 \u30e2\u30c7\u30eb\u3092\u624b\u52d5\u3067\u9078\u629e\u3057\u307e\u3059\u3002", - "title": "Xiaomi Miio\u30c7\u30d0\u30a4\u30b9\u307e\u305f\u306f\u3001Xiaomi Gateway\u306b\u63a5\u7d9a" - }, - "device": { - "data": { - "host": "IP\u30a2\u30c9\u30ec\u30b9", - "model": "\u30c7\u30d0\u30a4\u30b9\u30e2\u30c7\u30eb(\u30aa\u30d7\u30b7\u30e7\u30f3)", - "name": "\u30c7\u30d0\u30a4\u30b9\u306e\u540d\u524d", - "token": "API\u30c8\u30fc\u30af\u30f3" - }, - "description": "32\u6587\u5b57\u306eAPI\u30c8\u30fc\u30af\u30f3\u304c\u5fc5\u8981\u306b\u306a\u308a\u307e\u3059\u3002\u624b\u9806\u306f\u3001https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u6ce8\u610f\u4e8b\u9805: \u3053\u306eAPI\u30c8\u30fc\u30af\u30f3\u306f\u3001Xiaomi Aqara\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u4f7f\u7528\u3055\u308c\u3066\u3044\u308b\u30ad\u30fc\u3068\u306f\u7570\u306a\u308a\u307e\u3059\u3002", - "title": "Xiaomi Miio\u30c7\u30d0\u30a4\u30b9\u307e\u305f\u306f\u3001Xiaomi Gateway\u306b\u63a5\u7d9a" - }, - "gateway": { - "data": { - "host": "IP\u30a2\u30c9\u30ec\u30b9", - "name": "\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4\u306e\u540d\u524d", - "token": "API\u30c8\u30fc\u30af\u30f3" - }, - "description": "32\u6587\u5b57\u306eAPI\u30c8\u30fc\u30af\u30f3\u304c\u5fc5\u8981\u306b\u306a\u308a\u307e\u3059\u3002\u624b\u9806\u306f\u3001https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u6ce8\u610f\u4e8b\u9805: \u3053\u306eAPI\u30c8\u30fc\u30af\u30f3\u306f\u3001Xiaomi Aqara\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u4f7f\u7528\u3055\u308c\u3066\u3044\u308b\u30ad\u30fc\u3068\u306f\u7570\u306a\u308a\u307e\u3059\u3002", - "title": "Xiaomi Gateway\u306b\u63a5\u7d9a" + } }, "manual": { "data": { "host": "IP\u30a2\u30c9\u30ec\u30b9", "token": "API\u30c8\u30fc\u30af\u30f3" }, - "description": "32\u6587\u5b57\u306eAPI\u30c8\u30fc\u30af\u30f3\u304c\u5fc5\u8981\u306b\u306a\u308a\u307e\u3059\u3002\u624b\u9806\u306f\u3001https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u6ce8\u610f\u4e8b\u9805: \u3053\u306eAPI\u30c8\u30fc\u30af\u30f3\u306f\u3001Xiaomi Aqara\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u4f7f\u7528\u3055\u308c\u3066\u3044\u308b\u30ad\u30fc\u3068\u306f\u7570\u306a\u308a\u307e\u3059\u3002", - "title": "Xiaomi Miio\u30c7\u30d0\u30a4\u30b9\u307e\u305f\u306f\u3001Xiaomi Gateway\u306b\u63a5\u7d9a" + "description": "32\u6587\u5b57\u306eAPI\u30c8\u30fc\u30af\u30f3\u304c\u5fc5\u8981\u306b\u306a\u308a\u307e\u3059\u3002\u624b\u9806\u306f\u3001https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u6ce8\u610f\u4e8b\u9805: \u3053\u306eAPI\u30c8\u30fc\u30af\u30f3\u306f\u3001Xiaomi Aqara\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u4f7f\u7528\u3055\u308c\u3066\u3044\u308b\u30ad\u30fc\u3068\u306f\u7570\u306a\u308a\u307e\u3059\u3002" }, "reauth_confirm": { "description": "Xiaomi Miio\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306f\u3001\u30c8\u30fc\u30af\u30f3\u3092\u66f4\u65b0\u3057\u305f\u308a\u3001\u4e0d\u8db3\u3057\u3066\u3044\u308b\u30af\u30e9\u30a6\u30c9\u306e\u8a8d\u8a3c\u60c5\u5831\u3092\u8ffd\u52a0\u3059\u308b\u305f\u3081\u306b\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002", @@ -70,15 +46,7 @@ "data": { "select_device": "Miio\u30c7\u30d0\u30a4\u30b9" }, - "description": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3059\u308bXiaomi Miio\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3057\u307e\u3059\u3002", - "title": "Xiaomi Miio\u30c7\u30d0\u30a4\u30b9\u307e\u305f\u306f\u3001Xiaomi Gateway\u306b\u63a5\u7d9a" - }, - "user": { - "data": { - "gateway": "Xiaomi Gateway\u306b\u63a5\u7d9a" - }, - "description": "\u63a5\u7d9a\u3059\u308b\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3057\u307e\u3059\u3002", - "title": "Xiaomi Miio" + "description": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3059\u308bXiaomi Miio\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3057\u307e\u3059\u3002" } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "\u30af\u30e9\u30a6\u30c9\u3092\u4f7f\u7528\u3057\u3066\u63a5\u7d9a\u3055\u308c\u305f\u30b5\u30d6\u30c7\u30d0\u30a4\u30b9\u3092\u53d6\u5f97\u3059\u308b" - }, - "description": "\u30aa\u30d7\u30b7\u30e7\u30f3\u8a2d\u5b9a\u306e\u6307\u5b9a", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/ko.json b/homeassistant/components/xiaomi_miio/translations/ko.json index 03043f92957..2c188fa670b 100644 --- a/homeassistant/components/xiaomi_miio/translations/ko.json +++ b/homeassistant/components/xiaomi_miio/translations/ko.json @@ -6,37 +6,8 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "no_device_selected": "\uc120\ud0dd\ub41c \uae30\uae30\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", "unknown_device": "\uae30\uae30\uc758 \ubaa8\ub378\uc744 \uc54c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ud750\ub984\uc5d0\uc11c \uae30\uae30\ub97c \uc124\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, - "flow_title": "Xiaomi Miio: {name}", - "step": { - "device": { - "data": { - "host": "IP \uc8fc\uc18c", - "model": "\uae30\uae30 \ubaa8\ub378 (\uc120\ud0dd \uc0ac\ud56d)", - "name": "\uae30\uae30 \uc774\ub984", - "token": "API \ud1a0\ud070" - }, - "description": "32\uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694. \ucc38\uace0\ub85c \uc774 API \ud1a0\ud070\uc740 Xiaomi Aqara \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc0ac\uc6a9\ub418\ub294 \ud0a4\uc640 \ub2e4\ub985\ub2c8\ub2e4.", - "title": "Xiaomi Miio \uae30\uae30 \ub610\ub294 Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\uae30" - }, - "gateway": { - "data": { - "host": "IP \uc8fc\uc18c", - "name": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc774\ub984", - "token": "API \ud1a0\ud070" - }, - "description": "32\uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694. \ucc38\uace0\ub85c \uc774 API \ud1a0\ud070\uc740 Xiaomi Aqara \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc0ac\uc6a9\ub418\ub294 \ud0a4\uc640 \ub2e4\ub985\ub2c8\ub2e4.", - "title": "Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\uae30" - }, - "user": { - "data": { - "gateway": "Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0" - }, - "description": "\uc5f0\uacb0\ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", - "title": "Xiaomi Miio" - } - } + "flow_title": "Xiaomi Miio: {name}" } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/lb.json b/homeassistant/components/xiaomi_miio/translations/lb.json index 67e286d4fa1..9a747e8e315 100644 --- a/homeassistant/components/xiaomi_miio/translations/lb.json +++ b/homeassistant/components/xiaomi_miio/translations/lb.json @@ -5,27 +5,8 @@ "already_in_progress": "Konfiguratioun's Oflaf ass schonn am gaangen." }, "error": { - "cannot_connect": "Feeler beim verbannen", - "no_device_selected": "Keen Apparat ausgewielt, wiel een Apparat aus w.e.g." + "cannot_connect": "Feeler beim verbannen" }, - "flow_title": "Xiaomi Miio: {name}", - "step": { - "gateway": { - "data": { - "host": "IP Adresse", - "name": "Numm vum Gateway", - "token": "API Jeton" - }, - "description": "Du brauchs den 32 stellegen API Jeton, kuck https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token fir Instruktiounen. Remarque, d\u00ebst ass een aaneren API Jeton w\u00e9i en vun der Xiaomi Aqara Integratioun benotzt g\u00ebtt.", - "title": "Mat enger Xiaomi Gateway verbannen" - }, - "user": { - "data": { - "gateway": "Mat enger Xiaomi Gateway verbannen" - }, - "description": "Wielt den Apparat aus dee soll verbonne ginn", - "title": "Xiaomi Miio" - } - } + "flow_title": "Xiaomi Miio: {name}" } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/nl.json b/homeassistant/components/xiaomi_miio/translations/nl.json index 8ef2ef70fae..cc077839808 100644 --- a/homeassistant/components/xiaomi_miio/translations/nl.json +++ b/homeassistant/components/xiaomi_miio/translations/nl.json @@ -2,17 +2,16 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "incomplete_info": "Onvolledige informatie voor het instellen van het apparaat, geen host of token opgegeven.", "not_xiaomi_miio": "Apparaat wordt (nog) niet ondersteund door Xiaomi Miio.", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", "cloud_credentials_incomplete": "Cloud-inloggegevens onvolledig, vul gebruikersnaam, wachtwoord en land in", "cloud_login_error": "Kan niet inloggen op Xioami Miio Cloud, controleer de inloggegevens.", "cloud_no_devices": "Geen apparaten gevonden in dit Xiaomi Miio-cloudaccount.", - "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft", "unknown_device": "Het apparaatmodel is niet bekend, niet in staat om het apparaat in te stellen met config flow.", "wrong_token": "Checksum-fout, verkeerd token" }, @@ -25,60 +24,29 @@ "cloud_username": "Cloud gebruikersnaam", "manual": "Handmatig configureren (niet aanbevolen)" }, - "description": "Log in op de Xiaomi Miio-cloud, zie https://www.openhab.org/addons/bindings/miio/#country-servers voor de te gebruiken cloudserver.", - "title": "Maak verbinding met een Xiaomi Miio-apparaat of Xiaomi Gateway" + "description": "Log in op de Xiaomi Miio-cloud, zie https://www.openhab.org/addons/bindings/miio/#country-servers voor de te gebruiken cloudserver." }, "connect": { "data": { "model": "Apparaatmodel" - }, - "description": "Selecteer handmatig het apparaatmodel uit de ondersteunde modellen.", - "title": "Maak verbinding met een Xiaomi Miio-apparaat of Xiaomi Gateway" - }, - "device": { - "data": { - "host": "IP-adres", - "model": "Apparaatmodel (Optioneel)", - "name": "Naam van het apparaat", - "token": "API-token" - }, - "description": "U hebt de 32 karakter API-token nodig, zie https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token voor instructies. Let op, deze API-token is anders dan de sleutel die wordt gebruikt door de Xiaomi Aqara integratie.", - "title": "Verbinding maken met een Xiaomi Miio-apparaat of Xiaomi Gateway" - }, - "gateway": { - "data": { - "host": "IP-adres", - "name": "Naam van de gateway", - "token": "API-token" - }, - "description": "U heeft het API-token nodig, zie https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token voor instructies.", - "title": "Maak verbinding met een Xiaomi Gateway" + } }, "manual": { "data": { "host": "IP-adres", "token": "API-token" }, - "description": "U hebt het 32-teken API-token , zie https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token voor instructies. Houd er rekening mee dat deze API-token verschilt van de sleutel die wordt gebruikt door de Xiaomi Aqara-integratie.", - "title": "Maak verbinding met een Xiaomi Miio-apparaat of Xiaomi Gateway" + "description": "U hebt het 32-teken API-token , zie https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token voor instructies. Houd er rekening mee dat deze API-token verschilt van de sleutel die wordt gebruikt door de Xiaomi Aqara-integratie." }, "reauth_confirm": { "description": "De Xiaomi Miio-integratie moet uw account opnieuw verifi\u00ebren om de tokens bij te werken of ontbrekende cloudreferenties toe te voegen.", - "title": "Verifieer de integratie opnieuw" + "title": "Integratie herauthenticeren" }, "select": { "data": { "select_device": "Miio-apparaat" }, - "description": "Selecteer het Xiaomi Miio apparaat dat u wilt instellen.", - "title": "Maak verbinding met een Xiaomi Miio-apparaat of Xiaomi-gateway" - }, - "user": { - "data": { - "gateway": "Maak verbinding met een Xiaomi Gateway" - }, - "description": "Selecteer het apparaat waarmee u verbinding wilt maken", - "title": "Xiaomi Miio" + "description": "Selecteer het Xiaomi Miio apparaat dat u wilt instellen." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Gebruik de cloud om aangesloten subapparaten te krijgen" - }, - "description": "Optionele instellingen opgeven", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/no.json b/homeassistant/components/xiaomi_miio/translations/no.json index 9094cce023a..3d831df207c 100644 --- a/homeassistant/components/xiaomi_miio/translations/no.json +++ b/homeassistant/components/xiaomi_miio/translations/no.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Utskriftsinformasjon for skyen er fullstendig. Fyll ut brukernavn, passord og land", "cloud_login_error": "Kunne ikke logge inn p\u00e5 Xiaomi Miio Cloud, sjekk legitimasjonen.", "cloud_no_devices": "Ingen enheter funnet i denne Xiaomi Miio-skykontoen.", - "no_device_selected": "Ingen enhet valgt, vennligst velg en enhet.", "unknown_device": "Enhetsmodellen er ikke kjent, kan ikke konfigurere enheten ved hjelp av konfigurasjonsflyt.", "wrong_token": "Kontrollsumfeil, feil token" }, @@ -25,42 +24,19 @@ "cloud_username": "Brukernavn i skyen", "manual": "Konfigurer manuelt (anbefales ikke)" }, - "description": "Logg deg p\u00e5 Xiaomi Miio-skyen, se https://www.openhab.org/addons/bindings/miio/#country-servers for skyserveren du kan bruke.", - "title": "Koble til en Xiaomi Miio-enhet eller Xiaomi Gateway" + "description": "Logg deg p\u00e5 Xiaomi Miio-skyen, se https://www.openhab.org/addons/bindings/miio/#country-servers for skyserveren du kan bruke." }, "connect": { "data": { "model": "Enhetsmodell" - }, - "description": "Velg enhetsmodellen manuelt fra de st\u00f8ttede modellene.", - "title": "Koble til en Xiaomi Miio-enhet eller Xiaomi Gateway" - }, - "device": { - "data": { - "host": "IP adresse", - "model": "Enhetsmodell (valgfritt)", - "name": "Navnet p\u00e5 enheten", - "token": "API-token" - }, - "description": "Du trenger 32 tegn API-token , se https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instruksjoner. V\u00e6r oppmerksom p\u00e5 at denne API-token er forskjellig fra n\u00f8kkelen som brukes av Xiaomi Aqara-integrasjonen.", - "title": "Koble til en Xiaomi Miio-enhet eller Xiaomi Gateway" - }, - "gateway": { - "data": { - "host": "IP adresse", - "name": "Navnet p\u00e5 gatewayen", - "token": "API-token" - }, - "description": "Du trenger 32 tegn API-token , se https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instruksjoner. V\u00e6r oppmerksom p\u00e5 at denne API-token er forskjellig fra n\u00f8kkelen som brukes av Xiaomi Aqara-integrasjonen.", - "title": "Koble til en Xiaomi Gateway" + } }, "manual": { "data": { "host": "IP adresse", "token": "API-token" }, - "description": "Du trenger 32 tegn API-token , se https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instruksjoner. V\u00e6r oppmerksom p\u00e5 at denne API-token er forskjellig fra n\u00f8kkelen som brukes av Xiaomi Aqara-integrasjonen.", - "title": "Koble til en Xiaomi Miio-enhet eller Xiaomi Gateway" + "description": "Du trenger 32 tegn API-token , se https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instruksjoner. V\u00e6r oppmerksom p\u00e5 at denne API-token er forskjellig fra n\u00f8kkelen som brukes av Xiaomi Aqara-integrasjonen." }, "reauth_confirm": { "description": "Xiaomi Miio-integrasjonen m\u00e5 autentisere kontoen din p\u00e5 nytt for \u00e5 oppdatere tokens eller legge til manglende skylegitimasjon.", @@ -70,15 +46,7 @@ "data": { "select_device": "Miio-enhet" }, - "description": "Velg Xiaomi Miio-enheten du vil installere.", - "title": "Koble til en Xiaomi Miio-enhet eller Xiaomi Gateway" - }, - "user": { - "data": { - "gateway": "Koble til en Xiaomi Gateway" - }, - "description": "Velg hvilken enhet du vil koble til.", - "title": "" + "description": "Velg Xiaomi Miio-enheten du vil installere." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Bruk skyen for \u00e5 f\u00e5 tilkoblede underenheter" - }, - "description": "Spesifiser valgfrie innstillinger", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json index 60c82020a77..d0f3cc9a4a8 100644 --- a/homeassistant/components/xiaomi_miio/translations/pl.json +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Dane logowania do chmury niekompletne, prosz\u0119 poda\u0107 nazw\u0119 u\u017cytkownika, has\u0142o i kraj", "cloud_login_error": "Nie mo\u017cna zalogowa\u0107 si\u0119 do chmury Xiaomi Miio, sprawd\u017a po\u015bwiadczenia.", "cloud_no_devices": "Na tym koncie Xiaomi Miio nie znaleziono \u017cadnych urz\u0105dze\u0144.", - "no_device_selected": "Nie wybrano \u017cadnego urz\u0105dzenia, wybierz jedno urz\u0105dzenie", "unknown_device": "Model urz\u0105dzenia nie jest znany, nie mo\u017cna skonfigurowa\u0107 urz\u0105dzenia przy u\u017cyciu interfejsu u\u017cytkownika.", "wrong_token": "B\u0142\u0105d sumy kontrolnej, niew\u0142a\u015bciwy token" }, @@ -25,42 +24,19 @@ "cloud_username": "Nazwa u\u017cytkownika do chmury", "manual": "Skonfiguruj r\u0119cznie (niezalecane)" }, - "description": "Zaloguj si\u0119 do chmury Xiaomi Miio, zobacz https://www.openhab.org/addons/bindings/miio/#country-servers, aby zobaczy\u0107, kt\u00f3rego serwera u\u017cy\u0107.", - "title": "Po\u0142\u0105czenie z bramk\u0105 Xiaomi lub urz\u0105dzeniem Xiaomi Miio" + "description": "Zaloguj si\u0119 do chmury Xiaomi Miio, zobacz https://www.openhab.org/addons/bindings/miio/#country-servers, aby zobaczy\u0107, kt\u00f3rego serwera u\u017cy\u0107." }, "connect": { "data": { "model": "Model urz\u0105dzenia" - }, - "description": "Wybierz r\u0119cznie model urz\u0105dzenia z listy obs\u0142ugiwanych modeli.", - "title": "Po\u0142\u0105czenie z bramk\u0105 Xiaomi lub urz\u0105dzeniem Xiaomi Miio" - }, - "device": { - "data": { - "host": "Adres IP", - "model": "Model urz\u0105dzenia (opcjonalnie)", - "name": "Nazwa urz\u0105dzenia", - "token": "Token API" - }, - "description": "B\u0119dziesz potrzebowa\u0107 tokenu API (32 znaki), odwied\u017a https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token, aby uzyska\u0107 instrukcje. Zauwa\u017c i\u017c jest to inny token ni\u017c w integracji Xiaomi Aqara.", - "title": "Po\u0142\u0105czenie z bramk\u0105 Xiaomi lub urz\u0105dzeniem Xiaomi Miio" - }, - "gateway": { - "data": { - "host": "Adres IP", - "name": "Nazwa bramki", - "token": "Token API" - }, - "description": "B\u0119dziesz potrzebowa\u0107 tokenu API (32 znaki), odwied\u017a https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token, aby uzyska\u0107 instrukcje. Zauwa\u017c i\u017c jest to inny token ni\u017c w integracji Xiaomi Aqara .", - "title": "Po\u0142\u0105czenie z bramk\u0105 Xiaomi" + } }, "manual": { "data": { "host": "Adres IP", "token": "Token API" }, - "description": "B\u0119dziesz potrzebowa\u0107 tokenu API (32-znaki), zobacz https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token, aby uzyska\u0107 instrukcje. Pami\u0119taj, \u017ce ten token API r\u00f3\u017cni si\u0119 od klucza u\u017cywanego przez integracj\u0119 Xiaomi Aqara.", - "title": "Po\u0142\u0105czenie z bramk\u0105 Xiaomi lub urz\u0105dzeniem Xiaomi Miio" + "description": "B\u0119dziesz potrzebowa\u0107 tokenu API (32-znaki), zobacz https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token, aby uzyska\u0107 instrukcje. Pami\u0119taj, \u017ce ten token API r\u00f3\u017cni si\u0119 od klucza u\u017cywanego przez integracj\u0119 Xiaomi Aqara." }, "reauth_confirm": { "description": "Integracja Xiaomi Miio wymaga ponownego uwierzytelnienia Twoje konta, aby zaktualizowa\u0107 tokeny lub doda\u0107 brakuj\u0105ce dane uwierzytelniaj\u0105ce do chmury.", @@ -70,15 +46,7 @@ "data": { "select_device": "Urz\u0105dzenie Miio" }, - "description": "Wybierz urz\u0105dzenie Xiaomi Miio do skonfigurowania.", - "title": "Po\u0142\u0105czenie z bramk\u0105 Xiaomi lub urz\u0105dzeniem Xiaomi Miio" - }, - "user": { - "data": { - "gateway": "Po\u0142\u0105czenie z bramk\u0105 Xiaomi" - }, - "description": "Wybierz urz\u0105dzenie, z kt\u00f3rym chcesz si\u0119 po\u0142\u0105czy\u0107.", - "title": "Xiaomi Miio" + "description": "Wybierz urz\u0105dzenie Xiaomi Miio do skonfigurowania." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "U\u017cyj chmury, aby uzyska\u0107 pod\u0142\u0105czone podurz\u0105dzenia" - }, - "description": "Ustawienia opcjonalne", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/pt-BR.json b/homeassistant/components/xiaomi_miio/translations/pt-BR.json index 1116de258fa..ce67173f9b2 100644 --- a/homeassistant/components/xiaomi_miio/translations/pt-BR.json +++ b/homeassistant/components/xiaomi_miio/translations/pt-BR.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Credenciais da nuvem incompletas, preencha o nome de usu\u00e1rio, a senha e o pa\u00eds", "cloud_login_error": "N\u00e3o foi poss\u00edvel fazer login no Xiaomi Miio Cloud, verifique as credenciais.", "cloud_no_devices": "Nenhum dispositivo encontrado nesta conta de nuvem Xiaomi Miio.", - "no_device_selected": "Nenhum dispositivo selecionado, selecione um dispositivo.", "unknown_device": "O modelo do dispositivo n\u00e3o \u00e9 conhecido, n\u00e3o \u00e9 poss\u00edvel configurar o dispositivo usando o fluxo de configura\u00e7\u00e3o.", "wrong_token": "Erro de checksum, token errado" }, @@ -25,42 +24,19 @@ "cloud_username": "Usu\u00e1rio da Cloud", "manual": "Configurar manualmente (n\u00e3o recomendado)" }, - "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" + "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." }, "connect": { "data": { "model": "Modelo do dispositivo" - }, - "description": "Selecione manualmente o modelo do dispositivo entre os modelos suportados.", - "title": "Conecte-se a um dispositivo Xiaomi Miio ou Xiaomi Gateway" - }, - "device": { - "data": { - "host": "Endere\u00e7o IP", - "model": "Modelo do dispositivo (opcional)", - "name": "Nome do dispositivo", - "token": "Token da API" - }, - "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara.", - "title": "Conecte-se a um dispositivo Xiaomi Miio ou Xiaomi Gateway" - }, - "gateway": { - "data": { - "host": "Endere\u00e7o IP", - "name": "Nome do Gateway", - "token": "Token da API" - }, - "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara.", - "title": "Conecte-se a um Xiaomi Gateway" + } }, "manual": { "data": { "host": "Endere\u00e7o IP", "token": "Token da API" }, - "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara.", - "title": "Conecte-se a um dispositivo Xiaomi Miio ou Xiaomi Gateway" + "description": "Voc\u00ea precisar\u00e1 do Token da API com 32 caracteres, consulte https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token para obter instru\u00e7\u00f5es. Observe que o Token da API \u00e9 diferente da chave usada pela integra\u00e7\u00e3o Xiaomi Aqara." }, "reauth_confirm": { "description": "A integra\u00e7\u00e3o do Xiaomi Miio precisa autenticar novamente sua conta para atualizar os tokens ou adicionar credenciais de nuvem ausentes.", @@ -70,15 +46,7 @@ "data": { "select_device": "Dispositivo Miio" }, - "description": "Selecione o dispositivo Xiaomi Miio para configurar.", - "title": "Conecte-se a um dispositivo Xiaomi Miio ou Xiaomi Gateway" - }, - "user": { - "data": { - "gateway": "Conecte-se a um Xiaomi Gateway" - }, - "description": "Selecione a qual dispositivo voc\u00ea deseja se conectar.", - "title": "Xiaomi Miio" + "description": "Selecione o dispositivo Xiaomi Miio para configurar." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Use a cloud para obter subdispositivos conectados" - }, - "description": "Especificar configura\u00e7\u00f5es opcionais", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/pt.json b/homeassistant/components/xiaomi_miio/translations/pt.json index 922c2441c3d..db0e0c2a137 100644 --- a/homeassistant/components/xiaomi_miio/translations/pt.json +++ b/homeassistant/components/xiaomi_miio/translations/pt.json @@ -5,20 +5,6 @@ }, "error": { "cannot_connect": "Falha na liga\u00e7\u00e3o" - }, - "step": { - "device": { - "data": { - "host": "Endere\u00e7o IP", - "token": "API Token" - } - }, - "gateway": { - "data": { - "host": "Endere\u00e7o IP", - "token": "API Token" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/ru.json b/homeassistant/components/xiaomi_miio/translations/ru.json index 18bbc4271ad..1432666cb44 100644 --- a/homeassistant/components/xiaomi_miio/translations/ru.json +++ b/homeassistant/components/xiaomi_miio/translations/ru.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "\u0423\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u043e\u0431\u043b\u0430\u043a\u0435 \u043d\u0435\u043f\u043e\u043b\u043d\u044b\u0435. \u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043f\u0430\u0440\u043e\u043b\u044c \u0438 \u0441\u0442\u0440\u0430\u043d\u0443.", "cloud_login_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u043e\u0439\u0442\u0438 \u0432 Xiaomi Miio Cloud, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", "cloud_no_devices": "\u0412 \u044d\u0442\u043e\u0439 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Xiaomi Miio \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b.", - "no_device_selected": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u043d\u043e \u0438\u0437 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432.", "unknown_device": "\u041c\u043e\u0434\u0435\u043b\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430, \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0430\u0441\u0442\u0435\u0440\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.", "wrong_token": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u043d\u043e\u0439 \u0441\u0443\u043c\u043c\u044b, \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d." }, @@ -25,42 +24,19 @@ "cloud_username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u043b\u044f \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430", "manual": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0432\u0440\u0443\u0447\u043d\u0443\u044e (\u043d\u0435 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f)" }, - "description": "\u0412\u043e\u0439\u0434\u0438\u0442\u0435 \u0432 \u043e\u0431\u043b\u0430\u043a\u043e Xiaomi Miio, \u0441\u043c. https://www.openhab.org/addons/bindings/miio/#country-servers \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430.", - "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Xiaomi Miio \u0438\u043b\u0438 \u0448\u043b\u044e\u0437\u0443 Xiaomi" + "description": "\u0412\u043e\u0439\u0434\u0438\u0442\u0435 \u0432 \u043e\u0431\u043b\u0430\u043a\u043e Xiaomi Miio, \u0441\u043c. https://www.openhab.org/addons/bindings/miio/#country-servers \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430." }, "connect": { "data": { "model": "\u041c\u043e\u0434\u0435\u043b\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" - }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043c\u043e\u0434\u0435\u043b\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438\u0437 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445.", - "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Xiaomi Miio \u0438\u043b\u0438 \u0448\u043b\u044e\u0437\u0443 Xiaomi" - }, - "device": { - "data": { - "host": "IP-\u0430\u0434\u0440\u0435\u0441", - "model": "\u041c\u043e\u0434\u0435\u043b\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", - "token": "\u0422\u043e\u043a\u0435\u043d API" - }, - "description": "\u0414\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u0422\u043e\u043a\u0435\u043d API. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c: \nhttps://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token.\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0442\u043e\u043a\u0435\u043d \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043b\u044e\u0447\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Xiaomi Aqara.", - "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Xiaomi Miio \u0438\u043b\u0438 \u0448\u043b\u044e\u0437\u0443 Xiaomi" - }, - "gateway": { - "data": { - "host": "IP-\u0430\u0434\u0440\u0435\u0441", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", - "token": "\u0422\u043e\u043a\u0435\u043d API" - }, - "description": "\u0414\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u0422\u043e\u043a\u0435\u043d API. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c: \nhttps://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0442\u043e\u043a\u0435\u043d \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043b\u044e\u0447\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Xiaomi Aqara.", - "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0448\u043b\u044e\u0437\u0443 Xiaomi" + } }, "manual": { "data": { "host": "IP-\u0430\u0434\u0440\u0435\u0441", "token": "\u0422\u043e\u043a\u0435\u043d API" }, - "description": "\u0414\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u0422\u043e\u043a\u0435\u043d API. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c: \nhttps://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0442\u043e\u043a\u0435\u043d \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043b\u044e\u0447\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Xiaomi Aqara.", - "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Xiaomi Miio \u0438\u043b\u0438 \u0448\u043b\u044e\u0437\u0443 Xiaomi" + "description": "\u0414\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u0422\u043e\u043a\u0435\u043d API. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c: \nhttps://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0442\u043e\u043a\u0435\u043d \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043b\u044e\u0447\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Xiaomi Aqara." }, "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 Xiaomi Miio, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d\u044b \u0438\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u043e\u0431\u043b\u0430\u043a\u0430", @@ -70,15 +46,7 @@ "data": { "select_device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Miio" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Xiaomi Miio \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.", - "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Xiaomi Miio \u0438\u043b\u0438 \u0448\u043b\u044e\u0437\u0443 Xiaomi" - }, - "user": { - "data": { - "gateway": "\u0428\u043b\u044e\u0437 Xiaomi" - }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c.", - "title": "Xiaomi Miio" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Xiaomi Miio \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u043b\u0430\u043a\u043e \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432" - }, - "description": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/select.sk.json b/homeassistant/components/xiaomi_miio/translations/select.sk.json new file mode 100644 index 00000000000..8745c700fe6 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/select.sk.json @@ -0,0 +1,7 @@ +{ + "state": { + "xiaomi_miio__led_brightness": { + "off": "Vypnut\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/sk.json b/homeassistant/components/xiaomi_miio/translations/sk.json index 022e4103fb7..fca4223f4ac 100644 --- a/homeassistant/components/xiaomi_miio/translations/sk.json +++ b/homeassistant/components/xiaomi_miio/translations/sk.json @@ -6,16 +6,6 @@ "reauth_successful": "Op\u00e4tovn\u00e9 overenie bolo \u00faspe\u0161n\u00e9" }, "step": { - "device": { - "data": { - "token": "API token" - } - }, - "gateway": { - "data": { - "token": "API token" - } - }, "manual": { "data": { "token": "API token" diff --git a/homeassistant/components/xiaomi_miio/translations/sl.json b/homeassistant/components/xiaomi_miio/translations/sl.json index 472317182bf..8596992fe39 100644 --- a/homeassistant/components/xiaomi_miio/translations/sl.json +++ b/homeassistant/components/xiaomi_miio/translations/sl.json @@ -4,26 +4,7 @@ "already_configured": "Naprava je \u017ee konfigurirana" }, "error": { - "no_device_selected": "Izbrana ni nobena naprava, izberite eno napravo.", "wrong_token": "Napaka kontrolne vsote, napa\u010den \u017eeton" - }, - "step": { - "gateway": { - "data": { - "host": "IP naslov", - "name": "Ime prehoda", - "token": "API \u017eeton" - }, - "description": "Potrebujete API \u017deton, glej https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token za navodila.", - "title": "Pove\u017eite se s prehodom Xiaomi" - }, - "user": { - "data": { - "gateway": "Pove\u017eite se s prehodom Xiaomi" - }, - "description": "Izberite, s katero napravo se \u017eelite povezati.", - "title": "Xiaomi Miio" - } } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/sv.json b/homeassistant/components/xiaomi_miio/translations/sv.json deleted file mode 100644 index fcd6e641091..00000000000 --- a/homeassistant/components/xiaomi_miio/translations/sv.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "config": { - "error": { - "no_device_selected": "Ingen enhet har valts, v\u00e4lj en enhet." - }, - "step": { - "gateway": { - "data": { - "host": "IP-adress", - "name": "Namnet p\u00e5 Gatewayen", - "token": "API Token" - }, - "description": "Du beh\u00f6ver en API token, se https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token f\u00f6r mer instruktioner.", - "title": "Anslut till en Xiaomi Gateway" - }, - "user": { - "data": { - "gateway": "Anslut till en Xiaomi Gateway" - }, - "description": "V\u00e4lj den enhet som du vill ansluta till." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/tr.json b/homeassistant/components/xiaomi_miio/translations/tr.json index 097ccdab6ac..8c81f08ee9f 100644 --- a/homeassistant/components/xiaomi_miio/translations/tr.json +++ b/homeassistant/components/xiaomi_miio/translations/tr.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "Bulut kimlik bilgileri eksik, l\u00fctfen kullan\u0131c\u0131 ad\u0131n\u0131, \u015fifreyi ve \u00fclkeyi girin", "cloud_login_error": "Xiaomi Miio Cloud'da oturum a\u00e7\u0131lamad\u0131, kimlik bilgilerini kontrol edin.", "cloud_no_devices": "Bu Xiaomi Miio bulut hesab\u0131nda cihaz bulunamad\u0131.", - "no_device_selected": "Cihaz se\u00e7ilmedi, l\u00fctfen bir cihaz se\u00e7in.", "unknown_device": "Cihaz modeli bilinmiyor, cihaz yap\u0131land\u0131rma ak\u0131\u015f\u0131n\u0131 kullanarak kurulam\u0131yor.", "wrong_token": "Sa\u011flama toplam\u0131 hatas\u0131, yanl\u0131\u015f anahtar" }, @@ -25,42 +24,19 @@ "cloud_username": "Bulut kullan\u0131c\u0131 ad\u0131", "manual": "Manuel olarak yap\u0131land\u0131r\u0131n (\u00f6nerilmez)" }, - "description": "Xiaomi Miio bulutunda oturum a\u00e7\u0131n, bulut sunucusunun kullanmas\u0131 i\u00e7in https://www.openhab.org/addons/bindings/miio/#country-servers adresine bak\u0131n.", - "title": "Bir Xiaomi Miio Cihaz\u0131na veya Xiaomi A\u011f Ge\u00e7idine Ba\u011flan" + "description": "Xiaomi Miio bulutunda oturum a\u00e7\u0131n, bulut sunucusunun kullanmas\u0131 i\u00e7in https://www.openhab.org/addons/bindings/miio/#country-servers adresine bak\u0131n." }, "connect": { "data": { "model": "Cihaz modeli" - }, - "description": "Desteklenen modellerden cihaz modelini manuel olarak se\u00e7in.", - "title": "Bir Xiaomi Miio Cihaz\u0131na veya Xiaomi A\u011f Ge\u00e7idine Ba\u011flan" - }, - "device": { - "data": { - "host": "IP Adresi", - "model": "Cihaz modeli (Opsiyonel)", - "name": "Cihaz\u0131n ad\u0131", - "token": "API Anahtar\u0131" - }, - "description": "32 karaktere API Anahtar\u0131 , talimatlar i\u00e7in https://www.home-assistant.io/integrations/xiaomi_miio#retriiving-the-access-token adresine bak\u0131n. L\u00fctfen bu API Anahtar\u0131 \u00f6\u011fesinin Xiaomi Aqara entegrasyonu taraf\u0131ndan kullan\u0131lan anahtardan farkl\u0131 oldu\u011funu unutmay\u0131n.", - "title": "Bir Xiaomi Miio Cihaz\u0131na veya Xiaomi A\u011f Ge\u00e7idine Ba\u011flan" - }, - "gateway": { - "data": { - "host": "\u0130p Adresi", - "name": "A\u011f Ge\u00e7idinin Ad\u0131", - "token": "API Belirteci" - }, - "description": "32 karaktere API Anahtar\u0131 , bkz. talimatlar i\u00e7in. L\u00fctfen bu API Anahtar\u0131 \u00f6\u011fesinin Xiaomi Aqara entegrasyonu taraf\u0131ndan kullan\u0131lan anahtardan farkl\u0131 oldu\u011funu unutmay\u0131n.", - "title": "Bir Xiaomi A\u011f Ge\u00e7idine ba\u011flan\u0131n" + } }, "manual": { "data": { "host": "IP Adresi", "token": "API Anahtar\u0131" }, - "description": "32 karaktere API Anahtar\u0131 , talimatlar i\u00e7in https://www.home-assistant.io/integrations/xiaomi_miio#retriiving-the-access-token adresine bak\u0131n. L\u00fctfen bu API Anahtar\u0131 \u00f6\u011fesinin Xiaomi Aqara entegrasyonu taraf\u0131ndan kullan\u0131lan anahtardan farkl\u0131 oldu\u011funu unutmay\u0131n.", - "title": "Bir Xiaomi Miio Cihaz\u0131na veya Xiaomi A\u011f Ge\u00e7idine Ba\u011flan" + "description": "32 karaktere API Anahtar\u0131 , talimatlar i\u00e7in https://www.home-assistant.io/integrations/xiaomi_miio#retriiving-the-access-token adresine bak\u0131n. L\u00fctfen bu API Anahtar\u0131 \u00f6\u011fesinin Xiaomi Aqara entegrasyonu taraf\u0131ndan kullan\u0131lan anahtardan farkl\u0131 oldu\u011funu unutmay\u0131n." }, "reauth_confirm": { "description": "Anahtarlar\u0131 g\u00fcncellemek veya eksik bulut kimlik bilgilerini eklemek i\u00e7in Xiaomi Miio entegrasyonunun hesab\u0131n\u0131z\u0131 yeniden do\u011frulamas\u0131 gerekir.", @@ -70,15 +46,7 @@ "data": { "select_device": "Miio cihaz\u0131" }, - "description": "Kurulumu i\u00e7in Xiaomi Miio cihaz\u0131n\u0131 se\u00e7in.", - "title": "Bir Xiaomi Miio Cihaz\u0131na veya Xiaomi A\u011f Ge\u00e7idine Ba\u011flan" - }, - "user": { - "data": { - "gateway": "Bir Xiaomi A\u011f Ge\u00e7idine ba\u011flan\u0131n" - }, - "description": "Hangi cihaza ba\u011flanmak istedi\u011finizi se\u00e7in.", - "title": "Xiaomi Miio" + "description": "Kurulumu i\u00e7in Xiaomi Miio cihaz\u0131n\u0131 se\u00e7in." } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "Ba\u011fl\u0131 alt cihazlar almak i\u00e7in bulutu kullan\u0131n" - }, - "description": "\u0130ste\u011fe ba\u011fl\u0131 ayarlar\u0131 belirtin", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/uk.json b/homeassistant/components/xiaomi_miio/translations/uk.json index f32105589f6..bf1b8126e38 100644 --- a/homeassistant/components/xiaomi_miio/translations/uk.json +++ b/homeassistant/components/xiaomi_miio/translations/uk.json @@ -5,27 +5,8 @@ "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0442\u0440\u0438\u0432\u0430\u0454." }, "error": { - "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", - "no_device_selected": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0434\u0438\u043d \u0437 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432." + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" }, - "flow_title": "Xiaomi Miio: {name}", - "step": { - "gateway": { - "data": { - "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", - "name": "\u041d\u0430\u0437\u0432\u0430", - "token": "\u0422\u043e\u043a\u0435\u043d API" - }, - "description": "\u0414\u043b\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u0438\u0439 \u0422\u043e\u043a\u0435\u043d API . \u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0442\u043e\u043a\u0435\u043d, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0442\u0443\u0442:\nhttps://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.\n\u0417\u0432\u0435\u0440\u043d\u0456\u0442\u044c \u0443\u0432\u0430\u0433\u0443, \u0449\u043e \u0446\u0435\u0439 \u0442\u043e\u043a\u0435\u043d \u0432\u0456\u0434\u0440\u0456\u0437\u043d\u044f\u0454\u0442\u044c\u0441\u044f \u0432\u0456\u0434 \u043a\u043b\u044e\u0447\u0430, \u044f\u043a\u0438\u0439 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 Xiaomi Aqara.", - "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0448\u043b\u044e\u0437\u0443 Xiaomi" - }, - "user": { - "data": { - "gateway": "\u0428\u043b\u044e\u0437 Xiaomi" - }, - "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u044f\u043a\u0438\u0439 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438.", - "title": "Xiaomi Miio" - } - } + "flow_title": "Xiaomi Miio: {name}" } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hans.json b/homeassistant/components/xiaomi_miio/translations/zh-Hans.json index f488618b945..0dda09965fd 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hans.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hans.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "\u4e91\u7aef\u51ed\u636e\u4e0d\u5b8c\u6574\uff0c\u8bf7\u8f93\u5165\u7528\u6237\u540d\u3001\u5bc6\u7801\u548c\u56fd\u5bb6/\u5730\u533a", "cloud_login_error": "\u65e0\u6cd5\u767b\u5f55\u5c0f\u7c73\u4e91\u670d\u52a1\uff0c\u8bf7\u68c0\u67e5\u51ed\u636e\u3002", "cloud_no_devices": "\u672a\u5728\u5c0f\u7c73\u5e10\u6237\u4e2d\u53d1\u73b0\u8bbe\u5907\u3002", - "no_device_selected": "\u672a\u9009\u62e9\u8bbe\u5907\uff0c\u8bf7\u9009\u62e9\u4e00\u4e2a\u8bbe\u5907\u3002", "unknown_device": "\u8be5\u8bbe\u5907\u578b\u53f7\u6682\u672a\u9002\u914d\uff0c\u56e0\u6b64\u65e0\u6cd5\u901a\u8fc7\u914d\u7f6e\u5411\u5bfc\u6dfb\u52a0\u8bbe\u5907\u3002", "wrong_token": "\u6821\u9a8c\u548c\u9519\u8bef\uff0ctoken \u9519\u8bef" }, @@ -25,34 +24,12 @@ "cloud_username": "\u7528\u6237\u540d", "manual": "\u624b\u52a8\u914d\u7f6e\uff08\u4e0d\u63a8\u8350\uff09" }, - "description": "\u767b\u5f55\u5c0f\u7c73\u4e91\u670d\u52a1\u3002\u6709\u5173\u56fd\u5bb6/\u5730\u533a\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 https://www.openhab.org/addons/bindings/miio/#country-servers \u3002", - "title": "\u8fde\u63a5\u5230\u5c0f\u7c73 Miio \u8bbe\u5907\u6216\u5c0f\u7c73\u7f51\u5173" + "description": "\u767b\u5f55\u5c0f\u7c73\u4e91\u670d\u52a1\u3002\u6709\u5173\u56fd\u5bb6/\u5730\u533a\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 https://www.openhab.org/addons/bindings/miio/#country-servers \u3002" }, "connect": { "data": { "model": "\u8bbe\u5907 model" - }, - "description": "\u4ece\u652f\u6301\u7684\u578b\u53f7\u4e2d\u624b\u52a8\u9009\u62e9\u3002", - "title": "\u8fde\u63a5\u5230\u5c0f\u7c73 Miio \u8bbe\u5907\u6216\u5c0f\u7c73\u7f51\u5173" - }, - "device": { - "data": { - "host": "IP \u5730\u5740", - "model": "\u8bbe\u5907 model\uff08\u53ef\u9009\uff09", - "name": "\u8bbe\u5907\u540d\u79f0", - "token": "API Token" - }, - "description": "\u60a8\u9700\u8981\u83b7\u53d6\u4e00\u4e2a 32 \u4f4d\u7684 API Token\u3002\u5982\u9700\u5e2e\u52a9\uff0c\u8bf7\u53c2\u9605\u4ee5\u4e0b\u94fe\u63a5: https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u3002\u8bf7\u6ce8\u610f\u6b64 token \u4e0d\u540c\u4e8e\u201cXiaomi Aqara\u201d\u96c6\u6210\u6240\u9700\u7684 key\u3002", - "title": "\u8fde\u63a5\u5230\u5c0f\u7c73 Miio \u8bbe\u5907\u6216\u5c0f\u7c73\u7f51\u5173" - }, - "gateway": { - "data": { - "host": "IP \u5730\u5740", - "name": "\u7f51\u5173\u540d\u79f0", - "token": "API Token" - }, - "description": "\u60a8\u9700\u8981\u83b7\u53d6\u4e00\u4e2a 32 \u4f4d\u7684 API Token\u3002\u5982\u9700\u5e2e\u52a9\uff0c\u8bf7\u53c2\u9605\u4ee5\u4e0b\u94fe\u63a5: https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u3002\u8bf7\u6ce8\u610f\u6b64 token \u4e0d\u540c\u4e8e\u201cXiaomi Aqara\u201d\u96c6\u6210\u6240\u9700\u7684 key\u3002", - "title": "\u8fde\u63a5\u5230\u5c0f\u7c73\u7f51\u5173" + } }, "manual": { "data": { @@ -69,15 +46,7 @@ "data": { "select_device": "Miio \u8bbe\u5907" }, - "description": "\u9009\u62e9\u8981\u6dfb\u52a0\u7684\u5c0f\u7c73 Miio \u8bbe\u5907\u3002", - "title": "\u8fde\u63a5\u5230\u5c0f\u7c73 Miio \u8bbe\u5907\u6216\u5c0f\u7c73\u7f51\u5173" - }, - "user": { - "data": { - "gateway": "\u8fde\u63a5\u5230\u5c0f\u7c73\u7f51\u5173" - }, - "description": "\u8bf7\u9009\u62e9\u8981\u8fde\u63a5\u7684\u8bbe\u5907\u3002", - "title": "Xiaomi Miio" + "description": "\u9009\u62e9\u8981\u6dfb\u52a0\u7684\u5c0f\u7c73 Miio \u8bbe\u5907\u3002" } } }, @@ -89,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "\u901a\u8fc7\u4e91\u7aef\u83b7\u53d6\u8fde\u63a5\u7684\u5b50\u8bbe\u5907" - }, - "description": "\u6307\u5b9a\u53ef\u9009\u8bbe\u7f6e", - "title": "Xiaomi Miio" + } } } } diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json index 2812f91be7e..e38567ea37c 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -12,7 +12,6 @@ "cloud_credentials_incomplete": "\u96f2\u7aef\u6191\u8b49\u672a\u5b8c\u6210\uff0c\u8acb\u586b\u5beb\u4f7f\u7528\u8005\u540d\u7a31\u3001\u5bc6\u78bc\u8207\u570b\u5bb6", "cloud_login_error": "\u7121\u6cd5\u767b\u5165\u5c0f\u7c73 Miio \u96f2\u670d\u52d9\uff0c\u8acb\u6aa2\u67e5\u6191\u8b49\u3002", "cloud_no_devices": "\u5c0f\u7c73 Miio \u96f2\u7aef\u5e33\u865f\u672a\u627e\u5230\u4efb\u4f55\u88dd\u7f6e\u3002", - "no_device_selected": "\u672a\u9078\u64c7\u88dd\u7f6e\uff0c\u8acb\u9078\u64c7\u4e00\u9805\u88dd\u7f6e\u3002", "unknown_device": "\u88dd\u7f6e\u578b\u865f\u672a\u77e5\uff0c\u7121\u6cd5\u4f7f\u7528\u8a2d\u5b9a\u6d41\u7a0b\u3002", "wrong_token": "\u6838\u5c0d\u548c\u932f\u8aa4\u3001\u6b0a\u6756\u932f\u8aa4" }, @@ -25,42 +24,19 @@ "cloud_username": "\u96f2\u7aef\u670d\u52d9\u4f7f\u7528\u8005\u540d\u7a31", "manual": "\u624b\u52d5\u8a2d\u5b9a (\u4e0d\u5efa\u8b70)" }, - "description": "\u767b\u5165\u81f3\u5c0f\u7c73 Miio \u96f2\u670d\u52d9\uff0c\u8acb\u53c3\u95b1 https://www.openhab.org/addons/bindings/miio/#country-servers \u4ee5\u4e86\u89e3\u9078\u64c7\u54ea\u4e00\u7d44\u96f2\u7aef\u4f3a\u670d\u5668\u3002", - "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" + "description": "\u767b\u5165\u81f3\u5c0f\u7c73 Miio \u96f2\u670d\u52d9\uff0c\u8acb\u53c3\u95b1 https://www.openhab.org/addons/bindings/miio/#country-servers \u4ee5\u4e86\u89e3\u9078\u64c7\u54ea\u4e00\u7d44\u96f2\u7aef\u4f3a\u670d\u5668\u3002" }, "connect": { "data": { "model": "\u88dd\u7f6e\u578b\u865f" - }, - "description": "\u5f9e\u652f\u63f4\u7684\u578b\u865f\u4e2d\u624b\u52d5\u9078\u64c7\u88dd\u7f6e\u578b\u865f\u3002", - "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" - }, - "device": { - "data": { - "host": "IP \u4f4d\u5740", - "model": "\u88dd\u7f6e\u578b\u865f\uff08\u9078\u9805\uff09", - "name": "\u88dd\u7f6e\u540d\u7a31", - "token": "API \u6b0a\u6756" - }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64 API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", - "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" - }, - "gateway": { - "data": { - "host": "IP \u4f4d\u5740", - "name": "\u7db2\u95dc\u540d\u7a31", - "token": "API \u6b0a\u6756" - }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", - "title": "\u9023\u7dda\u81f3\u5c0f\u7c73\u7db2\u95dc" + } }, "manual": { "data": { "host": "IP \u4f4d\u5740", "token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u91d1\u9470\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64 API \u6b0a\u6756\u8207\u5c0f\u7c73 Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u91d1\u9470\u4e0d\u540c\u3002", - "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" + "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u91d1\u9470\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64 API \u6b0a\u6756\u8207\u5c0f\u7c73 Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u91d1\u9470\u4e0d\u540c\u3002" }, "reauth_confirm": { "description": "\u5c0f\u7c73 Miio \u6574\u5408\u9700\u8981\u91cd\u65b0\u8a8d\u8b49\u60a8\u7684\u5e33\u865f\u3001\u65b9\u80fd\u66f4\u65b0\u6b0a\u6756\u6216\u65b0\u589e\u907a\u5931\u7684\u96f2\u7aef\u6191\u8b49\u3002", @@ -70,15 +46,7 @@ "data": { "select_device": "Miio \u88dd\u7f6e" }, - "description": "\u9078\u64c7\u6240\u8981\u8a2d\u5b9a\u7684 \u5c0f\u7c73 Miio \u88dd\u7f6e\u3002", - "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" - }, - "user": { - "data": { - "gateway": "\u9023\u7dda\u81f3\u5c0f\u7c73\u7db2\u95dc" - }, - "description": "\u9078\u64c7\u6240\u8981\u9023\u7dda\u7684\u88dd\u7f6e\u3002", - "title": "\u5c0f\u7c73 Miio" + "description": "\u9078\u64c7\u6240\u8981\u8a2d\u5b9a\u7684 \u5c0f\u7c73 Miio \u88dd\u7f6e\u3002" } } }, @@ -90,9 +58,7 @@ "init": { "data": { "cloud_subdevices": "\u4f7f\u7528\u96f2\u7aef\u53d6\u5f97\u9023\u7dda\u5b50\u88dd\u7f6e" - }, - "description": "\u6307\u5b9a\u9078\u9805\u8a2d\u5b9a", - "title": "\u5c0f\u7c73 Miio" + } } } } diff --git a/homeassistant/components/yale_smart_alarm/translations/es.json b/homeassistant/components/yale_smart_alarm/translations/es.json index 4df58fda1b7..e9c24aab7f2 100644 --- a/homeassistant/components/yale_smart_alarm/translations/es.json +++ b/homeassistant/components/yale_smart_alarm/translations/es.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "La cuenta ya ha sido configurada", + "already_configured": "La cuenta ya est\u00e1 configurada", "reauth_successful": "La reautenticaci\u00f3n fue exitosa" }, "error": { + "cannot_connect": "Fall\u00f3 la conexi\u00f3n", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" }, "step": { diff --git a/homeassistant/components/yale_smart_alarm/translations/ko.json b/homeassistant/components/yale_smart_alarm/translations/ko.json new file mode 100644 index 00000000000..b213fdda32c --- /dev/null +++ b/homeassistant/components/yale_smart_alarm/translations/ko.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "lock_code_digits": "\ud540\ucf54\ub4dc \uc22b\uc790 \uac1c\uc218" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yale_smart_alarm/translations/nl.json b/homeassistant/components/yale_smart_alarm/translations/nl.json index 04dbe9245c1..15956572847 100644 --- a/homeassistant/components/yale_smart_alarm/translations/nl.json +++ b/homeassistant/components/yale_smart_alarm/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "reauth_successful": "Herauthenticatie was succesvol" + "reauth_successful": "Herauthenticatie geslaagd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -11,7 +11,7 @@ "step": { "reauth_confirm": { "data": { - "area_id": "Area ID", + "area_id": "Area-ID", "name": "Naam", "password": "Wachtwoord", "username": "Gebruikersnaam" @@ -19,7 +19,7 @@ }, "user": { "data": { - "area_id": "Area ID", + "area_id": "Area-ID", "name": "Naam", "password": "Wachtwoord", "username": "Gebruikersnaam" diff --git a/homeassistant/components/yale_smart_alarm/translations/sk.json b/homeassistant/components/yale_smart_alarm/translations/sk.json index 00dddc88d1d..6130380de31 100644 --- a/homeassistant/components/yale_smart_alarm/translations/sk.json +++ b/homeassistant/components/yale_smart_alarm/translations/sk.json @@ -9,7 +9,8 @@ "step": { "reauth_confirm": { "data": { - "name": "N\u00e1zov" + "name": "N\u00e1zov", + "password": "Heslo" } }, "user": { diff --git a/homeassistant/components/yamaha_musiccast/media_player.py b/homeassistant/components/yamaha_musiccast/media_player.py index d0141977f29..954942b2c6b 100644 --- a/homeassistant/components/yamaha_musiccast/media_player.py +++ b/homeassistant/components/yamaha_musiccast/media_player.py @@ -275,7 +275,9 @@ class MusicCastMediaPlayer(MusicCastDeviceEntity, MediaPlayerEntity): async def async_play_media(self, media_type: str, media_id: str, **kwargs) -> None: """Play media.""" if media_source.is_media_source_id(media_id): - play_item = await media_source.async_resolve_media(self.hass, media_id) + play_item = await media_source.async_resolve_media( + self.hass, media_id, self.entity_id + ) media_id = play_item.url if self.state == STATE_OFF: diff --git a/homeassistant/components/yamaha_musiccast/translations/ko.json b/homeassistant/components/yamaha_musiccast/translations/ko.json new file mode 100644 index 00000000000..20ad990e862 --- /dev/null +++ b/homeassistant/components/yamaha_musiccast/translations/ko.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yamaha_musiccast/translations/nl.json b/homeassistant/components/yamaha_musiccast/translations/nl.json index 8cb8265a1f0..e1e31149c06 100644 --- a/homeassistant/components/yamaha_musiccast/translations/nl.json +++ b/homeassistant/components/yamaha_musiccast/translations/nl.json @@ -10,7 +10,7 @@ "flow_title": "MusicCast: {name}", "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" }, "user": { "data": { diff --git a/homeassistant/components/yamaha_musiccast/translations/select.fr.json b/homeassistant/components/yamaha_musiccast/translations/select.fr.json index f8ce2da31a7..a12e6c3f26c 100644 --- a/homeassistant/components/yamaha_musiccast/translations/select.fr.json +++ b/homeassistant/components/yamaha_musiccast/translations/select.fr.json @@ -41,7 +41,7 @@ "dts_neo6_cinema": "DTS Neo:6 Cin\u00e9ma", "dts_neo6_music": "DTS Neo:6 Musique", "dts_neural_x": "DTS Neural:X", - "toggle": "Permuter" + "toggle": "Basculer" }, "yamaha_musiccast__zone_tone_control_mode": { "auto": "Auto", diff --git a/homeassistant/components/yamaha_musiccast/translations/sk.json b/homeassistant/components/yamaha_musiccast/translations/sk.json new file mode 100644 index 00000000000..f74ba4b46d2 --- /dev/null +++ b/homeassistant/components/yamaha_musiccast/translations/sk.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Zariadenie je u\u017e nakonfigurovan\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 3df38a41995..5ec224498be 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.29.0"], + "requirements": ["yeelight==0.7.10", "async-upnp-client==0.30.1"], "codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"], "config_flow": true, "dependencies": ["network"], diff --git a/homeassistant/components/yeelight/translations/ar.json b/homeassistant/components/yeelight/translations/ar.json index e4146138625..5838a65a334 100644 --- a/homeassistant/components/yeelight/translations/ar.json +++ b/homeassistant/components/yeelight/translations/ar.json @@ -12,8 +12,7 @@ "init": { "data": { "use_music_mode": "\u062a\u0645\u0643\u064a\u0646 \u0648\u0636\u0639 \u0627\u0644\u0645\u0648\u0633\u064a\u0642\u0649" - }, - "description": "\u0625\u0630\u0627 \u062a\u0631\u0643\u062a \u0627\u0644\u0646\u0645\u0648\u0630\u062c \u0641\u0627\u0631\u063a\u064b\u0627 \u060c \u0641\u0633\u064a\u062a\u0645 \u0627\u0643\u062a\u0634\u0627\u0641\u0647 \u062a\u0644\u0642\u0627\u0626\u064a\u064b\u0627." + } } } } diff --git a/homeassistant/components/yeelight/translations/bg.json b/homeassistant/components/yeelight/translations/bg.json index a53214e40e4..4a962bbf3d0 100644 --- a/homeassistant/components/yeelight/translations/bg.json +++ b/homeassistant/components/yeelight/translations/bg.json @@ -26,8 +26,7 @@ "init": { "data": { "model": "\u041c\u043e\u0434\u0435\u043b (\u043f\u043e \u0438\u0437\u0431\u043e\u0440)" - }, - "description": "\u0410\u043a\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u0435 \u043c\u043e\u0434\u0435\u043b\u0430 \u043f\u0440\u0430\u0437\u0435\u043d, \u0442\u043e\u0439 \u0449\u0435 \u0431\u044a\u0434\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0440\u0430\u0437\u043f\u043e\u0437\u043d\u0430\u0442." + } } } } diff --git a/homeassistant/components/yeelight/translations/ca.json b/homeassistant/components/yeelight/translations/ca.json index 0b62bebf1be..882c118b824 100644 --- a/homeassistant/components/yeelight/translations/ca.json +++ b/homeassistant/components/yeelight/translations/ca.json @@ -34,8 +34,7 @@ "save_on_change": "Desa l'estat en canviar", "transition": "Temps de transici\u00f3 (ms)", "use_music_mode": "Activa el mode M\u00fasica" - }, - "description": "Si deixes el model buit, es detectar\u00e0 autom\u00e0ticament." + } } } } diff --git a/homeassistant/components/yeelight/translations/cs.json b/homeassistant/components/yeelight/translations/cs.json index 2a3084fd3eb..5f91548bd4f 100644 --- a/homeassistant/components/yeelight/translations/cs.json +++ b/homeassistant/components/yeelight/translations/cs.json @@ -31,8 +31,7 @@ "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" - }, - "description": "Pokud ponech\u00e1te model pr\u00e1zdn\u00fd, bude automaticky rozpozn\u00e1n." + } } } } diff --git a/homeassistant/components/yeelight/translations/de.json b/homeassistant/components/yeelight/translations/de.json index 5108b075968..a2323edbbdb 100644 --- a/homeassistant/components/yeelight/translations/de.json +++ b/homeassistant/components/yeelight/translations/de.json @@ -34,8 +34,7 @@ "save_on_change": "Status bei \u00c4nderung speichern", "transition": "\u00dcbergangszeit (ms)", "use_music_mode": "Musik-Modus aktivieren" - }, - "description": "Wenn du das Modell leer l\u00e4sst, wird es automatisch erkannt." + } } } } diff --git a/homeassistant/components/yeelight/translations/el.json b/homeassistant/components/yeelight/translations/el.json index 8b6c0ee2b9c..e69edd2cf74 100644 --- a/homeassistant/components/yeelight/translations/el.json +++ b/homeassistant/components/yeelight/translations/el.json @@ -34,8 +34,7 @@ "save_on_change": "\u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae", "transition": "\u03a7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03bc\u03b5\u03c4\u03ac\u03b2\u03b1\u03c3\u03b7\u03c2 (ms)", "use_music_mode": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03bc\u03bf\u03c5\u03c3\u03b9\u03ba\u03ae\u03c2" - }, - "description": "\u0395\u03ac\u03bd \u03b1\u03c6\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03bc\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf \u03ba\u03b5\u03bd\u03cc, \u03b8\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03c4\u03b5\u03af \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b1." + } } } } diff --git a/homeassistant/components/yeelight/translations/en.json b/homeassistant/components/yeelight/translations/en.json index 5e452dcf098..b49e3d7658e 100644 --- a/homeassistant/components/yeelight/translations/en.json +++ b/homeassistant/components/yeelight/translations/en.json @@ -34,8 +34,7 @@ "save_on_change": "Save Status On Change", "transition": "Transition Time (ms)", "use_music_mode": "Enable Music Mode" - }, - "description": "If you leave model empty, it will be automatically detected." + } } } } diff --git a/homeassistant/components/yeelight/translations/es.json b/homeassistant/components/yeelight/translations/es.json index bc9f26f665a..ef93c17e72c 100644 --- a/homeassistant/components/yeelight/translations/es.json +++ b/homeassistant/components/yeelight/translations/es.json @@ -34,8 +34,7 @@ "save_on_change": "Guardar estado al cambiar", "transition": "Tiempo de transici\u00f3n (ms)", "use_music_mode": "Activar el Modo M\u00fasica" - }, - "description": "Si dejas el modelo vac\u00edo, se detectar\u00e1 autom\u00e1ticamente." + } } } } diff --git a/homeassistant/components/yeelight/translations/et.json b/homeassistant/components/yeelight/translations/et.json index 70fdc7c8dbf..b994119bb46 100644 --- a/homeassistant/components/yeelight/translations/et.json +++ b/homeassistant/components/yeelight/translations/et.json @@ -34,8 +34,7 @@ "save_on_change": "Salvesta olek peale muutmist", "transition": "\u00dclemineku aeg (ms)", "use_music_mode": "Luba muusikare\u017eiim" - }, - "description": "Kui j\u00e4tad mudeli m\u00e4\u00e4ramata tuvastatakse see automaatselt." + } } } } diff --git a/homeassistant/components/yeelight/translations/fr.json b/homeassistant/components/yeelight/translations/fr.json index a319f15e36a..5b443a45530 100644 --- a/homeassistant/components/yeelight/translations/fr.json +++ b/homeassistant/components/yeelight/translations/fr.json @@ -34,8 +34,7 @@ "save_on_change": "Enregistrer l'\u00e9tat lors d'un changement", "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/yeelight/translations/he.json b/homeassistant/components/yeelight/translations/he.json index 0fe8dd0f06e..94e0e87d87a 100644 --- a/homeassistant/components/yeelight/translations/he.json +++ b/homeassistant/components/yeelight/translations/he.json @@ -34,8 +34,7 @@ "save_on_change": "\u05e9\u05de\u05d5\u05e8 \u05e1\u05d8\u05d8\u05d5\u05e1 \u05d1\u05e9\u05d9\u05e0\u05d5\u05d9", "transition": "\u05d6\u05de\u05df \u05de\u05e2\u05d1\u05e8 (\u05d0\u05dc\u05e4\u05d9\u05d5\u05ea \u05e9\u05e0\u05d9\u05d4)", "use_music_mode": "\u05d4\u05e4\u05e2\u05dc\u05ea \u05de\u05e6\u05d1 \u05de\u05d5\u05e1\u05d9\u05e7\u05d4" - }, - "description": "\u05d0\u05dd \u05ea\u05e9\u05d0\u05d9\u05e8 \u05d0\u05ea \u05d4\u05d3\u05d2\u05dd \u05e8\u05d9\u05e7, \u05d4\u05d5\u05d0 \u05d9\u05d6\u05d5\u05d4\u05d4 \u05d1\u05d0\u05d5\u05e4\u05df \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9." + } } } } diff --git a/homeassistant/components/yeelight/translations/hu.json b/homeassistant/components/yeelight/translations/hu.json index 6cf10422c28..b3969a44f99 100644 --- a/homeassistant/components/yeelight/translations/hu.json +++ b/homeassistant/components/yeelight/translations/hu.json @@ -34,8 +34,7 @@ "save_on_change": "\u00c1llapot ment\u00e9se m\u00f3dos\u00edt\u00e1s ut\u00e1n", "transition": "\u00c1tmeneti id\u0151 (ms)", "use_music_mode": "Zene m\u00f3d enged\u00e9lyez\u00e9se" - }, - "description": "Ha modellt \u00fcresen hagyja, a rendszer automatikusan \u00e9rz\u00e9keli." + } } } } diff --git a/homeassistant/components/yeelight/translations/id.json b/homeassistant/components/yeelight/translations/id.json index 19537d658a1..9209e2d917b 100644 --- a/homeassistant/components/yeelight/translations/id.json +++ b/homeassistant/components/yeelight/translations/id.json @@ -34,8 +34,7 @@ "save_on_change": "Simpan Status Saat Berubah", "transition": "Waktu Transisi (milidetik)", "use_music_mode": "Aktifkan Mode Musik" - }, - "description": "Jika model dibiarkan kosong, model akan dideteksi secara otomatis." + } } } } diff --git a/homeassistant/components/yeelight/translations/it.json b/homeassistant/components/yeelight/translations/it.json index 7022a016ce8..25a56dfb6c2 100644 --- a/homeassistant/components/yeelight/translations/it.json +++ b/homeassistant/components/yeelight/translations/it.json @@ -34,8 +34,7 @@ "save_on_change": "Salva stato su modifica", "transition": "Tempo di transizione (ms)", "use_music_mode": "Abilita la modalit\u00e0 musica" - }, - "description": "Se lasci il modello vuoto, sar\u00e0 rilevato automaticamente." + } } } } diff --git a/homeassistant/components/yeelight/translations/ja.json b/homeassistant/components/yeelight/translations/ja.json index e032979dcee..5a290043963 100644 --- a/homeassistant/components/yeelight/translations/ja.json +++ b/homeassistant/components/yeelight/translations/ja.json @@ -21,7 +21,7 @@ "data": { "host": "\u30db\u30b9\u30c8" }, - "description": "\u30db\u30b9\u30c8\u3092\u7a7a\u306b\u3057\u3066\u304a\u304f\u3068\u3001\u30c7\u30a3\u30b9\u30ab\u30d0\u30ea\u30fc\u3092\u4f7f\u3063\u3066\u30c7\u30d0\u30a4\u30b9\u3092\u691c\u7d22\u3057\u307e\u3059\u3002" + "description": "\u30db\u30b9\u30c8\u3092\u7a7a\u767d\u306b\u3057\u3066\u304a\u304f\u3068\u3001\u30c7\u30a3\u30b9\u30ab\u30d0\u30ea\u30fc\u3092\u4f7f\u3063\u3066\u30c7\u30d0\u30a4\u30b9\u3092\u691c\u7d22\u3057\u307e\u3059\u3002" } } }, @@ -34,8 +34,7 @@ "save_on_change": "\u5909\u66f4\u6642\u306b\u30b9\u30c6\u30fc\u30bf\u30b9\u3092\u4fdd\u5b58", "transition": "\u9077\u79fb\u6642\u9593(Transition Time)(ms)", "use_music_mode": "\u97f3\u697d\u30e2\u30fc\u30c9\u3092\u6709\u52b9\u306b\u3059\u308b" - }, - "description": "\u30e2\u30c7\u30eb\u3092\u7a7a\u306b\u3057\u3066\u304a\u304f\u3068\u3001\u81ea\u52d5\u7684\u306b\u691c\u51fa\u3055\u308c\u307e\u3059\u3002" + } } } } diff --git a/homeassistant/components/yeelight/translations/ko.json b/homeassistant/components/yeelight/translations/ko.json index 4abb8fcbbff..d12c41b423e 100644 --- a/homeassistant/components/yeelight/translations/ko.json +++ b/homeassistant/components/yeelight/translations/ko.json @@ -7,7 +7,11 @@ "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "{model} {id} ({host})", "step": { + "discovery_confirm": { + "description": "{model} ( {host} )\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + }, "pick_device": { "data": { "device": "\uae30\uae30" @@ -30,8 +34,7 @@ "save_on_change": "\ubcc0\uacbd \uc2dc \uc0c1\ud0dc\ub97c \uc800\uc7a5\ud558\uae30", "transition": "\uc804\ud658 \uc2dc\uac04(ms)", "use_music_mode": "\uc74c\uc545 \ubaa8\ub4dc \ud65c\uc131\ud654\ud558\uae30" - }, - "description": "\ubaa8\ub378\uc744 \ube44\uc6cc \ub450\uba74 \uc790\ub3d9\uc73c\ub85c \uac80\uc0c9\ub429\ub2c8\ub2e4." + } } } } diff --git a/homeassistant/components/yeelight/translations/lb.json b/homeassistant/components/yeelight/translations/lb.json index 482cbf23e48..5ffa8e23a18 100644 --- a/homeassistant/components/yeelight/translations/lb.json +++ b/homeassistant/components/yeelight/translations/lb.json @@ -30,8 +30,7 @@ "save_on_change": "Status sp\u00e4icheren bei \u00c4nnerung", "transition": "Iwwergangsz\u00e4it (ms)", "use_music_mode": "Musek Modus aktiv\u00e9ieren" - }, - "description": "Falls Modell eidel gelass g\u00ebtt, g\u00ebtt et automatesch erkannt." + } } } } diff --git a/homeassistant/components/yeelight/translations/nl.json b/homeassistant/components/yeelight/translations/nl.json index a83ef72695c..540191e9222 100644 --- a/homeassistant/components/yeelight/translations/nl.json +++ b/homeassistant/components/yeelight/translations/nl.json @@ -5,7 +5,7 @@ "no_devices_found": "Geen apparaten gevonden op het netwerk" }, "error": { - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kan geen verbinding maken" }, "flow_title": "{model} {id} ({host})", "step": { @@ -34,8 +34,7 @@ "save_on_change": "Bewaar status bij wijziging", "transition": "Overgangstijd (ms)", "use_music_mode": "Schakel de muziekmodus in" - }, - "description": "Als u model leeg laat, wordt het automatisch gedetecteerd." + } } } } diff --git a/homeassistant/components/yeelight/translations/no.json b/homeassistant/components/yeelight/translations/no.json index ea4436d7769..a7ef2e05a5d 100644 --- a/homeassistant/components/yeelight/translations/no.json +++ b/homeassistant/components/yeelight/translations/no.json @@ -34,8 +34,7 @@ "save_on_change": "Lagre status ved endring", "transition": "Overgangstid (ms)", "use_music_mode": "Aktiver musikkmodus" - }, - "description": "Hvis du lar modellen v\u00e6re tom, blir den automatisk oppdaget." + } } } } diff --git a/homeassistant/components/yeelight/translations/pl.json b/homeassistant/components/yeelight/translations/pl.json index 818ff0946c4..612fab44a62 100644 --- a/homeassistant/components/yeelight/translations/pl.json +++ b/homeassistant/components/yeelight/translations/pl.json @@ -34,8 +34,7 @@ "save_on_change": "Zachowaj status po zmianie", "transition": "Czas przej\u015bcia (ms)", "use_music_mode": "W\u0142\u0105cz tryb muzyczny" - }, - "description": "Je\u015bli nie podasz modelu urz\u0105dzenia, zostanie on automatycznie wykryty." + } } } } diff --git a/homeassistant/components/yeelight/translations/pt-BR.json b/homeassistant/components/yeelight/translations/pt-BR.json index 2c54af41b25..482733df343 100644 --- a/homeassistant/components/yeelight/translations/pt-BR.json +++ b/homeassistant/components/yeelight/translations/pt-BR.json @@ -34,8 +34,7 @@ "save_on_change": "Salvar status na altera\u00e7\u00e3o", "transition": "Tempo de transi\u00e7\u00e3o (ms)", "use_music_mode": "Ativar o modo de m\u00fasica" - }, - "description": "Se voc\u00ea deixar o modelo vazio, ele ser\u00e1 detectado automaticamente." + } } } } diff --git a/homeassistant/components/yeelight/translations/pt.json b/homeassistant/components/yeelight/translations/pt.json index 6d350188989..b03d5b8fc6b 100644 --- a/homeassistant/components/yeelight/translations/pt.json +++ b/homeassistant/components/yeelight/translations/pt.json @@ -30,8 +30,7 @@ "save_on_change": "Salvar status ao alterar", "transition": "Tempo de transi\u00e7\u00e3o (ms)", "use_music_mode": "Ativar modo de m\u00fasica" - }, - "description": "Se voc\u00ea deixar o modelo vazio, ele ser\u00e1 detectado automaticamente." + } } } } diff --git a/homeassistant/components/yeelight/translations/ru.json b/homeassistant/components/yeelight/translations/ru.json index 70694147baa..88ab0a73735 100644 --- a/homeassistant/components/yeelight/translations/ru.json +++ b/homeassistant/components/yeelight/translations/ru.json @@ -34,8 +34,7 @@ "save_on_change": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438", "transition": "\u0412\u0440\u0435\u043c\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 (\u0432 \u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", "use_music_mode": "\u041c\u0443\u0437\u044b\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c" - }, - "description": "\u0415\u0441\u043b\u0438 \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u0430, \u043e\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438." + } } } } diff --git a/homeassistant/components/yeelight/translations/tr.json b/homeassistant/components/yeelight/translations/tr.json index 4eed4f477b4..21ec60cde68 100644 --- a/homeassistant/components/yeelight/translations/tr.json +++ b/homeassistant/components/yeelight/translations/tr.json @@ -34,8 +34,7 @@ "save_on_change": "De\u011fi\u015fiklikte Durumu Kaydet", "transition": "Ge\u00e7i\u015f S\u00fcresi (ms)", "use_music_mode": "M\u00fczik Modunu Etkinle\u015ftir" - }, - "description": "Modeli bo\u015f b\u0131rak\u0131rsan\u0131z, otomatik olarak alg\u0131lanacakt\u0131r." + } } } } diff --git a/homeassistant/components/yeelight/translations/uk.json b/homeassistant/components/yeelight/translations/uk.json index 0a173ccb6e4..2149302e940 100644 --- a/homeassistant/components/yeelight/translations/uk.json +++ b/homeassistant/components/yeelight/translations/uk.json @@ -30,8 +30,7 @@ "save_on_change": "\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u0442\u0438 \u0441\u0442\u0430\u0442\u0443\u0441 \u043f\u0440\u0438 \u0437\u043c\u0456\u043d\u0456", "transition": "\u0427\u0430\u0441 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0443 (\u0432 \u043c\u0456\u043b\u0456\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", "use_music_mode": "\u041c\u0443\u0437\u0438\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c" - }, - "description": "\u042f\u043a\u0449\u043e \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u0432\u0438\u0431\u0440\u0430\u043d\u043e, \u0432\u043e\u043d\u0430 \u0431\u0443\u0434\u0435 \u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e." + } } } } diff --git a/homeassistant/components/yeelight/translations/zh-Hans.json b/homeassistant/components/yeelight/translations/zh-Hans.json index 36add653d1d..846324d143c 100644 --- a/homeassistant/components/yeelight/translations/zh-Hans.json +++ b/homeassistant/components/yeelight/translations/zh-Hans.json @@ -34,8 +34,7 @@ "save_on_change": "\u4fdd\u5b58\u66f4\u6539\u72b6\u6001", "transition": "\u8fc7\u6e21\u65f6\u95f4\uff08\u6beb\u79d2\uff09", "use_music_mode": "\u542f\u7528\u97f3\u4e50\u6a21\u5f0f" - }, - "description": "\u5982\u679c\u5c06\u4fe1\u53f7\u680f\u7559\u7a7a\uff0c\u96c6\u6210\u5c06\u4f1a\u81ea\u52a8\u68c0\u6d4b\u76f8\u5173\u4fe1\u606f" + } } } } diff --git a/homeassistant/components/yeelight/translations/zh-Hant.json b/homeassistant/components/yeelight/translations/zh-Hant.json index 7601a0b9552..816e2b02937 100644 --- a/homeassistant/components/yeelight/translations/zh-Hant.json +++ b/homeassistant/components/yeelight/translations/zh-Hant.json @@ -34,8 +34,7 @@ "save_on_change": "\u65bc\u8b8a\u66f4\u6642\u5132\u5b58\u72c0\u614b", "transition": "\u8f49\u63db\u6642\u9593\uff08\u6beb\u79d2\uff09", "use_music_mode": "\u958b\u555f\u97f3\u6a02\u6a21\u5f0f" - }, - "description": "\u5047\u5982\u578b\u865f\u6b04\u4f4d\u70ba\u7a7a\u767d\uff0c\u5c07\u6703\u81ea\u52d5\u5075\u6e2c\u578b\u865f\u3002" + } } } } diff --git a/homeassistant/components/yolink/__init__.py b/homeassistant/components/yolink/__init__.py new file mode 100644 index 00000000000..7eb6b0229f0 --- /dev/null +++ b/homeassistant/components/yolink/__init__.py @@ -0,0 +1,112 @@ +"""The yolink integration.""" +from __future__ import annotations + +import asyncio +from datetime import timedelta + +import async_timeout +from yolink.client import YoLinkClient +from yolink.device import YoLinkDevice +from yolink.exception import YoLinkAuthFailError, YoLinkClientError +from yolink.model import BRDP +from yolink.mqtt_client import MqttClient + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow + +from . import api +from .const import ATTR_CLIENT, ATTR_COORDINATORS, ATTR_DEVICE, ATTR_MQTT_CLIENT, DOMAIN +from .coordinator import YoLinkCoordinator + +SCAN_INTERVAL = timedelta(minutes=5) + + +PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SIREN, Platform.SWITCH] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up yolink from a config entry.""" + hass.data.setdefault(DOMAIN, {}) + implementation = ( + await config_entry_oauth2_flow.async_get_config_entry_implementation( + hass, entry + ) + ) + + session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation) + + auth_mgr = api.ConfigEntryAuth( + hass, aiohttp_client.async_get_clientsession(hass), session + ) + + yolink_http_client = YoLinkClient(auth_mgr) + yolink_mqtt_client = MqttClient(auth_mgr) + + def on_message_callback(message: tuple[str, BRDP]) -> None: + data = message[1] + device_id = message[0] + if data.event is None: + return + event_param = data.event.split(".") + event_type = event_param[len(event_param) - 1] + if event_type not in ( + "Report", + "Alert", + "StatusChange", + "getState", + ): + return + resolved_state = data.data + if resolved_state is None: + return + entry_data = hass.data[DOMAIN].get(entry.entry_id) + if entry_data is None: + return + device_coordinators = entry_data.get(ATTR_COORDINATORS) + if device_coordinators is None: + return + device_coordinator = device_coordinators.get(device_id) + if device_coordinator is None: + return + device_coordinator.async_set_updated_data(resolved_state) + + try: + async with async_timeout.timeout(10): + device_response = await yolink_http_client.get_auth_devices() + home_info = await yolink_http_client.get_general_info() + await yolink_mqtt_client.init_home_connection( + home_info.data["id"], on_message_callback + ) + except YoLinkAuthFailError as yl_auth_err: + raise ConfigEntryAuthFailed from yl_auth_err + except (YoLinkClientError, asyncio.TimeoutError) as err: + raise ConfigEntryNotReady from err + + hass.data[DOMAIN][entry.entry_id] = { + ATTR_CLIENT: yolink_http_client, + ATTR_MQTT_CLIENT: yolink_mqtt_client, + } + auth_devices = device_response.data[ATTR_DEVICE] + device_coordinators = {} + for device_info in auth_devices: + device = YoLinkDevice(device_info, yolink_http_client) + device_coordinator = YoLinkCoordinator(hass, device) + try: + await device_coordinator.async_config_entry_first_refresh() + except ConfigEntryNotReady: + # Not failure by fetching device state + device_coordinator.data = {} + device_coordinators[device.device_id] = device_coordinator + hass.data[DOMAIN][entry.entry_id][ATTR_COORDINATORS] = device_coordinators + 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/yolink/api.py b/homeassistant/components/yolink/api.py new file mode 100644 index 00000000000..0991baed23f --- /dev/null +++ b/homeassistant/components/yolink/api.py @@ -0,0 +1,30 @@ +"""API for yolink bound to Home Assistant OAuth.""" +from aiohttp import ClientSession +from yolink.auth_mgr import YoLinkAuthMgr + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow + + +class ConfigEntryAuth(YoLinkAuthMgr): + """Provide yolink authentication tied to an OAuth2 based config entry.""" + + def __init__( + self, + hass: HomeAssistant, + websession: ClientSession, + oauth2Session: config_entry_oauth2_flow.OAuth2Session, + ) -> None: + """Initialize yolink Auth.""" + self.hass = hass + self.oauth_session = oauth2Session + super().__init__(websession) + + def access_token(self) -> str: + """Return the access token.""" + return self.oauth_session.token["access_token"] + + async def check_and_refresh_token(self) -> str: + """Check the token.""" + await self.oauth_session.async_ensure_token_valid() + return self.access_token() diff --git a/homeassistant/components/yolink/application_credentials.py b/homeassistant/components/yolink/application_credentials.py new file mode 100644 index 00000000000..f8378299952 --- /dev/null +++ b/homeassistant/components/yolink/application_credentials.py @@ -0,0 +1,14 @@ +"""Application credentials platform for yolink.""" + +from yolink.const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN + +from homeassistant.components.application_credentials import AuthorizationServer +from homeassistant.core import HomeAssistant + + +async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer: + """Return authorization server.""" + return AuthorizationServer( + authorize_url=OAUTH2_AUTHORIZE, + token_url=OAUTH2_TOKEN, + ) diff --git a/homeassistant/components/yolink/binary_sensor.py b/homeassistant/components/yolink/binary_sensor.py new file mode 100644 index 00000000000..cacba484fe9 --- /dev/null +++ b/homeassistant/components/yolink/binary_sensor.py @@ -0,0 +1,122 @@ +"""YoLink BinarySensor.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from yolink.device import YoLinkDevice + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ( + ATTR_COORDINATORS, + ATTR_DEVICE_DOOR_SENSOR, + ATTR_DEVICE_LEAK_SENSOR, + ATTR_DEVICE_MOTION_SENSOR, + DOMAIN, +) +from .coordinator import YoLinkCoordinator +from .entity import YoLinkEntity + + +@dataclass +class YoLinkBinarySensorEntityDescription(BinarySensorEntityDescription): + """YoLink BinarySensorEntityDescription.""" + + exists_fn: Callable[[YoLinkDevice], bool] = lambda _: True + state_key: str = "state" + value: Callable[[Any], bool | None] = lambda _: None + + +SENSOR_DEVICE_TYPE = [ + ATTR_DEVICE_DOOR_SENSOR, + ATTR_DEVICE_MOTION_SENSOR, + ATTR_DEVICE_LEAK_SENSOR, +] + +SENSOR_TYPES: tuple[YoLinkBinarySensorEntityDescription, ...] = ( + YoLinkBinarySensorEntityDescription( + key="door_state", + icon="mdi:door", + device_class=BinarySensorDeviceClass.DOOR, + name="State", + value=lambda value: value == "open" if value is not None else None, + exists_fn=lambda device: device.device_type in [ATTR_DEVICE_DOOR_SENSOR], + ), + YoLinkBinarySensorEntityDescription( + key="motion_state", + device_class=BinarySensorDeviceClass.MOTION, + name="Motion", + value=lambda value: value == "alert" if value is not None else None, + exists_fn=lambda device: device.device_type in [ATTR_DEVICE_MOTION_SENSOR], + ), + YoLinkBinarySensorEntityDescription( + key="leak_state", + name="Leak", + icon="mdi:water", + device_class=BinarySensorDeviceClass.MOISTURE, + value=lambda value: value == "alert" if value is not None else None, + exists_fn=lambda device: device.device_type in [ATTR_DEVICE_LEAK_SENSOR], + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up YoLink Sensor from a config entry.""" + device_coordinators = hass.data[DOMAIN][config_entry.entry_id][ATTR_COORDINATORS] + binary_sensor_device_coordinators = [ + device_coordinator + for device_coordinator in device_coordinators.values() + if device_coordinator.device.device_type in SENSOR_DEVICE_TYPE + ] + entities = [] + for binary_sensor_device_coordinator in binary_sensor_device_coordinators: + for description in SENSOR_TYPES: + if description.exists_fn(binary_sensor_device_coordinator.device): + entities.append( + YoLinkBinarySensorEntity( + binary_sensor_device_coordinator, description + ) + ) + async_add_entities(entities) + + +class YoLinkBinarySensorEntity(YoLinkEntity, BinarySensorEntity): + """YoLink Sensor Entity.""" + + entity_description: YoLinkBinarySensorEntityDescription + + def __init__( + self, + coordinator: YoLinkCoordinator, + description: YoLinkBinarySensorEntityDescription, + ) -> None: + """Init YoLink Sensor.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = ( + f"{coordinator.device.device_id} {self.entity_description.key}" + ) + self._attr_name = ( + f"{coordinator.device.device_name} ({self.entity_description.name})" + ) + + @callback + def update_entity_state(self, state: dict[str, Any]) -> None: + """Update HA Entity State.""" + self._attr_is_on = self.entity_description.value( + state.get(self.entity_description.state_key) + ) + self.async_write_ha_state() diff --git a/homeassistant/components/yolink/config_flow.py b/homeassistant/components/yolink/config_flow.py new file mode 100644 index 00000000000..35a4c4ebea8 --- /dev/null +++ b/homeassistant/components/yolink/config_flow.py @@ -0,0 +1,63 @@ +"""Config flow for yolink.""" +from __future__ import annotations + +import logging +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.data_entry_flow import FlowResult +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 yolink OAuth2 authentication.""" + + DOMAIN = DOMAIN + _reauth_entry: ConfigEntry | None = None + + @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.""" + scopes = ["create"] + return {"scope": " ".join(scopes)} + + async def async_step_reauth(self, user_input=None) -> FlowResult: + """Perform reauth upon an API authentication error.""" + self._reauth_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=None) -> FlowResult: + """Dialog that informs the user that reauth is required.""" + if user_input is None: + return self.async_show_form(step_id="reauth_confirm") + return await self.async_step_user() + + async def async_oauth_create_entry(self, data: dict) -> FlowResult: + """Create an oauth config entry or update existing entry for reauth.""" + if existing_entry := self._reauth_entry: + self.hass.config_entries.async_update_entry( + existing_entry, data=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="YoLink", data=data) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow start.""" + existing_entry = await self.async_set_unique_id(DOMAIN) + if existing_entry and not self._reauth_entry: + return self.async_abort(reason="already_configured") + return await super().async_step_user(user_input) diff --git a/homeassistant/components/yolink/const.py b/homeassistant/components/yolink/const.py new file mode 100644 index 00000000000..97252c5c989 --- /dev/null +++ b/homeassistant/components/yolink/const.py @@ -0,0 +1,21 @@ +"""Constants for the yolink integration.""" + +DOMAIN = "yolink" +MANUFACTURER = "YoLink" +HOME_ID = "homeId" +HOME_SUBSCRIPTION = "home_subscription" +ATTR_PLATFORM_SENSOR = "sensor" +ATTR_COORDINATORS = "coordinators" +ATTR_DEVICE = "devices" +ATTR_DEVICE_TYPE = "type" +ATTR_DEVICE_NAME = "name" +ATTR_DEVICE_STATE = "state" +ATTR_CLIENT = "client" +ATTR_MQTT_CLIENT = "mqtt_client" +ATTR_DEVICE_ID = "deviceId" +ATTR_DEVICE_DOOR_SENSOR = "DoorSensor" +ATTR_DEVICE_TH_SENSOR = "THSensor" +ATTR_DEVICE_MOTION_SENSOR = "MotionSensor" +ATTR_DEVICE_LEAK_SENSOR = "LeakSensor" +ATTR_DEVICE_OUTLET = "Outlet" +ATTR_DEVICE_SIREN = "Siren" diff --git a/homeassistant/components/yolink/coordinator.py b/homeassistant/components/yolink/coordinator.py new file mode 100644 index 00000000000..68a1aef42f7 --- /dev/null +++ b/homeassistant/components/yolink/coordinator.py @@ -0,0 +1,45 @@ +"""YoLink DataUpdateCoordinator.""" +from __future__ import annotations + +from datetime import timedelta +import logging + +import async_timeout +from yolink.device import YoLinkDevice +from yolink.exception import YoLinkAuthFailError, YoLinkClientError + +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import ATTR_DEVICE_STATE, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class YoLinkCoordinator(DataUpdateCoordinator[dict]): + """YoLink DataUpdateCoordinator.""" + + def __init__(self, hass: HomeAssistant, device: YoLinkDevice) -> None: + """Init YoLink DataUpdateCoordinator. + + fetch state every 30 minutes base on yolink device heartbeat interval + data is None before the first successful update, but we need to use data at first update + """ + super().__init__( + hass, _LOGGER, name=DOMAIN, update_interval=timedelta(minutes=30) + ) + self.device = device + + async def _async_update_data(self) -> dict: + """Fetch device state.""" + try: + async with async_timeout.timeout(10): + device_state_resp = await self.device.fetch_state_with_api() + except YoLinkAuthFailError as yl_auth_err: + raise ConfigEntryAuthFailed from yl_auth_err + except YoLinkClientError as yl_client_err: + raise UpdateFailed from yl_client_err + if ATTR_DEVICE_STATE in device_state_resp.data: + return device_state_resp.data[ATTR_DEVICE_STATE] + return {} diff --git a/homeassistant/components/yolink/entity.py b/homeassistant/components/yolink/entity.py new file mode 100644 index 00000000000..5365681739e --- /dev/null +++ b/homeassistant/components/yolink/entity.py @@ -0,0 +1,54 @@ +"""Support for YoLink Device.""" +from __future__ import annotations + +from abc import abstractmethod + +from homeassistant.core import callback +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, MANUFACTURER +from .coordinator import YoLinkCoordinator + + +class YoLinkEntity(CoordinatorEntity[YoLinkCoordinator]): + """YoLink Device Basic Entity.""" + + def __init__( + self, + coordinator: YoLinkCoordinator, + ) -> None: + """Init YoLink Entity.""" + super().__init__(coordinator) + + @property + def device_id(self) -> str: + """Return the device id of the YoLink device.""" + return self.coordinator.device.device_id + + async def async_added_to_hass(self) -> None: + """Update state.""" + await super().async_added_to_hass() + return self._handle_coordinator_update() + + @callback + def _handle_coordinator_update(self) -> None: + """Update state.""" + data = self.coordinator.data + if data is not None: + self.update_entity_state(data) + + @property + def device_info(self) -> DeviceInfo: + """Return the device info for HA.""" + return DeviceInfo( + identifiers={(DOMAIN, self.coordinator.device.device_id)}, + manufacturer=MANUFACTURER, + model=self.coordinator.device.device_type, + name=self.coordinator.device.device_name, + ) + + @callback + @abstractmethod + def update_entity_state(self, state: dict) -> None: + """Parse and update entity state, should be overridden.""" diff --git a/homeassistant/components/yolink/manifest.json b/homeassistant/components/yolink/manifest.json new file mode 100644 index 00000000000..a89934154e9 --- /dev/null +++ b/homeassistant/components/yolink/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "yolink", + "name": "YoLink", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/yolink", + "requirements": ["yolink-api==0.0.5"], + "dependencies": ["auth", "application_credentials"], + "codeowners": ["@matrixd2"], + "iot_class": "cloud_push" +} diff --git a/homeassistant/components/yolink/sensor.py b/homeassistant/components/yolink/sensor.py new file mode 100644 index 00000000000..463d8b14da4 --- /dev/null +++ b/homeassistant/components/yolink/sensor.py @@ -0,0 +1,139 @@ +"""YoLink Binary Sensor.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from yolink.device import YoLinkDevice + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE, TEMP_CELSIUS +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util import percentage + +from .const import ( + ATTR_COORDINATORS, + ATTR_DEVICE_DOOR_SENSOR, + ATTR_DEVICE_MOTION_SENSOR, + ATTR_DEVICE_TH_SENSOR, + DOMAIN, +) +from .coordinator import YoLinkCoordinator +from .entity import YoLinkEntity + + +@dataclass +class YoLinkSensorEntityDescriptionMixin: + """Mixin for device type.""" + + exists_fn: Callable[[YoLinkDevice], bool] = lambda _: True + + +@dataclass +class YoLinkSensorEntityDescription( + YoLinkSensorEntityDescriptionMixin, SensorEntityDescription +): + """YoLink SensorEntityDescription.""" + + value: Callable = lambda state: state + + +SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = ( + YoLinkSensorEntityDescription( + key="battery", + device_class=SensorDeviceClass.BATTERY, + native_unit_of_measurement=PERCENTAGE, + name="Battery", + state_class=SensorStateClass.MEASUREMENT, + value=lambda value: percentage.ordered_list_item_to_percentage( + [1, 2, 3, 4], value + ) + if value is not None + else None, + exists_fn=lambda device: device.device_type + in [ATTR_DEVICE_DOOR_SENSOR, ATTR_DEVICE_TH_SENSOR, ATTR_DEVICE_MOTION_SENSOR], + ), + YoLinkSensorEntityDescription( + key="humidity", + device_class=SensorDeviceClass.HUMIDITY, + native_unit_of_measurement=PERCENTAGE, + name="Humidity", + state_class=SensorStateClass.MEASUREMENT, + exists_fn=lambda device: device.device_type in [ATTR_DEVICE_TH_SENSOR], + ), + YoLinkSensorEntityDescription( + key="temperature", + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=TEMP_CELSIUS, + name="Temperature", + state_class=SensorStateClass.MEASUREMENT, + exists_fn=lambda device: device.device_type in [ATTR_DEVICE_TH_SENSOR], + ), +) + +SENSOR_DEVICE_TYPE = [ + ATTR_DEVICE_DOOR_SENSOR, + ATTR_DEVICE_MOTION_SENSOR, + ATTR_DEVICE_TH_SENSOR, +] + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up YoLink Sensor from a config entry.""" + device_coordinators = hass.data[DOMAIN][config_entry.entry_id][ATTR_COORDINATORS] + sensor_device_coordinators = [ + device_coordinator + for device_coordinator in device_coordinators.values() + if device_coordinator.device.device_type in SENSOR_DEVICE_TYPE + ] + entities = [] + for sensor_device_coordinator in sensor_device_coordinators: + for description in SENSOR_TYPES: + if description.exists_fn(sensor_device_coordinator.device): + entities.append( + YoLinkSensorEntity( + sensor_device_coordinator, + description, + ) + ) + async_add_entities(entities) + + +class YoLinkSensorEntity(YoLinkEntity, SensorEntity): + """YoLink Sensor Entity.""" + + entity_description: YoLinkSensorEntityDescription + + def __init__( + self, + coordinator: YoLinkCoordinator, + description: YoLinkSensorEntityDescription, + ) -> None: + """Init YoLink Sensor.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = ( + f"{coordinator.device.device_id} {self.entity_description.key}" + ) + self._attr_name = ( + f"{coordinator.device.device_name} ({self.entity_description.name})" + ) + + @callback + def update_entity_state(self, state: dict) -> None: + """Update HA Entity State.""" + self._attr_native_value = self.entity_description.value( + state.get(self.entity_description.key) + ) + self.async_write_ha_state() diff --git a/homeassistant/components/yolink/siren.py b/homeassistant/components/yolink/siren.py new file mode 100644 index 00000000000..7e67dfb12f1 --- /dev/null +++ b/homeassistant/components/yolink/siren.py @@ -0,0 +1,125 @@ +"""YoLink Siren.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from yolink.device import YoLinkDevice +from yolink.exception import YoLinkAuthFailError, YoLinkClientError + +from homeassistant.components.siren import ( + SirenEntity, + SirenEntityDescription, + SirenEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ATTR_COORDINATORS, ATTR_DEVICE_SIREN, DOMAIN +from .coordinator import YoLinkCoordinator +from .entity import YoLinkEntity + + +@dataclass +class YoLinkSirenEntityDescription(SirenEntityDescription): + """YoLink SirenEntityDescription.""" + + exists_fn: Callable[[YoLinkDevice], bool] = lambda _: True + value: Callable[[Any], bool | None] = lambda _: None + + +DEVICE_TYPES: tuple[YoLinkSirenEntityDescription, ...] = ( + YoLinkSirenEntityDescription( + key="state", + name="State", + value=lambda value: value == "alert" if value is not None else None, + exists_fn=lambda device: device.device_type in [ATTR_DEVICE_SIREN], + ), +) + +DEVICE_TYPE = [ATTR_DEVICE_SIREN] + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up YoLink siren from a config entry.""" + device_coordinators = hass.data[DOMAIN][config_entry.entry_id][ATTR_COORDINATORS] + siren_device_coordinators = [ + device_coordinator + for device_coordinator in device_coordinators.values() + if device_coordinator.device.device_type in DEVICE_TYPE + ] + entities = [] + for siren_device_coordinator in siren_device_coordinators: + for description in DEVICE_TYPES: + if description.exists_fn(siren_device_coordinator.device): + entities.append( + YoLinkSirenEntity( + config_entry, siren_device_coordinator, description + ) + ) + async_add_entities(entities) + + +class YoLinkSirenEntity(YoLinkEntity, SirenEntity): + """YoLink Siren Entity.""" + + entity_description: YoLinkSirenEntityDescription + + def __init__( + self, + config_entry: ConfigEntry, + coordinator: YoLinkCoordinator, + description: YoLinkSirenEntityDescription, + ) -> None: + """Init YoLink Siren.""" + super().__init__(coordinator) + self.config_entry = config_entry + self.entity_description = description + self._attr_unique_id = ( + f"{coordinator.device.device_id} {self.entity_description.key}" + ) + self._attr_name = ( + f"{coordinator.device.device_name} ({self.entity_description.name})" + ) + self._attr_supported_features = ( + SirenEntityFeature.TURN_ON | SirenEntityFeature.TURN_OFF + ) + + @callback + def update_entity_state(self, state: dict[str, Any]) -> None: + """Update HA Entity State.""" + self._attr_is_on = self.entity_description.value( + state.get(self.entity_description.key) + ) + self.async_write_ha_state() + + async def call_state_change(self, state: bool) -> None: + """Call setState api to change siren state.""" + try: + # call_device_http_api will check result, fail by raise YoLinkClientError + await self.coordinator.device.call_device_http_api( + "setState", {"state": {"alarm": state}} + ) + except YoLinkAuthFailError as yl_auth_err: + self.config_entry.async_start_reauth(self.hass) + raise HomeAssistantError(yl_auth_err) from yl_auth_err + except YoLinkClientError as yl_client_err: + self.coordinator.last_update_success = False + raise HomeAssistantError(yl_client_err) from yl_client_err + self._attr_is_on = self.entity_description.value("alert" if state else "normal") + self.async_write_ha_state() + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" + await self.call_state_change(True) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the entity off.""" + await self.call_state_change(False) diff --git a/homeassistant/components/yolink/strings.json b/homeassistant/components/yolink/strings.json new file mode 100644 index 00000000000..94fe5dc09aa --- /dev/null +++ b/homeassistant/components/yolink/strings.json @@ -0,0 +1,25 @@ +{ + "config": { + "step": { + "pick_implementation": { + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" + }, + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "The yolink integration needs to re-authenticate your account" + } + }, + "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%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + }, + "create_entry": { + "default": "[%key:common::config_flow::create_entry::authenticated%]" + } + } +} diff --git a/homeassistant/components/yolink/switch.py b/homeassistant/components/yolink/switch.py new file mode 100644 index 00000000000..f16dc781a9c --- /dev/null +++ b/homeassistant/components/yolink/switch.py @@ -0,0 +1,123 @@ +"""YoLink Switch.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from yolink.device import YoLinkDevice +from yolink.exception import YoLinkAuthFailError, YoLinkClientError + +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ATTR_COORDINATORS, ATTR_DEVICE_OUTLET, DOMAIN +from .coordinator import YoLinkCoordinator +from .entity import YoLinkEntity + + +@dataclass +class YoLinkSwitchEntityDescription(SwitchEntityDescription): + """YoLink SwitchEntityDescription.""" + + exists_fn: Callable[[YoLinkDevice], bool] = lambda _: True + value: Callable[[Any], bool | None] = lambda _: None + + +DEVICE_TYPES: tuple[YoLinkSwitchEntityDescription, ...] = ( + YoLinkSwitchEntityDescription( + key="state", + device_class=SwitchDeviceClass.OUTLET, + name="State", + value=lambda value: value == "open" if value is not None else None, + exists_fn=lambda device: device.device_type in [ATTR_DEVICE_OUTLET], + ), +) + +DEVICE_TYPE = [ATTR_DEVICE_OUTLET] + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up YoLink Sensor from a config entry.""" + device_coordinators = hass.data[DOMAIN][config_entry.entry_id][ATTR_COORDINATORS] + switch_device_coordinators = [ + device_coordinator + for device_coordinator in device_coordinators.values() + if device_coordinator.device.device_type in DEVICE_TYPE + ] + entities = [] + for switch_device_coordinator in switch_device_coordinators: + for description in DEVICE_TYPES: + if description.exists_fn(switch_device_coordinator.device): + entities.append( + YoLinkSwitchEntity( + config_entry, switch_device_coordinator, description + ) + ) + async_add_entities(entities) + + +class YoLinkSwitchEntity(YoLinkEntity, SwitchEntity): + """YoLink Switch Entity.""" + + entity_description: YoLinkSwitchEntityDescription + + def __init__( + self, + config_entry: ConfigEntry, + coordinator: YoLinkCoordinator, + description: YoLinkSwitchEntityDescription, + ) -> None: + """Init YoLink Outlet.""" + super().__init__(coordinator) + self.config_entry = config_entry + self.entity_description = description + self._attr_unique_id = ( + f"{coordinator.device.device_id} {self.entity_description.key}" + ) + self._attr_name = ( + f"{coordinator.device.device_name} ({self.entity_description.name})" + ) + + @callback + def update_entity_state(self, state: dict[str, Any]) -> None: + """Update HA Entity State.""" + self._attr_is_on = self.entity_description.value( + state.get(self.entity_description.key) + ) + self.async_write_ha_state() + + async def call_state_change(self, state: str) -> None: + """Call setState api to change outlet state.""" + try: + # call_device_http_api will check result, fail by raise YoLinkClientError + await self.coordinator.device.call_device_http_api( + "setState", {"state": state} + ) + except YoLinkAuthFailError as yl_auth_err: + self.config_entry.async_start_reauth(self.hass) + raise HomeAssistantError(yl_auth_err) from yl_auth_err + except YoLinkClientError as yl_client_err: + self.coordinator.last_update_success = False + raise HomeAssistantError(yl_client_err) from yl_client_err + self._attr_is_on = self.entity_description.value(state) + self.async_write_ha_state() + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" + await self.call_state_change("open") + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the entity off.""" + await self.call_state_change("close") diff --git a/homeassistant/components/yolink/translations/bg.json b/homeassistant/components/yolink/translations/bg.json new file mode 100644 index 00000000000..5f7e924f493 --- /dev/null +++ b/homeassistant/components/yolink/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\u044f\u0432\u0430\u043d\u0435" + }, + "step": { + "pick_implementation": { + "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u043c\u0435\u0442\u043e\u0434 \u0437\u0430 \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" + }, + "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/yolink/translations/ca.json b/homeassistant/components/yolink/translations/ca.json new file mode 100644 index 00000000000..db2adafa3f9 --- /dev/null +++ b/homeassistant/components/yolink/translations/ca.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" + }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" + }, + "reauth_confirm": { + "description": "La integraci\u00f3 yolink 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/yolink/translations/de.json b/homeassistant/components/yolink/translations/de.json new file mode 100644 index 00000000000..d9d5c9a6efa --- /dev/null +++ b/homeassistant/components/yolink/translations/de.json @@ -0,0 +1,25 @@ +{ + "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": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "oauth_error": "Ung\u00fcltige Token-Daten empfangen.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "create_entry": { + "default": "Erfolgreich authentifiziert" + }, + "step": { + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" + }, + "reauth_confirm": { + "description": "Die yolink-Integration muss dein Konto neu authentifizieren", + "title": "Integration erneut authentifizieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/el.json b/homeassistant/components/yolink/translations/el.json new file mode 100644 index 00000000000..87f7c0d4d41 --- /dev/null +++ b/homeassistant/components/yolink/translations/el.json @@ -0,0 +1,25 @@ +{ + "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.", + "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" + }, + "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" + }, + "reauth_confirm": { + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 yolink \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/yolink/translations/en.json b/homeassistant/components/yolink/translations/en.json new file mode 100644 index 00000000000..d1817fbe011 --- /dev/null +++ b/homeassistant/components/yolink/translations/en.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Re-authentication was successful" + }, + "create_entry": { + "default": "Successfully authenticated" + }, + "step": { + "pick_implementation": { + "title": "Pick Authentication Method" + }, + "reauth_confirm": { + "description": "The yolink integration needs to re-authenticate your account", + "title": "Reauthenticate Integration" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/et.json b/homeassistant/components/yolink/translations/et.json new file mode 100644 index 00000000000..0428b9229ad --- /dev/null +++ b/homeassistant/components/yolink/translations/et.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "create_entry": { + "default": "Tuvastamine \u00f5nnestus" + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + }, + "reauth_confirm": { + "description": "yolink sidumine peab konto uuesti tuvastama.", + "title": "Taastuvasta sidumine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/fr.json b/homeassistant/components/yolink/translations/fr.json new file mode 100644 index 00000000000..57fc1f3bb64 --- /dev/null +++ b/homeassistant/components/yolink/translations/fr.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" + }, + "create_entry": { + "default": "Authentification r\u00e9ussie" + }, + "step": { + "pick_implementation": { + "title": "S\u00e9lectionner une m\u00e9thode d'authentification" + }, + "reauth_confirm": { + "description": "L'int\u00e9gration yolink doit r\u00e9-authentifier votre compte", + "title": "R\u00e9-authentifier l'int\u00e9gration" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/he.json b/homeassistant/components/yolink/translations/he.json new file mode 100644 index 00000000000..525624782ac --- /dev/null +++ b/homeassistant/components/yolink/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", + "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.", + "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": { + "pick_implementation": { + "title": "\u05d1\u05d7\u05e8 \u05e9\u05d9\u05d8\u05ea \u05d0\u05d9\u05de\u05d5\u05ea" + }, + "reauth_confirm": { + "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/yolink/translations/hu.json b/homeassistant/components/yolink/translations/hu.json new file mode 100644 index 00000000000..fc0d5809e79 --- /dev/null +++ b/homeassistant/components/yolink/translations/hu.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "step": { + "pick_implementation": { + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" + }, + "reauth_confirm": { + "description": "A yolink integr\u00e1ci\u00f3nak \u00fajra kell hiteles\u00edtenie a fi\u00f3kj\u00e1t", + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/id.json b/homeassistant/components/yolink/translations/id.json new file mode 100644 index 00000000000..a8211681abf --- /dev/null +++ b/homeassistant/components/yolink/translations/id.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "reauth_confirm": { + "description": "Integrasi yolink perlu mengautentikasi ulang akun Anda", + "title": "Autentikasi Ulang Integrasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/it.json b/homeassistant/components/yolink/translations/it.json new file mode 100644 index 00000000000..343755c2171 --- /dev/null +++ b/homeassistant/components/yolink/translations/it.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "create_entry": { + "default": "Autenticazione riuscita" + }, + "step": { + "pick_implementation": { + "title": "Scegli il metodo di autenticazione" + }, + "reauth_confirm": { + "description": "L'integrazione yolink deve autenticare nuovamente il tuo account", + "title": "Autentica nuovamente l'integrazione" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/ja.json b/homeassistant/components/yolink/translations/ja.json new file mode 100644 index 00000000000..7d2545803bf --- /dev/null +++ b/homeassistant/components/yolink/translations/ja.json @@ -0,0 +1,25 @@ +{ + "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", + "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" + }, + "step": { + "pick_implementation": { + "title": "\u8a8d\u8a3c\u65b9\u6cd5\u306e\u9078\u629e" + }, + "reauth_confirm": { + "description": "Yolink\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", + "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/yolink/translations/ko.json b/homeassistant/components/yolink/translations/ko.json new file mode 100644 index 00000000000..5a613bf437e --- /dev/null +++ b/homeassistant/components/yolink/translations/ko.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "oauth_error": "\uc798\ubabb\ub41c \ud1a0\ud070 \ub370\uc774\ud130\ub97c \ubc1b\uc558\uc2b5\ub2c8\ub2e4.", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "create_entry": { + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, + "reauth_confirm": { + "description": "yolink \ud1b5\ud569\uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/nl.json b/homeassistant/components/yolink/translations/nl.json new file mode 100644 index 00000000000..55e8a016ff5 --- /dev/null +++ b/homeassistant/components/yolink/translations/nl.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "already_in_progress": "De configuratie is momenteel al bezig", + "authorize_url_timeout": "Time-out bij het genereren van autorisatie-URL.", + "missing_configuration": "Integratie niet geconfigureerd. Raadpleeg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [raadpleeg de documentatie]({docs_url})", + "oauth_error": "Ongeldige tokengegevens ontvangen.", + "reauth_successful": "Herauthenticatie geslaagd" + }, + "create_entry": { + "default": "Authenticatie geslaagd" + }, + "step": { + "pick_implementation": { + "title": "Kies een authenticatie methode" + }, + "reauth_confirm": { + "description": "De yolink-integratie moet opnieuw inloggen bij uw account", + "title": "Integratie herauthenticeren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/no.json b/homeassistant/components/yolink/translations/no.json new file mode 100644 index 00000000000..b5e26ac910d --- /dev/null +++ b/homeassistant/components/yolink/translations/no.json @@ -0,0 +1,25 @@ +{ + "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, vennligst f\u00f8lg dokumentasjonen", + "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk hjelpseksjonen]({docs_url})", + "oauth_error": "Mottatt ugyldige token data.", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "create_entry": { + "default": "Vellykket godkjenning" + }, + "step": { + "pick_implementation": { + "title": "Velg godkjenningsmetode" + }, + "reauth_confirm": { + "description": "Yolink-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/yolink/translations/pl.json b/homeassistant/components/yolink/translations/pl.json new file mode 100644 index 00000000000..5cddd2a4944 --- /dev/null +++ b/homeassistant/components/yolink/translations/pl.json @@ -0,0 +1,25 @@ +{ + "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": "Brak dost\u0119pnego adresu URL. Aby uzyska\u0107 informacje na temat tego b\u0142\u0119du, [sprawd\u017a sekcj\u0119 pomocy] ({docs_url})", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "create_entry": { + "default": "Pomy\u015blnie uwierzytelniono" + }, + "step": { + "pick_implementation": { + "title": "Wybierz metod\u0119 uwierzytelniania" + }, + "reauth_confirm": { + "description": "Integracja yolink wymaga ponownego uwierzytelnienia Twojego konta.", + "title": "Ponownie uwierzytelnij integracj\u0119" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/pt-BR.json b/homeassistant/components/yolink/translations/pt-BR.json new file mode 100644 index 00000000000..31a69f7ed3f --- /dev/null +++ b/homeassistant/components/yolink/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 est\u00e1 configurada", + "already_in_progress": "A 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": "Nenhuma URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre este erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "oauth_error": "Dados de token inv\u00e1lidos recebidos.", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "create_entry": { + "default": "Autenticado com sucesso" + }, + "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, + "reauth_confirm": { + "description": "A integra\u00e7\u00e3o do yolink precisa autenticar novamente sua conta", + "title": "Reautenticar Integra\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yolink/translations/ru.json b/homeassistant/components/yolink/translations/ru.json new file mode 100644 index 00000000000..a1db7e744a8 --- /dev/null +++ b/homeassistant/components/yolink/translations/ru.json @@ -0,0 +1,25 @@ +{ + "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.", + "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." + }, + "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" + }, + "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 yolink", + "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/yolink/translations/tr.json b/homeassistant/components/yolink/translations/tr.json new file mode 100644 index 00000000000..f75dd975f1c --- /dev/null +++ b/homeassistant/components/yolink/translations/tr.json @@ -0,0 +1,25 @@ +{ + "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.", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "create_entry": { + "default": "Ba\u015far\u0131yla do\u011fruland\u0131" + }, + "step": { + "pick_implementation": { + "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7" + }, + "reauth_confirm": { + "description": "Yollink 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/yolink/translations/zh-Hant.json b/homeassistant/components/yolink/translations/zh-Hant.json new file mode 100644 index 00000000000..48e9dc10c66 --- /dev/null +++ b/homeassistant/components/yolink/translations/zh-Hant.json @@ -0,0 +1,25 @@ +{ + "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", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, + "step": { + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" + }, + "reauth_confirm": { + "description": "Yolink \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/zabbix/__init__.py b/homeassistant/components/zabbix/__init__.py index 21c3edd56bf..3fc38af4cf1 100644 --- a/homeassistant/components/zabbix/__init__.py +++ b/homeassistant/components/zabbix/__init__.py @@ -90,7 +90,11 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: _LOGGER.error("HTTPError when connecting to Zabbix API: %s", http_error) zapi = None _LOGGER.error(RETRY_MESSAGE, http_error) - event_helper.call_later(hass, RETRY_INTERVAL, lambda _: setup(hass, config)) + event_helper.call_later( + hass, + RETRY_INTERVAL, + lambda _: setup(hass, config), # type: ignore[arg-type,return-value] + ) return True hass.data[DOMAIN] = zapi diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index ffe4140a434..29afd5fc236 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -28,9 +28,8 @@ from homeassistant.const import ( ) from homeassistant.core import Event, HomeAssistant, callback from homeassistant.data_entry_flow import BaseServiceInfo -from homeassistant.helpers import discovery_flow +from homeassistant.helpers import discovery_flow, instance_id import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.frame import report from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.typing import ConfigType from homeassistant.loader import ( @@ -108,36 +107,6 @@ class ZeroconfServiceInfo(BaseServiceInfo): name: str properties: dict[str, Any] - def __getitem__(self, name: str) -> Any: - """ - Enable method for compatibility reason. - - Deprecated, and will be removed in version 2022.6. - """ - report( - f"accessed discovery_info['{name}'] instead of discovery_info.{name}; " - "this will fail in version 2022.6", - exclude_integrations={DOMAIN}, - error_if_core=False, - ) - return getattr(self, name) - - def get(self, name: str, default: Any = None) -> Any: - """ - Enable method for compatibility reason. - - Deprecated, and will be removed in version 2022.6. - """ - report( - f"accessed discovery_info.get('{name}') instead of discovery_info.{name}; " - "this will fail in version 2022.6", - exclude_integrations={DOMAIN}, - error_if_core=False, - ) - if hasattr(self, name): - return getattr(self, name) - return default - @bind_hass async def async_get_instance(hass: HomeAssistant) -> HaZeroconf: @@ -229,7 +198,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: Wait till started or otherwise HTTP is not up and running. """ - uuid = await hass.helpers.instance_id.async_get() + uuid = await instance_id.async_get(hass) await _async_register_hass_zc_service(hass, aio_zc, uuid) async def _async_zeroconf_hass_stop(_event: Event) -> None: diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index e1ea2c82b1f..8cfc0698dc4 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.5"], + "requirements": ["zeroconf==0.38.6"], "dependencies": ["network", "api"], "codeowners": ["@bdraco"], "quality_scale": "internal", diff --git a/homeassistant/components/zerproc/translations/nl.json b/homeassistant/components/zerproc/translations/nl.json index 0671f0b3674..6fc4a03e824 100644 --- a/homeassistant/components/zerproc/translations/nl.json +++ b/homeassistant/components/zerproc/translations/nl.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "Geen apparaten gevonden op het netwerk", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "confirm": { - "description": "Wilt u beginnen met instellen?" + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index c512e104ae8..0e11d992a25 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -9,8 +9,8 @@ from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH from homeassistant import const as ha_const from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import ConfigType @@ -106,10 +106,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b coro = hass.config_entries.async_forward_entry_setup(config_entry, platform) zha_data[DATA_ZHA_PLATFORM_LOADED].append(hass.async_create_task(coro)) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(CONNECTION_ZIGBEE, str(zha_gateway.application_controller.ieee))}, + connections={ + (dr.CONNECTION_ZIGBEE, str(zha_gateway.application_controller.ieee)) + }, identifiers={(DOMAIN, str(zha_gateway.application_controller.ieee))}, name="Zigbee Coordinator", manufacturer="ZHA", diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 730748d74ae..954c60fa895 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -171,3 +171,16 @@ class IASZone(BinarySensor): value = await self._channel.get_attribute_value("zone_status") if value is not None: self._state = value & 3 + + +@MULTI_MATCH( + channel_names="tuya_manufacturer", + manufacturers={ + "_TZE200_htnnfasr", + }, +) +class FrostLock(BinarySensor, id_suffix="frost_lock"): + """ZHA BinarySensor.""" + + SENSOR_ATTR = "frost_lock" + _attr_device_class: BinarySensorDeviceClass = BinarySensorDeviceClass.LOCK diff --git a/homeassistant/components/zha/button.py b/homeassistant/components/zha/button.py index f130936df02..9f241795267 100644 --- a/homeassistant/components/zha/button.py +++ b/homeassistant/components/zha/button.py @@ -6,6 +6,9 @@ import functools import logging from typing import Any +import zigpy.exceptions +from zigpy.zcl.foundation import Status + from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform @@ -21,6 +24,9 @@ from .core.typing import ChannelType, ZhaDeviceType from .entity import ZhaEntity MULTI_MATCH = functools.partial(ZHA_ENTITIES.multipass_match, Platform.BUTTON) +CONFIG_DIAGNOSTIC_MATCH = functools.partial( + ZHA_ENTITIES.config_diagnostic_match, Platform.BUTTON +) DEFAULT_DURATION = 5 # seconds _LOGGER = logging.getLogger(__name__) @@ -103,3 +109,50 @@ class ZHAIdentifyButton(ZHAButton): """Return the arguments to use in the command.""" return [DEFAULT_DURATION] + + +class ZHAAttributeButton(ZhaEntity, ButtonEntity): + """Defines a ZHA button, which stes value to an attribute.""" + + _attribute_name: str = None + _attribute_value: Any = None + + def __init__( + self, + unique_id: str, + zha_device: ZhaDeviceType, + channels: list[ChannelType], + **kwargs, + ) -> None: + """Init this button.""" + super().__init__(unique_id, zha_device, channels, **kwargs) + self._channel: ChannelType = channels[0] + + async def async_press(self) -> None: + """Write attribute with defined value.""" + try: + result = await self._channel.cluster.write_attributes( + {self._attribute_name: self._attribute_value} + ) + except zigpy.exceptions.ZigbeeException as ex: + self.error("Could not set value: %s", ex) + return + if not isinstance(result, Exception) and all( + record.status == Status.SUCCESS for record in result[0] + ): + self.async_write_ha_state() + + +@CONFIG_DIAGNOSTIC_MATCH( + channel_names="tuya_manufacturer", + manufacturers={ + "_TZE200_htnnfasr", + }, +) +class FrostLockResetButton(ZHAAttributeButton, id_suffix="reset_frost_lock"): + """Defines a ZHA identify button.""" + + _attribute_name = "frost_lock_reset" + _attribute_value = 0 + _attr_device_class = ButtonDeviceClass.RESTART + _attr_entity_category = EntityCategory.CONFIG diff --git a/homeassistant/components/zha/config_flow.py b/homeassistant/components/zha/config_flow.py index f8f696c56e3..e116954cdcb 100644 --- a/homeassistant/components/zha/config_flow.py +++ b/homeassistant/components/zha/config_flow.py @@ -164,9 +164,15 @@ class ZhaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle zeroconf discovery.""" # Hostname is format: livingroom.local. local_name = discovery_info.hostname[:-1] + radio_type = discovery_info.properties.get("radio_type") or local_name node_name = local_name[: -len(".local")] host = discovery_info.host - device_path = f"socket://{host}:6638" + if local_name.startswith("tube") or "efr32" in local_name: + # This is hard coded to work with legacy devices + port = 6638 + else: + port = discovery_info.port + device_path = f"socket://{host}:{port}" if current_entry := await self.async_set_unique_id(node_name): self._abort_if_unique_id_configured( @@ -187,9 +193,12 @@ class ZhaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): } self._device_path = device_path - self._radio_type = ( - RadioType.ezsp.name if "efr32" in local_name else RadioType.znp.name - ) + if "efr32" in radio_type: + self._radio_type = RadioType.ezsp.name + elif "zigate" in radio_type: + self._radio_type = RadioType.zigate.name + else: + self._radio_type = RadioType.znp.name return await self.async_step_port_config() diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 00409794473..a0df976486f 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -277,7 +277,8 @@ class ChannelPool: pool = cls(channels, ep_id) pool.add_all_channels() pool.add_client_channels() - zha_disc.PROBE.discover_entities(pool) + if not channels.zha_device.is_coordinator: + zha_disc.PROBE.discover_entities(pool) return pool @callback diff --git a/homeassistant/components/zha/core/channels/base.py b/homeassistant/components/zha/core/channels/base.py index 7beefe2f0d0..7ba28a52116 100644 --- a/homeassistant/components/zha/core/channels/base.py +++ b/homeassistant/components/zha/core/channels/base.py @@ -446,7 +446,7 @@ class ZigbeeChannel(LogMixin): try: self.debug("Reading attributes in chunks: %s", chunk) read, _ = await self.cluster.read_attributes( - attributes, + chunk, allow_cache=from_cache, only_cache=only_cache, manufacturer=manufacturer, diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index 2e6093dd4f7..8b67c81db44 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -222,6 +222,14 @@ class LevelControlChannel(ZigbeeChannel): CURRENT_LEVEL = 0 REPORT_CONFIG = ({"attr": "current_level", "config": REPORT_CONFIG_ASAP},) + ZCL_INIT_ATTRS = { + "on_off_transition_time": True, + "on_level": True, + "on_transition_time": True, + "off_transition_time": True, + "default_move_rate": True, + "start_up_current_level": True, + } @property def current_level(self) -> int | None: diff --git a/homeassistant/components/zha/core/channels/manufacturerspecific.py b/homeassistant/components/zha/core/channels/manufacturerspecific.py index f31c7f51371..3bcab38e026 100644 --- a/homeassistant/components/zha/core/channels/manufacturerspecific.py +++ b/homeassistant/components/zha/core/channels/manufacturerspecific.py @@ -1,7 +1,9 @@ """Manufacturer specific channels module for Zigbee Home Automation.""" +import logging + from homeassistant.core import callback -from .. import registries +from .. import registries, typing as zha_typing from ..const import ( ATTR_ATTRIBUTE_ID, ATTR_ATTRIBUTE_NAME, @@ -14,6 +16,8 @@ from ..const import ( ) from .base import ClientChannel, ZigbeeChannel +_LOGGER = logging.getLogger(__name__) + @registries.ZIGBEE_CHANNEL_REGISTRY.register(registries.SMARTTHINGS_HUMIDITY_CLUSTER) class SmartThingsHumidity(ZigbeeChannel): @@ -50,6 +54,26 @@ class OppleRemote(ZigbeeChannel): REPORT_CONFIG = [] + def __init__( + self, cluster: zha_typing.ZigpyClusterType, ch_pool: zha_typing.ChannelPoolType + ) -> None: + """Initialize Opple channel.""" + super().__init__(cluster, ch_pool) + if self.cluster.endpoint.model == "lumi.motion.ac02": + self.ZCL_INIT_ATTRS = { # pylint: disable=C0103 + "detection_interval": True, + "motion_sensitivity": True, + "trigger_indicator": True, + } + + async def async_initialize_channel_specific(self, from_cache: bool) -> None: + """Initialize channel specific.""" + if self.cluster.endpoint.model == "lumi.motion.ac02": + interval = self.cluster.get("detection_interval", self.cluster.get(0x0102)) + if interval is not None: + self.debug("Loaded detection interval at startup: %s", interval) + self.cluster.endpoint.ias_zone.reset_s = int(interval) + @registries.ZIGBEE_CHANNEL_REGISTRY.register( registries.SMARTTHINGS_ACCELERATION_CLUSTER diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 854d80ffb78..e5b3403ba54 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -273,7 +273,7 @@ class ZHADevice(LogMixin): @property def skip_configuration(self) -> bool: """Return true if the device should not issue configuration related commands.""" - return self._zigpy_device.skip_configuration + return self._zigpy_device.skip_configuration or self.is_coordinator @property def gateway(self): diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 0ba375362f7..642a5b3ec55 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -25,18 +25,9 @@ from homeassistant.components.system_log import LogEntry, _figure_out_source from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers.device_registry import ( - CONNECTION_ZIGBEE, - DeviceRegistry, - async_get_registry as get_dev_reg, -) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.entity_registry import ( - EntityRegistry, - async_entries_for_device, - async_get_registry as get_ent_reg, -) from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType @@ -129,8 +120,8 @@ class ZHAGateway: # -- Set in async_initialize -- zha_storage: ZhaStorage - ha_device_registry: DeviceRegistry - ha_entity_registry: EntityRegistry + ha_device_registry: dr.DeviceRegistry + ha_entity_registry: er.EntityRegistry application_controller: ControllerApplication radio_description: str @@ -161,8 +152,8 @@ class ZHAGateway: discovery.GROUP_PROBE.initialize(self._hass) self.zha_storage = await async_get_registry(self._hass) - self.ha_device_registry = await get_dev_reg(self._hass) - self.ha_entity_registry = await get_ent_reg(self._hass) + self.ha_device_registry = dr.async_get(self._hass) + self.ha_entity_registry = er.async_get(self._hass) radio_type = self.config_entry.data[CONF_RADIO_TYPE] @@ -437,7 +428,7 @@ class ZHAGateway: ] # then we get all group entity entries tied to the coordinator - all_group_entity_entries = async_entries_for_device( + all_group_entity_entries = er.async_entries_for_device( self.ha_entity_registry, self.coordinator_zha_device.device_id, include_disabled_entities=True, @@ -528,7 +519,7 @@ class ZHAGateway: self._devices[zigpy_device.ieee] = zha_device device_registry_device = self.ha_device_registry.async_get_or_create( config_entry_id=self.config_entry.entry_id, - connections={(CONNECTION_ZIGBEE, str(zha_device.ieee))}, + connections={(dr.CONNECTION_ZIGBEE, str(zha_device.ieee))}, identifiers={(DOMAIN, str(zha_device.ieee))}, name=zha_device.name, manufacturer=zha_device.manufacturer, diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index fcd29c1619f..33d68822b9f 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -15,7 +15,7 @@ import itertools import logging from random import uniform import re -from typing import Any, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar import voluptuous as vol import zigpy.exceptions @@ -24,7 +24,8 @@ import zigpy.util import zigpy.zdo.types as zdo_types from homeassistant.config_entries import ConfigEntry -from homeassistant.core import State, callback +from homeassistant.core import HomeAssistant, State, callback +from homeassistant.helpers import device_registry as dr from .const import ( CLUSTER_TYPE_IN, @@ -36,6 +37,10 @@ from .const import ( from .registries import BINDABLE_CLUSTERS from .typing import ZhaDeviceType, ZigpyClusterType +if TYPE_CHECKING: + from .device import ZHADevice + from .gateway import ZHAGateway + _T = TypeVar("_T") @@ -159,11 +164,12 @@ def async_cluster_exists(hass, cluster_id): return False -async def async_get_zha_device(hass, device_id): +@callback +def async_get_zha_device(hass: HomeAssistant, device_id: str) -> ZHADevice: """Get a ZHA device for the given device registry id.""" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) registry_device = device_registry.async_get(device_id) - zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + zha_gateway: ZHAGateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] ieee_address = list(list(registry_device.identifiers)[0])[1] ieee = zigpy.types.EUI64.convert(ieee_address) return zha_gateway.devices[ieee] diff --git a/homeassistant/components/zha/core/store.py b/homeassistant/components/zha/core/store.py index 5d33375ace2..28983bdb427 100644 --- a/homeassistant/components/zha/core/store.py +++ b/homeassistant/components/zha/core/store.py @@ -10,6 +10,7 @@ from typing import cast import attr from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.storage import Store from homeassistant.loader import bind_hass from .typing import ZhaDeviceType @@ -38,7 +39,7 @@ class ZhaStorage: """Initialize the zha device storage.""" self.hass: HomeAssistant = hass self.devices: MutableMapping[str, ZhaDeviceEntry] = {} - self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY) @callback def async_create_device(self, device: ZhaDeviceType) -> ZhaDeviceEntry: diff --git a/homeassistant/components/zha/device_action.py b/homeassistant/components/zha/device_action.py index 36696517eb6..049ffbd40f3 100644 --- a/homeassistant/components/zha/device_action.py +++ b/homeassistant/components/zha/device_action.py @@ -48,7 +48,7 @@ async def async_call_action_from_config( hass: HomeAssistant, config: ConfigType, variables: TemplateVarsType, - context: Context, + context: Context | None, ) -> None: """Perform an action based on configuration.""" await ZHA_ACTION_TYPES[DEVICE_ACTION_TYPES[config[CONF_TYPE]]]( @@ -61,7 +61,7 @@ async def async_get_actions( ) -> list[dict[str, str]]: """List device actions.""" try: - zha_device = await async_get_zha_device(hass, device_id) + zha_device = async_get_zha_device(hass, device_id) except (KeyError, AttributeError): return [] cluster_channels = [ @@ -89,7 +89,7 @@ async def _execute_service_based_action( action_type = config[CONF_TYPE] service_name = SERVICE_NAMES[action_type] try: - zha_device = await async_get_zha_device(hass, config[CONF_DEVICE_ID]) + zha_device = async_get_zha_device(hass, config[CONF_DEVICE_ID]) except (KeyError, AttributeError): return diff --git a/homeassistant/components/zha/device_trigger.py b/homeassistant/components/zha/device_trigger.py index 03bdc32e6a6..670b1cc1477 100644 --- a/homeassistant/components/zha/device_trigger.py +++ b/homeassistant/components/zha/device_trigger.py @@ -1,12 +1,19 @@ """Provides device automations for ZHA devices that emit events.""" import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) from homeassistant.components.homeassistant.triggers import event as event_trigger from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.typing import ConfigType from . import DOMAIN from .core.helpers import async_get_zha_device @@ -21,14 +28,16 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( ) -async def async_validate_trigger_config(hass, config): +async def async_validate_trigger_config( + hass: HomeAssistant, config: ConfigType +) -> ConfigType: """Validate config.""" config = TRIGGER_SCHEMA(config) if "zha" in hass.config.components: trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) try: - zha_device = await async_get_zha_device(hass, config[CONF_DEVICE_ID]) + zha_device = async_get_zha_device(hass, config[CONF_DEVICE_ID]) except (KeyError, AttributeError) as err: raise InvalidDeviceAutomationConfig from err if ( @@ -40,18 +49,25 @@ async def async_validate_trigger_config(hass, config): return config -async def async_attach_trigger(hass, config, action, automation_info): +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" - trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) + trigger_key: tuple[str, str] = (config[CONF_TYPE], config[CONF_SUBTYPE]) try: - zha_device = await async_get_zha_device(hass, config[CONF_DEVICE_ID]) - except (KeyError, AttributeError): - return None + zha_device = async_get_zha_device(hass, config[CONF_DEVICE_ID]) + except (KeyError, AttributeError) as err: + raise HomeAssistantError( + f"Unable to get zha device {config[CONF_DEVICE_ID]}" + ) from err - if trigger not in zha_device.device_automation_triggers: - return None + if trigger_key not in zha_device.device_automation_triggers: + raise HomeAssistantError(f"Unable to find trigger {trigger_key}") - trigger = zha_device.device_automation_triggers[trigger] + trigger = zha_device.device_automation_triggers[trigger_key] event_config = { event_trigger.CONF_PLATFORM: "event", @@ -65,16 +81,18 @@ async def async_attach_trigger(hass, config, action, automation_info): ) -async def async_get_triggers(hass, device_id): +async def async_get_triggers( + hass: HomeAssistant, device_id: str +) -> list[dict[str, str]]: """List device triggers. Make sure the device supports device automations and if it does return the trigger list. """ - zha_device = await async_get_zha_device(hass, device_id) + zha_device = async_get_zha_device(hass, device_id) if not zha_device.device_automation_triggers: - return + return [] triggers = [] for trigger, subtype in zha_device.device_automation_triggers.keys(): diff --git a/homeassistant/components/zha/diagnostics.py b/homeassistant/components/zha/diagnostics.py index ec8dab241de..5ae2ff23e96 100644 --- a/homeassistant/components/zha/diagnostics.py +++ b/homeassistant/components/zha/diagnostics.py @@ -76,5 +76,5 @@ async def async_get_device_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry, device: dr.DeviceEntry ) -> dict: """Return diagnostics for a device.""" - zha_device: ZHADevice = await async_get_zha_device(hass, device.id) + zha_device: ZHADevice = async_get_zha_device(hass, device.id) return async_redact_data(zha_device.zha_device_info, KEYS_TO_REDACT) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index ef7205feb87..2ec507109a2 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -26,9 +26,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -91,10 +89,10 @@ SIGNAL_LIGHT_GROUP_STATE_CHANGED = "zha_light_group_state_changed" SUPPORT_GROUP_LIGHT = ( SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP - | SUPPORT_EFFECT - | SUPPORT_FLASH + | LightEntityFeature.EFFECT + | LightEntityFeature.FLASH | SUPPORT_COLOR - | SUPPORT_TRANSITION + | LightEntityFeature.TRANSITION ) @@ -290,7 +288,7 @@ class BaseLight(LogMixin, light.LightEntity): if ( effect == light.EFFECT_COLORLOOP - and self.supported_features & light.SUPPORT_EFFECT + and self.supported_features & light.LightEntityFeature.EFFECT ): result = await self._color_channel.color_loop_set( UPDATE_COLORLOOP_ACTION @@ -306,7 +304,7 @@ class BaseLight(LogMixin, light.LightEntity): elif ( self._effect == light.EFFECT_COLORLOOP and effect != light.EFFECT_COLORLOOP - and self.supported_features & light.SUPPORT_EFFECT + and self.supported_features & light.LightEntityFeature.EFFECT ): result = await self._color_channel.color_loop_set( UPDATE_COLORLOOP_ACTION, @@ -318,7 +316,10 @@ class BaseLight(LogMixin, light.LightEntity): t_log["color_loop_set"] = result self._effect = None - if flash is not None and self._supported_features & light.SUPPORT_FLASH: + if ( + flash is not None + and self._supported_features & light.LightEntityFeature.FLASH + ): result = await self._identify_channel.trigger_effect( FLASH_EFFECTS[flash], EFFECT_DEFAULT_VARIANT ) @@ -373,7 +374,7 @@ class Light(BaseLight, ZhaEntity): if self._level_channel: self._supported_features |= light.SUPPORT_BRIGHTNESS - self._supported_features |= light.SUPPORT_TRANSITION + self._supported_features |= light.LightEntityFeature.TRANSITION self._brightness = self._level_channel.current_level if self._color_channel: @@ -394,13 +395,13 @@ class Light(BaseLight, ZhaEntity): self._hs_color = (0, 0) if color_capabilities & CAPABILITIES_COLOR_LOOP: - self._supported_features |= light.SUPPORT_EFFECT + self._supported_features |= light.LightEntityFeature.EFFECT effect_list.append(light.EFFECT_COLORLOOP) if self._color_channel.color_loop_active == 1: self._effect = light.EFFECT_COLORLOOP if self._identify_channel: - self._supported_features |= light.SUPPORT_FLASH + self._supported_features |= light.LightEntityFeature.FLASH if effect_list: self._effect_list = effect_list diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 8befa039382..4f16b1c113e 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,10 +4,10 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.29.0", + "bellows==0.30.0", "pyserial==3.5", "pyserial-asyncio==0.6", - "zha-quirks==0.0.73", + "zha-quirks==0.0.75", "zigpy-deconz==0.16.0", "zigpy==0.45.1", "zigpy-xbee==0.14.0", @@ -57,6 +57,18 @@ "description": "*zigbee*", "known_devices": ["Nortek HUSBZB-1"] }, + { + "vid": "0403", + "pid": "6015", + "description": "*zigate*", + "known_devices": ["ZiGate+"] + }, + { + "vid": "10C4", + "pid": "EA60", + "description": "*zigate*", + "known_devices": ["ZiGate"] + }, { "vid": "10C4", "pid": "8B34", @@ -69,6 +81,10 @@ { "type": "_esphomelib._tcp.local.", "name": "tube*" + }, + { + "type": "_zigate-zigbee-gateway._tcp.local.", + "name": "*zigate*" } ], "after_dependencies": ["usb", "zeroconf"], diff --git a/homeassistant/components/zha/number.py b/homeassistant/components/zha/number.py index e1191b4ece4..216b9974df6 100644 --- a/homeassistant/components/zha/number.py +++ b/homeassistant/components/zha/number.py @@ -1,17 +1,25 @@ """Support for ZHA AnalogOutput cluster.""" +from __future__ import annotations + import functools import logging +from typing import TYPE_CHECKING + +import zigpy.exceptions +from zigpy.zcl.foundation import Status from homeassistant.components.number import NumberEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform 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 from .core import discovery from .core.const import ( CHANNEL_ANALOG_OUTPUT, + CHANNEL_LEVEL, DATA_ZHA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, @@ -19,9 +27,16 @@ from .core.const import ( from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity +if TYPE_CHECKING: + from .core.channels.base import ZigbeeChannel + from .core.device import ZHADevice + _LOGGER = logging.getLogger(__name__) STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, Platform.NUMBER) +CONFIG_DIAGNOSTIC_MATCH = functools.partial( + ZHA_ENTITIES.config_diagnostic_match, Platform.NUMBER +) UNITS = { @@ -342,3 +357,169 @@ class ZhaNumber(ZhaEntity, NumberEntity): "present_value", from_cache=False ) _LOGGER.debug("read value=%s", value) + + +class ZHANumberConfigurationEntity(ZhaEntity, NumberEntity): + """Representation of a ZHA number configuration entity.""" + + _attr_entity_category = EntityCategory.CONFIG + _attr_step: float = 1.0 + _zcl_attribute: str + + @classmethod + def create_entity( + cls, + unique_id: str, + zha_device: ZHADevice, + channels: list[ZigbeeChannel], + **kwargs, + ) -> ZhaEntity | None: + """Entity Factory. + + Return entity if it is a supported configuration, otherwise return None + """ + channel = channels[0] + if ( + cls._zcl_attribute in channel.cluster.unsupported_attributes + or channel.cluster.get(cls._zcl_attribute) is None + ): + _LOGGER.debug( + "%s is not supported - skipping %s entity creation", + cls._zcl_attribute, + cls.__name__, + ) + return None + + return cls(unique_id, zha_device, channels, **kwargs) + + def __init__( + self, + unique_id: str, + zha_device: ZHADevice, + channels: list[ZigbeeChannel], + **kwargs, + ) -> None: + """Init this number configuration entity.""" + self._channel: ZigbeeChannel = channels[0] + super().__init__(unique_id, zha_device, channels, **kwargs) + + @property + def value(self) -> float: + """Return the current value.""" + return self._channel.cluster.get(self._zcl_attribute) + + async def async_set_value(self, value: float) -> None: + """Update the current value from HA.""" + try: + res = await self._channel.cluster.write_attributes( + {self._zcl_attribute: int(value)} + ) + except zigpy.exceptions.ZigbeeException as ex: + self.error("Could not set value: %s", ex) + return + if not isinstance(res, Exception) and all( + record.status == Status.SUCCESS for record in res[0] + ): + self.async_write_ha_state() + + async def async_update(self) -> None: + """Attempt to retrieve the state of the entity.""" + await super().async_update() + _LOGGER.debug("polling current state") + if self._channel: + value = await self._channel.get_attribute_value( + self._zcl_attribute, from_cache=False + ) + _LOGGER.debug("read value=%s", value) + + +@CONFIG_DIAGNOSTIC_MATCH(channel_names="opple_cluster", models={"lumi.motion.ac02"}) +class AqaraMotionDetectionInterval( + ZHANumberConfigurationEntity, id_suffix="detection_interval" +): + """Representation of a ZHA on off transition time configuration entity.""" + + _attr_min_value: float = 2 + _attr_max_value: float = 65535 + _zcl_attribute: str = "detection_interval" + + +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_LEVEL) +class OnOffTransitionTimeConfigurationEntity( + ZHANumberConfigurationEntity, id_suffix="on_off_transition_time" +): + """Representation of a ZHA on off transition time configuration entity.""" + + _attr_min_value: float = 0x0000 + _attr_max_value: float = 0xFFFF + _zcl_attribute: str = "on_off_transition_time" + + +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_LEVEL) +class OnLevelConfigurationEntity(ZHANumberConfigurationEntity, id_suffix="on_level"): + """Representation of a ZHA on level configuration entity.""" + + _attr_min_value: float = 0x00 + _attr_max_value: float = 0xFF + _zcl_attribute: str = "on_level" + + +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_LEVEL) +class OnTransitionTimeConfigurationEntity( + ZHANumberConfigurationEntity, id_suffix="on_transition_time" +): + """Representation of a ZHA on transition time configuration entity.""" + + _attr_min_value: float = 0x0000 + _attr_max_value: float = 0xFFFE + _zcl_attribute: str = "on_transition_time" + + +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_LEVEL) +class OffTransitionTimeConfigurationEntity( + ZHANumberConfigurationEntity, id_suffix="off_transition_time" +): + """Representation of a ZHA off transition time configuration entity.""" + + _attr_min_value: float = 0x0000 + _attr_max_value: float = 0xFFFE + _zcl_attribute: str = "off_transition_time" + + +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_LEVEL) +class DefaultMoveRateConfigurationEntity( + ZHANumberConfigurationEntity, id_suffix="default_move_rate" +): + """Representation of a ZHA default move rate configuration entity.""" + + _attr_min_value: float = 0x00 + _attr_max_value: float = 0xFE + _zcl_attribute: str = "default_move_rate" + + +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_LEVEL) +class StartUpCurrentLevelConfigurationEntity( + ZHANumberConfigurationEntity, id_suffix="start_up_current_level" +): + """Representation of a ZHA startup current level configuration entity.""" + + _attr_min_value: float = 0x00 + _attr_max_value: float = 0xFF + _zcl_attribute: str = "start_up_current_level" + + +@CONFIG_DIAGNOSTIC_MATCH( + channel_names="tuya_manufacturer", + manufacturers={ + "_TZE200_htnnfasr", + }, +) +class TimerDurationMinutes(ZHANumberConfigurationEntity, id_suffix="timer_duration"): + """Representation of a ZHA timer duration configuration entity.""" + + _attr_entity_category = EntityCategory.CONFIG + _attr_icon: str = ICONS[14] + _attr_min_value: float = 0x00 + _attr_max_value: float = 0x257 + _attr_unit_of_measurement: str | None = UNITS[72] + _zcl_attribute: str = "timer_duration" diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index afa2ee18da9..8714d804790 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -5,6 +5,7 @@ from enum import Enum import functools import logging +from zigpy import types from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.clusters.security import IasWd @@ -208,3 +209,19 @@ class ZHAStartupOnOffSelectEntity( _select_attr = "start_up_on_off" _enum: Enum = OnOff.StartUpOnOff + + +class AqaraMotionSensitivities(types.enum8): + """Aqara motion sensitivities.""" + + Low = 0x01 + Medium = 0x02 + High = 0x03 + + +@CONFIG_DIAGNOSTIC_MATCH(channel_names="opple_cluster", models={"lumi.motion.ac02"}) +class AqaraMotionSensitivity(ZCLEnumSelectEntity, id_suffix="motion_sensitivity"): + """Representation of a ZHA on off transition time configuration entity.""" + + _select_attr = "motion_sensitivity" + _enum: Enum = AqaraMotionSensitivities diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 249034ef068..3e3017f6fa9 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -27,6 +27,7 @@ from homeassistant.const import ( PRESSURE_HPA, TEMP_CELSIUS, TIME_HOURS, + TIME_MINUTES, TIME_SECONDS, VOLUME_CUBIC_FEET, VOLUME_CUBIC_METERS, @@ -754,3 +755,18 @@ class RSSISensor(Sensor, id_suffix="rssi"): @MULTI_MATCH(channel_names=CHANNEL_BASIC) class LQISensor(RSSISensor, id_suffix="lqi"): """LQI sensor for a device.""" + + +@MULTI_MATCH( + channel_names="tuya_manufacturer", + manufacturers={ + "_TZE200_htnnfasr", + }, +) +class TimeLeft(Sensor, id_suffix="time_left"): + """Sensor that displays time left value.""" + + SENSOR_ATTR = "timer_time_left" + _attr_device_class: SensorDeviceClass = SensorDeviceClass.DURATION + _attr_icon = "mdi:timer" + _unit = TIME_MINUTES diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 76c41093ed6..d9199ed77c8 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -2,8 +2,10 @@ from __future__ import annotations import functools -from typing import Any +import logging +from typing import TYPE_CHECKING, Any +import zigpy.exceptions from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.foundation import Status @@ -12,6 +14,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, Platform from homeassistant.core import HomeAssistant, State, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .core import discovery @@ -24,8 +27,17 @@ from .core.const import ( from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity, ZhaGroupEntity +if TYPE_CHECKING: + from .core.channels.base import ZigbeeChannel + from .core.device import ZHADevice + STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, Platform.SWITCH) GROUP_MATCH = functools.partial(ZHA_ENTITIES.group_match, Platform.SWITCH) +CONFIG_DIAGNOSTIC_MATCH = functools.partial( + ZHA_ENTITIES.config_diagnostic_match, Platform.SWITCH +) + +_LOGGER = logging.getLogger(__name__) async def async_setup_entry( @@ -138,3 +150,127 @@ class SwitchGroup(ZhaGroupEntity, SwitchEntity): self._state = len(on_states) > 0 self._available = any(state.state != STATE_UNAVAILABLE for state in states) + + +class ZHASwitchConfigurationEntity(ZhaEntity, SwitchEntity): + """Representation of a ZHA switch configuration entity.""" + + _attr_entity_category = EntityCategory.CONFIG + _zcl_attribute: str + _zcl_inverter_attribute: str = "" + + @classmethod + def create_entity( + cls, + unique_id: str, + zha_device: ZHADevice, + channels: list[ZigbeeChannel], + **kwargs, + ) -> ZhaEntity | None: + """Entity Factory. + + Return entity if it is a supported configuration, otherwise return None + """ + channel = channels[0] + if ( + cls._zcl_attribute in channel.cluster.unsupported_attributes + or channel.cluster.get(cls._zcl_attribute) is None + ): + _LOGGER.debug( + "%s is not supported - skipping %s entity creation", + cls._zcl_attribute, + cls.__name__, + ) + return None + + return cls(unique_id, zha_device, channels, **kwargs) + + def __init__( + self, + unique_id: str, + zha_device: ZHADevice, + channels: list[ZigbeeChannel], + **kwargs, + ) -> None: + """Init this number configuration entity.""" + self._channel: ZigbeeChannel = channels[0] + super().__init__(unique_id, zha_device, channels, **kwargs) + + 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._channel, SIGNAL_ATTR_UPDATED, self.async_set_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() + + @property + def is_on(self) -> bool: + """Return if the switch is on based on the statemachine.""" + val = bool(self._channel.cluster.get(self._zcl_attribute)) + invert = bool(self._channel.cluster.get(self._zcl_inverter_attribute)) + return (not val) if invert else val + + async def async_turn_on_off(self, state) -> None: + """Turn the entity on or off.""" + try: + invert = bool(self._channel.cluster.get(self._zcl_inverter_attribute)) + result = await self._channel.cluster.write_attributes( + {self._zcl_attribute: not state if invert else state} + ) + except zigpy.exceptions.ZigbeeException as ex: + self.error("Could not set value: %s", ex) + return + if not isinstance(result, Exception) and all( + record.status == Status.SUCCESS for record in result[0] + ): + self.async_write_ha_state() + + async def async_turn_on(self, **kwargs) -> None: + """Turn the entity on.""" + await self.async_turn_on_off(True) + + async def async_turn_off(self, **kwargs) -> None: + """Turn the entity off.""" + await self.async_turn_on_off(False) + + async def async_update(self) -> None: + """Attempt to retrieve the state of the entity.""" + await super().async_update() + _LOGGER.error("Polling current state") + if self._channel: + value = await self._channel.get_attribute_value( + self._zcl_attribute, from_cache=False + ) + invert = await self._channel.get_attribute_value( + self._zcl_inverter_attribute, from_cache=False + ) + _LOGGER.debug("read value=%s, inverter=%s", value, bool(invert)) + + +@CONFIG_DIAGNOSTIC_MATCH( + channel_names="tuya_manufacturer", + manufacturers={ + "_TZE200_b6wax7g0", + }, +) +class OnOffWindowDetectionFunctionConfigurationEntity( + ZHASwitchConfigurationEntity, id_suffix="on_off_window_opened_detection" +): + """Representation of a ZHA window detection configuration entity.""" + + _zcl_attribute = "window_detection_function" + _zcl_inverter_attribute = "window_detection_function_inverter" + + +@CONFIG_DIAGNOSTIC_MATCH(channel_names="opple_cluster", models={"lumi.motion.ac02"}) +class P1MotionTriggerIndicatorSwitch( + ZHASwitchConfigurationEntity, id_suffix="trigger_indicator" +): + """Representation of a ZHA motion triggering configuration entity.""" + + _zcl_attribute = "trigger_indicator" diff --git a/homeassistant/components/zha/translations/es.json b/homeassistant/components/zha/translations/es.json index 36ab27c53b5..d8162401f3d 100644 --- a/homeassistant/components/zha/translations/es.json +++ b/homeassistant/components/zha/translations/es.json @@ -8,7 +8,7 @@ "error": { "cannot_connect": "No se pudo conectar" }, - "flow_title": "ZHA: {name}", + "flow_title": "{name}", "step": { "confirm": { "description": "\u00bfQuieres configurar {name} ?" diff --git a/homeassistant/components/zha/translations/fy.json b/homeassistant/components/zha/translations/fy.json new file mode 100644 index 00000000000..790851a469e --- /dev/null +++ b/homeassistant/components/zha/translations/fy.json @@ -0,0 +1,7 @@ +{ + "device_automation": { + "trigger_subtype": { + "turn_on": "Ynskeakelje" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/it.json b/homeassistant/components/zha/translations/it.json index 504ae7eadba..57be4c7acb6 100644 --- a/homeassistant/components/zha/translations/it.json +++ b/homeassistant/components/zha/translations/it.json @@ -86,7 +86,7 @@ "device_dropped": "Dispositivo caduto", "device_flipped": "Dispositivo capovolto \"{subtype}\"", "device_knocked": "Dispositivo bussato \"{subtype}\"", - "device_offline": "Dispositivo offline", + "device_offline": "Dispositivo non in linea", "device_rotated": "Dispositivo ruotato \" {subtype} \"", "device_shaken": "Dispositivo in vibrazione", "device_slid": "Dispositivo scivolato \"{subtype}\"", diff --git a/homeassistant/components/zha/translations/nl.json b/homeassistant/components/zha/translations/nl.json index 54692b22598..1de0e24def3 100644 --- a/homeassistant/components/zha/translations/nl.json +++ b/homeassistant/components/zha/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "not_zha_device": "Dit apparaat is niet een zha-apparaat.", - "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "usb_probe_failed": "Kon het USB apparaat niet onderzoeken" }, "error": { diff --git a/homeassistant/components/zodiac/translations/sensor.nl.json b/homeassistant/components/zodiac/translations/sensor.nl.json index 6dba645ed83..23088661aec 100644 --- a/homeassistant/components/zodiac/translations/sensor.nl.json +++ b/homeassistant/components/zodiac/translations/sensor.nl.json @@ -6,7 +6,7 @@ "cancer": "Kreeft", "capricorn": "Steenbok", "gemini": "Tweelingen", - "leo": "Leo", + "leo": "Leeuw", "libra": "Weegschaal", "pisces": "Vissen", "sagittarius": "Boogschutter", diff --git a/homeassistant/components/zone/translations/nl.json b/homeassistant/components/zone/translations/nl.json index 6dcf565ada6..021fbfb837c 100644 --- a/homeassistant/components/zone/translations/nl.json +++ b/homeassistant/components/zone/translations/nl.json @@ -6,7 +6,7 @@ "step": { "init": { "data": { - "icon": "Pictogram", + "icon": "Icoon", "latitude": "Breedtegraad", "longitude": "Lengtegraad", "name": "Naam", diff --git a/homeassistant/components/zone/trigger.py b/homeassistant/components/zone/trigger.py index 8c5af3a0ac2..0865182df80 100644 --- a/homeassistant/components/zone/trigger.py +++ b/homeassistant/components/zone/trigger.py @@ -3,6 +3,10 @@ import logging import voluptuous as vol +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) from homeassistant.const import ( ATTR_FRIENDLY_NAME, CONF_ENTITY_ID, @@ -56,11 +60,16 @@ async def async_validate_trigger_config( async def async_attach_trigger( - hass, config, action, automation_info, *, platform_type: str = "zone" + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, + *, + platform_type: str = "zone", ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" trigger_data = automation_info["trigger_data"] - entity_id = config.get(CONF_ENTITY_ID) + entity_id: list[str] = config[CONF_ENTITY_ID] zone_entity_id = config.get(CONF_ZONE) event = config.get(CONF_EVENT) job = HassJob(action) diff --git a/homeassistant/components/zoneminder/translations/nl.json b/homeassistant/components/zoneminder/translations/nl.json index 8aed5085391..5dbf8ca0d3d 100644 --- a/homeassistant/components/zoneminder/translations/nl.json +++ b/homeassistant/components/zoneminder/translations/nl.json @@ -11,7 +11,7 @@ }, "error": { "auth_fail": "Gebruikersnaam of wachtwoord is onjuist.", - "cannot_connect": "Kon niet verbinden", + "cannot_connect": "Kan geen verbinding maken", "connection_error": "Kan geen verbinding maken met een ZoneMinder-server.", "invalid_auth": "Ongeldige authenticatie" }, @@ -23,9 +23,9 @@ "password": "Wachtwoord", "path": "ZM-pad", "path_zms": "ZMS-pad", - "ssl": "Gebruik een SSL-certificaat", + "ssl": "Maakt gebruik van een SSL-certificaat", "username": "Gebruikersnaam", - "verify_ssl": "Verifieer SSL-certificaat" + "verify_ssl": "SSL-certificaat verifi\u00ebren" }, "title": "Voeg ZoneMinder server toe." } diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index d12c7df6a79..4f5756361c8 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -9,6 +9,7 @@ from typing import Any from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion +from zwave_js_server.model.driver import Driver from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.notification import ( EntryControlNotification, @@ -25,12 +26,6 @@ from homeassistant.const import ( ATTR_DEVICE_ID, ATTR_DOMAIN, ATTR_ENTITY_ID, - ATTR_IDENTIFIERS, - ATTR_MANUFACTURER, - ATTR_MODEL, - ATTR_NAME, - ATTR_SUGGESTED_AREA, - ATTR_SW_VERSION, CONF_URL, EVENT_HOMEASSISTANT_STOP, ) @@ -39,7 +34,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import UNDEFINED, ConfigType from .addon import AddonError, AddonManager, AddonState, get_addon_manager from .api import async_register_api @@ -129,13 +124,13 @@ def register_node_in_dev_reg( hass: HomeAssistant, entry: ConfigEntry, dev_reg: device_registry.DeviceRegistry, - client: ZwaveClient, + driver: Driver, node: ZwaveNode, remove_device_func: Callable[[device_registry.DeviceEntry], None], ) -> device_registry.DeviceEntry: """Register node in dev reg.""" - device_id = get_device_id(client, node) - device_id_ext = get_device_id_ext(client, node) + device_id = get_device_id(driver, node) + device_id_ext = get_device_id_ext(driver, node) device = dev_reg.async_get_device({device_id}) # Replace the device if it can be determined that this node is not the @@ -154,39 +149,105 @@ def register_node_in_dev_reg( else: ids = {device_id} - params = { - ATTR_IDENTIFIERS: ids, - ATTR_SW_VERSION: node.firmware_version, - ATTR_NAME: node.name - or node.device_config.description - or f"Node {node.node_id}", - ATTR_MODEL: node.device_config.label, - ATTR_MANUFACTURER: node.device_config.manufacturer, - } - if node.location: - params[ATTR_SUGGESTED_AREA] = node.location - device = dev_reg.async_get_or_create(config_entry_id=entry.entry_id, **params) + device = dev_reg.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers=ids, + sw_version=node.firmware_version, + name=node.name or node.device_config.description or f"Node {node.node_id}", + model=node.device_config.label, + manufacturer=node.device_config.manufacturer, + suggested_area=node.location if node.location else UNDEFINED, + ) async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device) return device -async def async_setup_entry( # noqa: C901 - hass: HomeAssistant, entry: ConfigEntry -) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave JS from a config entry.""" if use_addon := entry.data.get(CONF_USE_ADDON): await async_ensure_addon_running(hass, entry) client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) + entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {}) + + # connect and throw error if connection failed + try: + async with timeout(CONNECT_TIMEOUT): + await client.connect() + except InvalidServerVersion as err: + if not entry_hass_data.get(DATA_INVALID_SERVER_VERSION_LOGGED): + LOGGER.error("Invalid server version: %s", err) + entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = True + if use_addon: + async_ensure_addon_updated(hass) + raise ConfigEntryNotReady from err + except (asyncio.TimeoutError, BaseZwaveJSServerError) as err: + if not entry_hass_data.get(DATA_CONNECT_FAILED_LOGGED): + LOGGER.error("Failed to connect: %s", err) + entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = True + raise ConfigEntryNotReady from err + else: + LOGGER.info("Connected to Zwave JS Server") + entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = False + entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = False + + dev_reg = device_registry.async_get(hass) + ent_reg = entity_registry.async_get(hass) + services = ZWaveServices(hass, ent_reg, dev_reg) + services.async_register() + + # Set up websocket API + async_register_api(hass) + + platform_task = hass.async_create_task(start_platforms(hass, entry, client)) + entry_hass_data[DATA_START_PLATFORM_TASK] = platform_task + + return True + + +async def start_platforms( + hass: HomeAssistant, entry: ConfigEntry, client: ZwaveClient +) -> None: + """Start platforms and perform discovery.""" + entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {}) + entry_hass_data[DATA_CLIENT] = client + entry_hass_data[DATA_PLATFORM_SETUP] = {} + driver_ready = asyncio.Event() + + async def handle_ha_shutdown(event: Event) -> None: + """Handle HA shutdown.""" + await disconnect_client(hass, entry) + + listen_task = asyncio.create_task(client_listen(hass, entry, client, driver_ready)) + entry_hass_data[DATA_CLIENT_LISTEN_TASK] = listen_task + entry.async_on_unload( + hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown) + ) + + try: + await driver_ready.wait() + except asyncio.CancelledError: + LOGGER.debug("Cancelling start platforms") + return + + LOGGER.info("Connection to Zwave JS Server initialized") + + if client.driver is None: + raise RuntimeError("Driver not ready.") + + await setup_driver(hass, entry, client, client.driver) + + +async def setup_driver( # noqa: C901 + hass: HomeAssistant, entry: ConfigEntry, client: ZwaveClient, driver: Driver +) -> None: + """Set up devices using the ready driver.""" dev_reg = device_registry.async_get(hass) ent_reg = entity_registry.async_get(hass) entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {}) - - entry_hass_data[DATA_CLIENT] = client - platform_setup_tasks = entry_hass_data[DATA_PLATFORM_SETUP] = {} - + platform_setup_tasks = entry_hass_data[DATA_PLATFORM_SETUP] registered_unique_ids: dict[str, dict[str, set[str]]] = defaultdict(dict) discovered_value_ids: dict[str, set[str]] = defaultdict(set) @@ -220,7 +281,7 @@ async def async_setup_entry( # noqa: C901 ent_reg, registered_unique_ids[device.id][disc_info.platform], device, - client, + driver, disc_info, ) @@ -236,8 +297,8 @@ async def async_setup_entry( # noqa: C901 if not disc_info.assumed_state: return value_updates_disc_info[disc_info.primary_value.value_id] = disc_info - # If this is the first time we found a value we want to watch for updates, - # return early + # If this is not the first time we found a value we want to watch for updates, + # return early because we only need one listener for all values. if len(value_updates_disc_info) != 1: return # add listener for value updated events @@ -255,7 +316,7 @@ async def async_setup_entry( # noqa: C901 LOGGER.debug("Processing node %s", node) # register (or update) node in device registry device = register_node_in_dev_reg( - hass, entry, dev_reg, client, node, remove_device + hass, entry, dev_reg, driver, node, remove_device ) # We only want to create the defaultdict once, even on reinterviews if device.id not in registered_unique_ids: @@ -323,7 +384,7 @@ async def async_setup_entry( # noqa: C901 ) # we do submit the node to device registry so user has # some visual feedback that something is (in the process of) being added - register_node_in_dev_reg(hass, entry, dev_reg, client, node, remove_device) + register_node_in_dev_reg(hass, entry, dev_reg, driver, node, remove_device) async def async_on_value_added( value_updates_disc_info: dict[str, ZwaveDiscoveryInfo], value: Value @@ -332,7 +393,7 @@ async def async_setup_entry( # noqa: C901 # If node isn't ready or a device for this node doesn't already exist, we can # let the node ready event handler perform discovery. If a value has already # been processed, we don't need to do it again - device_id = get_device_id(client, value.node) + device_id = get_device_id(driver, value.node) if ( not value.node.ready or not (device := dev_reg.async_get_device({device_id})) @@ -356,7 +417,7 @@ async def async_setup_entry( # noqa: C901 node: ZwaveNode = event["node"] replaced: bool = event.get("replaced", False) # grab device in device registry attached to this node - dev_id = get_device_id(client, node) + dev_id = get_device_id(driver, node) device = dev_reg.async_get_device({dev_id}) # We assert because we know the device exists assert device @@ -365,7 +426,7 @@ async def async_setup_entry( # noqa: C901 async_dispatcher_send( hass, - f"{DOMAIN}_{get_valueless_base_unique_id(client, node)}_remove_entity", + f"{DOMAIN}_{get_valueless_base_unique_id(driver, node)}_remove_entity", ) else: remove_device(device) @@ -373,7 +434,7 @@ async def async_setup_entry( # noqa: C901 @callback def async_on_value_notification(notification: ValueNotification) -> None: """Relay stateless value notification events from Z-Wave nodes to hass.""" - device = dev_reg.async_get_device({get_device_id(client, notification.node)}) + device = dev_reg.async_get_device({get_device_id(driver, notification.node)}) # We assert because we know the device exists assert device raw_value = value = notification.value @@ -384,7 +445,7 @@ async def async_setup_entry( # noqa: C901 { ATTR_DOMAIN: DOMAIN, ATTR_NODE_ID: notification.node.node_id, - ATTR_HOME_ID: client.driver.controller.home_id, + ATTR_HOME_ID: driver.controller.home_id, ATTR_ENDPOINT: notification.endpoint, ATTR_DEVICE_ID: device.id, ATTR_COMMAND_CLASS: notification.command_class, @@ -408,13 +469,13 @@ async def async_setup_entry( # noqa: C901 notification: EntryControlNotification | NotificationNotification | PowerLevelNotification | MultilevelSwitchNotification = event[ "notification" ] - device = dev_reg.async_get_device({get_device_id(client, notification.node)}) + device = dev_reg.async_get_device({get_device_id(driver, notification.node)}) # We assert because we know the device exists assert device event_data = { ATTR_DOMAIN: DOMAIN, ATTR_NODE_ID: notification.node.node_id, - ATTR_HOME_ID: client.driver.controller.home_id, + ATTR_HOME_ID: driver.controller.home_id, ATTR_DEVICE_ID: device.id, ATTR_COMMAND_CLASS: notification.command_class, } @@ -442,7 +503,7 @@ async def async_setup_entry( # noqa: C901 elif isinstance(notification, PowerLevelNotification): event_data.update( { - ATTR_COMMAND_CLASS_NAME: "Power Level", + ATTR_COMMAND_CLASS_NAME: "Powerlevel", ATTR_TEST_NODE_ID: notification.test_node_id, ATTR_STATUS: notification.status, ATTR_ACKNOWLEDGED_FRAMES: notification.acknowledged_frames, @@ -472,11 +533,11 @@ async def async_setup_entry( # noqa: C901 return disc_info = value_updates_disc_info[value.value_id] - device = dev_reg.async_get_device({get_device_id(client, value.node)}) + device = dev_reg.async_get_device({get_device_id(driver, value.node)}) # We assert because we know the device exists assert device - unique_id = get_unique_id(client, disc_info.primary_value.value_id) + unique_id = get_unique_id(driver, disc_info.primary_value.value_id) entity_id = ent_reg.async_get_entity_id(disc_info.platform, DOMAIN, unique_id) raw_value = value_ = value.value @@ -487,7 +548,7 @@ async def async_setup_entry( # noqa: C901 ZWAVE_JS_VALUE_UPDATED_EVENT, { ATTR_NODE_ID: value.node.node_id, - ATTR_HOME_ID: client.driver.controller.home_id, + ATTR_HOME_ID: driver.controller.home_id, ATTR_DEVICE_ID: device.id, ATTR_ENTITY_ID: entity_id, ATTR_COMMAND_CLASS: value.command_class, @@ -502,105 +563,42 @@ async def async_setup_entry( # noqa: C901 }, ) - # connect and throw error if connection failed - try: - async with timeout(CONNECT_TIMEOUT): - await client.connect() - except InvalidServerVersion as err: - if not entry_hass_data.get(DATA_INVALID_SERVER_VERSION_LOGGED): - LOGGER.error("Invalid server version: %s", err) - entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = True - if use_addon: - async_ensure_addon_updated(hass) - raise ConfigEntryNotReady from err - except (asyncio.TimeoutError, BaseZwaveJSServerError) as err: - if not entry_hass_data.get(DATA_CONNECT_FAILED_LOGGED): - LOGGER.error("Failed to connect: %s", err) - entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = True - raise ConfigEntryNotReady from err - else: - LOGGER.info("Connected to Zwave JS Server") - entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = False - entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = False + # If opt in preference hasn't been specified yet, we do nothing, otherwise + # we apply the preference + if opted_in := entry.data.get(CONF_DATA_COLLECTION_OPTED_IN): + await async_enable_statistics(driver) + elif opted_in is False: + await driver.async_disable_statistics() - services = ZWaveServices(hass, ent_reg, dev_reg) - services.async_register() + # Check for nodes that no longer exist and remove them + stored_devices = device_registry.async_entries_for_config_entry( + dev_reg, entry.entry_id + ) + known_devices = [ + dev_reg.async_get_device({get_device_id(driver, node)}) + for node in driver.controller.nodes.values() + ] - # Set up websocket API - async_register_api(hass) + # Devices that are in the device registry that are not known by the controller can be removed + for device in stored_devices: + if device not in known_devices: + dev_reg.async_remove_device(device.id) - async def start_platforms() -> None: - """Start platforms and perform discovery.""" - driver_ready = asyncio.Event() + # run discovery on all ready nodes + await asyncio.gather( + *(async_on_node_added(node) for node in driver.controller.nodes.values()) + ) - async def handle_ha_shutdown(event: Event) -> None: - """Handle HA shutdown.""" - await disconnect_client(hass, entry) - - listen_task = asyncio.create_task( - client_listen(hass, entry, client, driver_ready) + # listen for new nodes being added to the mesh + entry.async_on_unload( + driver.controller.on( + "node added", + lambda event: hass.async_create_task(async_on_node_added(event["node"])), ) - entry_hass_data[DATA_CLIENT_LISTEN_TASK] = listen_task - entry.async_on_unload( - hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown) - ) - - try: - await driver_ready.wait() - except asyncio.CancelledError: - LOGGER.debug("Cancelling start platforms") - return - - LOGGER.info("Connection to Zwave JS Server initialized") - - # If opt in preference hasn't been specified yet, we do nothing, otherwise - # we apply the preference - if opted_in := entry.data.get(CONF_DATA_COLLECTION_OPTED_IN): - await async_enable_statistics(client) - elif opted_in is False: - await client.driver.async_disable_statistics() - - # Check for nodes that no longer exist and remove them - stored_devices = device_registry.async_entries_for_config_entry( - dev_reg, entry.entry_id - ) - known_devices = [ - dev_reg.async_get_device({get_device_id(client, node)}) - for node in client.driver.controller.nodes.values() - ] - - # Devices that are in the device registry that are not known by the controller can be removed - for device in stored_devices: - if device not in known_devices: - dev_reg.async_remove_device(device.id) - - # run discovery on all ready nodes - await asyncio.gather( - *( - async_on_node_added(node) - for node in client.driver.controller.nodes.values() - ) - ) - - # listen for new nodes being added to the mesh - entry.async_on_unload( - client.driver.controller.on( - "node added", - lambda event: hass.async_create_task( - async_on_node_added(event["node"]) - ), - ) - ) - # listen for nodes being removed from the mesh - # NOTE: This will not remove nodes that were removed when HA was not running - entry.async_on_unload( - client.driver.controller.on("node removed", async_on_node_removed) - ) - - platform_task = hass.async_create_task(start_platforms()) - entry_hass_data[DATA_START_PLATFORM_TASK] = platform_task - - return True + ) + # listen for nodes being removed from the mesh + # NOTE: This will not remove nodes that were removed when HA was not running + entry.async_on_unload(driver.controller.on("node removed", async_on_node_removed)) async def client_listen( diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 5608679fe90..5b8b95e951c 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Callable import dataclasses from functools import partial, wraps -from typing import Any +from typing import Any, Literal from aiohttp import web, web_exceptions, web_request import voluptuous as vol @@ -32,6 +32,7 @@ from zwave_js_server.model.controller import ( ProvisioningEntry, QRProvisioningInformation, ) +from zwave_js_server.model.driver import Driver from zwave_js_server.model.firmware import ( FirmwareUpdateFinished, FirmwareUpdateProgress, @@ -57,7 +58,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import Unauthorized from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.device_registry import DeviceEntry +import homeassistant.helpers.device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_connect from .config_validation import BITMASK_SCHEMA @@ -66,14 +67,11 @@ from .const import ( DATA_CLIENT, DOMAIN, EVENT_DEVICE_ADDED_TO_REGISTRY, - LOGGER, ) -from .helpers import async_enable_statistics, update_data_collection_preference -from .migrate import ( - ZWaveMigrationData, - async_get_migration_data, - async_map_legacy_zwave_values, - async_migrate_legacy_zwave, +from .helpers import ( + async_enable_statistics, + async_get_node_from_device_id, + update_data_collection_preference, ) DATA_UNSUBSCRIBE = "unsubs" @@ -83,6 +81,7 @@ ID = "id" ENTRY_ID = "entry_id" ERR_NOT_LOADED = "not_loaded" NODE_ID = "node_id" +DEVICE_ID = "device_id" COMMAND_CLASS_ID = "command_class_id" TYPE = "type" PROPERTY = "property" @@ -114,6 +113,21 @@ DRY_RUN = "dry_run" # constants for inclusion INCLUSION_STRATEGY = "inclusion_strategy" + +INCLUSION_STRATEGY_NOT_SMART_START: dict[ + int, + Literal[ + InclusionStrategy.DEFAULT, + InclusionStrategy.SECURITY_S0, + InclusionStrategy.SECURITY_S2, + InclusionStrategy.INSECURE, + ], +] = { + InclusionStrategy.DEFAULT.value: InclusionStrategy.DEFAULT, + InclusionStrategy.SECURITY_S0.value: InclusionStrategy.SECURITY_S0, + InclusionStrategy.SECURITY_S2.value: InclusionStrategy.SECURITY_S2, + InclusionStrategy.INSECURE.value: InclusionStrategy.INSECURE, +} PIN = "pin" FORCE_SECURITY = "force_security" PLANNED_PROVISIONING_ENTRY = "planned_provisioning_entry" @@ -144,20 +158,19 @@ MINIMUM_QR_STRING_LENGTH = 52 def convert_planned_provisioning_entry(info: dict) -> ProvisioningEntry: """Handle provisioning entry dict to ProvisioningEntry.""" - info = ProvisioningEntry( + return ProvisioningEntry( dsk=info[DSK], security_classes=[SecurityClass(sec_cls) for sec_cls in info[SECURITY_CLASSES]], additional_properties={ k: v for k, v in info.items() if k not in (DSK, SECURITY_CLASSES) }, ) - return info def convert_qr_provisioning_information(info: dict) -> QRProvisioningInformation: """Convert QR provisioning information dict to QRProvisioningInformation.""" protocols = [Protocols(proto) for proto in info.get(SUPPORTED_PROTOCOLS, [])] - info = QRProvisioningInformation( + return QRProvisioningInformation( version=QRCodeVersion(info[VERSION]), security_classes=[SecurityClass(sec_cls) for sec_cls in info[SECURITY_CLASSES]], dsk=info[DSK], @@ -173,7 +186,6 @@ def convert_qr_provisioning_information(info: dict) -> QRProvisioningInformation supported_protocols=protocols if protocols else None, additional_properties=info.get(ADDITIONAL_PROPERTIES, {}), ) - return info # Helper schemas @@ -223,6 +235,36 @@ QR_PROVISIONING_INFORMATION_SCHEMA = vol.All( QR_CODE_STRING_SCHEMA = vol.All(str, vol.Length(min=MINIMUM_QR_STRING_LENGTH)) +async def _async_get_entry( + hass: HomeAssistant, connection: ActiveConnection, msg: dict, entry_id: str +) -> tuple[ConfigEntry | None, Client | None, Driver | None]: + """Get config entry and client from message data.""" + entry = hass.config_entries.async_get_entry(entry_id) + if entry is None: + connection.send_error( + msg[ID], ERR_NOT_FOUND, f"Config entry {entry_id} not found" + ) + return None, None, None + + if entry.state is not ConfigEntryState.LOADED: + connection.send_error( + msg[ID], ERR_NOT_LOADED, f"Config entry {entry_id} not loaded" + ) + return None, None, None + + client: Client = hass.data[DOMAIN][entry_id][DATA_CLIENT] + + if client.driver is None: + connection.send_error( + msg[ID], + ERR_NOT_LOADED, + f"Config entry {msg[ENTRY_ID]} not loaded, driver not ready", + ) + return None, None, None + + return entry, client, client.driver + + def async_get_entry(orig_func: Callable) -> Callable: """Decorate async function to get entry.""" @@ -231,44 +273,43 @@ def async_get_entry(orig_func: Callable) -> Callable: hass: HomeAssistant, connection: ActiveConnection, msg: dict ) -> None: """Provide user specific data and store to function.""" - entry_id = msg[ENTRY_ID] - entry = hass.config_entries.async_get_entry(entry_id) - if entry is None: - connection.send_error( - msg[ID], ERR_NOT_FOUND, f"Config entry {entry_id} not found" - ) + entry, client, driver = await _async_get_entry( + hass, connection, msg, msg[ENTRY_ID] + ) + + if not entry and not client and not driver: return - if entry.state is not ConfigEntryState.LOADED: - connection.send_error( - msg[ID], ERR_NOT_LOADED, f"Config entry {entry_id} not loaded" - ) - return - - client = hass.data[DOMAIN][entry_id][DATA_CLIENT] - await orig_func(hass, connection, msg, entry, client) + await orig_func(hass, connection, msg, entry, client, driver) return async_get_entry_func +async def _async_get_node( + hass: HomeAssistant, connection: ActiveConnection, msg: dict, device_id: str +) -> Node | None: + """Get node from message data.""" + try: + node = async_get_node_from_device_id(hass, device_id) + except ValueError as err: + error_code = ERR_NOT_FOUND + if "loaded" in err.args[0]: + error_code = ERR_NOT_LOADED + connection.send_error(msg[ID], error_code, err.args[0]) + return None + return node + + def async_get_node(orig_func: Callable) -> Callable: """Decorate async function to get node.""" - @async_get_entry @wraps(orig_func) async def async_get_node_func( - hass: HomeAssistant, - connection: ActiveConnection, - msg: dict, - entry: ConfigEntry, - client: Client, + hass: HomeAssistant, connection: ActiveConnection, msg: dict ) -> None: """Provide user specific data and store to function.""" - node_id = msg[NODE_ID] - node = client.driver.controller.nodes.get(node_id) - - if node is None: - connection.send_error(msg[ID], ERR_NOT_FOUND, f"Node {node_id} not found") + node = await _async_get_node(hass, connection, msg, msg[DEVICE_ID]) + if not node: return await orig_func(hass, connection, msg, node) @@ -299,13 +340,27 @@ def async_handle_failed_command(orig_func: Callable) -> Callable: return async_handle_failed_command_func +def node_status(node: Node) -> dict[str, Any]: + """Get node status.""" + return { + "node_id": node.node_id, + "is_routing": node.is_routing, + "status": node.status, + "is_secure": node.is_secure, + "ready": node.ready, + "zwave_plus_version": node.zwave_plus_version, + "highest_security_class": node.highest_security_class, + "is_controller_node": node.is_controller_node, + } + + @callback def async_register_api(hass: HomeAssistant) -> None: """Register all of our api endpoints.""" websocket_api.async_register_command(hass, websocket_network_status) websocket_api.async_register_command(hass, websocket_node_status) websocket_api.async_register_command(hass, websocket_node_metadata) - websocket_api.async_register_command(hass, websocket_ping_node) + websocket_api.async_register_command(hass, websocket_node_comments) websocket_api.async_register_command(hass, websocket_add_node) websocket_api.async_register_command(hass, websocket_grant_security_classes) websocket_api.async_register_command(hass, websocket_validate_dsk_and_enter_pin) @@ -348,32 +403,48 @@ def async_register_api(hass: HomeAssistant) -> None: ) websocket_api.async_register_command(hass, websocket_subscribe_node_statistics) websocket_api.async_register_command(hass, websocket_node_ready) - websocket_api.async_register_command(hass, websocket_migrate_zwave) hass.http.register_view(FirmwareUploadView()) @websocket_api.require_admin @websocket_api.websocket_command( - {vol.Required(TYPE): "zwave_js/network_status", vol.Required(ENTRY_ID): str} + { + vol.Required(TYPE): "zwave_js/network_status", + vol.Exclusive(DEVICE_ID, "id"): str, + vol.Exclusive(ENTRY_ID, "id"): str, + } ) @websocket_api.async_response -@async_get_entry async def websocket_network_status( - hass: HomeAssistant, - connection: ActiveConnection, - msg: dict, - entry: ConfigEntry, - client: Client, + hass: HomeAssistant, connection: ActiveConnection, msg: dict ) -> None: """Get the status of the Z-Wave JS network.""" - controller = client.driver.controller + if ENTRY_ID in msg: + _, client, driver = await _async_get_entry(hass, connection, msg, msg[ENTRY_ID]) + if not client or not driver: + return + elif DEVICE_ID in msg: + node = await _async_get_node(hass, connection, msg, msg[DEVICE_ID]) + if not node: + return + client = node.client + assert client.driver + driver = client.driver + else: + connection.send_error( + msg[ID], ERR_INVALID_FORMAT, "Must specify either device_id or entry_id" + ) + return + controller = driver.controller await controller.async_get_state() + client_version_info = client.version + assert client_version_info # When client is connected version info is set. data = { "client": { "ws_server_url": client.ws_server_url, "state": "connected" if client.connected else "disconnected", - "driver_version": client.version.driver_version, - "server_version": client.version.server_version, + "driver_version": client_version_info.driver_version, + "server_version": client_version_info.server_version, }, "controller": { "home_id": controller.home_id, @@ -395,7 +466,7 @@ async def websocket_network_status( "supports_timers": controller.supports_timers, "is_heal_network_active": controller.is_heal_network_active, "inclusion_state": controller.inclusion_state, - "nodes": list(client.driver.controller.nodes), + "nodes": [node_status(node) for node in driver.controller.nodes.values()], }, } connection.send_result( @@ -407,8 +478,7 @@ async def websocket_network_status( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/node_ready", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, } ) @websocket_api.async_response @@ -443,8 +513,7 @@ async def websocket_node_ready( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/node_status", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, } ) @websocket_api.async_response @@ -456,27 +525,13 @@ async def websocket_node_status( node: Node, ) -> None: """Get the status of a Z-Wave JS node.""" - data = { - "node_id": node.node_id, - "is_routing": node.is_routing, - "status": node.status, - "is_secure": node.is_secure, - "ready": node.ready, - "zwave_plus_version": node.zwave_plus_version, - "highest_security_class": node.highest_security_class, - "is_controller_node": node.is_controller_node, - } - connection.send_result( - msg[ID], - data, - ) + connection.send_result(msg[ID], node_status(node)) @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/node_metadata", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, } ) @websocket_api.async_response @@ -496,7 +551,6 @@ async def websocket_node_metadata( "wakeup": node.device_config.metadata.wakeup, "reset": node.device_config.metadata.reset, "device_database_url": node.device_database_url, - "comments": node.device_config.metadata.comments, } connection.send_result( msg[ID], @@ -506,25 +560,22 @@ async def websocket_node_metadata( @websocket_api.websocket_command( { - vol.Required(TYPE): "zwave_js/ping_node", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(TYPE): "zwave_js/node_comments", + vol.Required(DEVICE_ID): str, } ) @websocket_api.async_response -@async_handle_failed_command @async_get_node -async def websocket_ping_node( +async def websocket_node_comments( hass: HomeAssistant, connection: ActiveConnection, msg: dict, node: Node, ) -> None: - """Ping a Z-Wave JS node.""" - result = await node.async_ping() + """Get the comments of a Z-Wave JS node.""" connection.send_result( msg[ID], - result, + {"comments": node.device_config.metadata.comments}, ) @@ -562,9 +613,10 @@ async def websocket_add_node( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Add a node to the Z-Wave network.""" - controller = client.driver.controller + controller = driver.controller inclusion_strategy = InclusionStrategy(msg[INCLUSION_STRATEGY]) force_security = msg.get(FORCE_SECURITY) provisioning = ( @@ -636,7 +688,7 @@ async def websocket_add_node( ) @callback - def device_registered(device: DeviceEntry) -> None: + def device_registered(device: dr.DeviceEntry) -> None: device_details = { "name": device.name, "id": device.id, @@ -650,7 +702,7 @@ async def websocket_add_node( ) connection.subscriptions[msg["id"]] = async_cleanup - msg[DATA_UNSUBSCRIBE] = unsubs = [ + unsubs: list[Callable[[], None]] = [ controller.on("inclusion started", forward_event), controller.on("inclusion failed", forward_event), controller.on("inclusion stopped", forward_event), @@ -661,10 +713,13 @@ async def websocket_add_node( hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device_registered ), ] + msg[DATA_UNSUBSCRIBE] = unsubs try: result = await controller.async_begin_inclusion( - inclusion_strategy, force_security=force_security, provisioning=provisioning + INCLUSION_STRATEGY_NOT_SMART_START[inclusion_strategy.value], + force_security=force_security, + provisioning=provisioning, ) except ValueError as err: connection.send_error( @@ -701,13 +756,14 @@ async def websocket_grant_security_classes( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Choose SecurityClass grants as part of S2 inclusion process.""" inclusion_grant = InclusionGrant( [SecurityClass(sec_cls) for sec_cls in msg[SECURITY_CLASSES]], msg[CLIENT_SIDE_AUTH], ) - await client.driver.controller.async_grant_security_classes(inclusion_grant) + await driver.controller.async_grant_security_classes(inclusion_grant) connection.send_result(msg[ID]) @@ -728,9 +784,10 @@ async def websocket_validate_dsk_and_enter_pin( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Validate DSK and enter PIN as part of S2 inclusion process.""" - await client.driver.controller.async_validate_dsk_and_enter_pin(msg[PIN]) + await driver.controller.async_validate_dsk_and_enter_pin(msg[PIN]) connection.send_result(msg[ID]) @@ -757,6 +814,7 @@ async def websocket_provision_smart_start_node( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Pre-provision a smart start node.""" try: @@ -787,7 +845,7 @@ async def websocket_provision_smart_start_node( "QR code version S2 is not supported for this command", ) return - await client.driver.controller.async_provision_smart_start_node(provisioning_info) + await driver.controller.async_provision_smart_start_node(provisioning_info) connection.send_result(msg[ID]) @@ -809,6 +867,7 @@ async def websocket_unprovision_smart_start_node( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Unprovision a smart start node.""" try: @@ -821,7 +880,7 @@ async def websocket_unprovision_smart_start_node( ) return dsk_or_node_id = msg.get(DSK) or msg[NODE_ID] - await client.driver.controller.async_unprovision_smart_start_node(dsk_or_node_id) + await driver.controller.async_unprovision_smart_start_node(dsk_or_node_id) connection.send_result(msg[ID]) @@ -841,11 +900,10 @@ async def websocket_get_provisioning_entries( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Get provisioning entries (entries that have been pre-provisioned).""" - provisioning_entries = ( - await client.driver.controller.async_get_provisioning_entries() - ) + provisioning_entries = await driver.controller.async_get_provisioning_entries() connection.send_result( msg[ID], [dataclasses.asdict(entry) for entry in provisioning_entries] ) @@ -868,6 +926,7 @@ async def websocket_parse_qr_code_string( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Parse a QR Code String and return QRProvisioningInformation dict.""" qr_provisioning_information = await async_parse_qr_code_string( @@ -893,9 +952,10 @@ async def websocket_supports_feature( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Check if controller supports a particular feature.""" - supported = await client.driver.controller.async_supports_feature(msg[FEATURE]) + supported = await driver.controller.async_supports_feature(msg[FEATURE]) connection.send_result( msg[ID], {"supported": supported}, @@ -918,9 +978,10 @@ async def websocket_stop_inclusion( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Cancel adding a node to the Z-Wave network.""" - controller = client.driver.controller + controller = driver.controller result = await controller.async_stop_inclusion() connection.send_result( msg[ID], @@ -944,9 +1005,10 @@ async def websocket_stop_exclusion( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Cancel removing a node from the Z-Wave network.""" - controller = client.driver.controller + controller = driver.controller result = await controller.async_stop_exclusion() connection.send_result( msg[ID], @@ -971,9 +1033,10 @@ async def websocket_remove_node( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Remove a node from the Z-Wave network.""" - controller = client.driver.controller + controller = driver.controller @callback def async_cleanup() -> None: @@ -1050,9 +1113,10 @@ async def websocket_replace_failed_node( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Replace a failed node with a new node.""" - controller = client.driver.controller + controller = driver.controller node_id = msg[NODE_ID] inclusion_strategy = InclusionStrategy(msg[INCLUSION_STRATEGY]) force_security = msg.get(FORCE_SECURITY) @@ -1137,7 +1201,7 @@ async def websocket_replace_failed_node( ) @callback - def device_registered(device: DeviceEntry) -> None: + def device_registered(device: dr.DeviceEntry) -> None: device_details = { "name": device.name, "id": device.id, @@ -1151,7 +1215,7 @@ async def websocket_replace_failed_node( ) connection.subscriptions[msg["id"]] = async_cleanup - msg[DATA_UNSUBSCRIBE] = unsubs = [ + unsubs: list[Callable[[], None]] = [ controller.on("inclusion started", forward_event), controller.on("inclusion failed", forward_event), controller.on("inclusion stopped", forward_event), @@ -1163,11 +1227,12 @@ async def websocket_replace_failed_node( hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device_registered ), ] + msg[DATA_UNSUBSCRIBE] = unsubs try: result = await controller.async_replace_failed_node( - node_id, - inclusion_strategy, + controller.nodes[node_id], + INCLUSION_STRATEGY_NOT_SMART_START[inclusion_strategy.value], force_security=force_security, provisioning=provisioning, ) @@ -1189,23 +1254,22 @@ async def websocket_replace_failed_node( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/remove_failed_node", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, } ) @websocket_api.async_response @async_handle_failed_command -@async_get_entry +@async_get_node async def websocket_remove_failed_node( hass: HomeAssistant, connection: ActiveConnection, msg: dict, - entry: ConfigEntry, - client: Client, + node: Node, ) -> None: """Remove a failed node from the Z-Wave network.""" - controller = client.driver.controller - node_id = msg[NODE_ID] + driver = node.client.driver + assert driver is not None # The node comes from the driver instance. + controller = driver.controller @callback def async_cleanup() -> None: @@ -1215,10 +1279,7 @@ async def websocket_remove_failed_node( @callback def node_removed(event: dict) -> None: - node = event["node"] - node_details = { - "node_id": node.node_id, - } + node_details = {"node_id": event["node"].node_id} connection.send_message( websocket_api.event_message( @@ -1229,11 +1290,8 @@ async def websocket_remove_failed_node( connection.subscriptions[msg["id"]] = async_cleanup msg[DATA_UNSUBSCRIBE] = unsubs = [controller.on("node removed", node_removed)] - result = await controller.async_remove_failed_node(node_id) - connection.send_result( - msg[ID], - result, - ) + await controller.async_remove_failed_node(node) + connection.send_result(msg[ID]) @websocket_api.require_admin @@ -1252,9 +1310,10 @@ async def websocket_begin_healing_network( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Begin healing the Z-Wave network.""" - controller = client.driver.controller + controller = driver.controller result = await controller.async_begin_healing_network() connection.send_result( @@ -1278,9 +1337,10 @@ async def websocket_subscribe_heal_network_progress( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Subscribe to heal Z-Wave network status updates.""" - controller = client.driver.controller + controller = driver.controller @callback def async_cleanup() -> None: @@ -1321,9 +1381,10 @@ async def websocket_stop_healing_network( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Stop healing the Z-Wave network.""" - controller = client.driver.controller + controller = driver.controller result = await controller.async_stop_healing_network() connection.send_result( msg[ID], @@ -1335,24 +1396,24 @@ async def websocket_stop_healing_network( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/heal_node", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, } ) @websocket_api.async_response @async_handle_failed_command -@async_get_entry +@async_get_node async def websocket_heal_node( hass: HomeAssistant, connection: ActiveConnection, msg: dict, - entry: ConfigEntry, - client: Client, + node: Node, ) -> None: """Heal a node on the Z-Wave network.""" - controller = client.driver.controller - node_id = msg[NODE_ID] - result = await controller.async_heal_node(node_id) + driver = node.client.driver + assert driver is not None # The node comes from the driver instance. + controller = driver.controller + + result = await controller.async_heal_node(node) connection.send_result( msg[ID], result, @@ -1363,8 +1424,7 @@ async def websocket_heal_node( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/refresh_node_info", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, }, ) @websocket_api.async_response @@ -1414,8 +1474,7 @@ async def websocket_refresh_node_info( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/refresh_node_values", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, }, ) @websocket_api.async_response @@ -1436,8 +1495,7 @@ async def websocket_refresh_node_values( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/refresh_node_cc_values", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, vol.Required(COMMAND_CLASS_ID): int, }, ) @@ -1469,8 +1527,7 @@ async def websocket_refresh_node_cc_values( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/set_config_parameter", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, vol.Required(PROPERTY): int, vol.Optional(PROPERTY_KEY): int, vol.Required(VALUE): vol.Any(int, BITMASK_SCHEMA), @@ -1521,8 +1578,7 @@ async def websocket_set_config_parameter( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/get_config_parameters", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, } ) @websocket_api.async_response @@ -1532,7 +1588,7 @@ async def websocket_get_config_parameters( ) -> None: """Get a list of configuration parameters for a Z-Wave node.""" values = node.get_configuration_values() - result = {} + result: dict[str, Any] = {} for value_id, zwave_value in values.items(): metadata = zwave_value.metadata result[value_id] = { @@ -1583,9 +1639,9 @@ async def websocket_subscribe_log_updates( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Subscribe to log message events from the server.""" - driver = client.driver @callback def async_cleanup() -> None: @@ -1670,9 +1726,10 @@ async def websocket_update_log_config( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Update the driver log config.""" - await client.driver.async_update_log_config(LogConfig(**msg[CONFIG])) + await driver.async_update_log_config(LogConfig(**msg[CONFIG])) connection.send_result( msg[ID], ) @@ -1693,11 +1750,13 @@ async def websocket_get_log_config( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Get log configuration for the Z-Wave JS driver.""" + assert client and client.driver connection.send_result( msg[ID], - dataclasses.asdict(client.driver.log_config), + dataclasses.asdict(driver.log_config), ) @@ -1718,15 +1777,16 @@ async def websocket_update_data_collection_preference( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Update preference for data collection and enable/disable collection.""" opted_in = msg[OPTED_IN] update_data_collection_preference(hass, entry, opted_in) if opted_in: - await async_enable_statistics(client) + await async_enable_statistics(driver) else: - await client.driver.async_disable_statistics() + await driver.async_disable_statistics() connection.send_result( msg[ID], @@ -1749,11 +1809,13 @@ async def websocket_data_collection_status( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Return data collection preference and status.""" + assert client and client.driver result = { OPTED_IN: entry.data.get(CONF_DATA_COLLECTION_OPTED_IN), - ENABLED: await client.driver.async_is_statistics_enabled(), + ENABLED: await driver.async_is_statistics_enabled(), } connection.send_result(msg[ID], result) @@ -1762,8 +1824,7 @@ async def websocket_data_collection_status( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/abort_firmware_update", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, } ) @websocket_api.async_response @@ -1794,8 +1855,7 @@ def _get_firmware_update_progress_dict( @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/subscribe_firmware_update_status", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, } ) @websocket_api.async_response @@ -1848,33 +1908,53 @@ async def websocket_subscribe_firmware_update_status( connection.subscriptions[msg["id"]] = async_cleanup progress = node.firmware_update_progress - connection.send_result( - msg[ID], _get_firmware_update_progress_dict(progress) if progress else None - ) + connection.send_result(msg[ID]) + if progress: + connection.send_message( + websocket_api.event_message( + msg[ID], + { + "event": "firmware update progress", + **_get_firmware_update_progress_dict(progress), + }, + ) + ) class FirmwareUploadView(HomeAssistantView): """View to upload firmware.""" - url = r"/api/zwave_js/firmware/upload/{config_entry_id}/{node_id:\d+}" + url = r"/api/zwave_js/firmware/upload/{device_id}" name = "api:zwave_js:firmware:upload" - async def post( - self, request: web.Request, config_entry_id: str, node_id: str - ) -> web.Response: + def __init__(self) -> None: + """Initialize view.""" + super().__init__() + self._dev_reg: dr.DeviceRegistry | None = None + + async def post(self, request: web.Request, device_id: str) -> web.Response: """Handle upload.""" if not request["hass_user"].is_admin: raise Unauthorized() hass = request.app["hass"] - if config_entry_id not in hass.data[DOMAIN]: - raise web_exceptions.HTTPBadRequest - entry = hass.config_entries.async_get_entry(config_entry_id) - client: Client = hass.data[DOMAIN][config_entry_id][DATA_CLIENT] - node = client.driver.controller.nodes.get(int(node_id)) - if not node: + try: + node = async_get_node_from_device_id(hass, device_id) + except ValueError as err: + if "not loaded" in err.args[0]: + raise web_exceptions.HTTPBadRequest raise web_exceptions.HTTPNotFound + if not self._dev_reg: + self._dev_reg = dr.async_get(hass) + device = self._dev_reg.async_get(device_id) + assert device + entry = next( + entry + for entry in hass.config_entries.async_entries(DOMAIN) + if entry.entry_id in device.config_entries + ) + # Increase max payload request._client_max_size = 1024 * 1024 * 10 # pylint: disable=protected-access @@ -1915,9 +1995,10 @@ async def websocket_check_for_config_updates( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Check for config updates.""" - config_update = await client.driver.async_check_for_config_updates() + config_update = await driver.async_check_for_config_updates() connection.send_result( msg[ID], { @@ -1943,9 +2024,10 @@ async def websocket_install_config_update( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Check for config updates.""" - success = await client.driver.async_install_config_update() + success = await driver.async_install_config_update() connection.send_result(msg[ID], success) @@ -1981,6 +2063,7 @@ async def websocket_subscribe_controller_statistics( msg: dict, entry: ConfigEntry, client: Client, + driver: Driver, ) -> None: """Subsribe to the statistics updates for a controller.""" @@ -2004,15 +2087,23 @@ async def websocket_subscribe_controller_statistics( ) ) - controller = client.driver.controller + controller = driver.controller msg[DATA_UNSUBSCRIBE] = unsubs = [ controller.on("statistics updated", forward_stats) ] connection.subscriptions[msg["id"]] = async_cleanup - connection.send_result( - msg[ID], _get_controller_statistics_dict(controller.statistics) + connection.send_result(msg[ID]) + connection.send_message( + websocket_api.event_message( + msg[ID], + { + "event": "statistics updated", + "source": "controller", + **_get_controller_statistics_dict(controller.statistics), + }, + ) ) @@ -2031,8 +2122,7 @@ def _get_node_statistics_dict(statistics: NodeStatistics) -> dict[str, int]: @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/subscribe_node_statistics", - vol.Required(ENTRY_ID): str, - vol.Required(NODE_ID): int, + vol.Required(DEVICE_ID): str, } ) @websocket_api.async_response @@ -2069,73 +2159,15 @@ async def websocket_subscribe_node_statistics( msg[DATA_UNSUBSCRIBE] = unsubs = [node.on("statistics updated", forward_stats)] connection.subscriptions[msg["id"]] = async_cleanup - connection.send_result(msg[ID], _get_node_statistics_dict(node.statistics)) - - -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required(TYPE): "zwave_js/migrate_zwave", - vol.Required(ENTRY_ID): str, - vol.Optional(DRY_RUN, default=True): bool, - } -) -@websocket_api.async_response -@async_get_entry -async def websocket_migrate_zwave( - hass: HomeAssistant, - connection: ActiveConnection, - msg: dict, - entry: ConfigEntry, - client: Client, -) -> None: - """Migrate Z-Wave device and entity data to Z-Wave JS integration.""" - if "zwave" not in hass.config.components: - connection.send_message( - websocket_api.error_message( - msg["id"], "zwave_not_loaded", "Integration zwave is not loaded" - ) + connection.send_result(msg[ID]) + connection.send_message( + websocket_api.event_message( + msg[ID], + { + "event": "statistics updated", + "source": "node", + "nodeId": node.node_id, + **_get_node_statistics_dict(node.statistics), + }, ) - return - - zwave = hass.components.zwave - zwave_config_entries = hass.config_entries.async_entries("zwave") - zwave_config_entry = zwave_config_entries[0] # zwave only has a single config entry - zwave_data: dict[str, ZWaveMigrationData] = await zwave.async_get_migration_data( - hass, zwave_config_entry - ) - LOGGER.debug("Migration zwave data: %s", zwave_data) - - zwave_js_config_entry = entry - zwave_js_data = await async_get_migration_data(hass, zwave_js_config_entry) - LOGGER.debug("Migration zwave_js data: %s", zwave_js_data) - - migration_map = async_map_legacy_zwave_values(zwave_data, zwave_js_data) - - zwave_entity_ids = [entry["entity_id"] for entry in zwave_data.values()] - zwave_js_entity_ids = [entry["entity_id"] for entry in zwave_js_data.values()] - migration_device_map = { - zwave_device_id: zwave_js_device_id - for zwave_js_device_id, zwave_device_id in migration_map.device_entries.items() - } - migration_entity_map = { - zwave_entry["entity_id"]: zwave_js_entity_id - for zwave_js_entity_id, zwave_entry in migration_map.entity_entries.items() - } - LOGGER.debug("Migration entity map: %s", migration_entity_map) - - if not msg[DRY_RUN]: - await async_migrate_legacy_zwave( - hass, zwave_config_entry, zwave_js_config_entry, migration_map - ) - - connection.send_result( - msg[ID], - { - "migration_device_map": migration_device_map, - "zwave_entity_ids": zwave_entity_ids, - "zwave_js_entity_ids": zwave_js_entity_ids, - "migration_entity_map": migration_entity_map, - "migrated": not msg[DRY_RUN], - }, ) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 88ab7221600..fb085cffe62 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -10,6 +10,7 @@ from zwave_js_server.const.command_class.lock import DOOR_STATUS_PROPERTY from zwave_js_server.const.command_class.notification import ( CC_SPECIFIC_NOTIFICATION_TYPE, ) +from zwave_js_server.model.driver import Driver from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, @@ -27,6 +28,8 @@ from .const import DATA_CLIENT, DOMAIN from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity +PARALLEL_UPDATES = 0 + LOGGER = logging.getLogger(__name__) @@ -245,7 +248,7 @@ PROPERTY_SENSOR_MAPPINGS: dict[str, PropertyZWaveJSEntityDescription] = { # Mappings for boolean sensors -BOOLEAN_SENSOR_MAPPINGS: dict[str, BinarySensorEntityDescription] = { +BOOLEAN_SENSOR_MAPPINGS: dict[int, BinarySensorEntityDescription] = { CommandClass.BATTERY: BinarySensorEntityDescription( key=str(CommandClass.BATTERY), device_class=BinarySensorDeviceClass.BATTERY, @@ -265,6 +268,8 @@ async def async_setup_entry( @callback def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Binary Sensor.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[BinarySensorEntity] = [] if info.platform_hint == "notification": @@ -296,22 +301,26 @@ async def async_setup_entry( entities.append( ZWaveNotificationBinarySensor( - config_entry, client, info, state_key, notification_description + config_entry, driver, info, state_key, notification_description ) ) - elif info.platform_hint == "property" and ( - property_description := PROPERTY_SENSOR_MAPPINGS.get( - info.primary_value.property_name + elif ( + info.platform_hint == "property" + and info.primary_value.property_name + and ( + property_description := PROPERTY_SENSOR_MAPPINGS.get( + info.primary_value.property_name + ) ) ): entities.append( ZWavePropertyBinarySensor( - config_entry, client, info, property_description + config_entry, driver, info, property_description ) ) else: # boolean sensor - entities.append(ZWaveBooleanBinarySensor(config_entry, client, info)) + entities.append(ZWaveBooleanBinarySensor(config_entry, driver, info)) async_add_entities(entities) @@ -330,11 +339,11 @@ class ZWaveBooleanBinarySensor(ZWaveBaseEntity, BinarySensorEntity): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, ) -> None: """Initialize a ZWaveBooleanBinarySensor entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) # Entity class attributes self._attr_name = self.generate_name(include_value_name=True) @@ -357,13 +366,13 @@ class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, state_key: str, description: NotificationZWaveJSEntityDescription | None = None, ) -> None: """Initialize a ZWaveNotificationBinarySensor entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self.state_key = state_key if description: self.entity_description = description @@ -392,12 +401,12 @@ class ZWavePropertyBinarySensor(ZWaveBaseEntity, BinarySensorEntity): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, description: PropertyZWaveJSEntityDescription, ) -> None: """Initialize a ZWavePropertyBinarySensor entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self.entity_description = description self._attr_name = self.generate_name(include_value_name=True) diff --git a/homeassistant/components/zwave_js/button.py b/homeassistant/components/zwave_js/button.py index ea987b56258..cef64f1724a 100644 --- a/homeassistant/components/zwave_js/button.py +++ b/homeassistant/components/zwave_js/button.py @@ -2,6 +2,7 @@ from __future__ import annotations from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.driver import Driver from zwave_js_server.model.node import Node as ZwaveNode from homeassistant.components.button import ButtonEntity @@ -14,6 +15,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DATA_CLIENT, DOMAIN, LOGGER from .helpers import get_device_id, get_valueless_base_unique_id +PARALLEL_UPDATES = 0 + async def async_setup_entry( hass: HomeAssistant, @@ -26,7 +29,9 @@ async def async_setup_entry( @callback def async_add_ping_button_entity(node: ZwaveNode) -> None: """Add ping button entity.""" - async_add_entities([ZWaveNodePingButton(client, node)]) + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. + async_add_entities([ZWaveNodePingButton(driver, node)]) config_entry.async_on_unload( async_dispatcher_connect( @@ -43,7 +48,7 @@ class ZWaveNodePingButton(ButtonEntity): _attr_should_poll = False _attr_entity_category = EntityCategory.CONFIG - def __init__(self, client: ZwaveClient, node: ZwaveNode) -> None: + def __init__(self, driver: Driver, node: ZwaveNode) -> None: """Initialize a ping Z-Wave device button entity.""" self.node = node name: str = ( @@ -51,11 +56,11 @@ class ZWaveNodePingButton(ButtonEntity): ) # Entity class attributes self._attr_name = f"{name}: Ping" - self._base_unique_id = get_valueless_base_unique_id(client, node) + self._base_unique_id = get_valueless_base_unique_id(driver, node) self._attr_unique_id = f"{self._base_unique_id}.ping" # device is precreated in main handler self._attr_device_info = DeviceInfo( - identifiers={get_device_id(client, node)}, + identifiers={get_device_id(driver, node)}, ) async def async_poll_value(self, _: bool) -> None: diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 188e627bb5e..d8037643488 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -17,6 +17,7 @@ from zwave_js_server.const.command_class.thermostat import ( ThermostatOperatingState, ThermostatSetpointType, ) +from zwave_js_server.model.driver import Driver from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.components.climate import ( @@ -52,6 +53,8 @@ from .discovery_data_template import DynamicCurrentTempClimateDataTemplate from .entity import ZWaveBaseEntity from .helpers import get_value_of_zwave_value +PARALLEL_UPDATES = 0 + # 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. @@ -63,7 +66,7 @@ ZW_HVAC_MODE_MAP: dict[int, HVACMode] = { ThermostatMode.AUTO: HVACMode.HEAT_COOL, ThermostatMode.AUXILIARY: HVACMode.HEAT, ThermostatMode.FAN: HVACMode.FAN_ONLY, - ThermostatMode.FURNANCE: HVACMode.HEAT, + ThermostatMode.FURNACE: HVACMode.HEAT, ThermostatMode.DRY: HVACMode.DRY, ThermostatMode.AUTO_CHANGE_OVER: HVACMode.HEAT_COOL, ThermostatMode.HEATING_ECON: HVACMode.HEAT, @@ -101,11 +104,13 @@ async def async_setup_entry( @callback def async_add_climate(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Climate.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "dynamic_current_temp": - entities.append(DynamicCurrentTempClimate(config_entry, client, info)) + entities.append(DynamicCurrentTempClimate(config_entry, driver, info)) else: - entities.append(ZWaveClimate(config_entry, client, info)) + entities.append(ZWaveClimate(config_entry, driver, info)) async_add_entities(entities) @@ -122,10 +127,10 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): """Representation of a Z-Wave climate.""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize thermostat.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self._hvac_modes: dict[HVACMode, int | None] = {} self._hvac_presets: dict[str, int | None] = {} self._unit_value: ZwaveValue | None = None @@ -133,7 +138,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): self._current_mode = self.get_zwave_value( THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE ) - self._setpoint_values: dict[ThermostatSetpointType, ZwaveValue] = {} + self._setpoint_values: dict[ThermostatSetpointType, ZwaveValue | None] = {} for enum in ThermostatSetpointType: self._setpoint_values[enum] = self.get_zwave_value( THERMOSTAT_SETPOINT_PROPERTY, @@ -228,12 +233,12 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): self._hvac_presets = all_presets @property - def _current_mode_setpoint_enums(self) -> list[ThermostatSetpointType | None]: + def _current_mode_setpoint_enums(self) -> list[ThermostatSetpointType]: """Return the list of enums that are relevant to the current thermostat mode.""" - if self._current_mode is None: + if self._current_mode is None or self._current_mode.value is None: # Thermostat(valve) with no support for setting a mode is considered heating-only return [ThermostatSetpointType.HEATING] - return THERMOSTAT_MODE_SETPOINT_MAP.get(int(self._current_mode.value), []) # type: ignore[no-any-return] + return THERMOSTAT_MODE_SETPOINT_MAP.get(int(self._current_mode.value), []) @property def temperature_unit(self) -> str: @@ -324,12 +329,13 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): @property def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" - if self._current_mode and self._current_mode.value is None: + if self._current_mode is None or self._current_mode.value is None: # guard missing value return None - if self._current_mode and int(self._current_mode.value) not in THERMOSTAT_MODES: - return_val: str = self._current_mode.metadata.states.get( - str(self._current_mode.value) + if int(self._current_mode.value) not in THERMOSTAT_MODES: + return_val: str = cast( + str, + self._current_mode.metadata.states.get(str(self._current_mode.value)), ) return return_val return PRESET_NONE @@ -463,6 +469,9 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new target preset mode.""" + if self._current_mode is None: + # Thermostat(valve) has no support for setting a mode, so we make it a no-op + return if preset_mode == PRESET_NONE: # try to restore to the (translated) main hvac mode await self.async_set_hvac_mode(self.hvac_mode) @@ -477,10 +486,10 @@ class DynamicCurrentTempClimate(ZWaveClimate): """Representation of a thermostat that can dynamically use a different Zwave Value for current temp.""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize thermostat.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self.data_template = cast( DynamicCurrentTempClimateDataTemplate, self.info.platform_data_template ) diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index 9281f0bc21c..ee83db4578c 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -15,6 +15,7 @@ from zwave_js_server.const.command_class.multilevel_switch import ( COVER_OPEN_PROPERTY, COVER_UP_PROPERTY, ) +from zwave_js_server.model.driver import Driver from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.components.cover import ( @@ -27,6 +28,7 @@ from homeassistant.components.cover import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -35,6 +37,8 @@ from .discovery import ZwaveDiscoveryInfo from .discovery_data_template import CoverTiltDataTemplate from .entity import ZWaveBaseEntity +PARALLEL_UPDATES = 0 + LOGGER = logging.getLogger(__name__) @@ -49,13 +53,15 @@ async def async_setup_entry( @callback def async_add_cover(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave cover.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "motorized_barrier": - entities.append(ZwaveMotorizedBarrier(config_entry, client, info)) + entities.append(ZwaveMotorizedBarrier(config_entry, driver, info)) elif info.platform_hint == "window_shutter_tilt": - entities.append(ZWaveTiltCover(config_entry, client, info)) + entities.append(ZWaveTiltCover(config_entry, driver, info)) else: - entities.append(ZWaveCover(config_entry, client, info)) + entities.append(ZWaveCover(config_entry, driver, info)) async_add_entities(entities) config_entry.async_on_unload( @@ -103,11 +109,11 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, ) -> None: """Initialize a ZWaveCover entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) # Entity class attributes self._attr_device_class = CoverDeviceClass.WINDOW @@ -135,6 +141,8 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity): async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) + if target_value is None: + raise HomeAssistantError("Missing target value on device.") await self.info.node.async_set_value( target_value, percent_to_zwave_position(kwargs[ATTR_POSITION]) ) @@ -142,11 +150,15 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity): async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) + if target_value is None: + raise HomeAssistantError("Missing target value on device.") await self.info.node.async_set_value(target_value, 99) async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) + if target_value is None: + raise HomeAssistantError("Missing target value on device.") await self.info.node.async_set_value(target_value, 0) async def async_stop_cover(self, **kwargs: Any) -> None: @@ -186,11 +198,11 @@ class ZWaveTiltCover(ZWaveCover): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, ) -> None: """Initialize a ZWaveCover entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self.data_template = cast( CoverTiltDataTemplate, self.info.platform_data_template ) @@ -202,7 +214,9 @@ class ZWaveTiltCover(ZWaveCover): None is unknown, 0 is closed, 100 is fully open. """ value = self.data_template.current_tilt_value(self.info.platform_data) - return zwave_tilt_to_percent(value.value) if value else None + if value is None or value.value is None: + return None + return zwave_tilt_to_percent(int(value.value)) async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Move the cover tilt to a specific position.""" @@ -231,13 +245,15 @@ class ZwaveMotorizedBarrier(ZWaveBaseEntity, CoverEntity): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, ) -> None: """Initialize a ZwaveMotorizedBarrier entity.""" - super().__init__(config_entry, client, info) - self._target_state: ZwaveValue = self.get_zwave_value( - TARGET_STATE_PROPERTY, add_to_watched_value_ids=False + super().__init__(config_entry, driver, info) + # TARGET_STATE_PROPERTY is required in the discovery schema. + self._target_state = cast( + ZwaveValue, + self.get_zwave_value(TARGET_STATE_PROPERTY, add_to_watched_value_ids=False), ) @property diff --git a/homeassistant/components/zwave_js/device_action.py b/homeassistant/components/zwave_js/device_action.py index a67bd44e533..728b376228e 100644 --- a/homeassistant/components/zwave_js/device_action.py +++ b/homeassistant/components/zwave_js/device_action.py @@ -27,7 +27,7 @@ from homeassistant.core import Context, HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from .config_validation import VALUE_SCHEMA from .const import ( @@ -141,10 +141,12 @@ ACTION_SCHEMA = vol.Any( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: +async def async_get_actions( + hass: HomeAssistant, device_id: str +) -> list[dict[str, Any]]: """List device actions for Z-Wave JS devices.""" registry = entity_registry.async_get(hass) - actions = [] + actions: list[dict] = [] node = async_get_node_from_device_id(hass, device_id) @@ -205,10 +207,13 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: # If the value has the meterType CC specific value, we can add a reset_meter # action for it if CC_SPECIFIC_METER_TYPE in value.metadata.cc_specific: - meter_endpoints[value.endpoint].setdefault( + endpoint_idx = value.endpoint + if endpoint_idx is None: + endpoint_idx = 0 + meter_endpoints[endpoint_idx].setdefault( CONF_ENTITY_ID, entry.entity_id ) - meter_endpoints[value.endpoint].setdefault(ATTR_METER_TYPE, set()).add( + meter_endpoints[endpoint_idx].setdefault(ATTR_METER_TYPE, set()).add( get_meter_type(value) ) @@ -238,7 +243,10 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Context | None + hass: HomeAssistant, + config: ConfigType, + variables: TemplateVarsType, + context: Context | None, ) -> None: """Execute a device action.""" action_type = service = config[CONF_TYPE] @@ -318,7 +326,9 @@ async def async_get_action_capabilities( vol.Required(ATTR_COMMAND_CLASS): vol.In( { CommandClass(cc.id).value: cc.name - for cc in sorted(node.command_classes, key=lambda cc: cc.name) # type: ignore[no-any-return] + for cc in sorted( + node.command_classes, key=lambda cc: cc.name + ) } ), vol.Required(ATTR_PROPERTY): cv.string, diff --git a/homeassistant/components/zwave_js/device_automation_helpers.py b/homeassistant/components/zwave_js/device_automation_helpers.py index f17ddccf03c..25cce978df1 100644 --- a/homeassistant/components/zwave_js/device_automation_helpers.py +++ b/homeassistant/components/zwave_js/device_automation_helpers.py @@ -44,6 +44,8 @@ def generate_config_parameter_subtype(config_value: ConfigurationValue) -> str: """Generate the config parameter name used in a device automation subtype.""" parameter = str(config_value.property_) if config_value.property_key: + # Property keys for config values are always an int + assert isinstance(config_value.property_key, int) parameter = f"{parameter}[{hex(config_value.property_key)}]" return f"{parameter} ({config_value.property_name})" diff --git a/homeassistant/components/zwave_js/device_condition.py b/homeassistant/components/zwave_js/device_condition.py index 549319d23f4..7775995437a 100644 --- a/homeassistant/components/zwave_js/device_condition.py +++ b/homeassistant/components/zwave_js/device_condition.py @@ -221,7 +221,9 @@ async def async_get_condition_capabilities( vol.Required(ATTR_COMMAND_CLASS): vol.In( { CommandClass(cc.id).value: cc.name - for cc in sorted(node.command_classes, key=lambda cc: cc.name) # type: ignore[no-any-return] + for cc in sorted( + node.command_classes, key=lambda cc: cc.name + ) if cc.id != CommandClass.CONFIGURATION } ), diff --git a/homeassistant/components/zwave_js/device_trigger.py b/homeassistant/components/zwave_js/device_trigger.py index 0b6369654fe..dfca2eb4b27 100644 --- a/homeassistant/components/zwave_js/device_trigger.py +++ b/homeassistant/components/zwave_js/device_trigger.py @@ -254,7 +254,7 @@ async def async_get_triggers( dev_reg = device_registry.async_get(hass) node = async_get_node_from_device_id(hass, device_id, dev_reg) - triggers = [] + triggers: list[dict] = [] base_trigger = { CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, @@ -266,7 +266,11 @@ async def async_get_triggers( entity_id = async_get_node_status_sensor_entity_id( hass, device_id, ent_reg, dev_reg ) - if (entity := ent_reg.async_get(entity_id)) is not None and not entity.disabled: + if ( + entity_id + and (entity := ent_reg.async_get(entity_id)) is not None + and not entity.disabled + ): triggers.append( {**base_trigger, CONF_TYPE: NODE_STATUS, CONF_ENTITY_ID: entity_id} ) @@ -530,7 +534,9 @@ async def async_get_trigger_capabilities( vol.Required(ATTR_COMMAND_CLASS): vol.In( { CommandClass(cc.id).value: cc.name - for cc in sorted(node.command_classes, key=lambda cc: cc.name) # type: ignore[no-any-return] + for cc in sorted( + node.command_classes, key=lambda cc: cc.name + ) if cc.id != CommandClass.CONFIGURATION } ), diff --git a/homeassistant/components/zwave_js/diagnostics.py b/homeassistant/components/zwave_js/diagnostics.py index dfb6661b5c0..3372b0eeec0 100644 --- a/homeassistant/components/zwave_js/diagnostics.py +++ b/homeassistant/components/zwave_js/diagnostics.py @@ -1,7 +1,8 @@ """Provides diagnostics for Z-Wave JS.""" from __future__ import annotations -from dataclasses import astuple +from copy import deepcopy +from dataclasses import astuple, dataclass from typing import Any from zwave_js_server.client import Client @@ -20,25 +21,43 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DATA_CLIENT, DOMAIN from .helpers import ( - ZwaveValueID, get_home_and_node_id_from_device_entry, get_state_key_from_unique_id, get_value_id_from_unique_id, ) + +@dataclass +class ZwaveValueMatcher: + """Class to allow matching a Z-Wave Value.""" + + property_: str | int | None = None + command_class: int | None = None + endpoint: int | None = None + property_key: str | int | None = None + + def __post_init__(self) -> None: + """Post initialization check.""" + if all(val is None for val in astuple(self)): + raise ValueError("At least one of the fields must be set.") + + KEYS_TO_REDACT = {"homeId", "location"} VALUES_TO_REDACT = ( - ZwaveValueID(property_="userCode", command_class=CommandClass.USER_CODE), + ZwaveValueMatcher(property_="userCode", command_class=CommandClass.USER_CODE), ) def redact_value_of_zwave_value(zwave_value: ValueDataType) -> ValueDataType: """Redact value of a Z-Wave value.""" for value_to_redact in VALUES_TO_REDACT: - zwave_value_id = ZwaveValueID( - property_=zwave_value["property"], - command_class=CommandClass(zwave_value["commandClass"]), + command_class = None + if "commandClass" in zwave_value: + command_class = CommandClass(zwave_value["commandClass"]) + zwave_value_id = ZwaveValueMatcher( + property_=zwave_value.get("property"), + command_class=command_class, endpoint=zwave_value.get("endpoint"), property_key=zwave_value.get("propertyKey"), ) @@ -48,19 +67,19 @@ def redact_value_of_zwave_value(zwave_value: ValueDataType) -> ValueDataType: astuple(value_to_redact), astuple(zwave_value_id) ) ): - return {**zwave_value, "value": REDACTED} + redacted_value: ValueDataType = deepcopy(zwave_value) + redacted_value["value"] = REDACTED + return redacted_value return zwave_value def redact_node_state(node_state: NodeDataType) -> NodeDataType: """Redact node state.""" - return { - **node_state, - "values": [ - redact_value_of_zwave_value(zwave_value) - for zwave_value in node_state["values"] - ], - } + redacted_state: NodeDataType = deepcopy(node_state) + redacted_state["values"] = [ + redact_value_of_zwave_value(zwave_value) for zwave_value in node_state["values"] + ] + return redacted_state def get_device_entities( @@ -125,15 +144,19 @@ async def async_get_config_entry_diagnostics( async def async_get_device_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry, device: dr.DeviceEntry -) -> NodeDataType: +) -> dict: """Return diagnostics for a device.""" client: Client = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] identifiers = get_home_and_node_id_from_device_entry(device) node_id = identifiers[1] if identifiers else None - if node_id is None or node_id not in client.driver.controller.nodes: + assert (driver := client.driver) + if node_id is None or node_id not in driver.controller.nodes: raise ValueError(f"Node for device {device.id} can't be found") - node = client.driver.controller.nodes[node_id] + node = driver.controller.nodes[node_id] entities = get_device_entities(hass, node, device) + assert client.version + node_state = redact_node_state(async_redact_data(node.data, KEYS_TO_REDACT)) + node_state["statistics"] = node.statistics.data return { "versionInfo": { "driverVersion": client.version.driver_version, @@ -142,5 +165,5 @@ async def async_get_device_diagnostics( "maxSchemaVersion": client.version.max_schema_version, }, "entities": entities, - "state": redact_node_state(async_redact_data(node.data, KEYS_TO_REDACT)), + "state": node_state, } diff --git a/homeassistant/components/zwave_js/discovery_data_template.py b/homeassistant/components/zwave_js/discovery_data_template.py index 9dc90a43f3d..74847c3f4da 100644 --- a/homeassistant/components/zwave_js/discovery_data_template.py +++ b/homeassistant/components/zwave_js/discovery_data_template.py @@ -1,10 +1,10 @@ """Data template classes for discovery used to generate additional data for setup.""" from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Iterable, Mapping from dataclasses import dataclass, field import logging -from typing import Any +from typing import Any, Union, cast from zwave_js_server.const import CommandClass from zwave_js_server.const.command_class.meter import ( @@ -242,7 +242,7 @@ class BaseDiscoverySchemaDataTemplate: """ return {} - def values_to_watch(self, resolved_data: Any) -> Iterable[ZwaveValue]: + def values_to_watch(self, resolved_data: Any) -> Iterable[ZwaveValue | None]: """ Return list of all ZwaveValues resolved by helper that should be watched. @@ -261,7 +261,7 @@ class BaseDiscoverySchemaDataTemplate: @staticmethod def _get_value_from_id( node: ZwaveNode, value_id_obj: ZwaveValueID - ) -> ZwaveValue | None: + ) -> ZwaveValue | ZwaveConfigurationValue | None: """Get a ZwaveValue from a node using a ZwaveValueDict.""" value_id = get_value_id( node, @@ -295,7 +295,9 @@ class DynamicCurrentTempClimateDataTemplate(BaseDiscoverySchemaDataTemplate): return data - def values_to_watch(self, resolved_data: dict[str, Any]) -> Iterable[ZwaveValue]: + def values_to_watch( + self, resolved_data: dict[str, Any] + ) -> Iterable[ZwaveValue | None]: """Return list of all ZwaveValues resolved by helper that should be watched.""" return [ *resolved_data["lookup_table"].values(), @@ -331,8 +333,11 @@ class NumericSensorDataTemplate(BaseDiscoverySchemaDataTemplate): @staticmethod def find_key_from_matching_set( enum_value: MultilevelSensorType | MultilevelSensorScaleType | MeterScaleType, - set_map: dict[ - str, set[MultilevelSensorType | MultilevelSensorScaleType | MeterScaleType] + set_map: Mapping[ + str, + set[MultilevelSensorType] + | set[MultilevelSensorScaleType] + | set[MeterScaleType], ], ) -> str | None: """Find a key in a set map that matches a given enum value.""" @@ -354,11 +359,11 @@ class NumericSensorDataTemplate(BaseDiscoverySchemaDataTemplate): return NumericSensorDataTemplateData(ENTITY_DESC_KEY_BATTERY, PERCENTAGE) if value.command_class == CommandClass.METER: - scale_type = get_meter_scale_type(value) - unit = self.find_key_from_matching_set(scale_type, METER_UNIT_MAP) + meter_scale_type = get_meter_scale_type(value) + unit = self.find_key_from_matching_set(meter_scale_type, METER_UNIT_MAP) # We do this because even though these are energy scales, they don't meet # the unit requirements for the energy device class. - if scale_type in ( + if meter_scale_type in ( ElectricScale.PULSE_COUNT, ElectricScale.KILOVOLT_AMPERE_HOUR, ElectricScale.KILOVOLT_AMPERE_REACTIVE_HOUR, @@ -368,19 +373,21 @@ class NumericSensorDataTemplate(BaseDiscoverySchemaDataTemplate): ) # We do this because even though these are power scales, they don't meet # the unit requirements for the power device class. - if scale_type == ElectricScale.KILOVOLT_AMPERE_REACTIVE: + if meter_scale_type == ElectricScale.KILOVOLT_AMPERE_REACTIVE: return NumericSensorDataTemplateData(ENTITY_DESC_KEY_MEASUREMENT, unit) return NumericSensorDataTemplateData( - self.find_key_from_matching_set(scale_type, METER_DEVICE_CLASS_MAP), + self.find_key_from_matching_set( + meter_scale_type, METER_DEVICE_CLASS_MAP + ), unit, ) if value.command_class == CommandClass.SENSOR_MULTILEVEL: sensor_type = get_multilevel_sensor_type(value) - scale_type = get_multilevel_sensor_scale_type(value) + multilevel_sensor_scale_type = get_multilevel_sensor_scale_type(value) unit = self.find_key_from_matching_set( - scale_type, MULTILEVEL_SENSOR_UNIT_MAP + multilevel_sensor_scale_type, MULTILEVEL_SENSOR_UNIT_MAP ) if sensor_type == MultilevelSensorType.TARGET_TEMPERATURE: return NumericSensorDataTemplateData( @@ -406,16 +413,20 @@ class TiltValueMix: class CoverTiltDataTemplate(BaseDiscoverySchemaDataTemplate, TiltValueMix): """Tilt data template class for Z-Wave Cover entities.""" - def resolve_data(self, value: ZwaveValue) -> dict[str, Any]: + def resolve_data(self, value: ZwaveValue) -> dict[str, ZwaveValue | None]: """Resolve helper class data for a discovered value.""" return {"tilt_value": self._get_value_from_id(value.node, self.tilt_value_id)} - def values_to_watch(self, resolved_data: dict[str, Any]) -> Iterable[ZwaveValue]: + def values_to_watch( + self, resolved_data: dict[str, Any] + ) -> Iterable[ZwaveValue | None]: """Return list of all ZwaveValues resolved by helper that should be watched.""" return [resolved_data["tilt_value"]] @staticmethod - def current_tilt_value(resolved_data: dict[str, Any]) -> ZwaveValue | None: + def current_tilt_value( + resolved_data: dict[str, ZwaveValue | None] + ) -> ZwaveValue | None: """Get current tilt ZwaveValue from resolved data.""" return resolved_data["tilt_value"] @@ -490,24 +501,29 @@ class ConfigurableFanValueMappingDataTemplate( `configuration_option` to the value mapping object. """ - def resolve_data(self, value: ZwaveValue) -> dict[str, ZwaveConfigurationValue]: + def resolve_data( + self, value: ZwaveValue + ) -> dict[str, ZwaveConfigurationValue | None]: """Resolve helper class data for a discovered value.""" - zwave_value: ZwaveValue = self._get_value_from_id( - value.node, self.configuration_option + zwave_value = cast( + Union[ZwaveConfigurationValue, None], + self._get_value_from_id(value.node, self.configuration_option), ) return {"configuration_value": zwave_value} - def values_to_watch(self, resolved_data: dict[str, Any]) -> Iterable[ZwaveValue]: + def values_to_watch( + self, resolved_data: dict[str, ZwaveConfigurationValue | None] + ) -> Iterable[ZwaveConfigurationValue | None]: """Return list of all ZwaveValues that should be watched.""" return [ resolved_data["configuration_value"], ] def get_fan_value_mapping( - self, resolved_data: dict[str, ZwaveConfigurationValue] + self, resolved_data: dict[str, ZwaveConfigurationValue | None] ) -> FanValueMapping | None: """Get current fan properties from resolved data.""" - zwave_value: ZwaveValue = resolved_data["configuration_value"] + zwave_value = resolved_data["configuration_value"] if zwave_value is None: _LOGGER.warning("Unable to read device configuration value") diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index c6ed1902568..a4271ac1c02 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -3,8 +3,8 @@ from __future__ import annotations import logging -from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import NodeStatus +from zwave_js_server.model.driver import Driver from zwave_js_server.model.value import Value as ZwaveValue, get_value_id from homeassistant.config_entries import ConfigEntry @@ -15,7 +15,6 @@ from homeassistant.helpers.entity import DeviceInfo, Entity from .const import DOMAIN from .discovery import ZwaveDiscoveryInfo from .helpers import get_device_id, get_unique_id -from .migrate import async_add_migration_entity_value LOGGER = logging.getLogger(__name__) @@ -31,11 +30,11 @@ class ZWaveBaseEntity(Entity): _attr_should_poll = False def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize a generic Z-Wave device entity.""" self.config_entry = config_entry - self.client = client + self.driver = driver self.info = info # entities requiring additional values, can add extra ids to this list self.watched_value_ids = {self.info.primary_value.value_id} @@ -47,16 +46,14 @@ class ZWaveBaseEntity(Entity): # Entity class attributes self._attr_name = self.generate_name() - self._attr_unique_id = get_unique_id( - self.client, self.info.primary_value.value_id - ) + self._attr_unique_id = get_unique_id(driver, self.info.primary_value.value_id) self._attr_entity_registry_enabled_default = ( self.info.entity_registry_enabled_default ) self._attr_assumed_state = self.info.assumed_state # device is precreated in main handler self._attr_device_info = DeviceInfo( - identifiers={get_device_id(self.client, self.info.node)}, + identifiers={get_device_id(driver, self.info.node)}, ) @callback @@ -117,11 +114,6 @@ class ZWaveBaseEntity(Entity): ) ) - # Add legacy Z-Wave migration data. - await async_add_migration_entity_value( - self.hass, self.config_entry, self.entity_id, self.info - ) - def generate_name( self, include_value_name: bool = False, @@ -151,7 +143,10 @@ class ZWaveBaseEntity(Entity): if item: name += f" - {item}" # append endpoint if > 1 - if self.info.primary_value.endpoint > 1: + if ( + self.info.primary_value.endpoint is not None + and self.info.primary_value.endpoint > 1 + ): name += f" ({self.info.primary_value.endpoint})" return name @@ -160,7 +155,7 @@ class ZWaveBaseEntity(Entity): def available(self) -> bool: """Return entity availability.""" return ( - self.client.connected + self.driver.client.connected and bool(self.info.node.ready) and self.info.node.status != NodeStatus.DEAD ) diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index 623ee072f3a..ae9df47b420 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -10,6 +10,7 @@ from zwave_js_server.const.command_class.thermostat import ( THERMOSTAT_FAN_OFF_PROPERTY, THERMOSTAT_FAN_STATE_PROPERTY, ) +from zwave_js_server.model.driver import Driver from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.components.fan import ( @@ -35,6 +36,8 @@ from .discovery_data_template import FanValueMapping, FanValueMappingDataTemplat from .entity import ZWaveBaseEntity from .helpers import get_value_of_zwave_value +PARALLEL_UPDATES = 0 + DEFAULT_SPEED_RANGE = (1, 99) # off is not included ATTR_FAN_STATE = "fan_state" @@ -51,13 +54,15 @@ async def async_setup_entry( @callback def async_add_fan(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave fan.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "has_fan_value_mapping": - entities.append(ValueMappingZwaveFan(config_entry, client, info)) + entities.append(ValueMappingZwaveFan(config_entry, driver, info)) elif info.platform_hint == "thermostat_fan": - entities.append(ZwaveThermostatFan(config_entry, client, info)) + entities.append(ZwaveThermostatFan(config_entry, driver, info)) else: - entities.append(ZwaveFan(config_entry, client, info)) + entities.append(ZwaveFan(config_entry, driver, info)) async_add_entities(entities) @@ -76,10 +81,10 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): _attr_supported_features = FanEntityFeature.SET_SPEED def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize the fan.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self._target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) async def async_set_percentage(self, percentage: int) -> None: @@ -91,7 +96,9 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): percentage_to_ranged_value(DEFAULT_SPEED_RANGE, percentage) ) - await self.info.node.async_set_value(self._target_value, zwave_speed) + if (target_value := self._target_value) is None: + raise HomeAssistantError("Missing target value on device.") + await self.info.node.async_set_value(target_value, zwave_speed) async def async_turn_on( self, @@ -105,12 +112,16 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): elif preset_mode is not None: await self.async_set_preset_mode(preset_mode) else: + if (target_value := self._target_value) is None: + raise HomeAssistantError("Missing target value on device.") # Value 255 tells device to return to previous value - await self.info.node.async_set_value(self._target_value, 255) + await self.info.node.async_set_value(target_value, 255) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" - await self.info.node.async_set_value(self._target_value, 0) + if (target_value := self._target_value) is None: + raise HomeAssistantError("Missing target value on device.") + await self.info.node.async_set_value(target_value, 0) @property def is_on(self) -> bool | None: @@ -145,24 +156,28 @@ class ValueMappingZwaveFan(ZwaveFan): """A Zwave fan with a value mapping data (e.g., 1-24 is low).""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize the fan.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self.data_template = cast( FanValueMappingDataTemplate, self.info.platform_data_template ) async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" + if (target_value := self._target_value) is None: + raise HomeAssistantError("Missing target value on device.") zwave_speed = self.percentage_to_zwave_speed(percentage) - await self.info.node.async_set_value(self._target_value, zwave_speed) + await self.info.node.async_set_value(target_value, zwave_speed) async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" + if (target_value := self._target_value) is None: + raise HomeAssistantError("Missing target value on device.") for zwave_value, mapped_preset_mode in self.fan_value_mapping.presets.items(): if preset_mode == mapped_preset_mode: - await self.info.node.async_set_value(self._target_value, zwave_value) + await self.info.node.async_set_value(target_value, zwave_value) return raise NotValidPresetModeError( @@ -205,7 +220,9 @@ class ValueMappingZwaveFan(ZwaveFan): @property def preset_mode(self) -> str | None: """Return the current preset mode.""" - return self.fan_value_mapping.presets.get(self.info.primary_value.value) + if (value := self.info.primary_value.value) is None: + return None + return self.fan_value_mapping.presets.get(value) @property def has_fan_value_mapping(self) -> bool: @@ -298,10 +315,10 @@ class ZwaveThermostatFan(ZWaveBaseEntity, FanEntity): _fan_state: ZwaveValue | None = None def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize the thermostat fan.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self._fan_mode = self.info.primary_value diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index a5a2da23206..c047a3a9903 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -2,13 +2,14 @@ from __future__ import annotations from collections.abc import Callable -from dataclasses import astuple, dataclass +from dataclasses import dataclass import logging from typing import Any, cast import voluptuous as vol from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ConfigurationValueType +from zwave_js_server.model.driver import Driver from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.value import ( ConfigurationValue, @@ -47,16 +48,11 @@ from .const import ( class ZwaveValueID: """Class to represent a value ID.""" - property_: str | int | None = None - command_class: int | None = None + property_: str | int + command_class: int endpoint: int | None = None property_key: str | int | None = None - def __post_init__(self) -> None: - """Post initialization check.""" - if all(val is None for val in astuple(self)): - raise ValueError("At least one of the fields must be set.") - @callback def get_value_id_from_unique_id(unique_id: str) -> str | None: @@ -92,9 +88,10 @@ def get_value_of_zwave_value(value: ZwaveValue | None) -> Any | None: return value.value if value else None -async def async_enable_statistics(client: ZwaveClient) -> None: +async def async_enable_statistics(driver: Driver) -> None: """Enable statistics on the driver.""" - await client.driver.async_enable_statistics("Home Assistant", HA_VERSION) + await driver.async_enable_statistics("Home Assistant", HA_VERSION) + await driver.async_enable_error_reporting() @callback @@ -108,29 +105,29 @@ def update_data_collection_preference( @callback -def get_valueless_base_unique_id(client: ZwaveClient, node: ZwaveNode) -> str: +def get_valueless_base_unique_id(driver: Driver, node: ZwaveNode) -> str: """Return the base unique ID for an entity that is not based on a value.""" - return f"{client.driver.controller.home_id}.{node.node_id}" + return f"{driver.controller.home_id}.{node.node_id}" -def get_unique_id(client: ZwaveClient, value_id: str) -> str: +def get_unique_id(driver: Driver, value_id: str) -> str: """Get unique ID from client and value ID.""" - return f"{client.driver.controller.home_id}.{value_id}" + return f"{driver.controller.home_id}.{value_id}" @callback -def get_device_id(client: ZwaveClient, node: ZwaveNode) -> tuple[str, str]: +def get_device_id(driver: Driver, node: ZwaveNode) -> tuple[str, str]: """Get device registry identifier for Z-Wave node.""" - return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}") + return (DOMAIN, f"{driver.controller.home_id}-{node.node_id}") @callback -def get_device_id_ext(client: ZwaveClient, node: ZwaveNode) -> tuple[str, str] | None: +def get_device_id_ext(driver: Driver, node: ZwaveNode) -> tuple[str, str] | None: """Get extended device registry identifier for Z-Wave node.""" if None in (node.manufacturer_id, node.product_type, node.product_id): return None - domain, dev_id = get_device_id(client, node) + domain, dev_id = get_device_id(driver, node) return ( domain, f"{dev_id}-{node.manufacturer_id}:{node.product_type}:{node.product_id}", @@ -178,24 +175,26 @@ def async_get_node_from_device_id( # Use device config entry ID's to validate that this is a valid zwave_js device # and to get the client config_entry_ids = device_entry.config_entries - config_entry_id = next( + entry = next( ( - config_entry_id - for config_entry_id in config_entry_ids - if cast( - ConfigEntry, - hass.config_entries.async_get_entry(config_entry_id), - ).domain - == DOMAIN + entry + for entry in hass.config_entries.async_entries(DOMAIN) + if entry.entry_id in config_entry_ids ), None, ) - if config_entry_id is None or config_entry_id not in hass.data[DOMAIN]: + if entry and entry.state != ConfigEntryState.LOADED: + raise ValueError(f"Device {device_id} config entry is not loaded") + if entry is None or entry.entry_id not in hass.data[DOMAIN]: raise ValueError( f"Device {device_id} is not from an existing zwave_js config entry" ) - client = hass.data[DOMAIN][config_entry_id][DATA_CLIENT] + client: ZwaveClient = hass.data[DOMAIN][entry.entry_id][DATA_CLIENT] + driver = client.driver + + if driver is None: + raise ValueError("Driver is not ready.") # Get node ID from device identifier, perform some validation, and then get the # node @@ -203,10 +202,10 @@ def async_get_node_from_device_id( node_id = identifiers[1] if identifiers else None - if node_id is None or node_id not in client.driver.controller.nodes: + if node_id is None or node_id not in driver.controller.nodes: raise ValueError(f"Node for device {device_id} can't be found") - return client.driver.controller.nodes[node_id] + return driver.controller.nodes[node_id] @callback @@ -329,13 +328,22 @@ def get_zwave_value_from_config(node: ZwaveNode, config: ConfigType) -> ZwaveVal return node.values[value_id] +def _zwave_js_config_entry(hass: HomeAssistant, device: dr.DeviceEntry) -> str | None: + """Find zwave_js config entry from a device.""" + for entry_id in device.config_entries: + entry = hass.config_entries.async_get_entry(entry_id) + if entry and entry.domain == DOMAIN: + return entry_id + return None + + @callback def async_get_node_status_sensor_entity_id( hass: HomeAssistant, device_id: str, ent_reg: er.EntityRegistry | None = None, dev_reg: dr.DeviceRegistry | None = None, -) -> str: +) -> str | None: """Get the node status sensor entity ID for a given Z-Wave JS device.""" if not ent_reg: ent_reg = er.async_get(hass) @@ -344,20 +352,16 @@ def async_get_node_status_sensor_entity_id( if not (device := dev_reg.async_get(device_id)): raise HomeAssistantError("Invalid Device ID provided") - entry_id = next(entry_id for entry_id in device.config_entries) + if not (entry_id := _zwave_js_config_entry(hass, device)): + return None + client = hass.data[DOMAIN][entry_id][DATA_CLIENT] node = async_get_node_from_device_id(hass, device_id, dev_reg) - entity_id = ent_reg.async_get_entity_id( + return ent_reg.async_get_entity_id( SENSOR_DOMAIN, DOMAIN, f"{client.driver.controller.home_id}.{node.node_id}.node_status", ) - if not entity_id: - raise HomeAssistantError( - "Node status sensor entity not found. Device may not be a zwave_js device" - ) - - return entity_id def remove_keys_with_empty_values(config: ConfigType) -> ConfigType: @@ -384,21 +388,6 @@ def copy_available_params( ) -@callback -def async_is_device_config_entry_not_loaded( - hass: HomeAssistant, device_id: str -) -> bool: - """Return whether device's config entries are not loaded.""" - dev_reg = dr.async_get(hass) - if (device := dev_reg.async_get(device_id)) is None: - raise ValueError(f"Device {device_id} not found") - return any( - (entry := hass.config_entries.async_get_entry(entry_id)) - and entry.state != ConfigEntryState.LOADED - for entry_id in device.config_entries - ) - - def get_value_state_schema( value: ZwaveValue, ) -> vol.Schema | None: diff --git a/homeassistant/components/zwave_js/humidifier.py b/homeassistant/components/zwave_js/humidifier.py index 44d7bc19bbb..5aeb6d0272f 100644 --- a/homeassistant/components/zwave_js/humidifier.py +++ b/homeassistant/components/zwave_js/humidifier.py @@ -11,6 +11,7 @@ from zwave_js_server.const.command_class.humidity_control import ( HumidityControlMode, HumidityControlSetpointType, ) +from zwave_js_server.model.driver import Driver from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.components.humidifier import ( @@ -32,6 +33,8 @@ from .const import DATA_CLIENT, DOMAIN from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity +PARALLEL_UPDATES = 0 + @dataclass class ZwaveHumidifierEntityDescriptionRequiredKeys: @@ -83,6 +86,8 @@ async def async_setup_entry( @callback def async_add_humidifier(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Humidifier.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[ZWaveBaseEntity] = [] if ( @@ -91,7 +96,7 @@ async def async_setup_entry( ): entities.append( ZWaveHumidifier( - config_entry, client, info, HUMIDIFIER_ENTITY_DESCRIPTION + config_entry, driver, info, HUMIDIFIER_ENTITY_DESCRIPTION ) ) @@ -101,7 +106,7 @@ async def async_setup_entry( ): entities.append( ZWaveHumidifier( - config_entry, client, info, DEHUMIDIFIER_ENTITY_DESCRIPTION + config_entry, driver, info, DEHUMIDIFIER_ENTITY_DESCRIPTION ) ) @@ -126,12 +131,12 @@ class ZWaveHumidifier(ZWaveBaseEntity, HumidifierEntity): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, description: ZwaveHumidifierEntityDescription, ) -> None: """Initialize humidifier.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self.entity_description = description diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index ca51bc83986..17293e85a21 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Any +from typing import Any, cast from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ( @@ -23,6 +23,8 @@ from zwave_js_server.const.command_class.color_switch import ( TARGET_COLOR_PROPERTY, ColorComponent, ) +from zwave_js_server.model.driver import Driver +from zwave_js_server.model.value import Value from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -45,6 +47,8 @@ from .const import DATA_CLIENT, DOMAIN from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity +PARALLEL_UPDATES = 0 + LOGGER = logging.getLogger(__name__) MULTI_COLOR_MAP = { @@ -70,11 +74,13 @@ async def async_setup_entry( @callback def async_add_light(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Light.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. if info.platform_hint == "black_is_off": - async_add_entities([ZwaveBlackIsOffLight(config_entry, client, info)]) + async_add_entities([ZwaveBlackIsOffLight(config_entry, driver, info)]) else: - async_add_entities([ZwaveLight(config_entry, client, info)]) + async_add_entities([ZwaveLight(config_entry, driver, info)]) config_entry.async_on_unload( async_dispatcher_connect( @@ -99,10 +105,10 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): """Representation of a Z-Wave light.""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize the light.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self._supports_color = False self._supports_rgbw = False self._supports_color_temp = False @@ -296,10 +302,14 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): """Set (multiple) defined colors to given value(s).""" # prefer the (new) combined color property # https://github.com/zwave-js/node-zwave-js/pull/1782 - combined_color_val = self.get_zwave_value( - "targetColor", - CommandClass.SWITCH_COLOR, - value_property_key=None, + # Setting colors is only done if there's a target color value. + combined_color_val = cast( + Value, + self.get_zwave_value( + "targetColor", + CommandClass.SWITCH_COLOR, + value_property_key=None, + ), ) zwave_transition = None @@ -443,10 +453,10 @@ class ZwaveBlackIsOffLight(ZwaveLight): """ def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize the light.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self._last_color: dict[str, int] | None = None self._supported_color_modes.discard(ColorMode.BRIGHTNESS) diff --git a/homeassistant/components/zwave_js/lock.py b/homeassistant/components/zwave_js/lock.py index d70b6ef2009..ffe99373991 100644 --- a/homeassistant/components/zwave_js/lock.py +++ b/homeassistant/components/zwave_js/lock.py @@ -14,7 +14,6 @@ from zwave_js_server.const.command_class.lock import ( LOCK_CMD_CLASS_TO_PROPERTY_MAP, DoorLockMode, ) -from zwave_js_server.model.value import Value as ZwaveValue from zwave_js_server.util.lock import clear_usercode, set_usercode from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockEntity @@ -34,6 +33,8 @@ from .const import ( from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity +PARALLEL_UPDATES = 0 + LOGGER = logging.getLogger(__name__) STATE_TO_ZWAVE_MAP: dict[int, dict[str, int | bool]] = { @@ -59,8 +60,10 @@ async def async_setup_entry( @callback def async_add_lock(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Lock.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[ZWaveBaseEntity] = [] - entities.append(ZWaveLock(config_entry, client, info)) + entities.append(ZWaveLock(config_entry, driver, info)) async_add_entities(entities) @@ -107,8 +110,10 @@ class ZWaveLock(ZWaveBaseEntity, LockEntity): async def _set_lock_state(self, target_state: str, **kwargs: Any) -> None: """Set the lock state.""" - target_value: ZwaveValue = self.get_zwave_value( - LOCK_CMD_CLASS_TO_PROPERTY_MAP[self.info.primary_value.command_class] + target_value = self.get_zwave_value( + LOCK_CMD_CLASS_TO_PROPERTY_MAP[ + CommandClass(self.info.primary_value.command_class) + ] ) if target_value is not None: await self.info.node.async_set_value( diff --git a/homeassistant/components/zwave_js/logbook.py b/homeassistant/components/zwave_js/logbook.py new file mode 100644 index 00000000000..1fe1ff79ec6 --- /dev/null +++ b/homeassistant/components/zwave_js/logbook.py @@ -0,0 +1,115 @@ +"""Describe Z-Wave JS logbook events.""" +from __future__ import annotations + +from collections.abc import Callable + +from zwave_js_server.const import CommandClass + +from homeassistant.components.logbook.const import ( + LOGBOOK_ENTRY_MESSAGE, + LOGBOOK_ENTRY_NAME, +) +from homeassistant.const import ATTR_DEVICE_ID +from homeassistant.core import Event, HomeAssistant, callback +import homeassistant.helpers.device_registry as dr + +from .const import ( + ATTR_COMMAND_CLASS, + ATTR_COMMAND_CLASS_NAME, + ATTR_DATA_TYPE, + ATTR_DIRECTION, + ATTR_EVENT_LABEL, + ATTR_EVENT_TYPE, + ATTR_LABEL, + ATTR_VALUE, + DOMAIN, + ZWAVE_JS_NOTIFICATION_EVENT, + ZWAVE_JS_VALUE_NOTIFICATION_EVENT, +) + + +@callback +def async_describe_events( + hass: HomeAssistant, + async_describe_event: Callable[[str, str, Callable[[Event], dict[str, str]]], None], +) -> None: + """Describe logbook events.""" + dev_reg = dr.async_get(hass) + + @callback + def async_describe_zwave_js_notification_event( + event: Event, + ) -> dict[str, str]: + """Describe Z-Wave JS notification event.""" + device = dev_reg.devices[event.data[ATTR_DEVICE_ID]] + # Z-Wave JS devices always have a name + device_name = device.name_by_user or device.name + assert device_name + + command_class = event.data[ATTR_COMMAND_CLASS] + command_class_name = event.data[ATTR_COMMAND_CLASS_NAME] + + data: dict[str, str] = {LOGBOOK_ENTRY_NAME: device_name} + prefix = f"fired {command_class_name} CC 'notification' event" + + if command_class == CommandClass.NOTIFICATION: + label = event.data[ATTR_LABEL] + event_label = event.data[ATTR_EVENT_LABEL] + return { + **data, + LOGBOOK_ENTRY_MESSAGE: f"{prefix} '{label}': '{event_label}'", + } + + if command_class == CommandClass.ENTRY_CONTROL: + event_type = event.data[ATTR_EVENT_TYPE] + data_type = event.data[ATTR_DATA_TYPE] + return { + **data, + LOGBOOK_ENTRY_MESSAGE: ( + f"{prefix} for event type '{event_type}' with data type " + f"'{data_type}'" + ), + } + + if command_class == CommandClass.SWITCH_MULTILEVEL: + event_type = event.data[ATTR_EVENT_TYPE] + direction = event.data[ATTR_DIRECTION] + return { + **data, + LOGBOOK_ENTRY_MESSAGE: ( + f"{prefix} for event type '{event_type}': '{direction}'" + ), + } + + return {**data, LOGBOOK_ENTRY_MESSAGE: prefix} + + @callback + def async_describe_zwave_js_value_notification_event( + event: Event, + ) -> dict[str, str]: + """Describe Z-Wave JS value notification event.""" + device = dev_reg.devices[event.data[ATTR_DEVICE_ID]] + # Z-Wave JS devices always have a name + device_name = device.name_by_user or device.name + assert device_name + + command_class = event.data[ATTR_COMMAND_CLASS_NAME] + label = event.data[ATTR_LABEL] + value = event.data[ATTR_VALUE] + + return { + LOGBOOK_ENTRY_NAME: device_name, + LOGBOOK_ENTRY_MESSAGE: ( + f"fired {command_class} CC 'value notification' event for '{label}': " + f"'{value}'" + ), + } + + async_describe_event( + DOMAIN, ZWAVE_JS_NOTIFICATION_EVENT, async_describe_zwave_js_notification_event + ) + async_describe_event( + DOMAIN, + ZWAVE_JS_VALUE_NOTIFICATION_EVENT, + async_describe_zwave_js_value_notification_event, + ) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 934c3f9a3f5..1c7eabb4e86 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.36.1"], + "requirements": ["zwave-js-server-python==0.37.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["usb", "http", "websocket_api"], "iot_class": "local_push", diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py index 204b5d0aebd..400c2b3cffe 100644 --- a/homeassistant/components/zwave_js/migrate.py +++ b/homeassistant/components/zwave_js/migrate.py @@ -1,357 +1,27 @@ """Functions used to migrate unique IDs for Z-Wave JS entities.""" from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import dataclass import logging -from typing import TypedDict, cast -from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.driver import Driver from zwave_js_server.model.value import Value as ZwaveValue -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import LIGHT_LUX, STATE_UNAVAILABLE +from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.device_registry import ( - DeviceEntry, - async_get as async_get_device_registry, -) +from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.entity_registry import ( EntityRegistry, RegistryEntry, async_entries_for_device, - async_get as async_get_entity_registry, ) -from homeassistant.helpers.singleton import singleton -from homeassistant.helpers.storage import Store from .const import DOMAIN from .discovery import ZwaveDiscoveryInfo -from .helpers import get_device_id, get_unique_id +from .helpers import get_unique_id _LOGGER = logging.getLogger(__name__) -LEGACY_ZWAVE_MIGRATION = f"{DOMAIN}_legacy_zwave_migration" -MIGRATED = "migrated" -STORAGE_WRITE_DELAY = 30 -STORAGE_KEY = f"{DOMAIN}.legacy_zwave_migration" -STORAGE_VERSION = 1 - -NOTIFICATION_CC_LABEL_TO_PROPERTY_NAME = { - "Smoke": "Smoke Alarm", - "Carbon Monoxide": "CO Alarm", - "Carbon Dioxide": "CO2 Alarm", - "Heat": "Heat Alarm", - "Flood": "Water Alarm", - "Access Control": "Access Control", - "Burglar": "Home Security", - "Power Management": "Power Management", - "System": "System", - "Emergency": "Siren", - "Clock": "Clock", - "Appliance": "Appliance", - "HomeHealth": "Home Health", -} - -SENSOR_MULTILEVEL_CC_LABEL_TO_PROPERTY_NAME = { - "Temperature": "Air temperature", - "General": "General purpose", - "Luminance": "Illuminance", - "Power": "Power", - "Relative Humidity": "Humidity", - "Velocity": "Velocity", - "Direction": "Direction", - "Atmospheric Pressure": "Atmospheric pressure", - "Barometric Pressure": "Barometric pressure", - "Solar Radiation": "Solar radiation", - "Dew Point": "Dew point", - "Rain Rate": "Rain rate", - "Tide Level": "Tide level", - "Weight": "Weight", - "Voltage": "Voltage", - "Current": "Current", - "CO2 Level": "Carbon dioxide (COâ‚‚) level", - "Air Flow": "Air flow", - "Tank Capacity": "Tank capacity", - "Distance": "Distance", - "Angle Position": "Angle position", - "Rotation": "Rotation", - "Water Temperature": "Water temperature", - "Soil Temperature": "Soil temperature", - "Seismic Intensity": "Seismic Intensity", - "Seismic Magnitude": "Seismic magnitude", - "Ultraviolet": "Ultraviolet", - "Electrical Resistivity": "Electrical resistivity", - "Electrical Conductivity": "Electrical conductivity", - "Loudness": "Loudness", - "Moisture": "Moisture", -} - -CC_ID_LABEL_TO_PROPERTY = { - 49: SENSOR_MULTILEVEL_CC_LABEL_TO_PROPERTY_NAME, - 113: NOTIFICATION_CC_LABEL_TO_PROPERTY_NAME, -} - -UNIT_LEGACY_MIGRATION_MAP = {LIGHT_LUX: "Lux"} - - -class ZWaveMigrationData(TypedDict): - """Represent the Z-Wave migration data dict.""" - - node_id: int - node_instance: int - command_class: int - command_class_label: str - value_index: int - device_id: str - domain: str - entity_id: str - unique_id: str - unit_of_measurement: str | None - - -class ZWaveJSMigrationData(TypedDict): - """Represent the Z-Wave JS migration data dict.""" - - node_id: int - endpoint_index: int - command_class: int - value_property_name: str - value_property_key_name: str | None - value_id: str - device_id: str - domain: str - entity_id: str - unique_id: str - unit_of_measurement: str | None - - -@dataclass -class LegacyZWaveMappedData: - """Represent the mapped data between Z-Wave and Z-Wave JS.""" - - entity_entries: dict[str, ZWaveMigrationData] = field(default_factory=dict) - device_entries: dict[str, str] = field(default_factory=dict) - - -async def async_add_migration_entity_value( - hass: HomeAssistant, - config_entry: ConfigEntry, - entity_id: str, - discovery_info: ZwaveDiscoveryInfo, -) -> None: - """Add Z-Wave JS entity value for legacy Z-Wave migration.""" - migration_handler: LegacyZWaveMigration = await get_legacy_zwave_migration(hass) - migration_handler.add_entity_value(config_entry, entity_id, discovery_info) - - -async def async_get_migration_data( - hass: HomeAssistant, config_entry: ConfigEntry -) -> dict[str, ZWaveJSMigrationData]: - """Return Z-Wave JS migration data.""" - migration_handler: LegacyZWaveMigration = await get_legacy_zwave_migration(hass) - return await migration_handler.get_data(config_entry) - - -@singleton(LEGACY_ZWAVE_MIGRATION) -async def get_legacy_zwave_migration(hass: HomeAssistant) -> LegacyZWaveMigration: - """Return legacy Z-Wave migration handler.""" - migration_handler = LegacyZWaveMigration(hass) - await migration_handler.load_data() - return migration_handler - - -class LegacyZWaveMigration: - """Handle the migration from zwave to zwave_js.""" - - def __init__(self, hass: HomeAssistant) -> None: - """Set up migration instance.""" - self._hass = hass - self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY) - self._data: dict[str, dict[str, ZWaveJSMigrationData]] = {} - - async def load_data(self) -> None: - """Load Z-Wave JS migration data.""" - stored = cast(dict, await self._store.async_load()) - if stored: - self._data = stored - - @callback - def save_data( - self, config_entry_id: str, entity_id: str, data: ZWaveJSMigrationData - ) -> None: - """Save Z-Wave JS migration data.""" - if config_entry_id not in self._data: - self._data[config_entry_id] = {} - self._data[config_entry_id][entity_id] = data - self._store.async_delay_save(self._data_to_save, STORAGE_WRITE_DELAY) - - @callback - def _data_to_save(self) -> dict[str, dict[str, ZWaveJSMigrationData]]: - """Return data to save.""" - return self._data - - @callback - def add_entity_value( - self, - config_entry: ConfigEntry, - entity_id: str, - discovery_info: ZwaveDiscoveryInfo, - ) -> None: - """Add info for one entity and Z-Wave JS value.""" - ent_reg = async_get_entity_registry(self._hass) - dev_reg = async_get_device_registry(self._hass) - - node = discovery_info.node - primary_value = discovery_info.primary_value - entity_entry = ent_reg.async_get(entity_id) - assert entity_entry - device_identifier = get_device_id(node.client, node) - device_entry = dev_reg.async_get_device({device_identifier}, set()) - assert device_entry - - # Normalize unit of measurement. - if unit := entity_entry.unit_of_measurement: - _unit = UNIT_LEGACY_MIGRATION_MAP.get(unit, unit) - unit = _unit.lower() - if unit == "": - unit = None - - data: ZWaveJSMigrationData = { - "node_id": node.node_id, - "endpoint_index": node.index, - "command_class": primary_value.command_class, - "value_property_name": primary_value.property_name, - "value_property_key_name": primary_value.property_key_name, - "value_id": primary_value.value_id, - "device_id": device_entry.id, - "domain": entity_entry.domain, - "entity_id": entity_id, - "unique_id": entity_entry.unique_id, - "unit_of_measurement": unit, - } - - self.save_data(config_entry.entry_id, entity_id, data) - - async def get_data( - self, config_entry: ConfigEntry - ) -> dict[str, ZWaveJSMigrationData]: - """Return Z-Wave JS migration data for a config entry.""" - await self.load_data() - data = self._data.get(config_entry.entry_id) - return data or {} - - -@callback -def async_map_legacy_zwave_values( - zwave_data: dict[str, ZWaveMigrationData], - zwave_js_data: dict[str, ZWaveJSMigrationData], -) -> LegacyZWaveMappedData: - """Map Z-Wave node values onto Z-Wave JS node values.""" - migration_map = LegacyZWaveMappedData() - zwave_proc_data: dict[ - tuple[int, int, int, str, str | None, str | None], - ZWaveMigrationData | None, - ] = {} - zwave_js_proc_data: dict[ - tuple[int, int, int, str, str | None, str | None], - ZWaveJSMigrationData | None, - ] = {} - - for zwave_item in zwave_data.values(): - zwave_js_property_name = CC_ID_LABEL_TO_PROPERTY.get( - zwave_item["command_class"], {} - ).get(zwave_item["command_class_label"]) - item_id = ( - zwave_item["node_id"], - zwave_item["command_class"], - zwave_item["node_instance"] - 1, - zwave_item["domain"], - zwave_item["unit_of_measurement"], - zwave_js_property_name, - ) - - # Filter out duplicates that are not resolvable. - if item_id in zwave_proc_data: - zwave_proc_data[item_id] = None - continue - - zwave_proc_data[item_id] = zwave_item - - for zwave_js_item in zwave_js_data.values(): - # Only identify with property name if there is a command class label map. - if zwave_js_item["command_class"] in CC_ID_LABEL_TO_PROPERTY: - zwave_js_property_name = zwave_js_item["value_property_name"] - else: - zwave_js_property_name = None - item_id = ( - zwave_js_item["node_id"], - zwave_js_item["command_class"], - zwave_js_item["endpoint_index"], - zwave_js_item["domain"], - zwave_js_item["unit_of_measurement"], - zwave_js_property_name, - ) - - # Filter out duplicates that are not resolvable. - if item_id in zwave_js_proc_data: - zwave_js_proc_data[item_id] = None - continue - - zwave_js_proc_data[item_id] = zwave_js_item - - for item_id, zwave_entry in zwave_proc_data.items(): - zwave_js_entry = zwave_js_proc_data.pop(item_id, None) - - if zwave_entry is None or zwave_js_entry is None: - continue - - migration_map.entity_entries[zwave_js_entry["entity_id"]] = zwave_entry - migration_map.device_entries[zwave_js_entry["device_id"]] = zwave_entry[ - "device_id" - ] - - return migration_map - - -async def async_migrate_legacy_zwave( - hass: HomeAssistant, - zwave_config_entry: ConfigEntry, - zwave_js_config_entry: ConfigEntry, - migration_map: LegacyZWaveMappedData, -) -> None: - """Perform Z-Wave to Z-Wave JS migration.""" - dev_reg = async_get_device_registry(hass) - for zwave_js_device_id, zwave_device_id in migration_map.device_entries.items(): - zwave_device_entry = dev_reg.async_get(zwave_device_id) - if not zwave_device_entry: - continue - dev_reg.async_update_device( - zwave_js_device_id, - area_id=zwave_device_entry.area_id, - name_by_user=zwave_device_entry.name_by_user, - ) - - ent_reg = async_get_entity_registry(hass) - for zwave_js_entity_id, zwave_entry in migration_map.entity_entries.items(): - zwave_entity_id = zwave_entry["entity_id"] - if not (entity_entry := ent_reg.async_get(zwave_entity_id)): - continue - ent_reg.async_remove(zwave_entity_id) - ent_reg.async_update_entity( - zwave_js_entity_id, - new_entity_id=entity_entry.entity_id, - name=entity_entry.name, - icon=entity_entry.icon, - ) - - await hass.config_entries.async_remove(zwave_config_entry.entry_id) - - updates = { - **zwave_js_config_entry.data, - MIGRATED: True, - } - hass.config_entries.async_update_entry(zwave_js_config_entry, data=updates) - @dataclass class ValueID: @@ -468,12 +138,12 @@ def async_migrate_discovered_value( ent_reg: EntityRegistry, registered_unique_ids: set[str], device: DeviceEntry, - client: ZwaveClient, + driver: Driver, disc_info: ZwaveDiscoveryInfo, ) -> None: """Migrate unique ID for entity/entities tied to discovered value.""" - new_unique_id = get_unique_id(client, disc_info.primary_value.value_id) + new_unique_id = get_unique_id(driver, disc_info.primary_value.value_id) # On reinterviews, there is no point in going through this logic again for already # discovered values @@ -485,7 +155,7 @@ def async_migrate_discovered_value( # 2021.2.*, 2021.3.0b0, and 2021.3.0 formats old_unique_ids = [ - get_unique_id(client, value_id) + get_unique_id(driver, value_id) for value_id in get_old_value_ids(disc_info.primary_value) ] diff --git a/homeassistant/components/zwave_js/number.py b/homeassistant/components/zwave_js/number.py index 16434b51108..737b872b7bc 100644 --- a/homeassistant/components/zwave_js/number.py +++ b/homeassistant/components/zwave_js/number.py @@ -1,12 +1,17 @@ """Support for Z-Wave controls using the number platform.""" from __future__ import annotations +from typing import cast + from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import TARGET_VALUE_PROPERTY +from zwave_js_server.model.driver import Driver +from zwave_js_server.model.value import Value from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN, NumberEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -14,6 +19,8 @@ from .const import DATA_CLIENT, DOMAIN from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity +PARALLEL_UPDATES = 0 + async def async_setup_entry( hass: HomeAssistant, @@ -26,11 +33,13 @@ async def async_setup_entry( @callback def async_add_number(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave number entity.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "volume": - entities.append(ZwaveVolumeNumberEntity(config_entry, client, info)) + entities.append(ZwaveVolumeNumberEntity(config_entry, driver, info)) else: - entities.append(ZwaveNumberEntity(config_entry, client, info)) + entities.append(ZwaveNumberEntity(config_entry, driver, info)) async_add_entities(entities) config_entry.async_on_unload( @@ -46,10 +55,11 @@ class ZwaveNumberEntity(ZWaveBaseEntity, NumberEntity): """Representation of a Z-Wave number entity.""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize a ZwaveNumberEntity entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) + self._target_value: Value | None if self.info.primary_value.metadata.writeable: self._target_value = self.info.primary_value else: @@ -90,20 +100,22 @@ class ZwaveNumberEntity(ZWaveBaseEntity, NumberEntity): async def async_set_value(self, value: float) -> None: """Set new value.""" - await self.info.node.async_set_value(self._target_value, value) + if (target_value := self._target_value) is None: + raise HomeAssistantError("Missing target value on device.") + await self.info.node.async_set_value(target_value, value) class ZwaveVolumeNumberEntity(ZWaveBaseEntity, NumberEntity): """Representation of a volume number entity.""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize a ZwaveVolumeNumberEntity entity.""" - super().__init__(config_entry, client, info) - self.correction_factor = int( - self.info.primary_value.metadata.max - self.info.primary_value.metadata.min - ) + super().__init__(config_entry, driver, info) + max_value = cast(int, self.info.primary_value.metadata.max) + min_value = cast(int, self.info.primary_value.metadata.min) + self.correction_factor = max_value - min_value # Fallback in case we can't properly calculate correction factor if self.correction_factor == 0: self.correction_factor = 1 diff --git a/homeassistant/components/zwave_js/select.py b/homeassistant/components/zwave_js/select.py index c6bc11a4804..f61149e5de7 100644 --- a/homeassistant/components/zwave_js/select.py +++ b/homeassistant/components/zwave_js/select.py @@ -6,10 +6,12 @@ from typing import cast from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import TARGET_VALUE_PROPERTY, CommandClass from zwave_js_server.const.command_class.sound_switch import ToneID +from zwave_js_server.model.driver import Driver from homeassistant.components.select import DOMAIN as SELECT_DOMAIN, SelectEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -18,6 +20,8 @@ from .const import DATA_CLIENT, DOMAIN from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity +PARALLEL_UPDATES = 0 + async def async_setup_entry( hass: HomeAssistant, @@ -30,15 +34,17 @@ async def async_setup_entry( @callback def async_add_select(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave select entity.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "Default tone": - entities.append(ZwaveDefaultToneSelectEntity(config_entry, client, info)) + entities.append(ZwaveDefaultToneSelectEntity(config_entry, driver, info)) elif info.platform_hint == "multilevel_switch": entities.append( - ZwaveMultilevelSwitchSelectEntity(config_entry, client, info) + ZwaveMultilevelSwitchSelectEntity(config_entry, driver, info) ) else: - entities.append(ZwaveSelectEntity(config_entry, client, info)) + entities.append(ZwaveSelectEntity(config_entry, driver, info)) async_add_entities(entities) config_entry.async_on_unload( @@ -56,10 +62,10 @@ class ZwaveSelectEntity(ZWaveBaseEntity, SelectEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize a ZwaveSelectEntity entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) # Entity class attributes self._attr_name = self.generate_name(include_value_name=True) @@ -92,10 +98,10 @@ class ZwaveDefaultToneSelectEntity(ZWaveBaseEntity, SelectEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize a ZwaveDefaultToneSelectEntity entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self._tones_value = self.get_zwave_value( "toneId", command_class=CommandClass.SOUND_SWITCH ) @@ -143,10 +149,10 @@ class ZwaveMultilevelSwitchSelectEntity(ZWaveBaseEntity, SelectEntity): """Representation of a Z-Wave Multilevel Switch CC select entity.""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize a ZwaveSelectEntity entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self._target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) assert self.info.platform_data_template self._lookup_map = cast( @@ -169,5 +175,7 @@ class ZwaveMultilevelSwitchSelectEntity(ZWaveBaseEntity, SelectEntity): async def async_select_option(self, option: str) -> None: """Change the selected option.""" + if (target_value := self._target_value) is None: + raise HomeAssistantError("Missing target value on device.") key = next(key for key, val in self._lookup_map.items() if val == option) - await self.info.node.async_set_value(self._target_value, int(key)) + await self.info.node.async_set_value(target_value, int(key)) diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index e60a0793608..2b2e2a0de2b 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -12,6 +12,7 @@ from zwave_js_server.const.command_class.meter import ( RESET_METER_OPTION_TARGET_VALUE, RESET_METER_OPTION_TYPE, ) +from zwave_js_server.model.driver import Driver from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.value import ConfigurationValue from zwave_js_server.util.command_class.meter import get_meter_type @@ -25,6 +26,7 @@ from homeassistant.components.sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo, EntityCategory @@ -63,6 +65,8 @@ from .discovery_data_template import ( from .entity import ZWaveBaseEntity from .helpers import get_device_id, get_valueless_base_unique_id +PARALLEL_UPDATES = 0 + LOGGER = logging.getLogger(__name__) STATUS_ICON: dict[NodeStatus, str] = { @@ -177,6 +181,8 @@ async def async_setup_entry( @callback def async_add_sensor(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Sensor.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[ZWaveBaseEntity] = [] if info.platform_data: @@ -189,13 +195,13 @@ async def async_setup_entry( if info.platform_hint == "string_sensor": entities.append( - ZWaveStringSensor(config_entry, client, info, entity_description) + ZWaveStringSensor(config_entry, driver, info, entity_description) ) elif info.platform_hint == "numeric_sensor": entities.append( ZWaveNumericSensor( config_entry, - client, + driver, info, entity_description, data.unit_of_measurement, @@ -203,23 +209,23 @@ async def async_setup_entry( ) elif info.platform_hint == "list_sensor": entities.append( - ZWaveListSensor(config_entry, client, info, entity_description) + ZWaveListSensor(config_entry, driver, info, entity_description) ) elif info.platform_hint == "config_parameter": entities.append( ZWaveConfigParameterSensor( - config_entry, client, info, entity_description + config_entry, driver, info, entity_description ) ) elif info.platform_hint == "meter": entities.append( - ZWaveMeterSensor(config_entry, client, info, entity_description) + ZWaveMeterSensor(config_entry, driver, info, entity_description) ) else: LOGGER.warning( "Sensor not implemented for %s/%s", info.platform_hint, - info.primary_value.propertyname, + info.primary_value.property_name, ) return @@ -228,7 +234,9 @@ async def async_setup_entry( @callback def async_add_node_status_sensor(node: ZwaveNode) -> None: """Add node status sensor.""" - async_add_entities([ZWaveNodeStatusSensor(config_entry, client, node)]) + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. + async_add_entities([ZWaveNodeStatusSensor(config_entry, driver, node)]) config_entry.async_on_unload( async_dispatcher_connect( @@ -263,13 +271,13 @@ class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, entity_description: SensorEntityDescription, unit_of_measurement: str | None = None, ) -> None: """Initialize a ZWaveSensorBase entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self.entity_description = entity_description self._attr_native_unit_of_measurement = unit_of_measurement @@ -345,13 +353,15 @@ class ZWaveMeterSensor(ZWaveNumericSensor): """Reset meter(s) on device.""" node = self.info.node primary_value = self.info.primary_value + if (endpoint := primary_value.endpoint) is None: + raise HomeAssistantError("Missing endpoint on device.") options = {} if meter_type is not None: options[RESET_METER_OPTION_TYPE] = meter_type if value is not None: options[RESET_METER_OPTION_TARGET_VALUE] = value args = [options] if options else [] - await node.endpoints[primary_value.endpoint].async_invoke_cc_api( + await node.endpoints[endpoint].async_invoke_cc_api( CommandClass.METER, "reset", *args, wait_for_result=False ) LOGGER.debug( @@ -368,21 +378,22 @@ class ZWaveListSensor(ZwaveSensorBase): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, entity_description: SensorEntityDescription, unit_of_measurement: str | None = None, ) -> None: """Initialize a ZWaveListSensor entity.""" super().__init__( - config_entry, client, info, entity_description, unit_of_measurement + config_entry, driver, info, entity_description, unit_of_measurement ) + property_key_name = self.info.primary_value.property_key_name # Entity class attributes self._attr_name = self.generate_name( include_value_name=True, alternate_value_name=self.info.primary_value.property_name, - additional_info=[self.info.primary_value.property_key_name], + additional_info=[property_key_name] if property_key_name else None, ) @property @@ -402,8 +413,10 @@ class ZWaveListSensor(ZwaveSensorBase): @property def extra_state_attributes(self) -> dict[str, str] | None: """Return the device specific state attributes.""" + if (value := self.info.primary_value.value) is None: + return None # add the value's int value as property for multi-value (list) items - return {ATTR_VALUE: self.info.primary_value.value} + return {ATTR_VALUE: value} class ZWaveConfigParameterSensor(ZwaveSensorBase): @@ -412,22 +425,23 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, entity_description: SensorEntityDescription, unit_of_measurement: str | None = None, ) -> None: """Initialize a ZWaveConfigParameterSensor entity.""" super().__init__( - config_entry, client, info, entity_description, unit_of_measurement + config_entry, driver, info, entity_description, unit_of_measurement ) self._primary_value = cast(ConfigurationValue, self.info.primary_value) + property_key_name = self.info.primary_value.property_key_name # Entity class attributes self._attr_name = self.generate_name( include_value_name=True, alternate_value_name=self.info.primary_value.property_name, - additional_info=[self.info.primary_value.property_key_name], + additional_info=[property_key_name] if property_key_name else None, name_suffix="Config Parameter", ) @@ -451,10 +465,13 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase): @property def extra_state_attributes(self) -> dict[str, str] | None: """Return the device specific state attributes.""" - if self._primary_value.configuration_value_type == ConfigurationValueType.RANGE: + if ( + self._primary_value.configuration_value_type == ConfigurationValueType.RANGE + or (value := self.info.primary_value.value) is None + ): return None # add the value's int value as property for multi-value (list) items - return {ATTR_VALUE: self.info.primary_value.value} + return {ATTR_VALUE: value} class ZWaveNodeStatusSensor(SensorEntity): @@ -464,11 +481,10 @@ class ZWaveNodeStatusSensor(SensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, node: ZwaveNode + self, config_entry: ConfigEntry, driver: Driver, node: ZwaveNode ) -> None: """Initialize a generic Z-Wave device entity.""" self.config_entry = config_entry - self.client = client self.node = node name: str = ( self.node.name @@ -477,11 +493,11 @@ class ZWaveNodeStatusSensor(SensorEntity): ) # Entity class attributes self._attr_name = f"{name}: Node Status" - self._base_unique_id = get_valueless_base_unique_id(client, node) + self._base_unique_id = get_valueless_base_unique_id(driver, node) self._attr_unique_id = f"{self._base_unique_id}.node_status" # device is precreated in main handler self._attr_device_info = DeviceInfo( - identifiers={get_device_id(self.client, self.node)}, + identifiers={get_device_id(driver, self.node)}, ) self._attr_native_value: str = node.status.name.lower() diff --git a/homeassistant/components/zwave_js/services.py b/homeassistant/components/zwave_js/services.py index 70099859d39..d60532fcf75 100644 --- a/homeassistant/components/zwave_js/services.py +++ b/homeassistant/components/zwave_js/services.py @@ -2,16 +2,17 @@ from __future__ import annotations import asyncio +from collections.abc import Generator, Sequence import logging -from typing import Any, cast +from typing import Any import voluptuous as vol from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import CommandClass, CommandStatus -from zwave_js_server.exceptions import FailedCommand, SetValueFailed +from zwave_js_server.exceptions import 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.model.value import ValueDataType, get_value_id from zwave_js_server.util.multicast import async_multicast_set_value from zwave_js_server.util.node import ( async_bulk_set_partial_config_parameters, @@ -38,6 +39,12 @@ from .helpers import ( _LOGGER = logging.getLogger(__name__) +SET_VALUE_FAILED_EXC = SetValueFailed( + "Unable to set value, refer to " + "https://zwave-js.github.io/node-zwave-js/#/api/node?id=setvalue for " + "possible reasons" +) + def parameter_name_does_not_need_bitmask( val: dict[str, int | str | list[str]] @@ -64,6 +71,33 @@ def broadcast_command(val: dict[str, Any]) -> dict[str, Any]: ) +def get_valid_responses_from_results( + zwave_objects: Sequence[ZwaveNode | Endpoint], results: Sequence[Any] +) -> Generator[tuple[ZwaveNode | Endpoint, Any], None, None]: + """Return valid responses from a list of results.""" + for zwave_object, result in zip(zwave_objects, results): + if not isinstance(result, Exception): + yield zwave_object, result + + +def raise_exceptions_from_results( + zwave_objects: Sequence[ZwaveNode | Endpoint], + results: Sequence[Any], +) -> None: + """Raise list of exceptions from a list of results.""" + if errors := [ + tup for tup in zip(zwave_objects, results) if isinstance(tup[1], Exception) + ]: + lines = ( + f"{len(errors)} error(s):", + *( + f"{zwave_object} - {error.__class__.__name__}: {error.args[0]}" + for zwave_object, error in errors + ), + ) + raise HomeAssistantError("\n".join(lines)) + + class ZWaveServices: """Class that holds our services (Zwave Commands) that should be published to hass.""" @@ -119,12 +153,20 @@ class ZWaveServices: first_node = next((node for node in nodes), None) + if first_node and not all(node.client.driver is not None for node in nodes): + raise vol.Invalid(f"Driver not ready for all nodes: {nodes}") + # If any nodes don't have matching home IDs, we can't run the command because # we can't multicast across multiple networks - if first_node and any( - node.client.driver.controller.home_id - != first_node.client.driver.controller.home_id - for node in nodes + if ( + first_node + and first_node.client.driver # We checked the driver was ready above. + and any( + node.client.driver.controller.home_id + != first_node.client.driver.controller.home_id + for node in nodes + if node.client.driver is not None + ) ): raise vol.Invalid( "Multicast commands only work on devices in the same network" @@ -366,19 +408,27 @@ class ZWaveServices: async def async_set_config_parameter(self, service: ServiceCall) -> None: """Set a config value on a node.""" - nodes = service.data[const.ATTR_NODES] + nodes: set[ZwaveNode] = 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) new_value = service.data[const.ATTR_CONFIG_VALUE] - for node in nodes: - zwave_value, cmd_status = await async_set_config_parameter( - node, - new_value, - property_or_property_name, - property_key=property_key, - ) - + results = await asyncio.gather( + *( + async_set_config_parameter( + node, + new_value, + property_or_property_name, + property_key=property_key, + ) + for node in nodes + ), + return_exceptions=True, + ) + nodes_list = list(nodes) + for node, result in get_valid_responses_from_results(nodes_list, results): + zwave_value = result[0] + cmd_status = result[1] if cmd_status == CommandStatus.ACCEPTED: msg = "Set configuration parameter %s on Node %s with value %s" else: @@ -386,34 +436,43 @@ class ZWaveServices: "Added command to queue to set configuration parameter %s on Node " "%s with value %s. Parameter will be set when the device wakes up" ) - _LOGGER.info(msg, zwave_value, node, new_value) + raise_exceptions_from_results(nodes_list, results) async def async_bulk_set_partial_config_parameters( self, service: ServiceCall ) -> None: """Bulk set multiple partial config values on a node.""" - nodes = service.data[const.ATTR_NODES] + nodes: set[ZwaveNode] = service.data[const.ATTR_NODES] property_ = service.data[const.ATTR_CONFIG_PARAMETER] new_value = service.data[const.ATTR_CONFIG_VALUE] - for node in nodes: - cmd_status = await async_bulk_set_partial_config_parameters( - node, - property_, - new_value, - ) + results = await asyncio.gather( + *( + async_bulk_set_partial_config_parameters( + node, + property_, + new_value, + ) + for node in nodes + ), + return_exceptions=True, + ) + nodes_list = list(nodes) + for node, cmd_status in get_valid_responses_from_results(nodes_list, results): if cmd_status == CommandStatus.ACCEPTED: msg = "Bulk set partials for configuration parameter %s on Node %s" else: msg = ( - "Added command to queue to bulk set partials for configuration " - "parameter %s on Node %s" + "Queued command to bulk set partials for configuration parameter " + "%s on Node %s" ) _LOGGER.info(msg, property_, node) + raise_exceptions_from_results(nodes_list, results) + async def async_poll_value(self, service: ServiceCall) -> None: """Poll value on a node.""" for entity_id in service.data[ATTR_ENTITY_ID]: @@ -428,14 +487,15 @@ class ZWaveServices: async def async_set_value(self, service: ServiceCall) -> None: """Set a value on a node.""" nodes: set[ZwaveNode] = service.data[const.ATTR_NODES] - command_class = service.data[const.ATTR_COMMAND_CLASS] - property_ = service.data[const.ATTR_PROPERTY] - property_key = service.data.get(const.ATTR_PROPERTY_KEY) - endpoint = service.data.get(const.ATTR_ENDPOINT) + command_class: CommandClass = service.data[const.ATTR_COMMAND_CLASS] + property_: int | str = service.data[const.ATTR_PROPERTY] + property_key: int | str | None = service.data.get(const.ATTR_PROPERTY_KEY) + endpoint: int | None = service.data.get(const.ATTR_ENDPOINT) new_value = service.data[const.ATTR_VALUE] wait_for_result = service.data.get(const.ATTR_WAIT_FOR_RESULT) options = service.data.get(const.ATTR_OPTIONS) + coros = [] for node in nodes: value_id = get_value_id( node, @@ -455,23 +515,34 @@ class ZWaveServices: new_value_ = str(new_value) else: new_value_ = new_value - success = await node.async_set_value( - value_id, - new_value_, - options=options, - wait_for_result=wait_for_result, + coros.append( + node.async_set_value( + value_id, + new_value_, + options=options, + wait_for_result=wait_for_result, + ) ) + results = await asyncio.gather(*coros, return_exceptions=True) + nodes_list = list(nodes) + # multiple set_values my fail so we will track the entire list + set_value_failed_nodes_list: list[ZwaveNode | Endpoint] = [] + for node_, success in get_valid_responses_from_results(nodes_list, results): if success is False: - 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 + # If we failed to set a value, add node to SetValueFailed exception list + set_value_failed_nodes_list.append(node_) + + # Add the SetValueFailed exception to the results and the nodes to the node + # list. No-op if there are no SetValueFailed exceptions + raise_exceptions_from_results( + (*nodes_list, *set_value_failed_nodes_list), + (*results, *([SET_VALUE_FAILED_EXC] * len(set_value_failed_nodes_list))), + ) async def async_multicast_set_value(self, service: ServiceCall) -> None: """Set a value via multicast to multiple nodes.""" - nodes = service.data[const.ATTR_NODES] + nodes: set[ZwaveNode] = service.data[const.ATTR_NODES] broadcast: bool = service.data[const.ATTR_BROADCAST] options = service.data.get(const.ATTR_OPTIONS) @@ -483,29 +554,31 @@ class ZWaveServices: await self.async_set_value(service) return - command_class = service.data[const.ATTR_COMMAND_CLASS] - property_ = service.data[const.ATTR_PROPERTY] - property_key = service.data.get(const.ATTR_PROPERTY_KEY) - endpoint = service.data.get(const.ATTR_ENDPOINT) + command_class: CommandClass = service.data[const.ATTR_COMMAND_CLASS] + property_: int | str = service.data[const.ATTR_PROPERTY] + property_key: int | str | None = service.data.get(const.ATTR_PROPERTY_KEY) + endpoint: int | None = service.data.get(const.ATTR_ENDPOINT) + + value = ValueDataType(commandClass=command_class, property=property_) + if property_key is not None: + value["propertyKey"] = property_key + if endpoint is not None: + value["endpoint"] = endpoint - value = { - "commandClass": command_class, - "property": property_, - "propertyKey": property_key, - "endpoint": endpoint, - } new_value = service.data[const.ATTR_VALUE] # If there are no nodes, we can assume there is only one config entry due to # schema validation and can use that to get the client, otherwise we can just # get the client from the node. - client: ZwaveClient = None - first_node: ZwaveNode = next((node for node in nodes), None) - if first_node: + client: ZwaveClient + first_node: ZwaveNode + try: + first_node = next(node for node in nodes) client = first_node.client - else: + except StopIteration: entry_id = self._hass.config_entries.async_entries(const.DOMAIN)[0].entry_id client = self._hass.data[const.DOMAIN][entry_id][const.DATA_CLIENT] + assert client.driver first_node = next( node for node in client.driver.controller.nodes.values() @@ -528,7 +601,7 @@ class ZWaveServices: success = await async_multicast_set_value( client=client, new_value=new_value, - value_data={k: v for k, v in value.items() if v is not None}, + value_data=value, nodes=None if broadcast else list(nodes), options=options, ) @@ -540,7 +613,6 @@ class ZWaveServices: async def async_ping(self, service: ServiceCall) -> None: """Ping node(s).""" - # pylint: disable=no-self-use _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 " @@ -557,24 +629,30 @@ class ZWaveServices: 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: + results = await asyncio.gather( + *( + endpoint.async_invoke_cc_api( + command_class, method_name, *parameters + ) + for endpoint in endpoints + ), + return_exceptions=True, + ) + endpoints_list = list(endpoints) + for endpoint, result in get_valid_responses_from_results( + endpoints_list, results + ): _LOGGER.info( - "Invoking %s CC API method %s on endpoint %s", + ( + "Invoked %s CC API method %s on endpoint %s with the following " + "result: %s" + ), command_class.name, method_name, endpoint, + result, ) - 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]) - ) + raise_exceptions_from_results(endpoints_list, results) # If an endpoint is provided, we assume the user wants to call the CC API on # that endpoint for all target nodes @@ -624,6 +702,9 @@ class ZWaveServices: _LOGGER.warning("Skipping entity %s as it has no value ID", entity_id) continue - endpoints.add(node.endpoints[node.values[value_id].endpoint]) + endpoint_idx = node.values[value_id].endpoint + endpoints.add( + node.endpoints[endpoint_idx if endpoint_idx is not None else 0] + ) await _async_invoke_cc_api(endpoints) diff --git a/homeassistant/components/zwave_js/siren.py b/homeassistant/components/zwave_js/siren.py index e7443f33dae..67e6aa4afb4 100644 --- a/homeassistant/components/zwave_js/siren.py +++ b/homeassistant/components/zwave_js/siren.py @@ -5,6 +5,7 @@ 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 zwave_js_server.model.driver import Driver from homeassistant.components.siren import ( DOMAIN as SIREN_DOMAIN, @@ -21,6 +22,8 @@ from .const import DATA_CLIENT, DOMAIN from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity +PARALLEL_UPDATES = 0 + async def async_setup_entry( hass: HomeAssistant, @@ -33,8 +36,10 @@ async def async_setup_entry( @callback def async_add_siren(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave siren entity.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[ZWaveBaseEntity] = [] - entities.append(ZwaveSirenEntity(config_entry, client, info)) + entities.append(ZwaveSirenEntity(config_entry, driver, info)) async_add_entities(entities) config_entry.async_on_unload( @@ -50,10 +55,10 @@ class ZwaveSirenEntity(ZWaveBaseEntity, SirenEntity): """Representation of a Z-Wave siren entity.""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize a ZwaveSirenEntity entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) # Entity class attributes self._attr_available_tones = { int(id): val for id, val in self.info.primary_value.metadata.states.items() diff --git a/homeassistant/components/zwave_js/switch.py b/homeassistant/components/zwave_js/switch.py index a680a8fb04a..52b8f813326 100644 --- a/homeassistant/components/zwave_js/switch.py +++ b/homeassistant/components/zwave_js/switch.py @@ -9,6 +9,7 @@ from zwave_js_server.const import TARGET_VALUE_PROPERTY from zwave_js_server.const.command_class.barrier_operator import ( BarrierEventSignalingSubsystemState, ) +from zwave_js_server.model.driver import Driver from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -20,6 +21,8 @@ from .const import DATA_CLIENT, DOMAIN from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity +PARALLEL_UPDATES = 0 + LOGGER = logging.getLogger(__name__) @@ -34,13 +37,15 @@ async def async_setup_entry( @callback def async_add_switch(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Switch.""" + driver = client.driver + assert driver is not None # Driver is ready before platforms are loaded. entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "barrier_event_signaling_state": entities.append( - ZWaveBarrierEventSignalingSwitch(config_entry, client, info) + ZWaveBarrierEventSignalingSwitch(config_entry, driver, info) ) else: - entities.append(ZWaveSwitch(config_entry, client, info)) + entities.append(ZWaveSwitch(config_entry, driver, info)) async_add_entities(entities) @@ -57,10 +62,10 @@ class ZWaveSwitch(ZWaveBaseEntity, SwitchEntity): """Representation of a Z-Wave switch.""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo ) -> None: """Initialize the switch.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self._target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) @@ -89,11 +94,11 @@ class ZWaveBarrierEventSignalingSwitch(ZWaveBaseEntity, SwitchEntity): def __init__( self, config_entry: ConfigEntry, - client: ZwaveClient, + driver: Driver, info: ZwaveDiscoveryInfo, ) -> None: """Initialize a ZWaveBarrierEventSignalingSwitch entity.""" - super().__init__(config_entry, client, info) + super().__init__(config_entry, driver, info) self._state: bool | None = None self._update_state() diff --git a/homeassistant/components/zwave_js/translations/bg.json b/homeassistant/components/zwave_js/translations/bg.json index 59fcf968740..5d2486acbc1 100644 --- a/homeassistant/components/zwave_js/translations/bg.json +++ b/homeassistant/components/zwave_js/translations/bg.json @@ -35,7 +35,6 @@ "step": { "configure_addon": { "data": { - "network_key": "\u041c\u0440\u0435\u0436\u043e\u0432 \u043a\u043b\u044e\u0447", "s0_legacy_key": "S0 \u043a\u043b\u044e\u0447 (\u043d\u0430\u0441\u043b\u0435\u0434\u0435\u043d)", "s2_access_control_key": "S2 \u043a\u043b\u044e\u0447 \u0437\u0430 \u043a\u043e\u043d\u0442\u0440\u043e\u043b \u043d\u0430 \u0434\u043e\u0441\u0442\u044a\u043f\u0430", "s2_authenticated_key": "S2 \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u0435\u043d \u043a\u043b\u044e\u0447", diff --git a/homeassistant/components/zwave_js/translations/ca.json b/homeassistant/components/zwave_js/translations/ca.json index 6691894b79d..de21cc3f232 100644 --- a/homeassistant/components/zwave_js/translations/ca.json +++ b/homeassistant/components/zwave_js/translations/ca.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Clau de xarxa", "s0_legacy_key": "Clau d'S0 (est\u00e0ndard)", "s2_access_control_key": "Clau de control d'acc\u00e9s d'S2", "s2_authenticated_key": "Clau d'S2 autenticat", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Emula maquinari", "log_level": "Nivell dels registres", - "network_key": "Clau de xarxa", "s0_legacy_key": "Clau d'S0 (est\u00e0ndard)", "s2_access_control_key": "Clau de control d'acc\u00e9s d'S2", "s2_authenticated_key": "Clau d'S2 autenticat", @@ -146,6 +144,5 @@ "title": "El complement Z-Wave JS s'est\u00e0 iniciant." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/cs.json b/homeassistant/components/zwave_js/translations/cs.json index 5c16938b23b..6417d5a587f 100644 --- a/homeassistant/components/zwave_js/translations/cs.json +++ b/homeassistant/components/zwave_js/translations/cs.json @@ -45,8 +45,7 @@ "step": { "configure_addon": { "data": { - "log_level": "\u00darove\u0148 protokolu", - "network_key": "S\u00ed\u0165ov\u00fd kl\u00ed\u010d" + "log_level": "\u00darove\u0148 protokolu" } }, "manual": { diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json index 02418f44306..ec0157f9a66 100644 --- a/homeassistant/components/zwave_js/translations/de.json +++ b/homeassistant/components/zwave_js/translations/de.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Netzwerk-Schl\u00fcssel", "s0_legacy_key": "S0 Schl\u00fcssel (Legacy)", "s2_access_control_key": "S2 Zugangskontrollschl\u00fcssel", "s2_authenticated_key": "S2 Authentifizierter Schl\u00fcssel", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Hardware emulieren", "log_level": "Protokollstufe", - "network_key": "Netzwerkschl\u00fcssel", "s0_legacy_key": "S0 Schl\u00fcssel (Legacy)", "s2_access_control_key": "S2 Zugangskontrollschl\u00fcssel", "s2_authenticated_key": "S2 Authentifizierter Schl\u00fcssel", @@ -146,6 +144,5 @@ "title": "Das Z-Wave JS Add-on wird gestartet." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/el.json b/homeassistant/components/zwave_js/translations/el.json index 66330206b60..930c82aa891 100644 --- a/homeassistant/components/zwave_js/translations/el.json +++ b/homeassistant/components/zwave_js/translations/el.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", "s0_legacy_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af S0 (\u03c0\u03b1\u03bb\u03b1\u03b9\u03bf\u03cd \u03c4\u03cd\u03c0\u03bf\u03c5)", "s2_access_control_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 S2", "s2_authenticated_key": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "\u0395\u03be\u03bf\u03bc\u03bf\u03af\u03c9\u03c3\u03b7 \u03c5\u03bb\u03b9\u03ba\u03bf\u03cd", "log_level": "\u0395\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2", - "network_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5", "s0_legacy_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af S0 (\u03c0\u03b1\u03bb\u03b1\u03b9\u03bf\u03cd \u03c4\u03cd\u03c0\u03bf\u03c5)", "s2_access_control_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 S2", "s2_authenticated_key": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af S2", @@ -146,6 +144,5 @@ "title": "\u03a4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS \u03be\u03b5\u03ba\u03b9\u03bd\u03ac." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index fa9794ed847..843f2aaf284 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Network Key", "s0_legacy_key": "S0 Key (Legacy)", "s2_access_control_key": "S2 Access Control Key", "s2_authenticated_key": "S2 Authenticated Key", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Emulate Hardware", "log_level": "Log level", - "network_key": "Network Key", "s0_legacy_key": "S0 Key (Legacy)", "s2_access_control_key": "S2 Access Control Key", "s2_authenticated_key": "S2 Authenticated Key", @@ -146,6 +144,5 @@ "title": "The Z-Wave JS add-on is starting." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/es.json b/homeassistant/components/zwave_js/translations/es.json index 1002d0ad0b3..5d3efb0e7f4 100644 --- a/homeassistant/components/zwave_js/translations/es.json +++ b/homeassistant/components/zwave_js/translations/es.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Clave de red", "s0_legacy_key": "Clave S0 (heredada)", "s2_access_control_key": "Clave de control de acceso S2", "s2_authenticated_key": "Clave autenticada de S2", @@ -59,6 +58,10 @@ }, "usb_confirm": { "description": "\u00bfQuieres configurar {name} con el complemento Z-Wave JS?" + }, + "zeroconf_confirm": { + "description": "\u00bfQuieres a\u00f1adir el servidor Z-Wave JS con ID {home_id} que se encuentra en {url} en Home Assistant?", + "title": "Servidor Z-Wave JS descubierto" } } }, @@ -113,7 +116,6 @@ "data": { "emulate_hardware": "Emular el hardware", "log_level": "Nivel de registro", - "network_key": "Clave de red", "s0_legacy_key": "Tecla S0 (heredada)", "s2_access_control_key": "Clave de control de acceso S2", "s2_authenticated_key": "Clave autenticada de S2", @@ -142,6 +144,5 @@ "title": "Se est\u00e1 iniciando el complemento Z-Wave JS." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/et.json b/homeassistant/components/zwave_js/translations/et.json index 29f50ccb290..ea0686e424f 100644 --- a/homeassistant/components/zwave_js/translations/et.json +++ b/homeassistant/components/zwave_js/translations/et.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "V\u00f5rgu v\u00f5ti", "s0_legacy_key": "S0 vana t\u00fc\u00fcpi v\u00f5ti", "s2_access_control_key": "S2 juurdep\u00e4\u00e4suv\u00f5ti", "s2_authenticated_key": "Autenditud S2 v\u00f5ti", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Riistvara emuleerimine", "log_level": "Logimise tase", - "network_key": "V\u00f5rgu v\u00f5ti", "s0_legacy_key": "S0 vana t\u00fc\u00fcpi v\u00f5ti", "s2_access_control_key": "S2 juurdep\u00e4\u00e4suv\u00f5ti", "s2_authenticated_key": "Autenditud S2 v\u00f5ti", @@ -146,6 +144,5 @@ "title": "Z-Wave JS lisandmoodul k\u00e4ivitub." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json index 22e8ed19e32..47c3086489c 100644 --- a/homeassistant/components/zwave_js/translations/fr.json +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Cl\u00e9 r\u00e9seau", "s0_legacy_key": "Cl\u00e9 S0 (h\u00e9rit\u00e9e)", "s2_access_control_key": "Cl\u00e9 de contr\u00f4le d'acc\u00e8s S2", "s2_authenticated_key": "Cl\u00e9 d'authentification S2", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "\u00c9muler le mat\u00e9riel", "log_level": "Niveau du journal", - "network_key": "Cl\u00e9 r\u00e9seau", "s0_legacy_key": "Cl\u00e9 S0 (h\u00e9rit\u00e9e)", "s2_access_control_key": "Cl\u00e9 de contr\u00f4le d'acc\u00e8s S2", "s2_authenticated_key": "Cl\u00e9 d'authentification S2", @@ -146,6 +144,5 @@ "title": "Le module compl\u00e9mentaire Z-Wave JS d\u00e9marre." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/he.json b/homeassistant/components/zwave_js/translations/he.json index 041e1cafec6..0d6b2a9f619 100644 --- a/homeassistant/components/zwave_js/translations/he.json +++ b/homeassistant/components/zwave_js/translations/he.json @@ -48,7 +48,6 @@ "configure_addon": { "data": { "log_level": "\u05e8\u05de\u05ea \u05d9\u05d5\u05de\u05df \u05e8\u05d9\u05e9\u05d5\u05dd", - "network_key": "\u05de\u05e4\u05ea\u05d7 \u05e8\u05e9\u05ea", "usb_path": "\u05e0\u05ea\u05d9\u05d1 \u05d4\u05ea\u05e7\u05df USB" } }, @@ -64,6 +63,5 @@ "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05e9\u05ea\u05de\u05e9 \u05d1\u05d4\u05e8\u05d7\u05d1\u05d4 \u05de\u05e4\u05e7\u05d7 Z-Wave JS?" } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/hu.json b/homeassistant/components/zwave_js/translations/hu.json index 07dbb93b703..37e19e5471f 100644 --- a/homeassistant/components/zwave_js/translations/hu.json +++ b/homeassistant/components/zwave_js/translations/hu.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "H\u00e1l\u00f3zati kulcs", "s0_legacy_key": "S0 kulcs (r\u00e9gi)", "s2_access_control_key": "S2 Hozz\u00e1f\u00e9r\u00e9s kulcs", "s2_authenticated_key": "S2 hiteles\u00edtett kulcs", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Hardver emul\u00e1ci\u00f3", "log_level": "Napl\u00f3szint", - "network_key": "H\u00e1l\u00f3zati kulcs", "s0_legacy_key": "S0 kulcs (r\u00e9gi)", "s2_access_control_key": "S2 hozz\u00e1f\u00e9r\u00e9si ", "s2_authenticated_key": "S2 hiteles\u00edtett kulcs", @@ -146,6 +144,5 @@ "title": "Indul a Z-Wave JS b\u0151v\u00edtm\u00e9ny." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/id.json b/homeassistant/components/zwave_js/translations/id.json index ef14a550210..340266e9a9b 100644 --- a/homeassistant/components/zwave_js/translations/id.json +++ b/homeassistant/components/zwave_js/translations/id.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Kunci Jaringan", "s0_legacy_key": "Kunci S0 (Warisan)", "s2_access_control_key": "Kunci Kontrol Akses S2", "s2_authenticated_key": "Kunci Autentikasi S2", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Emulasikan Perangkat Keras", "log_level": "Tingkat log", - "network_key": "Kunci Jaringan", "s0_legacy_key": "Kunci S0 (Warisan)", "s2_access_control_key": "Kunci Kontrol Akses S2", "s2_authenticated_key": "Kunci Autentikasi S2", @@ -146,6 +144,5 @@ "title": "Add-on Z-Wave JS sedang dimulai." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/it.json b/homeassistant/components/zwave_js/translations/it.json index 5922601921a..eb1e829a52c 100644 --- a/homeassistant/components/zwave_js/translations/it.json +++ b/homeassistant/components/zwave_js/translations/it.json @@ -9,7 +9,7 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "cannot_connect": "Impossibile connettersi", - "discovery_requires_supervisor": "Il rilevamento richiede il supervisore.", + "discovery_requires_supervisor": "Il rilevamento richiede il Supervisor.", "not_zwave_device": "Il dispositivo rilevato non \u00e8 un dispositivo Z-Wave." }, "error": { @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Chiave di rete", "s0_legacy_key": "Chiave S0 (Obsoleta)", "s2_access_control_key": "Chiave di controllo di accesso S2", "s2_authenticated_key": "Chiave S2 autenticata", @@ -49,9 +48,9 @@ }, "on_supervisor": { "data": { - "use_addon": "Usa il componente aggiuntivo Z-Wave JS del supervisore" + "use_addon": "Usa il componente aggiuntivo Z-Wave JS del Supervisor" }, - "description": "Desideri utilizzare il componente aggiuntivo Z-Wave JS del supervisore?", + "description": "Desideri utilizzare il componente aggiuntivo Z-Wave JS del Supervisor?", "title": "Seleziona il metodo di connessione" }, "start_addon": { @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Emula l'hardware", "log_level": "Livello di registro", - "network_key": "Chiave di rete", "s0_legacy_key": "Chiave S0 (Obsoleta)", "s2_access_control_key": "Chiave di controllo di accesso S2", "s2_authenticated_key": "Chiave S2 autenticata", @@ -137,15 +135,14 @@ }, "on_supervisor": { "data": { - "use_addon": "Usa il componente aggiuntivo Z-Wave JS del supervisore" + "use_addon": "Usa il componente aggiuntivo Z-Wave JS di Supervisor" }, - "description": "Desideri utilizzare il componente aggiuntivo Z-Wave JS del supervisore?", + "description": "Desideri utilizzare il componente aggiuntivo Z-Wave JS del Supervisor?", "title": "Seleziona il metodo di connessione" }, "start_addon": { "title": "Il componente aggiuntivo Z-Wave JS si sta avviando." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/ja.json b/homeassistant/components/zwave_js/translations/ja.json index 6df591dd0ab..902955c1b9e 100644 --- a/homeassistant/components/zwave_js/translations/ja.json +++ b/homeassistant/components/zwave_js/translations/ja.json @@ -26,14 +26,13 @@ "step": { "configure_addon": { "data": { - "network_key": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30ad\u30fc", "s0_legacy_key": "S0\u30ad\u30fc (\u30ec\u30ac\u30b7\u30fc)", "s2_access_control_key": "S2\u30a2\u30af\u30bb\u30b9\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30ad\u30fc", "s2_authenticated_key": "S2\u8a8d\u8a3c\u6e08\u307f\u306a\u30ad\u30fc", "s2_unauthenticated_key": "S2\u8a8d\u8a3c\u3055\u308c\u3066\u3044\u306a\u3044\u30ad\u30fc", "usb_path": "USB\u30c7\u30d0\u30a4\u30b9\u306e\u30d1\u30b9" }, - "description": "\u3053\u308c\u3089\u306e\u30d5\u30a3\u30fc\u30eb\u30c9\u304c\u7a7a\u306e\u307e\u307e\u306e\u5834\u5408\u3001\u30a2\u30c9\u30aa\u30f3\u306f\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ad\u30fc\u3092\u751f\u6210\u3057\u307e\u3059\u3002", + "description": "\u3053\u308c\u3089\u306e\u30d5\u30a3\u30fc\u30eb\u30c9\u304c\u7a7a\u767d\u306e\u307e\u307e\u306e\u5834\u5408\u3001\u30a2\u30c9\u30aa\u30f3\u306f\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ad\u30fc\u3092\u751f\u6210\u3057\u307e\u3059\u3002", "title": "Z-Wave JS\u30a2\u30c9\u30aa\u30f3\u306e\u8a2d\u5b9a\u3092\u5165\u529b" }, "hassio_confirm": { @@ -117,14 +116,13 @@ "data": { "emulate_hardware": "\u30cf\u30fc\u30c9\u30a6\u30a7\u30a2\u306e\u30a8\u30df\u30e5\u30ec\u30fc\u30b7\u30e7\u30f3", "log_level": "\u30ed\u30b0\u30ec\u30d9\u30eb", - "network_key": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af", "s0_legacy_key": "S0\u30ad\u30fc (\u30ec\u30ac\u30b7\u30fc)", "s2_access_control_key": "S2\u30a2\u30af\u30bb\u30b9\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30ad\u30fc", "s2_authenticated_key": "S2\u8a8d\u8a3c\u6e08\u307f\u306a\u30ad\u30fc", "s2_unauthenticated_key": "S2\u8a8d\u8a3c\u3055\u308c\u3066\u3044\u306a\u3044\u30ad\u30fc", "usb_path": "USB\u30c7\u30d0\u30a4\u30b9\u306e\u30d1\u30b9" }, - "description": "\u3053\u308c\u3089\u306e\u30d5\u30a3\u30fc\u30eb\u30c9\u304c\u7a7a\u306e\u307e\u307e\u306e\u5834\u5408\u3001\u30a2\u30c9\u30aa\u30f3\u306f\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ad\u30fc\u3092\u751f\u6210\u3057\u307e\u3059\u3002", + "description": "\u3053\u308c\u3089\u306e\u30d5\u30a3\u30fc\u30eb\u30c9\u304c\u7a7a\u767d\u306e\u307e\u307e\u306e\u5834\u5408\u3001\u30a2\u30c9\u30aa\u30f3\u306f\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ad\u30fc\u3092\u751f\u6210\u3057\u307e\u3059\u3002", "title": "Z-Wave JS\u306e\u30a2\u30c9\u30aa\u30f3\u304c\u59cb\u307e\u308a\u307e\u3059\u3002" }, "install_addon": { @@ -146,6 +144,5 @@ "title": "Z-Wave JS \u30a2\u30c9\u30aa\u30f3\u304c\u8d77\u52d5\u3057\u3066\u3044\u307e\u3059\u3002" } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/ko.json b/homeassistant/components/zwave_js/translations/ko.json index 08e3dc8c7d7..0d00d27fea2 100644 --- a/homeassistant/components/zwave_js/translations/ko.json +++ b/homeassistant/components/zwave_js/translations/ko.json @@ -8,7 +8,9 @@ "addon_start_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "discovery_requires_supervisor": "\uc7a5\uce58\uac80\uc0c9\uc744 \uc704\ud574 supervisor\uac00 \ud544\uc694\ud569\ub2c8\ub2e4.", + "not_zwave_device": "\ubc1c\uacac\ub41c \uc7a5\uce58\uac00 Z-Wave \uc7a5\uce58\uac00 \uc544\ub2d9\ub2c8\ub2e4." }, "error": { "addon_start_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", @@ -23,7 +25,6 @@ "step": { "configure_addon": { "data": { - "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4", "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" }, "title": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" @@ -48,8 +49,20 @@ }, "start_addon": { "title": "Z-Wave JS \uc560\ub4dc\uc628\uc774 \uc2dc\uc791\ud558\ub294 \uc911\uc785\ub2c8\ub2e4." + }, + "zeroconf_confirm": { + "title": "Z-Wave JS \uc11c\ubc84 \ubc1c\uacac" } } }, - "title": "Z-Wave JS" + "device_automation": { + "action_type": { + "set_config_parameter": "\uad6c\uc131 \ub9e4\uac1c\ubcc0\uc218 {subtype} \uc758 \uac12 \uc124\uc815", + "set_lock_usercode": "{entity_name} \uc5d0 \uc0ac\uc6a9\uc790 \ucf54\ub4dc \uc124\uc815", + "set_value": "Z-Wave Value\uc758 \uc124\uc815\uac12" + }, + "trigger_type": { + "event.value_notification.scene_activation": "{subtype} \uc5d0\uc11c \uc7a5\uba74 \ud65c\uc131\ud654" + } + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/lb.json b/homeassistant/components/zwave_js/translations/lb.json index d84c5323fb9..04259de2303 100644 --- a/homeassistant/components/zwave_js/translations/lb.json +++ b/homeassistant/components/zwave_js/translations/lb.json @@ -8,6 +8,5 @@ "invalid_ws_url": "Ong\u00eblteg Websocket URL", "unknown": "Onerwaarte Feeler" } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/nl.json b/homeassistant/components/zwave_js/translations/nl.json index 2d3ce5421ed..f87b7a701e3 100644 --- a/homeassistant/components/zwave_js/translations/nl.json +++ b/homeassistant/components/zwave_js/translations/nl.json @@ -7,7 +7,7 @@ "addon_set_config_failed": "Instellen van de Z-Wave JS configuratie is mislukt.", "addon_start_failed": "Kan de Z-Wave JS add-on niet starten.", "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang", + "already_in_progress": "De configuratie is momenteel al bezig", "cannot_connect": "Kan geen verbinding maken", "discovery_requires_supervisor": "Ontdekking vereist de Supervisor.", "not_zwave_device": "Het ontdekte apparaat is niet een Z-Wave apparaat." @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Netwerksleutel", "s0_legacy_key": "S0 Sleutel (Legacy)", "s2_access_control_key": "S2 Toegangscontrolesleutel", "s2_authenticated_key": "S2 geverifieerde sleutel", @@ -78,7 +77,7 @@ }, "condition_type": { "config_parameter": "Config parameter {subtype} waarde", - "node_status": "Knooppuntstatus", + "node_status": "Apparaatstatus", "value": "Huidige waarde van een Z-Wave-waarde" }, "trigger_type": { @@ -87,7 +86,7 @@ "event.value_notification.basic": "Basis CC-evenement op {subtype}", "event.value_notification.central_scene": "Centrale Sc\u00e8ne actie op {subtype}", "event.value_notification.scene_activation": "Sc\u00e8ne-activering op {subtype}", - "state.node_status": "Knooppuntstatus gewijzigd", + "state.node_status": "Apparaatstatus gewijzigd", "zwave_js.value_updated.config_parameter": "Waardeverandering op configuratieparameter {subtype}", "zwave_js.value_updated.value": "Waardeverandering op een Z-Wave JS-waarde" } @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Emulate Hardware", "log_level": "Log level", - "network_key": "Netwerksleutel", "s0_legacy_key": "S0 Sleutel (Legacy)", "s2_access_control_key": "S2 Toegangscontrolesleutel", "s2_authenticated_key": "S2 geverifieerde sleutel", @@ -146,6 +144,5 @@ "title": "The Z-Wave JS add-on is aan het starten." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json index 854bd307abc..c89e5582677 100644 --- a/homeassistant/components/zwave_js/translations/no.json +++ b/homeassistant/components/zwave_js/translations/no.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Nettverksn\u00f8kkel", "s0_legacy_key": "S0-n\u00f8kkel (eldre)", "s2_access_control_key": "N\u00f8kkel for S2-tilgangskontroll", "s2_authenticated_key": "S2 Autentisert n\u00f8kkel", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Emuler maskinvare", "log_level": "Loggniv\u00e5", - "network_key": "Nettverksn\u00f8kkel", "s0_legacy_key": "S0-n\u00f8kkel (eldre)", "s2_access_control_key": "N\u00f8kkel for S2-tilgangskontroll", "s2_authenticated_key": "S2 Autentisert n\u00f8kkel", @@ -146,6 +144,5 @@ "title": "Z-Wave JS-tillegget starter" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/pl.json b/homeassistant/components/zwave_js/translations/pl.json index 2edb9fd8c1f..3929aa668ef 100644 --- a/homeassistant/components/zwave_js/translations/pl.json +++ b/homeassistant/components/zwave_js/translations/pl.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Klucz sieci", "s0_legacy_key": "Klucz S0 (Legacy)", "s2_access_control_key": "Klucz kontroli dost\u0119pu S2", "s2_authenticated_key": "Klucz uwierzytelniony S2", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Emulacja sprz\u0119tu", "log_level": "Poziom loga", - "network_key": "Klucz sieci", "s0_legacy_key": "Klucz S0 (Legacy)", "s2_access_control_key": "Klucz kontroli dost\u0119pu S2", "s2_authenticated_key": "Klucz uwierzytelniony S2", @@ -146,6 +144,5 @@ "title": "Dodatek Z-Wave JS uruchamia si\u0119..." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/pt-BR.json b/homeassistant/components/zwave_js/translations/pt-BR.json index 5e1e8610fc7..e8587aa6f94 100644 --- a/homeassistant/components/zwave_js/translations/pt-BR.json +++ b/homeassistant/components/zwave_js/translations/pt-BR.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "Chave de rede", "s0_legacy_key": "Chave S0 (Legado)", "s2_access_control_key": "Chave de controle de acesso S2", "s2_authenticated_key": "Chave autenticada S2", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Emular hardware", "log_level": "N\u00edvel de registro", - "network_key": "Chave de rede", "s0_legacy_key": "Chave S0 (Legado)", "s2_access_control_key": "Chave de controle de acesso S2", "s2_authenticated_key": "Chave autenticada S2", @@ -146,6 +144,5 @@ "title": "O add-on Z-Wave JS est\u00e1 iniciando." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/ru.json b/homeassistant/components/zwave_js/translations/ru.json index 14011c78517..44979a6cfe9 100644 --- a/homeassistant/components/zwave_js/translations/ru.json +++ b/homeassistant/components/zwave_js/translations/ru.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "\u041a\u043b\u044e\u0447 \u0441\u0435\u0442\u0438", "s0_legacy_key": "\u041a\u043b\u044e\u0447 S0 (\u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439)", "s2_access_control_key": "\u041a\u043b\u044e\u0447 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 S2", "s2_authenticated_key": "\u041a\u043b\u044e\u0447 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 S2", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "\u042d\u043c\u0443\u043b\u044f\u0446\u0438\u044f \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044f", "log_level": "\u0423\u0440\u043e\u0432\u0435\u043d\u044c \u0436\u0443\u0440\u043d\u0430\u043b\u0430", - "network_key": "\u041a\u043b\u044e\u0447 \u0441\u0435\u0442\u0438", "s0_legacy_key": "\u041a\u043b\u044e\u0447 S0 (\u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439)", "s2_access_control_key": "\u041a\u043b\u044e\u0447 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 S2", "s2_authenticated_key": "\u041a\u043b\u044e\u0447 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 S2", @@ -146,6 +144,5 @@ "title": "\u0414\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f" } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/tr.json b/homeassistant/components/zwave_js/translations/tr.json index a3b5f6fb9f4..21d8f03bec6 100644 --- a/homeassistant/components/zwave_js/translations/tr.json +++ b/homeassistant/components/zwave_js/translations/tr.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "A\u011f Anahtar\u0131", "s0_legacy_key": "S0 Anahtar\u0131 (Eski)", "s2_access_control_key": "S2 Eri\u015fim Kontrol Anahtar\u0131", "s2_authenticated_key": "S2 Kimli\u011fi Do\u011frulanm\u0131\u015f Anahtar", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "Donan\u0131m\u0131 Taklit Et", "log_level": "G\u00fcnl\u00fck d\u00fczeyi", - "network_key": "A\u011f Anahtar\u0131", "s0_legacy_key": "S0 Anahtar\u0131 (Eski)", "s2_access_control_key": "S2 Eri\u015fim Kontrol Anahtar\u0131", "s2_authenticated_key": "S2 Kimli\u011fi Do\u011frulanm\u0131\u015f Anahtar", @@ -146,6 +144,5 @@ "title": "Z-Wave JS eklentisi ba\u015fl\u0131yor." } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/uk.json b/homeassistant/components/zwave_js/translations/uk.json index fe77655ac29..95c9b44fb86 100644 --- a/homeassistant/components/zwave_js/translations/uk.json +++ b/homeassistant/components/zwave_js/translations/uk.json @@ -8,6 +8,5 @@ "invalid_ws_url": "\u041d\u0435\u0434\u0456\u0439\u0441\u043d\u0430 URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u0432\u0435\u0431-\u0441\u043e\u043a\u0435\u0442\u0430", "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/zh-Hant.json b/homeassistant/components/zwave_js/translations/zh-Hant.json index 2ea728a591e..246800d1048 100644 --- a/homeassistant/components/zwave_js/translations/zh-Hant.json +++ b/homeassistant/components/zwave_js/translations/zh-Hant.json @@ -26,7 +26,6 @@ "step": { "configure_addon": { "data": { - "network_key": "\u7db2\u8def\u91d1\u9470", "s0_legacy_key": "S0 \u91d1\u9470\uff08\u820a\u7248\uff09", "s2_access_control_key": "S2 \u5b58\u53d6\u63a7\u5236\u91d1\u9470", "s2_authenticated_key": "S2 \u9a57\u8b49\u91d1\u9470", @@ -117,7 +116,6 @@ "data": { "emulate_hardware": "\u6a21\u64ec\u786c\u9ad4", "log_level": "\u65e5\u8a8c\u8a18\u9304\u7b49\u7d1a", - "network_key": "\u7db2\u8def\u91d1\u9470", "s0_legacy_key": "S0 \u91d1\u9470\uff08\u820a\u7248\uff09", "s2_access_control_key": "S2 \u5b58\u53d6\u63a7\u5236\u91d1\u9470", "s2_authenticated_key": "S2 \u9a57\u8b49\u91d1\u9470", @@ -146,6 +144,5 @@ "title": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u555f\u59cb\u4e2d\u3002" } } - }, - "title": "Z-Wave JS" + } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/triggers/event.py b/homeassistant/components/zwave_js/triggers/event.py index fd46c89832b..784ae74777b 100644 --- a/homeassistant/components/zwave_js/triggers/event.py +++ b/homeassistant/components/zwave_js/triggers/event.py @@ -8,7 +8,7 @@ import voluptuous as vol from zwave_js_server.client import Client from zwave_js_server.model.controller import CONTROLLER_EVENT_MODEL_MAP from zwave_js_server.model.driver import DRIVER_EVENT_MODEL_MAP -from zwave_js_server.model.node import NODE_EVENT_MODEL_MAP, Node +from zwave_js_server.model.node import NODE_EVENT_MODEL_MAP from homeassistant.components.automation import ( AutomationActionType, @@ -20,7 +20,6 @@ from homeassistant.components.zwave_js.const import ( ATTR_EVENT_DATA, ATTR_EVENT_SOURCE, ATTR_NODE_ID, - ATTR_NODES, ATTR_PARTIAL_DICT_MATCH, DATA_CLIENT, DOMAIN, @@ -30,21 +29,16 @@ from homeassistant.components.zwave_js.helpers import ( get_device_id, get_home_and_node_id_from_device_entry, ) -from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID, CONF_PLATFORM from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.typing import ConfigType +from .helpers import async_bypass_dynamic_config_validation + # Platform type should be . PLATFORM_TYPE = f"{DOMAIN}.{__name__.rsplit('.', maxsplit=1)[-1]}" -EVENT_MODEL_MAP = { - "controller": CONTROLLER_EVENT_MODEL_MAP, - "driver": DRIVER_EVENT_MODEL_MAP, - "node": NODE_EVENT_MODEL_MAP, -} - def validate_non_node_event_source(obj: dict) -> dict: """Validate that a trigger for a non node event source has a config entry.""" @@ -58,7 +52,12 @@ def validate_event_name(obj: dict) -> dict: event_source = obj[ATTR_EVENT_SOURCE] event_name = obj[ATTR_EVENT] # the keys to the event source's model map are the event names - vol.In(EVENT_MODEL_MAP[event_source])(event_name) + if event_source == "controller": + vol.In(CONTROLLER_EVENT_MODEL_MAP)(event_name) + elif event_source == "driver": + vol.In(DRIVER_EVENT_MODEL_MAP)(event_name) + else: + vol.In(NODE_EVENT_MODEL_MAP)(event_name) return obj @@ -68,11 +67,16 @@ def validate_event_data(obj: dict) -> dict: if ATTR_EVENT_DATA not in obj: return obj - event_source = obj[ATTR_EVENT_SOURCE] - event_name = obj[ATTR_EVENT] - event_data = obj[ATTR_EVENT_DATA] + event_source: str = obj[ATTR_EVENT_SOURCE] + event_name: str = obj[ATTR_EVENT] + event_data: dict = obj[ATTR_EVENT_DATA] try: - EVENT_MODEL_MAP[event_source][event_name](**event_data) + if event_source == "controller": + CONTROLLER_EVENT_MODEL_MAP[event_name](**event_data) + elif event_source == "driver": + DRIVER_EVENT_MODEL_MAP[event_name](**event_data) + else: + NODE_EVENT_MODEL_MAP[event_name](**event_data) except ValidationError as exc: # Filter out required field errors if keys can be missing, and if there are # still errors, raise an exception @@ -90,7 +94,7 @@ TRIGGER_SCHEMA = vol.All( vol.Optional(ATTR_CONFIG_ENTRY_ID): str, vol.Optional(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_EVENT_SOURCE): vol.In(EVENT_MODEL_MAP), + vol.Required(ATTR_EVENT_SOURCE): vol.In(["controller", "driver", "node"]), vol.Required(ATTR_EVENT): cv.string, vol.Optional(ATTR_EVENT_DATA): dict, vol.Optional(ATTR_PARTIAL_DICT_MATCH, default=False): bool, @@ -111,22 +115,20 @@ async def async_validate_trigger_config( """Validate config.""" config = TRIGGER_SCHEMA(config) - if config[ATTR_EVENT_SOURCE] == "node": - config[ATTR_NODES] = async_get_nodes_from_targets(hass, config) - if not config[ATTR_NODES]: - raise vol.Invalid( - f"No nodes found for given {ATTR_DEVICE_ID}s or {ATTR_ENTITY_ID}s." - ) + if ATTR_CONFIG_ENTRY_ID in config: + entry_id = config[ATTR_CONFIG_ENTRY_ID] + if hass.config_entries.async_get_entry(entry_id) is None: + raise vol.Invalid(f"Config entry '{entry_id}' not found") - if ATTR_CONFIG_ENTRY_ID not in config: + if async_bypass_dynamic_config_validation(hass, config): return config - entry_id = config[ATTR_CONFIG_ENTRY_ID] - if (entry := hass.config_entries.async_get_entry(entry_id)) is None: - raise vol.Invalid(f"Config entry '{entry_id}' not found") - - if entry.state is not ConfigEntryState.LOADED: - raise vol.Invalid(f"Config entry '{entry_id}' not loaded") + if config[ATTR_EVENT_SOURCE] == "node" and not async_get_nodes_from_targets( + hass, config + ): + raise vol.Invalid( + f"No nodes found for given {ATTR_DEVICE_ID}s or {ATTR_ENTITY_ID}s." + ) return config @@ -140,7 +142,12 @@ async def async_attach_trigger( platform_type: str = PLATFORM_TYPE, ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" - nodes: set[Node] = config.get(ATTR_NODES, {}) + dev_reg = dr.async_get(hass) + nodes = async_get_nodes_from_targets(hass, config, dev_reg=dev_reg) + if config[ATTR_EVENT_SOURCE] == "node" and not nodes: + raise ValueError( + f"No nodes found for given {ATTR_DEVICE_ID}s or {ATTR_ENTITY_ID}s." + ) event_source = config[ATTR_EVENT_SOURCE] event_name = config[ATTR_EVENT] @@ -195,19 +202,19 @@ async def async_attach_trigger( hass.async_run_hass_job(job, {"trigger": payload}) - dev_reg = dr.async_get(hass) - if not nodes: entry_id = config[ATTR_CONFIG_ENTRY_ID] client: Client = hass.data[DOMAIN][entry_id][DATA_CLIENT] + assert client.driver if event_source == "controller": - source = client.driver.controller + unsubs.append(client.driver.controller.on(event_name, async_on_event)) else: - source = client.driver - unsubs.append(source.on(event_name, async_on_event)) + unsubs.append(client.driver.on(event_name, async_on_event)) for node in nodes: - device_identifier = get_device_id(node.client, node) + driver = node.client.driver + assert driver is not None # The node comes from the driver. + device_identifier = get_device_id(driver, node) device = dev_reg.async_get_device({device_identifier}) assert device # We need to store the device for the callback diff --git a/homeassistant/components/zwave_js/triggers/helpers.py b/homeassistant/components/zwave_js/triggers/helpers.py new file mode 100644 index 00000000000..2fbc585c887 --- /dev/null +++ b/homeassistant/components/zwave_js/triggers/helpers.py @@ -0,0 +1,35 @@ +"""Helpers for Z-Wave JS custom triggers.""" +from homeassistant.components.zwave_js.const import ATTR_CONFIG_ENTRY_ID, DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.typing import ConfigType + + +@callback +def async_bypass_dynamic_config_validation( + hass: HomeAssistant, config: ConfigType +) -> bool: + """Return whether target zwave_js config entry is not loaded.""" + # If the config entry is not loaded for a zwave_js device, entity, or the + # config entry ID provided, we can't perform dynamic validation + dev_reg = dr.async_get(hass) + ent_reg = er.async_get(hass) + trigger_devices = config.get(ATTR_DEVICE_ID, []) + trigger_entities = config.get(ATTR_ENTITY_ID, []) + return any( + entry.state != ConfigEntryState.LOADED + and ( + entry.entry_id == config.get(ATTR_CONFIG_ENTRY_ID) + or any( + device.id in trigger_devices + for device in dr.async_entries_for_config_entry(dev_reg, entry.entry_id) + ) + or ( + entity.entity_id in trigger_entities + for entity in er.async_entries_for_config_entry(ent_reg, entry.entry_id) + ) + ) + for entry in hass.config_entries.async_entries(DOMAIN) + ) diff --git a/homeassistant/components/zwave_js/triggers/value_updated.py b/homeassistant/components/zwave_js/triggers/value_updated.py index 8a0b287c26b..29b4b4d06d6 100644 --- a/homeassistant/components/zwave_js/triggers/value_updated.py +++ b/homeassistant/components/zwave_js/triggers/value_updated.py @@ -5,14 +5,13 @@ import functools import voluptuous as vol from zwave_js_server.const import CommandClass -from zwave_js_server.event import Event -from zwave_js_server.model.node import Node from zwave_js_server.model.value import Value, get_value_id from homeassistant.components.automation import ( AutomationActionType, AutomationTriggerInfo, ) +from homeassistant.components.zwave_js.config_validation import VALUE_SCHEMA from homeassistant.components.zwave_js.const import ( ATTR_COMMAND_CLASS, ATTR_COMMAND_CLASS_NAME, @@ -20,7 +19,6 @@ from homeassistant.components.zwave_js.const import ( ATTR_CURRENT_VALUE_RAW, ATTR_ENDPOINT, ATTR_NODE_ID, - ATTR_NODES, ATTR_PREVIOUS_VALUE, ATTR_PREVIOUS_VALUE_RAW, ATTR_PROPERTY, @@ -38,7 +36,7 @@ from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.typing import ConfigType -from ..config_validation import VALUE_SCHEMA +from .helpers import async_bypass_dynamic_config_validation # Platform type should be . PLATFORM_TYPE = f"{DOMAIN}.{__name__.rsplit('.', maxsplit=1)[-1]}" @@ -76,8 +74,10 @@ async def async_validate_trigger_config( """Validate config.""" config = TRIGGER_SCHEMA(config) - config[ATTR_NODES] = async_get_nodes_from_targets(hass, config) - if not config[ATTR_NODES]: + if async_bypass_dynamic_config_validation(hass, config): + return config + + if not async_get_nodes_from_targets(hass, config): raise vol.Invalid( f"No nodes found for given {ATTR_DEVICE_ID}s or {ATTR_ENTITY_ID}s." ) @@ -93,7 +93,11 @@ async def async_attach_trigger( platform_type: str = PLATFORM_TYPE, ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" - nodes: set[Node] = config[ATTR_NODES] + dev_reg = dr.async_get(hass) + if not (nodes := async_get_nodes_from_targets(hass, config, dev_reg=dev_reg)): + raise ValueError( + f"No nodes found for given {ATTR_DEVICE_ID}s or {ATTR_ENTITY_ID}s." + ) from_value = config[ATTR_FROM] to_value = config[ATTR_TO] @@ -108,7 +112,7 @@ async def async_attach_trigger( @callback def async_on_value_updated( - value: Value, device: dr.DeviceEntry, event: Event + value: Value, device: dr.DeviceEntry, event: dict ) -> None: """Handle value update.""" event_value: Value = event["value"] @@ -160,9 +164,10 @@ async def async_attach_trigger( hass.async_run_hass_job(job, {"trigger": payload}) - dev_reg = dr.async_get(hass) for node in nodes: - device_identifier = get_device_id(node.client, node) + driver = node.client.driver + assert driver is not None # The node comes from the driver. + device_identifier = get_device_id(driver, node) device = dev_reg.async_get_device({device_identifier}) assert device value_id = get_value_id(node, command_class, property_, endpoint, property_key) diff --git a/homeassistant/components/zwave_me/translations/es.json b/homeassistant/components/zwave_me/translations/es.json index eab23bbd0ff..2443547e59d 100644 --- a/homeassistant/components/zwave_me/translations/es.json +++ b/homeassistant/components/zwave_me/translations/es.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "token": "Token", + "token": "Token API", "url": "URL" }, "description": "Direcci\u00f3n IP de entrada del servidor Z-Way y token de acceso Z-Way. La direcci\u00f3n IP se puede prefijar con wss:// si se debe usar HTTPS en lugar de HTTP. Para obtener el token, vaya a la interfaz de usuario de Z-Way > Configuraci\u00f3n de > de men\u00fa > token de API de > de usuario. Se sugiere crear un nuevo usuario para Home Assistant y conceder acceso a los dispositivos que necesita controlar desde Home Assistant. Tambi\u00e9n es posible utilizar el acceso remoto a trav\u00e9s de find.z-wave.me para conectar un Z-Way remoto. Ingrese wss://find.z-wave.me en el campo IP y copie el token con alcance global (inicie sesi\u00f3n en Z-Way a trav\u00e9s de find.z-wave.me para esto)." diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 97278f3b2e3..0ac02adb8d0 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -186,6 +186,7 @@ class ConfigEntry: "reason", "_async_cancel_retry_setup", "_on_unload", + "reload_lock", ) def __init__( @@ -275,6 +276,9 @@ class ConfigEntry: # Hold list for functions to call on unload. self._on_unload: list[CALLBACK_TYPE] | None = None + # Reload lock to prevent conflicting reloads + self.reload_lock = asyncio.Lock() + async def async_setup( self, hass: HomeAssistant, @@ -1005,12 +1009,13 @@ class ConfigEntries: if (entry := self.async_get_entry(entry_id)) is None: raise UnknownEntry - unload_result = await self.async_unload(entry_id) + async with entry.reload_lock: + unload_result = await self.async_unload(entry_id) - if not unload_result or entry.disabled_by: - return unload_result + if not unload_result or entry.disabled_by: + return unload_result - return await self.async_setup(entry_id) + return await self.async_setup(entry_id) async def async_set_disabled_by( self, entry_id: str, disabled_by: ConfigEntryDisabler | None @@ -1549,7 +1554,7 @@ class EntityRegistryDisabledHandler: async def _handle_entry_updated(self, event: Event) -> None: """Handle entity registry entry update.""" if self.registry is None: - self.registry = await entity_registry.async_get_registry(self.hass) + self.registry = entity_registry.async_get(self.hass) entity_entry = self.registry.async_get(event.data["entity_id"]) diff --git a/homeassistant/const.py b/homeassistant/const.py index e41189fda07..428aa84d5fb 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 = 5 -PATCH_VERSION: Final = "5" +MINOR_VERSION: Final = 6 +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) diff --git a/homeassistant/core.py b/homeassistant/core.py index 7245dad7864..d7cae4e411e 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -37,7 +37,6 @@ from typing import ( ) from urllib.parse import urlparse -import attr import voluptuous as vol import yarl @@ -133,9 +132,12 @@ SOURCE_YAML = ConfigSource.YAML.value # How long to wait until things that run on startup have to finish. TIMEOUT_EVENT_START = 15 +MAX_EXPECTED_ENTITY_IDS = 16384 + _LOGGER = logging.getLogger(__name__) +@functools.lru_cache(MAX_EXPECTED_ENTITY_IDS) def split_entity_id(entity_id: str) -> tuple[str, str]: """Split a state entity ID into domain and object ID.""" domain, _, object_id = entity_id.partition(".") @@ -403,7 +405,12 @@ class HomeAssistant: if asyncio.iscoroutine(target): return self.async_create_task(target) - target = cast(Callable[..., Union[Coroutine[Any, Any, _R], _R]], target) + # This code path is performance sensitive and uses + # if TYPE_CHECKING to avoid the overhead of constructing + # the type used for the cast. For history see: + # https://github.com/home-assistant/core/pull/71960 + if TYPE_CHECKING: + target = cast(Callable[..., Union[Coroutine[Any, Any, _R], _R]], target) return self.async_add_hass_job(HassJob(target), *args) @overload @@ -431,17 +438,25 @@ class HomeAssistant: args: parameters for method to call. """ task: asyncio.Future[_R] + # This code path is performance sensitive and uses + # if TYPE_CHECKING to avoid the overhead of constructing + # the type used for the cast. For history see: + # https://github.com/home-assistant/core/pull/71960 if hassjob.job_type == HassJobType.Coroutinefunction: - task = self.loop.create_task( - cast(Callable[..., Coroutine[Any, Any, _R]], hassjob.target)(*args) - ) + if TYPE_CHECKING: + hassjob.target = cast( + Callable[..., Coroutine[Any, Any, _R]], hassjob.target + ) + task = self.loop.create_task(hassjob.target(*args)) elif hassjob.job_type == HassJobType.Callback: - self.loop.call_soon(cast(Callable[..., _R], hassjob.target), *args) + if TYPE_CHECKING: + hassjob.target = cast(Callable[..., _R], hassjob.target) + self.loop.call_soon(hassjob.target, *args) return None else: - task = self.loop.run_in_executor( - None, cast(Callable[..., _R], hassjob.target), *args - ) + if TYPE_CHECKING: + hassjob.target = cast(Callable[..., _R], hassjob.target) + task = self.loop.run_in_executor(None, hassjob.target, *args) # If a task is scheduled if self._track_task: @@ -519,8 +534,14 @@ class HomeAssistant: hassjob: HassJob args: parameters for method to call. """ + # This code path is performance sensitive and uses + # if TYPE_CHECKING to avoid the overhead of constructing + # the type used for the cast. For history see: + # https://github.com/home-assistant/core/pull/71960 if hassjob.job_type == HassJobType.Callback: - cast(Callable[..., _R], hassjob.target)(*args) + if TYPE_CHECKING: + hassjob.target = cast(Callable[..., _R], hassjob.target) + hassjob.target(*args) return None return self.async_add_hass_job(hassjob, *args) @@ -562,7 +583,12 @@ class HomeAssistant: if asyncio.iscoroutine(target): return self.async_create_task(target) - target = cast(Callable[..., Union[Coroutine[Any, Any, _R], _R]], target) + # This code path is performance sensitive and uses + # if TYPE_CHECKING to avoid the overhead of constructing + # the type used for the cast. For history see: + # https://github.com/home-assistant/core/pull/71960 + if TYPE_CHECKING: + 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: @@ -689,13 +715,26 @@ class HomeAssistant: self._stopped.set() -@attr.s(slots=True, frozen=True) class Context: """The context that triggered something.""" - user_id: str | None = attr.ib(default=None) - parent_id: str | None = attr.ib(default=None) - id: str = attr.ib(factory=ulid_util.ulid_hex) + __slots__ = ("user_id", "parent_id", "id", "origin_event") + + def __init__( + self, + user_id: str | None = None, + parent_id: str | None = None, + id: str | None = None, # pylint: disable=redefined-builtin + ) -> None: + """Init the context.""" + self.id = id or ulid_util.ulid() + self.user_id = user_id + self.parent_id = parent_id + self.origin_event: Event | None = None + + def __eq__(self, other: Any) -> bool: + """Compare contexts.""" + return bool(self.__class__ == other.__class__ and self.id == other.id) def as_dict(self) -> dict[str, str | None]: """Return a dictionary representation of the context.""" @@ -731,7 +770,9 @@ class Event: self.data = data or {} self.origin = origin self.time_fired = time_fired or dt_util.utcnow() - self.context: Context = context or Context() + self.context: Context = context or Context( + id=ulid_util.ulid(dt_util.utc_to_timestamp(self.time_fired)) + ) def __hash__(self) -> int: """Make hashable.""" @@ -775,6 +816,7 @@ class _FilterableJob(NamedTuple): job: HassJob[None | Awaitable[None]] event_filter: Callable[[Event], bool] | None + run_immediately: bool class EventBus: @@ -836,13 +878,15 @@ class EventBus: listeners = match_all_listeners + listeners event = Event(event_type, event_data, origin, time_fired, context) + if not event.context.origin_event: + event.context.origin_event = event _LOGGER.debug("Bus:Handling %s", event) if not listeners: return - for job, event_filter in listeners: + for job, event_filter, run_immediately in listeners: if event_filter is not None: try: if not event_filter(event): @@ -850,7 +894,13 @@ class EventBus: except Exception: # pylint: disable=broad-except _LOGGER.exception("Error in event filter") continue - self._hass.async_add_hass_job(job, event) + if run_immediately: + try: + job.target(event) + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Error running job: %s", job) + else: + self._hass.async_add_hass_job(job, event) def listen( self, @@ -878,6 +928,7 @@ class EventBus: event_type: str, listener: Callable[[Event], None | Awaitable[None]], event_filter: Callable[[Event], bool] | None = None, + run_immediately: bool = False, ) -> CALLBACK_TYPE: """Listen for all events or events of a specific type. @@ -888,12 +939,18 @@ class EventBus: @callback that returns a boolean value, determines if the listener callable should run. + If run_immediately is passed, the callback will be run + right away instead of using call_soon. Only use this if + the callback results in scheduling another task. + This method must be run in the event loop. """ if event_filter is not None and not is_callback(event_filter): raise HomeAssistantError(f"Event filter {event_filter} is not a callback") + if run_immediately and not is_callback(listener): + raise HomeAssistantError(f"Event listener {listener} is not a callback") return self._async_listen_filterable_job( - event_type, _FilterableJob(HassJob(listener), event_filter) + event_type, _FilterableJob(HassJob(listener), event_filter, run_immediately) ) @callback @@ -963,7 +1020,7 @@ class EventBus: _onetime_listener, listener, ("__name__", "__qualname__", "__module__"), [] ) - filterable_job = _FilterableJob(HassJob(_onetime_listener), None) + filterable_job = _FilterableJob(HassJob(_onetime_listener), None, False) return self._async_listen_filterable_job(event_type, filterable_job) @@ -1117,6 +1174,24 @@ class State: context, ) + def expire(self) -> None: + """Mark the state as old. + + We give up the original reference to the context to ensure + the context can be garbage collected by replacing it with + a new one with the same id to ensure the old state + can still be examined for comparison against the new state. + + Since we are always going to fire a EVENT_STATE_CHANGED event + after we remove a state from the state machine we need to make + sure we don't end up holding a reference to the original context + since it can never be garbage collected as each event would + reference the previous one. + """ + self.context = Context( + self.context.user_id, self.context.parent_id, self.context.id + ) + def __eq__(self, other: Any) -> bool: """Return the comparison of the state.""" return ( # type: ignore[no-any-return] @@ -1257,6 +1332,7 @@ class StateMachine: if old_state is None: return False + old_state.expire() self._bus.async_fire( EVENT_STATE_CHANGED, {"entity_id": entity_id, "old_state": old_state, "new_state": None}, @@ -1346,11 +1422,10 @@ class StateMachine: if same_state and same_attr: return - if context is None: - context = Context() - now = dt_util.utcnow() + if context is None: + context = Context(id=ulid_util.ulid(dt_util.utc_to_timestamp(now))) state = State( entity_id, new_state, @@ -1360,6 +1435,8 @@ class StateMachine: context, old_state is None, ) + if old_state is not None: + old_state.expire() self._states[entity_id] = state self._bus.async_fire( EVENT_STATE_CHANGED, @@ -1852,11 +1929,19 @@ class Config: async def async_load(self) -> None: """Load [homeassistant] core config.""" - store = self.hass.helpers.storage.Store( - CORE_STORAGE_VERSION, CORE_STORAGE_KEY, private=True, atomic_writes=True + # Circular dep + # pylint: disable=import-outside-toplevel + from .helpers.storage import Store + + store = Store( + self.hass, + CORE_STORAGE_VERSION, + CORE_STORAGE_KEY, + private=True, + atomic_writes=True, ) - if not (data := await store.async_load()): + if not (data := await store.async_load()) or not isinstance(data, dict): return # In 2021.9 we fixed validation to disallow a path (because that's never correct) @@ -1888,6 +1973,10 @@ class Config: async def async_store(self) -> None: """Store [homeassistant] core config.""" + # Circular dep + # pylint: disable=import-outside-toplevel + from .helpers.storage import Store + data = { "latitude": self.latitude, "longitude": self.longitude, @@ -1900,7 +1989,11 @@ class Config: "currency": self.currency, } - store = self.hass.helpers.storage.Store( - CORE_STORAGE_VERSION, CORE_STORAGE_KEY, private=True, atomic_writes=True + store = Store( + self.hass, + CORE_STORAGE_VERSION, + CORE_STORAGE_KEY, + private=True, + atomic_writes=True, ) await store.async_save(data) diff --git a/homeassistant/generated/application_credentials.py b/homeassistant/generated/application_credentials.py new file mode 100644 index 00000000000..6d40b3fdef7 --- /dev/null +++ b/homeassistant/generated/application_credentials.py @@ -0,0 +1,20 @@ +"""Automatically generated by hassfest. + +To update, run python3 -m script.hassfest +""" + +# fmt: off + +APPLICATION_CREDENTIALS = [ + "geocaching", + "google", + "home_connect", + "lyric", + "neato", + "netatmo", + "senz", + "spotify", + "withings", + "xbox", + "yolink" +] diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 510adc74e61..e9ba5971e07 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -21,6 +21,7 @@ FLOWS = { "airtouch4", "airvisual", "airzone", + "aladdin_connect", "alarmdecoder", "almond", "ambee", @@ -41,6 +42,7 @@ FLOWS = { "axis", "azure_devops", "azure_event_hub", + "baf", "balboa", "blebox", "blink", @@ -121,6 +123,7 @@ FLOWS = { "garages_amsterdam", "gdacs", "generic", + "geocaching", "geofency", "geonetnz_quakes", "geonetnz_volcano", @@ -140,6 +143,7 @@ FLOWS = { "hangouts", "harmony", "heos", + "here_travel_time", "hisense_aehw4a1", "hive", "hlk_sw16", @@ -157,6 +161,7 @@ FLOWS = { "hvv_departures", "hyperion", "ialarm", + "ialarm_xr", "iaqualink", "icloud", "ifttt", @@ -183,6 +188,7 @@ FLOWS = { "kraken", "kulersky", "launch_library", + "laundrify", "life360", "lifx", "litejet", @@ -304,6 +310,7 @@ FLOWS = { "shopping_list", "sia", "simplisafe", + "slack", "sleepiq", "slimproto", "sma", @@ -400,12 +407,14 @@ FLOWS = { "wiz", "wled", "wolflink", + "ws66i", "xbox", "xiaomi_aqara", "xiaomi_miio", "yale_smart_alarm", "yamaha_musiccast", "yeelight", + "yolink", "youless", "zerproc", "zha", diff --git a/homeassistant/generated/usb.py b/homeassistant/generated/usb.py index 2e5104ce66d..ef75fbef4d1 100644 --- a/homeassistant/generated/usb.py +++ b/homeassistant/generated/usb.py @@ -77,6 +77,18 @@ USB = [ "pid": "8A2A", "description": "*zigbee*" }, + { + "domain": "zha", + "vid": "0403", + "pid": "6015", + "description": "*zigate*" + }, + { + "domain": "zha", + "vid": "10C4", + "pid": "EA60", + "description": "*zigate*" + }, { "domain": "zha", "vid": "10C4", diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index b93d7249211..415e2746c6d 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -42,6 +42,20 @@ ZEROCONF = { "domain": "apple_tv" } ], + "_api._tcp.local.": [ + { + "domain": "baf", + "properties": { + "model": "haiku*" + } + }, + { + "domain": "baf", + "properties": { + "model": "i6*" + } + } + ], "_api._udp.local.": [ { "domain": "guardian" @@ -381,6 +395,12 @@ ZEROCONF = { "domain": "kodi" } ], + "_zigate-zigbee-gateway._tcp.local.": [ + { + "domain": "zha", + "name": "*zigate*" + } + ], "_zwave-js-server._tcp.local.": [ { "domain": "zwave_js" diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 660c433d8c7..e5d35ccbf44 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -12,6 +12,8 @@ from homeassistant.loader import bind_hass from homeassistant.util import slugify from . import device_registry as dr, entity_registry as er +from .frame import report +from .storage import Store from .typing import UNDEFINED, UndefinedType DATA_REGISTRY = "area_registry" @@ -47,9 +49,7 @@ class AreaRegistry: """Initialize the area registry.""" self.hass = hass self.areas: MutableMapping[str, AreaEntry] = {} - self._store = hass.helpers.storage.Store( - STORAGE_VERSION, STORAGE_KEY, atomic_writes=True - ) + self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY, atomic_writes=True) self._normalized_name_area_idx: dict[str, str] = {} @callback @@ -176,7 +176,7 @@ class AreaRegistry: areas: MutableMapping[str, AreaEntry] = OrderedDict() - if data is not None: + if isinstance(data, dict): for area in data["areas"]: normalized_name = normalize_area_name(area["name"]) areas[area["id"]] = AreaEntry( @@ -227,6 +227,9 @@ async def async_get_registry(hass: HomeAssistant) -> AreaRegistry: This is deprecated and will be removed in the future. Use async_get instead. """ + report( + "uses deprecated `async_get_registry` to access area registry, use async_get instead" + ) return async_get(hass) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 8985b7b721c..a628cdefff4 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -829,6 +829,12 @@ def zone( else: entity_id = entity.entity_id + if entity.state in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + ): + return False + latitude = entity.attributes.get(ATTR_LATITUDE) longitude = entity.attributes.get(ATTR_LONGITUDE) diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index e2b21522d42..9322d6e9dc1 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -25,6 +25,7 @@ from homeassistant import config_entries from homeassistant.components import http from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult +from homeassistant.loader import async_get_application_credentials from .aiohttp_client import async_get_clientsession from .network import NoURLAvailableError @@ -36,6 +37,7 @@ DATA_IMPLEMENTATIONS = "oauth2_impl" DATA_PROVIDERS = "oauth2_providers" AUTH_CALLBACK_PATH = "/auth/external/callback" HEADER_FRONTEND_BASE = "HA-Frontend-Base" +MY_AUTH_CALLBACK_PATH = "https://my.home-assistant.io/redirect/oauth" CLOCK_OUT_OF_SYNC_MAX_SEC = 20 @@ -128,6 +130,9 @@ class LocalOAuth2Implementation(AbstractOAuth2Implementation): @property def redirect_uri(self) -> str: """Return the redirect uri.""" + if "my" in self.hass.config.components: + return MY_AUTH_CALLBACK_PATH + if (req := http.current_request.get()) is None: raise RuntimeError("No current request in context") @@ -239,6 +244,8 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): return await self.async_step_auth() if not implementations: + if self.DOMAIN in await async_get_application_credentials(self.hass): + return self.async_abort(reason="missing_credentials") return self.async_abort(reason="missing_configuration") req = http.current_request.get() @@ -264,9 +271,10 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): ) -> FlowResult: """Create an entry for auth.""" # Flow has been triggered by external data - if user_input: + if user_input is not None: self.external_data = user_input - return self.async_external_step_done(next_step_id="creation") + next_step = "authorize_rejected" if "error" in user_input else "creation" + return self.async_external_step_done(next_step_id=next_step) try: async with async_timeout.timeout(10): @@ -304,6 +312,13 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): {"auth_implementation": self.flow_impl.domain, "token": token} ) + async def async_step_authorize_rejected(self, data: None = None) -> FlowResult: + """Step to handle flow rejection.""" + return self.async_abort( + reason="user_rejected_authorize", + description_placeholders={"error": self.external_data["error"]}, + ) + async def async_oauth_create_entry(self, data: dict) -> FlowResult: """Create an entry for the flow. @@ -347,10 +362,9 @@ async def async_get_implementations( return registered registered = dict(registered) - - for provider_domain, get_impl in hass.data[DATA_PROVIDERS].items(): - if (implementation := await get_impl(hass, domain)) is not None: - registered[provider_domain] = implementation + for get_impl in list(hass.data[DATA_PROVIDERS].values()): + for impl in await get_impl(hass, domain): + registered[impl.domain] = impl return registered @@ -373,7 +387,7 @@ def async_add_implementation_provider( hass: HomeAssistant, provider_domain: str, async_provide_implementation: Callable[ - [HomeAssistant, str], Awaitable[AbstractOAuth2Implementation | None] + [HomeAssistant, str], Awaitable[list[AbstractOAuth2Implementation]] ], ) -> None: """Add an implementation provider. @@ -394,10 +408,8 @@ class OAuth2AuthorizeCallbackView(http.HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """Receive authorization code.""" - 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}" - ) + if "state" not in request.query: + return web.Response(text="Missing state parameter") hass = request.app["hass"] @@ -406,9 +418,17 @@ class OAuth2AuthorizeCallbackView(http.HomeAssistantView): if state is None: return web.Response(text="Invalid state") + user_input: dict[str, Any] = {"state": state} + + if "code" in request.query: + user_input["code"] = request.query["code"] + elif "error" in request.query: + user_input["error"] = request.query["error"] + else: + return web.Response(text="Missing code or error parameter") + await hass.config_entries.flow.async_configure( - flow_id=state["flow_id"], - user_input={"state": state, "code": request.query["code"]}, + flow_id=state["flow_id"], user_input=user_input ) return web.Response( @@ -473,13 +493,13 @@ async def async_oauth2_request( This method will not refresh tokens. Use OAuth2 session for that. """ session = async_get_clientsession(hass) - + headers = kwargs.pop("headers", {}) return await session.request( method, url, **kwargs, headers={ - **(kwargs.get("headers") or {}), + **headers, "authorization": f"Bearer {token['access_token']}", }, ) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index c45b7d5f37b..ed3d5a7b06f 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -420,6 +420,10 @@ class DeviceRegistry: via_device_id: str | None | UndefinedType = UNDEFINED, ) -> DeviceEntry | None: """Update device attributes.""" + # Circular dep + # pylint: disable=import-outside-toplevel + from . import area_registry as ar + old = self.devices[device_id] new_values: dict[str, Any] = {} # Dict with new key/value pairs @@ -442,13 +446,13 @@ class DeviceRegistry: disabled_by = DeviceEntryDisabler(disabled_by) if ( - suggested_area not in (UNDEFINED, None, "") + suggested_area is not None + and suggested_area is not UNDEFINED + and suggested_area != "" and area_id is UNDEFINED and old.area_id is None ): - area = self.hass.helpers.area_registry.async_get( - self.hass - ).async_get_or_create(suggested_area) + area = ar.async_get(self.hass).async_get_or_create(suggested_area) area_id = area.id if ( @@ -716,6 +720,9 @@ async def async_get_registry(hass: HomeAssistant) -> DeviceRegistry: This is deprecated and will be removed in the future. Use async_get instead. """ + report( + "uses deprecated `async_get_registry` to access device registry, use async_get instead" + ) return async_get(hass) @@ -823,7 +830,7 @@ def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None: async def cleanup() -> None: """Cleanup.""" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = entity_registry.async_get(hass) async_cleanup(hass, dev_reg, ent_reg) debounced_cleanup = Debouncer( diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 32163c4498a..1c03e2334fe 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC import asyncio -from collections.abc import Awaitable, Iterable, Mapping, MutableMapping +from collections.abc import Coroutine, Iterable, Mapping, MutableMapping from dataclasses import dataclass from datetime import datetime, timedelta from enum import Enum, auto @@ -618,7 +618,7 @@ class Entity(ABC): if DATA_CUSTOMIZE in self.hass.data: attr.update(self.hass.data[DATA_CUSTOMIZE].get(self.entity_id)) - def _convert_temperature(state: str, attr: dict) -> str: + def _convert_temperature(state: str, attr: dict[str, Any]) -> str: # Convert temperature if we detect one # pylint: disable-next=import-outside-toplevel from homeassistant.components.sensor import SensorEntity @@ -955,7 +955,7 @@ class Entity(ABC): """Return the representation.""" return f"" - async def async_request_call(self, coro: Awaitable) -> None: + async def async_request_call(self, coro: Coroutine[Any, Any, Any]) -> None: """Process request batched.""" if self.parallel_updates: await self.parallel_updates.acquire() @@ -1025,15 +1025,20 @@ class ToggleEntity(Entity): """Turn the entity off.""" await self.hass.async_add_executor_job(ft.partial(self.turn_off, **kwargs)) + @final def toggle(self, **kwargs: Any) -> None: - """Toggle the entity.""" - if self.is_on: - self.turn_off(**kwargs) - else: - self.turn_on(**kwargs) + """Toggle the entity. + + This method will never be called by Home Assistant and should not be implemented + by integrations. + """ async def async_toggle(self, **kwargs: Any) -> None: - """Toggle the entity.""" + """Toggle the entity. + + This method should typically not be implemented by integrations, it's enough to + implement async_turn_on + async_turn_off or turn_on + turn_off. + """ if self.is_on: await self.async_turn_off(**kwargs) else: diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 3d57a9c3dc0..ecf2125962a 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -120,7 +120,7 @@ class EntityPlatform: @callback def _get_parallel_updates_semaphore( - self, entity_has_async_update: bool + self, entity_has_sync_update: bool ) -> asyncio.Semaphore | None: """Get or create a semaphore for parallel updates. @@ -138,7 +138,7 @@ class EntityPlatform: parallel_updates = getattr(self.platform, "PARALLEL_UPDATES", None) - if parallel_updates is None and not entity_has_async_update: + if parallel_updates is None and entity_has_sync_update: parallel_updates = 1 if parallel_updates == 0: @@ -422,7 +422,7 @@ class EntityPlatform: entity.add_to_platform_start( self.hass, self, - self._get_parallel_updates_semaphore(hasattr(entity, "async_update")), + self._get_parallel_updates_semaphore(hasattr(entity, "update")), ) # Update properties before we generate the entity_id diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 64ab0323f6c..d03d272b1ac 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -45,6 +45,7 @@ from homeassistant.util.yaml import load_yaml from . import device_registry as dr, storage from .device_registry import EVENT_DEVICE_REGISTRY_UPDATED +from .frame import report from .typing import UNDEFINED, UndefinedType if TYPE_CHECKING: @@ -819,6 +820,9 @@ async def async_get_registry(hass: HomeAssistant) -> EntityRegistry: This is deprecated and will be removed in the future. Use async_get instead. """ + report( + "uses deprecated `async_get_registry` to access entity registry, use async_get instead" + ) return async_get(hass) @@ -995,7 +999,7 @@ async def async_migrate_entries( entry_callback: Callable[[RegistryEntry], dict[str, Any] | None], ) -> None: """Migrator of unique IDs.""" - ent_reg = await async_get_registry(hass) + ent_reg = async_get(hass) for entry in ent_reg.entities.values(): if entry.config_entry_id != config_entry_id: diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index b85af09cfb9..c1229dc3e7c 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -68,8 +68,8 @@ class TrackStates: """Class for keeping track of states being tracked. all_states: All states on the system are being tracked - entities: Entities to track - domains: Domains to track + entities: Lowercased entities to track + domains: Lowercased domains to track """ all_states: bool @@ -111,8 +111,8 @@ class TrackTemplateResult: def threaded_listener_factory( - async_factory: Callable[Concatenate[HomeAssistant, _P], Any] # type: ignore[misc] -) -> Callable[Concatenate[HomeAssistant, _P], CALLBACK_TYPE]: # type: ignore[misc] + async_factory: Callable[Concatenate[HomeAssistant, _P], Any] +) -> Callable[Concatenate[HomeAssistant, _P], CALLBACK_TYPE]: """Convert an async event helper to a threaded one.""" @ft.wraps(async_factory) @@ -248,7 +248,16 @@ def async_track_state_change_event( """ if not (entity_ids := _async_string_to_lower_list(entity_ids)): return _remove_empty_listener + return _async_track_state_change_event(hass, entity_ids, action) + +@bind_hass +def _async_track_state_change_event( + hass: HomeAssistant, + entity_ids: str | Iterable[str], + action: Callable[[Event], Any], +) -> CALLBACK_TYPE: + """async_track_state_change_event without lowercasing.""" entity_callbacks = hass.data.setdefault(TRACK_STATE_CHANGE_CALLBACKS, {}) if TRACK_STATE_CHANGE_LISTENER not in hass.data: @@ -419,7 +428,16 @@ def async_track_state_added_domain( """Track state change events when an entity is added to domains.""" if not (domains := _async_string_to_lower_list(domains)): return _remove_empty_listener + return _async_track_state_added_domain(hass, domains, action) + +@bind_hass +def _async_track_state_added_domain( + hass: HomeAssistant, + domains: str | Iterable[str], + action: Callable[[Event], Any], +) -> CALLBACK_TYPE: + """async_track_state_added_domain without lowercasing.""" domain_callbacks = hass.data.setdefault(TRACK_STATE_ADDED_DOMAIN_CALLBACKS, {}) if TRACK_STATE_ADDED_DOMAIN_LISTENER not in hass.data: @@ -558,7 +576,7 @@ class _TrackStateChangeFiltered: self._setup_entities_listener(track_states.domains, track_states.entities) @property - def listeners(self) -> dict: + def listeners(self) -> dict[str, bool | set[str]]: """State changes that will cause a re-render.""" track_states = self._last_track_states return { @@ -626,7 +644,7 @@ class _TrackStateChangeFiltered: if not entities: return - self._listeners[_ENTITIES_LISTENER] = async_track_state_change_event( + self._listeners[_ENTITIES_LISTENER] = _async_track_state_change_event( self.hass, entities, self._action ) @@ -643,7 +661,7 @@ class _TrackStateChangeFiltered: if not domains: return - self._listeners[_DOMAINS_LISTENER] = async_track_state_added_domain( + self._listeners[_DOMAINS_LISTENER] = _async_track_state_added_domain( self.hass, domains, self._state_added ) @@ -853,7 +871,7 @@ class _TrackTemplateResultInfo: ) @property - def listeners(self) -> dict: + def listeners(self) -> dict[str, bool | set[str]]: """State changes that will cause a re-render.""" assert self._track_state_changes return { @@ -1217,7 +1235,7 @@ def async_track_same_state( else: async_remove_state_for_cancel = async_track_state_change_event( hass, - [entity_ids] if isinstance(entity_ids, str) else entity_ids, + entity_ids, state_for_cancel_listener, ) diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index 21d15e4fc73..9255824cddf 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -48,7 +48,7 @@ async def _async_process_single_integration_platform_component( return try: - await integration_platform.process_platform(hass, component_name, platform) # type: ignore[misc,operator] # https://github.com/python/mypy/issues/5485 + await integration_platform.process_platform(hass, component_name, platform) except Exception: # pylint: disable=broad-except _LOGGER.exception( "Error processing platform %s.%s", component_name, platform_name diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 9d446f10913..bc3451c24c0 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -127,7 +127,10 @@ class SelectedEntities: if not parts: return - _LOGGER.warning("Unable to find referenced %s", ", ".join(parts)) + _LOGGER.warning( + "Unable to find referenced %s or it is/they are currently not available", + ", ".join(parts), + ) @bind_hass @@ -777,7 +780,7 @@ def verify_domain_control( user_id=call.context.user_id, ) - reg = await hass.helpers.entity_registry.async_get_registry() + reg = entity_registry.async_get(hass) authorized = False diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index 09a329cd275..25bef38ed0b 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -1,8 +1,9 @@ """Helpers for sun events.""" from __future__ import annotations +from collections.abc import Callable import datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, cast from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET from homeassistant.core import HomeAssistant, callback @@ -11,11 +12,14 @@ from homeassistant.util import dt as dt_util if TYPE_CHECKING: import astral + import astral.location DATA_LOCATION_CACHE = "astral_location_cache" ELEVATION_AGNOSTIC_EVENTS = ("noon", "midnight") +_AstralSunEventCallable = Callable[..., datetime.datetime] + @callback @bind_hass @@ -73,15 +77,15 @@ def get_location_astral_event_next( if utc_point_in_time is None: utc_point_in_time = dt_util.utcnow() - kwargs = {"local": False} + kwargs: dict[str, Any] = {"local": False} if event not in ELEVATION_AGNOSTIC_EVENTS: kwargs["observer_elevation"] = elevation mod = -1 while True: try: - next_dt: datetime.datetime = ( - getattr(location, event)( + next_dt = ( + cast(_AstralSunEventCallable, getattr(location, event))( dt_util.as_local(utc_point_in_time).date() + datetime.timedelta(days=mod), **kwargs, @@ -111,12 +115,12 @@ def get_astral_event_date( if isinstance(date, datetime.datetime): date = dt_util.as_local(date).date() - kwargs = {"local": False} + kwargs: dict[str, Any] = {"local": False} if event not in ELEVATION_AGNOSTIC_EVENTS: kwargs["observer_elevation"] = elevation try: - return getattr(location, event)(date, **kwargs) # type: ignore[no-any-return] + return cast(_AstralSunEventCallable, getattr(location, event))(date, **kwargs) except ValueError: # Event never occurs for specified date. return None diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 3f084674a1b..1a8febf5ac2 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1325,21 +1325,12 @@ def utcnow(hass: HomeAssistant) -> datetime: return dt_util.utcnow() -def warn_no_default(function, value, default): +def raise_no_default(function, value): """Log warning if no default is specified.""" template, action = template_cv.get() or ("", "rendering or compiling") - _LOGGER.warning( - ( - "Template warning: '%s' got invalid input '%s' when %s template '%s' " - "but no default was specified. Currently '%s' will return '%s', however this template will fail " - "to render in Home Assistant core 2022.6" - ), - function, - value, - action, - template, - function, - default, + raise ValueError( + f"Template error: {function} got invalid input '{value}' when {action} template '{template}' " + "but no default was specified" ) @@ -1361,8 +1352,7 @@ def forgiving_round(value, precision=0, method="common", default=_SENTINEL): except (ValueError, TypeError): # If value can't be converted to float if default is _SENTINEL: - warn_no_default("round", value, value) - return value + raise_no_default("round", value) return default @@ -1373,20 +1363,25 @@ def multiply(value, amount, default=_SENTINEL): except (ValueError, TypeError): # If value can't be converted to float if default is _SENTINEL: - warn_no_default("multiply", value, value) - return value + raise_no_default("multiply", value) return default def logarithm(value, base=math.e, default=_SENTINEL): """Filter and function to get logarithm of the value with a specific base.""" try: - return math.log(float(value), float(base)) + value_float = float(value) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("log", value, value) - return value + raise_no_default("log", value) return default + try: + base_float = float(base) + except (ValueError, TypeError): + if default is _SENTINEL: + raise_no_default("log", base) + return default + return math.log(value_float, base_float) def sine(value, default=_SENTINEL): @@ -1395,8 +1390,7 @@ def sine(value, default=_SENTINEL): return math.sin(float(value)) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("sin", value, value) - return value + raise_no_default("sin", value) return default @@ -1406,8 +1400,7 @@ def cosine(value, default=_SENTINEL): return math.cos(float(value)) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("cos", value, value) - return value + raise_no_default("cos", value) return default @@ -1417,8 +1410,7 @@ def tangent(value, default=_SENTINEL): return math.tan(float(value)) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("tan", value, value) - return value + raise_no_default("tan", value) return default @@ -1428,8 +1420,7 @@ def arc_sine(value, default=_SENTINEL): return math.asin(float(value)) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("asin", value, value) - return value + raise_no_default("asin", value) return default @@ -1439,8 +1430,7 @@ def arc_cosine(value, default=_SENTINEL): return math.acos(float(value)) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("acos", value, value) - return value + raise_no_default("acos", value) return default @@ -1450,8 +1440,7 @@ def arc_tangent(value, default=_SENTINEL): return math.atan(float(value)) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("atan", value, value) - return value + raise_no_default("atan", value) return default @@ -1474,8 +1463,7 @@ def arc_tangent2(*args, default=_SENTINEL): return math.atan2(float(args[0]), float(args[1])) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("atan2", args, args) - return args + raise_no_default("atan2", args) return default @@ -1485,8 +1473,7 @@ def square_root(value, default=_SENTINEL): return math.sqrt(float(value)) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("sqrt", value, value) - return value + raise_no_default("sqrt", value) return default @@ -1502,8 +1489,7 @@ def timestamp_custom(value, date_format=DATE_STR_FORMAT, local=True, default=_SE except (ValueError, TypeError): # If timestamp can't be converted if default is _SENTINEL: - warn_no_default("timestamp_custom", value, value) - return value + raise_no_default("timestamp_custom", value) return default @@ -1514,8 +1500,7 @@ def timestamp_local(value, default=_SENTINEL): except (ValueError, TypeError): # If timestamp can't be converted if default is _SENTINEL: - warn_no_default("timestamp_local", value, value) - return value + raise_no_default("timestamp_local", value) return default @@ -1526,8 +1511,7 @@ def timestamp_utc(value, default=_SENTINEL): except (ValueError, TypeError): # If timestamp can't be converted if default is _SENTINEL: - warn_no_default("timestamp_utc", value, value) - return value + raise_no_default("timestamp_utc", value) return default @@ -1537,8 +1521,7 @@ def forgiving_as_timestamp(value, default=_SENTINEL): return dt_util.as_timestamp(value) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("as_timestamp", value, None) - return None + raise_no_default("as_timestamp", value) return default @@ -1552,14 +1535,18 @@ def as_datetime(value): return dt_util.parse_datetime(value) +def as_timedelta(value: str) -> timedelta | None: + """Parse a ISO8601 duration like 'PT10M' to a timedelta.""" + return dt_util.parse_duration(value) + + def strptime(string, fmt, default=_SENTINEL): """Parse a time string to datetime.""" try: return datetime.strptime(string, fmt) except (ValueError, AttributeError, TypeError): if default is _SENTINEL: - warn_no_default("strptime", string, string) - return string + raise_no_default("strptime", string) return default @@ -1618,8 +1605,7 @@ def forgiving_float(value, default=_SENTINEL): return float(value) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("float", value, value) - return value + raise_no_default("float", value) return default @@ -1629,26 +1615,23 @@ def forgiving_float_filter(value, default=_SENTINEL): return float(value) except (ValueError, TypeError): if default is _SENTINEL: - warn_no_default("float", value, 0) - return 0 + raise_no_default("float", value) return default def forgiving_int(value, default=_SENTINEL, base=10): - """Try to convert value to an int, and warn if it fails.""" + """Try to convert value to an int, and raise if it fails.""" result = jinja2.filters.do_int(value, default=default, base=base) if result is _SENTINEL: - warn_no_default("int", value, value) - return value + raise_no_default("int", value) return result def forgiving_int_filter(value, default=_SENTINEL, base=10): - """Try to convert value to an int, and warn if it fails.""" + """Try to convert value to an int, and raise if it fails.""" result = jinja2.filters.do_int(value, default=default, base=base) if result is _SENTINEL: - warn_no_default("int", value, 0) - return 0 + raise_no_default("int", value) return result @@ -1924,6 +1907,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.filters["atan2"] = arc_tangent2 self.filters["sqrt"] = square_root self.filters["as_datetime"] = as_datetime + self.filters["as_timedelta"] = as_timedelta self.filters["as_timestamp"] = forgiving_as_timestamp self.filters["today_at"] = today_at self.filters["as_local"] = dt_util.as_local @@ -1969,6 +1953,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.globals["float"] = forgiving_float self.globals["as_datetime"] = as_datetime self.globals["as_local"] = dt_util.as_local + self.globals["as_timedelta"] = as_timedelta self.globals["as_timestamp"] = forgiving_as_timestamp self.globals["today_at"] = today_at self.globals["relative_time"] = relative_time diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index 976c66dda56..cda50de535b 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio from collections import ChainMap -from collections.abc import Mapping +from collections.abc import Iterable, Mapping import logging from typing import Any @@ -286,7 +286,7 @@ async def async_get_translations( hass: HomeAssistant, language: str, category: str, - integration: str | None = None, + integrations: Iterable[str] | None = None, config_flow: bool | None = None, ) -> dict[str, Any]: """Return all backend translations. @@ -297,8 +297,8 @@ async def async_get_translations( """ lock = hass.data.setdefault(TRANSLATION_LOAD_LOCK, asyncio.Lock()) - if integration is not None: - components = {integration} + if integrations is not None: + components = set(integrations) elif config_flow: components = (await async_get_config_flows(hass)) - hass.config.components elif category == "state": @@ -310,7 +310,10 @@ async def async_get_translations( } async with lock: - cache = hass.data.setdefault(TRANSLATION_FLATTEN_CACHE, _TranslationCache(hass)) + if TRANSLATION_FLATTEN_CACHE in hass.data: + cache = hass.data[TRANSLATION_FLATTEN_CACHE] + else: + cache = hass.data[TRANSLATION_FLATTEN_CACHE] = _TranslationCache(hass) cached = await cache.async_fetch(language, category, components) return dict(ChainMap(*cached)) diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index a3cb2f9421d..e90c684365d 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -5,7 +5,7 @@ import asyncio from collections.abc import Callable import functools import logging -from typing import Any +from typing import TYPE_CHECKING, Any import voluptuous as vol @@ -16,13 +16,20 @@ from homeassistant.loader import IntegrationNotFound, async_get_integration from .typing import ConfigType, TemplateVarsType +if TYPE_CHECKING: + from homeassistant.components.device_automation.trigger import ( + DeviceAutomationTriggerProtocol, + ) + _PLATFORM_ALIASES = { "device_automation": ("device",), "homeassistant": ("event", "numeric_state", "state", "time_pattern", "time"), } -async def _async_get_trigger_platform(hass: HomeAssistant, config: ConfigType) -> Any: +async def _async_get_trigger_platform( + hass: HomeAssistant, config: ConfigType +) -> DeviceAutomationTriggerProtocol: platform_and_sub_type = config[CONF_PLATFORM].split(".") platform = platform_and_sub_type[0] for alias, triggers in _PLATFORM_ALIASES.items(): @@ -86,6 +93,10 @@ async def async_initialize_triggers( variables: TemplateVarsType = None, ) -> CALLBACK_TYPE | None: """Initialize triggers.""" + from homeassistant.components.automation import ( # pylint:disable=[import-outside-toplevel] + AutomationTriggerData, + AutomationTriggerInfo, + ) triggers = [] for idx, conf in enumerate(trigger_config): @@ -96,14 +107,14 @@ async def async_initialize_triggers( platform = await _async_get_trigger_platform(hass, conf) trigger_id = conf.get(CONF_ID, f"{idx}") trigger_idx = f"{idx}" - trigger_data = {"id": trigger_id, "idx": trigger_idx} - info = { - "domain": domain, - "name": name, - "home_assistant_start": home_assistant_start, - "variables": variables, - "trigger_data": trigger_data, - } + trigger_data = AutomationTriggerData(id=trigger_id, idx=trigger_idx) + info = AutomationTriggerInfo( + domain=domain, + name=name, + home_assistant_start=home_assistant_start, + variables=variables, + trigger_data=trigger_data, + ) triggers.append( platform.async_attach_trigger( diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 364f212a1be..589f316532b 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -24,6 +24,7 @@ from awesomeversion import ( AwesomeVersionStrategy, ) +from .generated.application_credentials import APPLICATION_CREDENTIALS from .generated.dhcp import DHCP from .generated.mqtt import MQTT from .generated.ssdp import SSDP @@ -210,6 +211,20 @@ async def async_get_config_flows( return flows +async def async_get_application_credentials(hass: HomeAssistant) -> list[str]: + """Return cached list of application credentials.""" + integrations = await async_get_custom_components(hass) + + return [ + *APPLICATION_CREDENTIALS, + *[ + integration.domain + for integration in integrations.values() + if "application_credentials" in integration.dependencies + ], + ] + + def async_process_zeroconf_match_dict(entry: dict[str, Any]) -> dict[str, Any]: """Handle backwards compat with zeroconf matchers.""" entry_without_type: dict[str, Any] = entry.copy() diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 43c0d2ad4c4..ad2539233f4 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,40 +1,40 @@ -PyJWT==2.3.0 +PyJWT==2.4.0 PyNaCl==1.5.0 aiodiscover==1.4.11 aiohttp==3.8.1 aiohttp_cors==0.7.0 astral==2.2 -async-upnp-client==0.29.0 +async-upnp-client==0.30.1 async_timeout==4.0.2 atomicwrites==1.4.0 attrs==21.2.0 -awesomeversion==22.2.0 +awesomeversion==22.5.2 bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 cryptography==36.0.2 fnvhash==0.1.0 hass-nabucasa==0.54.0 -home-assistant-frontend==20220504.1 -httpx==0.22.0 +home-assistant-frontend==20220531.0 +httpx==0.23.0 ifaddr==0.1.7 -jinja2==3.1.1 +jinja2==3.1.2 lru-dict==1.1.7 paho-mqtt==1.6.1 -pillow==9.1.0 -pip>=21.0,<22.1 +pillow==9.1.1 +pip>=21.0,<22.2 pyserial==3.5 python-slugify==4.0.1 pyudev==0.22.0 pyyaml==6.0 requests==2.27.1 scapy==2.4.5 -sqlalchemy==1.4.36 +sqlalchemy==1.4.37 typing-extensions>=3.10.0.2,<5.0 voluptuous-serialize==2.5.0 voluptuous==0.13.1 yarl==1.7.2 -zeroconf==0.38.5 +zeroconf==0.38.6 # Constrain pycryptodome to avoid vulnerability # see https://github.com/home-assistant/core/pull/16238 @@ -50,8 +50,8 @@ httplib2>=0.19.0 # gRPC is an implicit dependency that we want to make explicit so we manage # upgrades intentionally. It is a large package to build from source and we # want to ensure we have wheels built. -grpcio==1.45.0 -grpcio-status==1.45.0 +grpcio==1.46.1 +grpcio-status==1.46.1 # libcst >=0.4.0 requires a newer Rust than we currently have available, # thus our wheels builds fail. This pins it to the last working version, @@ -78,9 +78,9 @@ regex==2021.8.28 # these requirements are quite loose. As the entire stack has some outstanding issues, and # even newer versions seem to introduce new issues, it's useful for us to pin all these # requirements so we can directly link HA versions to these library versions. -anyio==3.5.0 +anyio==3.6.1 h11==0.12.0 -httpcore==0.14.7 +httpcore==0.15.0 # Ensure we have a hyperframe version that works in Python 3.10 # 5.2.0 fixed a collections abc deprecation @@ -106,3 +106,7 @@ authlib<1.0 # Pin backoff for compatibility until most libraries have been updated # https://github.com/home-assistant/core/pull/70817 backoff<2.0 + +# Breaking change in version +# https://github.com/samuelcolvin/pydantic/issues/4092 +pydantic!=1.9.1 diff --git a/homeassistant/runner.py b/homeassistant/runner.py index 472d399713d..5788a6b155a 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -48,7 +48,7 @@ class RuntimeConfig: open_ui: bool = False -class HassEventLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore[valid-type,misc] +class HassEventLoopPolicy(asyncio.DefaultEventLoopPolicy): """Event loop policy for Home Assistant.""" def __init__(self, debug: bool) -> None: @@ -59,7 +59,7 @@ class HassEventLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore[valid @property def loop_name(self) -> str: """Return name of the loop.""" - return self._loop_factory.__name__ # type: ignore[no-any-return] + return self._loop_factory.__name__ # type: ignore[no-any-return,attr-defined] def new_event_loop(self) -> asyncio.AbstractEventLoop: """Get the event loop.""" diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index 35df75d5a1c..a681b3e210d 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -15,8 +15,11 @@ from homeassistant import core from homeassistant.components.websocket_api.const import JSON_DUMP from homeassistant.const import EVENT_STATE_CHANGED from homeassistant.helpers.entityfilter import convert_include_exclude_filter +from homeassistant.helpers.event import ( + async_track_state_change, + async_track_state_change_event, +) from homeassistant.helpers.json import JSONEncoder -from homeassistant.util import dt as dt_util # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs # mypy: no-warn-return-any @@ -135,9 +138,7 @@ async def state_changed_helper(hass): event.set() for idx in range(1000): - hass.helpers.event.async_track_state_change( - f"{entity_id}{idx}", listener, "off", "on" - ) + async_track_state_change(hass, f"{entity_id}{idx}", listener, "off", "on") event_data = { "entity_id": f"{entity_id}0", "old_state": core.State(entity_id, "off"), @@ -167,8 +168,8 @@ async def state_changed_event_helper(hass): nonlocal count count += 1 - hass.helpers.event.async_track_state_change_event( - [f"{entity_id}{idx}" for idx in range(1000)], listener + async_track_state_change_event( + hass, [f"{entity_id}{idx}" for idx in range(1000)], listener ) event_data = { @@ -202,8 +203,8 @@ async def state_changed_event_filter_helper(hass): nonlocal count count += 1 - hass.helpers.event.async_track_state_change_event( - [f"{entity_id}{idx}" for idx in range(1000)], listener + async_track_state_change_event( + hass, [f"{entity_id}{idx}" for idx in range(1000)], listener ) event_data = { @@ -224,57 +225,6 @@ async def state_changed_event_filter_helper(hass): return timer() - start -@benchmark -async def logbook_filtering_state(hass): - """Filter state changes.""" - return await _logbook_filtering(hass, 1, 1) - - -@benchmark -async def logbook_filtering_attributes(hass): - """Filter attribute changes.""" - return await _logbook_filtering(hass, 1, 2) - - -@benchmark -async def _logbook_filtering(hass, last_changed, last_updated): - # pylint: disable=import-outside-toplevel - from homeassistant.components import logbook - - entity_id = "test.entity" - - old_state = {"entity_id": entity_id, "state": "off"} - - new_state = { - "entity_id": entity_id, - "state": "on", - "last_updated": last_updated, - "last_changed": last_changed, - } - - event = _create_state_changed_event_from_old_new( - entity_id, dt_util.utcnow(), old_state, new_state - ) - - entity_attr_cache = logbook.EntityAttributeCache(hass) - - entities_filter = convert_include_exclude_filter( - logbook.INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA({}) - ) - - def yield_events(event): - for _ in range(10**5): - # pylint: disable=protected-access - if logbook._keep_event(hass, event, entities_filter): - yield event - - start = timer() - - list(logbook.humanify(hass, yield_events(event), entity_attr_cache, {})) - - return timer() - start - - @benchmark async def filtering_entity_id(hass): """Run a 100k state changes through entity filter.""" @@ -404,4 +354,4 @@ def _create_state_changed_event_from_old_new( # pylint: disable=import-outside-toplevel from homeassistant.components import logbook - return logbook.LazyEventPartialState(row) + return logbook.LazyEventPartialState(row, {}) diff --git a/homeassistant/strings.json b/homeassistant/strings.json index 9dcf8c7fe49..9ae30becaee 100644 --- a/homeassistant/strings.json +++ b/homeassistant/strings.json @@ -71,8 +71,10 @@ "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages.", "oauth2_error": "Received invalid token data.", "oauth2_missing_configuration": "The component is not configured. Please follow the documentation.", + "oauth2_missing_credentials": "The integration requires application credentials.", "oauth2_authorize_url_timeout": "Timeout generating authorize URL.", "oauth2_no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", + "oauth2_user_rejected_authorize": "Account linking rejected: {error}", "reauth_successful": "Re-authentication was successful", "unknown_authorize_url_generation": "Unknown error generating an authorize URL.", "cloud_not_connected": "Not connected to Home Assistant Cloud." diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 4e7d05f7c2e..8b96e85664d 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -94,8 +94,14 @@ def run_callback_threadsafe( return future -def check_loop(func: Callable[..., Any], strict: bool = True) -> None: - """Warn if called inside the event loop. Raise if `strict` is True.""" +def check_loop( + func: Callable[..., Any], strict: bool = True, advise_msg: str | None = None +) -> None: + """Warn if called inside the event loop. Raise if `strict` is True. + + The default advisory message is 'Use `await hass.async_add_executor_job()' + Set `advise_msg` to an alternate message if the the solution differs. + """ try: get_running_loop() in_loop = True @@ -134,6 +140,7 @@ def check_loop(func: Callable[..., Any], strict: bool = True) -> None: if found_frame is None: raise RuntimeError( f"Detected blocking call to {func.__name__} inside the event loop. " + f"{advise_msg or 'Use `await hass.async_add_executor_job()`'}; " "This is causing stability issues. Please report issue" ) @@ -160,7 +167,7 @@ def check_loop(func: Callable[..., Any], strict: bool = True) -> None: if strict: raise RuntimeError( "Blocking calls must be done in the executor or a separate thread; " - "Use `await hass.async_add_executor_job()` " + f"{advise_msg or 'Use `await hass.async_add_executor_job()`'}; " f"at {found_frame.filename[index:]}, line {found_frame.lineno}: {(found_frame.line or '?').strip()}" ) diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 21c877f9377..448b417cc97 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -3,7 +3,7 @@ from __future__ import annotations import colorsys import math -from typing import NamedTuple, cast +from typing import NamedTuple import attr @@ -295,9 +295,9 @@ def color_xy_brightness_to_RGB( # Apply reverse gamma correction. r, g, b = map( - lambda x: (12.92 * x) + lambda x: (12.92 * x) # type: ignore[no-any-return] if (x <= 0.0031308) - else ((1.0 + 0.055) * cast(float, pow(x, (1.0 / 2.4))) - 0.055), + else ((1.0 + 0.055) * pow(x, (1.0 / 2.4)) - 0.055), [r, g, b], ) diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index 4b4b798a2d8..80b322c1a14 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -14,6 +14,10 @@ DATE_STR_FORMAT = "%Y-%m-%d" UTC = dt.timezone.utc DEFAULT_TIME_ZONE: dt.tzinfo = dt.timezone.utc +# EPOCHORDINAL is not exposed as a constant +# https://github.com/python/cpython/blob/3.10/Lib/zoneinfo/_zoneinfo.py#L12 +EPOCHORDINAL = dt.datetime(1970, 1, 1).toordinal() + # Copyright (c) Django Software Foundation and individual contributors. # All rights reserved. # https://github.com/django/django/blob/master/LICENSE @@ -24,6 +28,49 @@ DATETIME_RE = re.compile( r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$" ) +# Copyright (c) Django Software Foundation and individual contributors. +# All rights reserved. +# https://github.com/django/django/blob/master/LICENSE +STANDARD_DURATION_RE = re.compile( + r"^" + r"(?:(?P-?\d+) (days?, )?)?" + r"(?P-?)" + r"((?:(?P\d+):)(?=\d+:\d+))?" + r"(?:(?P\d+):)?" + r"(?P\d+)" + r"(?:[\.,](?P\d{1,6})\d{0,6})?" + r"$" +) + +# Copyright (c) Django Software Foundation and individual contributors. +# All rights reserved. +# https://github.com/django/django/blob/master/LICENSE +ISO8601_DURATION_RE = re.compile( + r"^(?P[-+]?)" + r"P" + r"(?:(?P\d+([\.,]\d+)?)D)?" + r"(?:T" + r"(?:(?P\d+([\.,]\d+)?)H)?" + r"(?:(?P\d+([\.,]\d+)?)M)?" + r"(?:(?P\d+([\.,]\d+)?)S)?" + r")?" + r"$" +) + +# Copyright (c) Django Software Foundation and individual contributors. +# All rights reserved. +# https://github.com/django/django/blob/master/LICENSE +POSTGRES_INTERVAL_RE = re.compile( + r"^" + r"(?:(?P-?\d+) (days? ?))?" + r"(?:(?P[-+])?" + r"(?P\d+):" + r"(?P\d\d):" + r"(?P\d\d)" + r"(?:\.(?P\d{1,6}))?" + r")?$" +) + def set_default_time_zone(time_zone: dt.tzinfo) -> None: """Set a default time zone to be used when none is specified. @@ -98,6 +145,19 @@ def utc_from_timestamp(timestamp: float) -> dt.datetime: return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=UTC) +def utc_to_timestamp(utc_dt: dt.datetime) -> float: + """Fast conversion of a datetime in UTC to a timestamp.""" + # Taken from + # https://github.com/python/cpython/blob/3.10/Lib/zoneinfo/_zoneinfo.py#L185 + return ( + (utc_dt.toordinal() - EPOCHORDINAL) * 86400 + + utc_dt.hour * 3600 + + utc_dt.minute * 60 + + utc_dt.second + + (utc_dt.microsecond / 1000000) + ) + + def start_of_local_day(dt_or_d: dt.date | dt.datetime | None = None) -> dt.datetime: """Return local datetime object of start of day from date or datetime.""" if dt_or_d is None: @@ -154,6 +214,35 @@ def parse_date(dt_str: str) -> dt.date | None: return None +# Copyright (c) Django Software Foundation and individual contributors. +# All rights reserved. +# https://github.com/django/django/blob/master/LICENSE +def parse_duration(value: str) -> dt.timedelta | None: + """Parse a duration string and return a datetime.timedelta. + + Also supports ISO 8601 representation and PostgreSQL's day-time interval + format. + """ + match = ( + STANDARD_DURATION_RE.match(value) + or ISO8601_DURATION_RE.match(value) + or POSTGRES_INTERVAL_RE.match(value) + ) + if match: + kws = match.groupdict() + sign = -1 if kws.pop("sign", "+") == "-" else 1 + if kws.get("microseconds"): + kws["microseconds"] = kws["microseconds"].ljust(6, "0") + time_delta_args: dict[str, float] = { + k: float(v.replace(",", ".")) for k, v in kws.items() if v is not None + } + days = dt.timedelta(float(time_delta_args.pop("days", 0.0) or 0.0)) + if match.re == ISO8601_DURATION_RE: + days *= sign + return days + sign * dt.timedelta(**time_delta_args) + return None + + def parse_time(time_str: str) -> dt.time | None: """Parse a time string (00:20:00) into Time object. @@ -322,8 +411,8 @@ def find_next_time_expression_time( now += dt.timedelta(seconds=1) continue - now_is_ambiguous = _datetime_ambiguous(now) - result_is_ambiguous = _datetime_ambiguous(result) + if not _datetime_ambiguous(now): + return result # When leaving DST and clocks are turned backward. # Then there are wall clock times that are ambiguous i.e. exist with DST and without DST @@ -331,7 +420,7 @@ def find_next_time_expression_time( # in a day. # Example: on 2021.10.31 02:00:00 in CET timezone clocks are turned backward an hour - if now_is_ambiguous and result_is_ambiguous: + if _datetime_ambiguous(result): # `now` and `result` are both ambiguous, so the next match happens # _within_ the current fold. @@ -340,7 +429,7 @@ def find_next_time_expression_time( # 2. 2021.10.31 02:00:00+01:00 with pattern 02:30 -> 2021.10.31 02:30:00+01:00 return result.replace(fold=now.fold) - if now_is_ambiguous and now.fold == 0 and not result_is_ambiguous: + if now.fold == 0: # `now` is in the first fold, but result is not ambiguous (meaning it no longer matches # within the fold). # -> Check if result matches in the next fold. If so, emit that match diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index e653b439e0e..b4d7274ded7 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -6,9 +6,8 @@ detect_location_info and elevation are mocked by default during tests. from __future__ import annotations import asyncio -import collections import math -from typing import Any +from typing import Any, NamedTuple import aiohttp @@ -30,22 +29,21 @@ MILES_PER_KILOMETER = 0.621371 MAX_ITERATIONS = 200 CONVERGENCE_THRESHOLD = 1e-12 -LocationInfo = collections.namedtuple( - "LocationInfo", - [ - "ip", - "country_code", - "currency", - "region_code", - "region_name", - "city", - "zip_code", - "time_zone", - "latitude", - "longitude", - "use_metric", - ], -) + +class LocationInfo(NamedTuple): + """Tuple with location information.""" + + ip: str + country_code: str + currency: str + region_code: str + region_name: str + city: str + zip_code: str + time_zone: str + latitude: float + longitude: float + use_metric: bool async def async_detect_location_info( diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index d09feec5237..6c4a55f51f2 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -2,18 +2,20 @@ from __future__ import annotations import asyncio -from collections.abc import Awaitable, Callable, Coroutine +from collections.abc import Callable, Coroutine from functools import partial, wraps import inspect import logging import logging.handlers import queue import traceback -from typing import Any, cast, overload +from typing import Any, TypeVar, cast, overload from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE from homeassistant.core import HomeAssistant, callback, is_callback +_T = TypeVar("_T") + class HideSensitiveDataFilter(logging.Filter): """Filter API password calls.""" @@ -115,22 +117,24 @@ def log_exception(format_err: Callable[..., Any], *args: Any) -> None: @overload -def catch_log_exception( # type: ignore[misc] - func: Callable[..., Awaitable[Any]], format_err: Callable[..., Any], *args: Any -) -> Callable[..., Awaitable[None]]: - """Overload for Callables that return an Awaitable.""" +def catch_log_exception( + func: Callable[..., Coroutine[Any, Any, Any]], + format_err: Callable[..., Any], + *args: Any, +) -> Callable[..., Coroutine[Any, Any, None]]: + """Overload for Callables that return a Coroutine.""" @overload def catch_log_exception( func: Callable[..., Any], format_err: Callable[..., Any], *args: Any -) -> Callable[..., None]: +) -> Callable[..., None | Coroutine[Any, Any, None]]: """Overload for Callables that return Any.""" def catch_log_exception( func: Callable[..., Any], format_err: Callable[..., Any], *args: Any -) -> Callable[..., None] | Callable[..., Awaitable[None]]: +) -> Callable[..., None | Coroutine[Any, Any, None]]: """Decorate a callback to catch and log exceptions.""" # Check for partials to properly determine if coroutine function @@ -138,9 +142,9 @@ def catch_log_exception( while isinstance(check_func, partial): check_func = check_func.func - wrapper_func: Callable[..., None] | Callable[..., Awaitable[None]] + wrapper_func: Callable[..., None | Coroutine[Any, Any, None]] if asyncio.iscoroutinefunction(check_func): - async_func = cast(Callable[..., Awaitable[None]], func) + async_func = cast(Callable[..., Coroutine[Any, Any, None]], func) @wraps(async_func) async def async_wrapper(*args: Any) -> None: @@ -170,11 +174,11 @@ def catch_log_exception( def catch_log_coro_exception( - target: Coroutine[Any, Any, Any], format_err: Callable[..., Any], *args: Any -) -> Coroutine[Any, Any, Any]: + target: Coroutine[Any, Any, _T], format_err: Callable[..., Any], *args: Any +) -> Coroutine[Any, Any, _T | None]: """Decorate a coroutine to catch and log exceptions.""" - async def coro_wrapper(*args: Any) -> Any: + async def coro_wrapper(*args: Any) -> _T | None: """Catch and log exception.""" try: return await target @@ -182,10 +186,12 @@ def catch_log_coro_exception( log_exception(format_err, *args) return None - return coro_wrapper() + return coro_wrapper(*args) -def async_create_catching_coro(target: Coroutine) -> Coroutine: +def async_create_catching_coro( + target: Coroutine[Any, Any, _T] +) -> Coroutine[Any, Any, _T | None]: """Wrap a coroutine to catch and log exceptions. The exception will be logged together with a stacktrace of where the @@ -196,7 +202,7 @@ def async_create_catching_coro(target: Coroutine) -> Coroutine: trace = traceback.extract_stack() wrapped_target = catch_log_coro_exception( target, - lambda *args: "Exception in {} called from\n {}".format( + lambda: "Exception in {} called from\n {}".format( target.__name__, "".join(traceback.format_list(trace[:-1])), ), diff --git a/homeassistant/util/ulid.py b/homeassistant/util/ulid.py index 888b5b2074b..d40b0f48e16 100644 --- a/homeassistant/util/ulid.py +++ b/homeassistant/util/ulid.py @@ -1,14 +1,25 @@ """Helpers to generate ulids.""" +from __future__ import annotations from random import getrandbits import time -# In the future once we require python 3.10 and above, we can -# create a new function that uses base64.b32encodehex to shorten -# these to 26 characters. def ulid_hex() -> str: - """Generate a ULID in hex that will work for a UUID. + """Generate a ULID in lowercase hex that will work for a UUID. + + This ulid should not be used for cryptographically secure + operations. + + This string can be converted with https://github.com/ahawker/ulid + + ulid.from_uuid(uuid.UUID(ulid_hex)) + """ + return f"{int(time.time()*1000):012x}{getrandbits(80):020x}" + + +def ulid(timestamp: float | None = None) -> str: + """Generate a ULID. This ulid should not be used for cryptographically secure operations. @@ -18,8 +29,47 @@ def ulid_hex() -> str: Timestamp Randomness 48bits 80bits - This string can be converted with https://github.com/ahawker/ulid + This string can be loaded directly with https://github.com/ahawker/ulid - ulid.from_uuid(uuid.UUID(value)) + import homeassistant.util.ulid as ulid_util + import ulid + ulid.parse(ulid_util.ulid()) """ - return f"{int(time.time()*1000):012x}{getrandbits(80):020x}" + ulid_bytes = int((timestamp or time.time()) * 1000).to_bytes( + 6, byteorder="big" + ) + int(getrandbits(80)).to_bytes(10, byteorder="big") + + # This is base32 crockford encoding with the loop unrolled for performance + # + # This code is adapted from: + # https://github.com/ahawker/ulid/blob/06289583e9de4286b4d80b4ad000d137816502ca/ulid/base32.py#L102 + # + enc = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" + return ( + enc[(ulid_bytes[0] & 224) >> 5] + + enc[ulid_bytes[0] & 31] + + enc[(ulid_bytes[1] & 248) >> 3] + + enc[((ulid_bytes[1] & 7) << 2) | ((ulid_bytes[2] & 192) >> 6)] + + enc[((ulid_bytes[2] & 62) >> 1)] + + enc[((ulid_bytes[2] & 1) << 4) | ((ulid_bytes[3] & 240) >> 4)] + + enc[((ulid_bytes[3] & 15) << 1) | ((ulid_bytes[4] & 128) >> 7)] + + enc[(ulid_bytes[4] & 124) >> 2] + + enc[((ulid_bytes[4] & 3) << 3) | ((ulid_bytes[5] & 224) >> 5)] + + enc[ulid_bytes[5] & 31] + + enc[(ulid_bytes[6] & 248) >> 3] + + enc[((ulid_bytes[6] & 7) << 2) | ((ulid_bytes[7] & 192) >> 6)] + + enc[(ulid_bytes[7] & 62) >> 1] + + enc[((ulid_bytes[7] & 1) << 4) | ((ulid_bytes[8] & 240) >> 4)] + + enc[((ulid_bytes[8] & 15) << 1) | ((ulid_bytes[9] & 128) >> 7)] + + enc[(ulid_bytes[9] & 124) >> 2] + + enc[((ulid_bytes[9] & 3) << 3) | ((ulid_bytes[10] & 224) >> 5)] + + enc[ulid_bytes[10] & 31] + + enc[(ulid_bytes[11] & 248) >> 3] + + enc[((ulid_bytes[11] & 7) << 2) | ((ulid_bytes[12] & 192) >> 6)] + + enc[(ulid_bytes[12] & 62) >> 1] + + enc[((ulid_bytes[12] & 1) << 4) | ((ulid_bytes[13] & 240) >> 4)] + + enc[((ulid_bytes[13] & 15) << 1) | ((ulid_bytes[14] & 128) >> 7)] + + enc[(ulid_bytes[14] & 124) >> 2] + + enc[((ulid_bytes[14] & 3) << 3) | ((ulid_bytes[15] & 224) >> 5)] + + enc[ulid_bytes[15] & 31] + ) diff --git a/machine/raspberrypi b/machine/raspberrypi index 960e343792d..8ea2d08bba7 100644 --- a/machine/raspberrypi +++ b/machine/raspberrypi @@ -4,11 +4,7 @@ FROM homeassistant/armhf-homeassistant:$BUILD_VERSION RUN apk --no-cache add \ raspberrypi \ raspberrypi-libs \ - usbutils \ - && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ - && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO -c /usr/src/homeassistant/requirements_all.txt \ - --use-deprecated=legacy-resolver + usbutils ## # Set symlinks for raspberry pi camera binaries. diff --git a/machine/raspberrypi2 b/machine/raspberrypi2 index 225c45423a1..45f7a9c80d1 100644 --- a/machine/raspberrypi2 +++ b/machine/raspberrypi2 @@ -4,11 +4,7 @@ FROM homeassistant/armv7-homeassistant:$BUILD_VERSION RUN apk --no-cache add \ raspberrypi \ raspberrypi-libs \ - usbutils \ - && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ - && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO -c /usr/src/homeassistant/requirements_all.txt \ - --use-deprecated=legacy-resolver + usbutils ## # Set symlinks for raspberry pi binaries. diff --git a/machine/raspberrypi3 b/machine/raspberrypi3 index 6315cc3e885..9985b4a3b7a 100644 --- a/machine/raspberrypi3 +++ b/machine/raspberrypi3 @@ -5,9 +5,8 @@ RUN apk --no-cache add \ raspberrypi \ raspberrypi-libs \ usbutils \ - && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ + bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ --use-deprecated=legacy-resolver ## diff --git a/machine/raspberrypi3-64 b/machine/raspberrypi3-64 index 51f41d68320..35c6eec77de 100644 --- a/machine/raspberrypi3-64 +++ b/machine/raspberrypi3-64 @@ -5,9 +5,8 @@ RUN apk --no-cache add \ raspberrypi \ raspberrypi-libs \ usbutils \ - && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ + bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ --use-deprecated=legacy-resolver ## diff --git a/machine/raspberrypi4 b/machine/raspberrypi4 index 6315cc3e885..9985b4a3b7a 100644 --- a/machine/raspberrypi4 +++ b/machine/raspberrypi4 @@ -5,9 +5,8 @@ RUN apk --no-cache add \ raspberrypi \ raspberrypi-libs \ usbutils \ - && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ + bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ --use-deprecated=legacy-resolver ## diff --git a/machine/raspberrypi4-64 b/machine/raspberrypi4-64 index 51f41d68320..35c6eec77de 100644 --- a/machine/raspberrypi4-64 +++ b/machine/raspberrypi4-64 @@ -5,9 +5,8 @@ RUN apk --no-cache add \ raspberrypi \ raspberrypi-libs \ usbutils \ - && sed -i "s|# RPi.GPIO|RPi.GPIO|g" /usr/src/homeassistant/requirements_all.txt \ && pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \ - RPi.GPIO bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ + bluepy pybluez -c /usr/src/homeassistant/requirements_all.txt \ --use-deprecated=legacy-resolver ## diff --git a/mypy.ini b/mypy.ini index 6010475602d..e0c512782fb 100644 --- a/mypy.ini +++ b/mypy.ini @@ -13,6 +13,7 @@ warn_redundant_casts = true warn_unused_configs = true warn_unused_ignores = true enable_error_code = ignore-without-code +strict_concatenate = false check_untyped_defs = true disallow_incomplete_defs = true disallow_subclassing_any = true @@ -59,15 +60,24 @@ disallow_any_generics = true [mypy-homeassistant.helpers.discovery] disallow_any_generics = true +[mypy-homeassistant.helpers.entity] +disallow_any_generics = true + [mypy-homeassistant.helpers.entity_values] disallow_any_generics = true +[mypy-homeassistant.helpers.event] +disallow_any_generics = true + [mypy-homeassistant.helpers.reload] disallow_any_generics = true [mypy-homeassistant.helpers.script_variables] disallow_any_generics = true +[mypy-homeassistant.helpers.sun] +disallow_any_generics = true + [mypy-homeassistant.helpers.translation] disallow_any_generics = true @@ -80,6 +90,12 @@ disallow_any_generics = true [mypy-homeassistant.util.decorator] disallow_any_generics = true +[mypy-homeassistant.util.location] +disallow_any_generics = true + +[mypy-homeassistant.util.logging] +disallow_any_generics = true + [mypy-homeassistant.util.process] disallow_any_generics = true @@ -220,6 +236,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.airzone.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.aladdin_connect.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -341,6 +368,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.baf.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.binary_sensor.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -803,6 +841,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.geocaching.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.gios.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -825,6 +874,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.google.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.greeneye_monitor.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -1100,6 +1160,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.ialarm_xr.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.image_processing.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -1232,6 +1303,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.laundrify.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.lcn.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -1276,6 +1358,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.logbook.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.lookin.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -1518,6 +1611,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.nut.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.oncue.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -1650,6 +1754,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.qnap_qsw.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.rainmachine.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -1683,128 +1798,7 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true -[mypy-homeassistant.components.recorder] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.const] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.backup] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.executor] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.history] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.models] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.pool] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.purge] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.repack] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.statistics] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.util] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_return_any = true -warn_unreachable = true - -[mypy-homeassistant.components.recorder.websocket_api] +[mypy-homeassistant.components.recorder.*] check_untyped_defs = true disallow_incomplete_defs = true disallow_subclassing_any = true @@ -2067,6 +2061,7 @@ disallow_untyped_defs = true no_implicit_optional = true warn_return_any = true warn_unreachable = true +no_implicit_reexport = true [mypy-homeassistant.components.sun.*] check_untyped_defs = true @@ -2309,6 +2304,7 @@ disallow_untyped_defs = true no_implicit_optional = true warn_return_any = true warn_unreachable = true +no_implicit_reexport = true [mypy-homeassistant.components.uptime.*] check_untyped_defs = true @@ -2552,9 +2548,15 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.application_credentials.*] +no_implicit_reexport = true + [mypy-homeassistant.components.diagnostics.*] no_implicit_reexport = true +[mypy-homeassistant.components.spotify.*] +no_implicit_reexport = true + [mypy-tests.*] check_untyped_defs = false disallow_incomplete_defs = false diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index cb90499b6ca..e230c27b4ee 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -31,6 +31,8 @@ _TYPE_HINT_MATCHERS: dict[str, re.Pattern] = { "x_of_y": re.compile(r"^(\w+)\[(.*?]*)\]$"), # x_of_y_comma_z matches items such as "Callable[..., Awaitable[None]]" "x_of_y_comma_z": re.compile(r"^(\w+)\[(.*?]*), (.*?]*)\]$"), + # x_of_y_of_z_comma_a matches items such as "list[dict[str, Any]]" + "x_of_y_of_z_comma_a": re.compile(r"^(\w+)\[(\w+)\[(.*?]*), (.*?]*)\]\]$"), } _MODULE_FILTERS: dict[str, re.Pattern] = { @@ -40,12 +42,28 @@ _MODULE_FILTERS: dict[str, re.Pattern] = { "any_platform": re.compile( f"^homeassistant\\.components\\.\\w+\\.({'|'.join([platform.value for platform in Platform])})$" ), + # application_credentials matches only in the package root (application_credentials.py) + "application_credentials": re.compile( + r"^homeassistant\.components\.\w+\.(application_credentials)$" + ), + # backup matches only in the package root (backup.py) + "backup": re.compile(r"^homeassistant\.components\.\w+\.(backup)$"), + # cast matches only in the package root (cast.py) + "cast": re.compile(r"^homeassistant\.components\.\w+\.(cast)$"), + # config_flow matches only in the package root (config_flow.py) + "config_flow": re.compile(r"^homeassistant\.components\.\w+\.(config_flow)$"), + # device_action matches only in the package root (device_action.py) + "device_action": re.compile(r"^homeassistant\.components\.\w+\.(device_action)$"), + # device_condition matches only in the package root (device_condition.py) + "device_condition": re.compile( + r"^homeassistant\.components\.\w+\.(device_condition)$" + ), # device_tracker matches only in the package root (device_tracker.py) "device_tracker": re.compile(r"^homeassistant\.components\.\w+\.(device_tracker)$"), + # device_trigger matches only in the package root (device_trigger.py) + "device_trigger": re.compile(r"^homeassistant\.components\.\w+\.(device_trigger)$"), # diagnostics matches only in the package root (diagnostics.py) "diagnostics": re.compile(r"^homeassistant\.components\.\w+\.(diagnostics)$"), - # config_flow matches only in the package root (config_flow.py) - "config_flow": re.compile(r"^homeassistant\.components\.\w+\.(config_flow)$") } _METHOD_MATCH: list[TypeHintMatch] = [ @@ -135,6 +153,154 @@ _METHOD_MATCH: list[TypeHintMatch] = [ }, return_type=None, ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["application_credentials"], + function_name="async_get_auth_implementation", + arg_types={ + 0: "HomeAssistant", + 1: "str", + 2: "ClientCredential", + }, + return_type="AbstractOAuth2Implementation", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["application_credentials"], + function_name="async_get_authorization_server", + arg_types={ + 0: "HomeAssistant", + }, + return_type="AuthorizationServer", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["backup"], + function_name="async_pre_backup", + arg_types={ + 0: "HomeAssistant", + }, + return_type=None, + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["backup"], + function_name="async_post_backup", + arg_types={ + 0: "HomeAssistant", + }, + return_type=None, + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["cast"], + function_name="async_get_media_browser_root_object", + arg_types={ + 0: "HomeAssistant", + 1: "str", + }, + return_type="list[BrowseMedia]", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["cast"], + function_name="async_browse_media", + arg_types={ + 0: "HomeAssistant", + 1: "str", + 2: "str", + 3: "str", + }, + return_type=["BrowseMedia", "BrowseMedia | None"], + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["cast"], + function_name="async_play_media", + arg_types={ + 0: "HomeAssistant", + 1: "str", + 2: "Chromecast", + 3: "str", + 4: "str", + }, + return_type="bool", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["config_flow"], + function_name="_async_has_devices", + arg_types={ + 0: "HomeAssistant", + }, + return_type="bool", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_action"], + function_name="async_validate_action_config", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigType", + }, + return_type="ConfigType", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_action"], + function_name="async_call_action_from_config", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigType", + 2: "TemplateVarsType", + 3: "Context | None", + }, + return_type=None, + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_action"], + function_name="async_get_action_capabilities", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigType", + }, + return_type="dict[str, Schema]", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_action"], + function_name="async_get_actions", + arg_types={ + 0: "HomeAssistant", + 1: "str", + }, + return_type=["list[dict[str, str]]", "list[dict[str, Any]]"], + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_condition"], + function_name="async_validate_condition_config", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigType", + }, + return_type="ConfigType", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_condition"], + function_name="async_condition_from_config", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigType", + }, + return_type="ConditionCheckerType", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_condition"], + function_name="async_get_condition_capabilities", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigType", + }, + return_type="dict[str, Schema]", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_condition"], + function_name="async_get_conditions", + arg_types={ + 0: "HomeAssistant", + 1: "str", + }, + return_type=["list[dict[str, str]]", "list[dict[str, Any]]"], + ), TypeHintMatch( module_filter=_MODULE_FILTERS["device_tracker"], function_name="setup_scanner", @@ -175,6 +341,44 @@ _METHOD_MATCH: list[TypeHintMatch] = [ }, return_type=["DeviceScanner", "DeviceScanner | None"], ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_trigger"], + function_name="async_validate_condition_config", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigType", + }, + return_type="ConfigType", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_trigger"], + function_name="async_attach_trigger", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigType", + 2: "AutomationActionType", + 3: "AutomationTriggerInfo", + }, + return_type="CALLBACK_TYPE", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_trigger"], + function_name="async_get_trigger_capabilities", + arg_types={ + 0: "HomeAssistant", + 1: "ConfigType", + }, + return_type="dict[str, Schema]", + ), + TypeHintMatch( + module_filter=_MODULE_FILTERS["device_trigger"], + function_name="async_get_triggers", + arg_types={ + 0: "HomeAssistant", + 1: "str", + }, + return_type=["list[dict[str, str]]", "list[dict[str, Any]]"], + ), TypeHintMatch( module_filter=_MODULE_FILTERS["diagnostics"], function_name="async_get_config_entry_diagnostics", @@ -194,14 +398,6 @@ _METHOD_MATCH: list[TypeHintMatch] = [ }, return_type=UNDEFINED, ), - TypeHintMatch( - module_filter=_MODULE_FILTERS["config_flow"], - function_name="_async_has_devices", - arg_types={ - 0: "HomeAssistant", - }, - return_type="bool", - ), ] @@ -232,6 +428,18 @@ def _is_valid_type(expected_type: list[str] | str | None, node: astroid.NodeNG) and _is_valid_type(match.group(2), node.right) ) + # Special case for xxx[yyy[zzz, aaa]]` + if match := _TYPE_HINT_MATCHERS["x_of_y_of_z_comma_a"].match(expected_type): + return ( + isinstance(node, astroid.Subscript) + and _is_valid_type(match.group(1), node.value) + and isinstance(subnode := node.slice, astroid.Subscript) + and _is_valid_type(match.group(2), subnode.value) + and isinstance(subnode.slice, astroid.Tuple) + and _is_valid_type(match.group(3), subnode.slice.elts[0]) + and _is_valid_type(match.group(4), subnode.slice.elts[1]) + ) + # Special case for xxx[yyy, zzz]` if match := _TYPE_HINT_MATCHERS["x_of_y_comma_z"].match(expected_type): return ( diff --git a/pylint/plugins/hass_imports.py b/pylint/plugins/hass_imports.py index 5b1a76e94a5..c6f6c25c7b6 100644 --- a/pylint/plugins/hass_imports.py +++ b/pylint/plugins/hass_imports.py @@ -62,12 +62,24 @@ _OBSOLETE_IMPORT: dict[str, list[ObsoleteImportMatch]] = { ), ], "homeassistant.components.climate": [ + ObsoleteImportMatch( + reason="replaced by HVACMode enum", + constant=re.compile(r"^HVAC_MODE_(\w*)$"), + ), ObsoleteImportMatch( reason="replaced by ClimateEntityFeature enum", constant=re.compile(r"^SUPPORT_(\w*)$"), ), ], "homeassistant.components.climate.const": [ + ObsoleteImportMatch( + reason="replaced by HVACAction enum", + constant=re.compile(r"^CURRENT_HVAC_(\w*)$"), + ), + ObsoleteImportMatch( + reason="replaced by HVACMode enum", + constant=re.compile(r"^HVAC_MODE_(\w*)$"), + ), ObsoleteImportMatch( reason="replaced by ClimateEntityFeature enum", constant=re.compile(r"^SUPPORT_(\w*)$"), @@ -120,6 +132,10 @@ _OBSOLETE_IMPORT: dict[str, list[ObsoleteImportMatch]] = { reason="replaced by ColorMode enum", constant=re.compile(r"^COLOR_MODE_(\w*)$"), ), + ObsoleteImportMatch( + reason="replaced by LightEntityFeature enum", + constant=re.compile("^SUPPORT_(EFFECT|FLASH|TRANSITION)$"), + ), ], "homeassistant.components.media_player": [ ObsoleteImportMatch( diff --git a/pyproject.toml b/pyproject.toml index 203c3ef1686..e0db5324d02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,46 @@ [build-system] -requires = ["setuptools~=60.5", "wheel~=0.37.1"] +requires = ["setuptools~=62.3", "wheel~=0.37.1"] build-backend = "setuptools.build_meta" +[project] +name = "homeassistant" +license = {text = "Apache-2.0"} +description = "Open-source home automation platform running on Python 3." +readme = "README.rst" +authors = [ + {name = "The Home Assistant Authors", email = "hello@home-assistant.io"} +] +keywords = ["home", "automation"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: End Users/Desktop", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Home Automation", +] +dynamic = ["version", "requires-python", "dependencies"] + +[project.urls] +"Source Code" = "https://github.com/home-assistant/core" +"Bug Reports" = "https://github.com/home-assistant/core/issues" +"Docs: Dev" = "https://developers.home-assistant.io/" +"Discord" = "https://www.home-assistant.io/join-chat/" +"Forum" = "https://community.home-assistant.io/" + +[project.scripts] +hass = "homeassistant.__main__:main" + +[tool.setuptools] +platforms = ["any"] +zip-safe = false +include-package-data = true + +[tool.setuptools.packages.find] +include = ["homeassistant*"] + [tool.black] target-version = ["py39", "py310"] exclude = 'generated' diff --git a/requirements.txt b/requirements.txt index ed129d0d944..fe2bf87ad25 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,16 +6,16 @@ astral==2.2 async_timeout==4.0.2 attrs==21.2.0 atomicwrites==1.4.0 -awesomeversion==22.2.0 +awesomeversion==22.5.2 bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 -httpx==0.22.0 +httpx==0.23.0 ifaddr==0.1.7 -jinja2==3.1.1 -PyJWT==2.3.0 +jinja2==3.1.2 +PyJWT==2.4.0 cryptography==36.0.2 -pip>=21.0,<22.1 +pip>=21.0,<22.2 python-slugify==4.0.1 pyyaml==6.0 requests==2.27.1 diff --git a/requirements_all.txt b/requirements_all.txt index b851ba6b9d2..6341528b09a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.adax -Adax-local==0.1.3 +Adax-local==0.1.4 # homeassistant.components.homekit HAP-python==4.4.0 @@ -44,14 +44,11 @@ PyTransportNSW==0.1.1 PyTurboJPEG==1.6.6 # homeassistant.components.vicare -PyViCare==2.16.1 +PyViCare==2.16.2 # homeassistant.components.xiaomi_aqara PyXiaomiGateway==0.13.4 -# homeassistant.components.rpi_gpio -# RPi.GPIO==0.7.1a4 - # homeassistant.components.remember_the_milk RtmAPI==0.7.2 @@ -110,7 +107,7 @@ aio_geojson_nsw_rfs_incidents==0.4 aio_georss_gdacs==0.7 # homeassistant.components.airzone -aioairzone==0.4.3 +aioairzone==0.4.4 # homeassistant.components.ambient_station aioambient==2021.11.0 @@ -124,6 +121,9 @@ aioasuswrt==1.4.0 # homeassistant.components.azure_devops aioazuredevops==1.3.5 +# homeassistant.components.baf +aiobafi6==0.3.0 + # homeassistant.components.aws aiobotocore==2.1.0 @@ -144,7 +144,7 @@ aioeagle==1.1.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==10.8.2 +aioesphomeapi==10.10.0 # homeassistant.components.flo aioflo==2021.11.0 @@ -171,9 +171,6 @@ aiohttp_cors==0.7.0 # homeassistant.components.hue aiohue==4.4.1 -# homeassistant.components.homewizard -aiohwenergy==0.8.0 - # homeassistant.components.imap aioimaplib==0.9.0 @@ -226,7 +223,7 @@ aiopvpc==3.0.0 aiopyarr==22.2.2 # homeassistant.components.qnap_qsw -aioqsw==0.0.5 +aioqsw==0.0.8 # homeassistant.components.recollect_waste aiorecollect==1.0.8 @@ -313,7 +310,7 @@ anthemav==1.2.0 apcaccess==0.0.13 # homeassistant.components.apprise -apprise==0.9.7 +apprise==0.9.8.3 # homeassistant.components.aprs aprslib==0.7.0 @@ -339,7 +336,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.29.0 +async-upnp-client==0.30.1 # homeassistant.components.supla asyncpysupla==0.0.5 @@ -394,10 +391,10 @@ beautifulsoup4==4.11.1 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.29.0 +bellows==0.30.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.8.12 +bimmer_connected==0.9.3 # homeassistant.components.bizkaibus bizkaibus==0.1.1 @@ -420,7 +417,7 @@ blockchain==1.4.4 # bluepy==1.3.0 # homeassistant.components.bond -bond-api==0.1.16 +bond-async==0.1.20 # homeassistant.components.bosch_shc boschshcpy==0.2.30 @@ -433,7 +430,7 @@ boto3==1.20.24 bravia-tv==1.0.11 # homeassistant.components.broadlink -broadlink==0.18.1 +broadlink==0.18.2 # homeassistant.components.brother brother==1.1.0 @@ -460,7 +457,7 @@ btsmarthub_devicelist==0.2.0 buienradar==1.0.5 # homeassistant.components.caldav -caldav==0.8.2 +caldav==0.9.0 # homeassistant.components.circuit circuit-webhook==1.0.1 @@ -539,10 +536,10 @@ deluge-client==1.7.1 denonavr==0.10.11 # homeassistant.components.devolo_home_control -devolo-home-control-api==0.18.1 +devolo-home-control-api==0.18.2 # homeassistant.components.devolo_home_network -devolo-plc-api==0.7.1 +devolo-plc-api==0.8.0 # homeassistant.components.directv directv==0.4.0 @@ -560,7 +557,7 @@ doorbirdpy==2.1.0 dovado==0.4.1 # homeassistant.components.dsmr -dsmr_parser==0.32 +dsmr_parser==0.33 # homeassistant.components.dwd_weather_warnings dwdwfsapi==1.0.5 @@ -571,6 +568,9 @@ dweepy==0.3.0 # homeassistant.components.dynalite dynalite_devices==0.1.46 +# homeassistant.components.rainforest_eagle +eagle100==0.1.1 + # homeassistant.components.ebusd ebusdpy==0.0.17 @@ -584,7 +584,7 @@ elgato==3.0.0 eliqonline==1.2.2 # homeassistant.components.elkm1 -elkm1-lib==1.3.5 +elkm1-lib==2.0.0 # homeassistant.components.elmax elmax_api==0.0.2 @@ -657,7 +657,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.2 # homeassistant.components.flux_led -flux_led==0.28.28 +flux_led==0.28.30 # homeassistant.components.homekit # homeassistant.components.recorder @@ -689,11 +689,14 @@ gTTS==2.2.4 garages-amsterdam==3.0.0 # homeassistant.components.google -gcal-sync==0.7.1 +gcal-sync==0.9.0 # homeassistant.components.geniushub geniushub-client==0.6.30 +# homeassistant.components.geocaching +geocachingapi==0.2.1 + # homeassistant.components.usgs_earthquakes_feed geojson_client==0.6 @@ -792,7 +795,7 @@ hass-nabucasa==0.54.0 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.4.1 +hatasmota==0.5.1 # homeassistant.components.jewish_calendar hdate==0.10.4 @@ -819,7 +822,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220504.1 +home-assistant-frontend==20220531.0 # homeassistant.components.home_connect homeconnect==0.7.0 @@ -837,7 +840,7 @@ horimote==0.4.1 httplib2==0.20.4 # homeassistant.components.huawei_lte -huawei-lte-api==1.4.18 +huawei-lte-api==1.6.0 # homeassistant.components.huisbaasje huisbaasje-client==0.1.0 @@ -923,6 +926,9 @@ krakenex==2.1.0 # homeassistant.components.eufy lakeside==0.12 +# homeassistant.components.laundrify +laundrify_aio==1.1.2 + # homeassistant.components.foscam libpyfoscam==1.0 @@ -974,6 +980,9 @@ lupupy==0.0.24 # homeassistant.components.lw12wifi lw12==0.9.2 +# homeassistant.components.scrape +lxml==4.8.0 + # homeassistant.components.nmap_tracker mac-vendor-lookup==0.1.11 @@ -1026,10 +1035,10 @@ minio==5.0.10 mitemp_bt==0.0.5 # homeassistant.components.moehlenhoff_alpha2 -moehlenhoff-alpha2==1.1.2 +moehlenhoff-alpha2==1.2.1 # homeassistant.components.motion_blinds -motionblinds==0.6.5 +motionblinds==0.6.8 # homeassistant.components.motioneye motioneye-client==0.3.12 @@ -1071,7 +1080,7 @@ nettigo-air-monitor==1.2.4 neurio==0.3.1 # homeassistant.components.nexia -nexia==0.9.13 +nexia==1.0.1 # homeassistant.components.nextcloud nextcloudmonitor==1.1.0 @@ -1207,9 +1216,6 @@ pexpect==4.6.0 # homeassistant.components.modem_callerid phone_modem==0.1.1 -# homeassistant.components.onewire -pi1wire==0.1.0 - # homeassistant.components.remote_rpi_gpio pigpio==1.78 @@ -1224,13 +1230,13 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==9.1.0 +pillow==9.1.1 # homeassistant.components.dominos pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.10.1 +plexapi==4.11.2 # homeassistant.components.plex plexauth==0.0.6 @@ -1239,7 +1245,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.17.3 +plugwise==0.18.5 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 @@ -1324,7 +1330,7 @@ pyMetEireann==2021.8.0 pyMetno==0.9.0 # homeassistant.components.rfxtrx -pyRFXtrx==0.28.0 +pyRFXtrx==0.29.0 # homeassistant.components.switchmate # pySwitchmate==0.4.6 @@ -1399,7 +1405,7 @@ pycfdns==1.2.2 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==12.1.2 +pychromecast==12.1.3 # homeassistant.components.pocketcasts pycketcasts==1.0.0 @@ -1477,7 +1483,7 @@ pyeverlights==0.1.0 pyevilgenius==2.0.0 # homeassistant.components.ezviz -pyezviz==0.2.0.6 +pyezviz==0.2.0.8 # homeassistant.components.fido pyfido==2.1.1 @@ -1532,7 +1538,7 @@ pyheos==0.7.2 pyhik==0.3.0 # homeassistant.components.hive -pyhiveapi==0.4.2 +pyhiveapi==0.5.4 # homeassistant.components.homematic pyhomematic==0.1.77 @@ -1543,6 +1549,9 @@ pyhomeworks==0.0.6 # homeassistant.components.ialarm pyialarm==1.9.0 +# homeassistant.components.ialarm_xr +pyialarmxr==1.0.18 + # homeassistant.components.icloud pyicloud==1.0.0 @@ -1894,6 +1903,9 @@ python-gc100==1.0.3a0 # homeassistant.components.gitlab_ci python-gitlab==1.6.0 +# homeassistant.components.homewizard +python-homewizard-energy==1.0.3 + # homeassistant.components.hp_ilo python-hpilo==4.3 @@ -1975,13 +1987,13 @@ pytradfri[async]==9.0.0 # homeassistant.components.trafikverket_ferry # homeassistant.components.trafikverket_train # homeassistant.components.trafikverket_weatherstation -pytrafikverket==0.1.6.2 +pytrafikverket==0.2.0.1 # homeassistant.components.usb pyudev==0.22.0 # homeassistant.components.unifiprotect -pyunifiprotect==3.5.1 +pyunifiprotect==3.6.0 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 @@ -2011,7 +2023,7 @@ pyvolumio==0.1.5 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==0.7.0 +pywemo==0.8.1 # homeassistant.components.wilight pywilight==0.0.70 @@ -2019,6 +2031,9 @@ pywilight==0.0.70 # homeassistant.components.wiz pywizlight==0.5.13 +# homeassistant.components.ws66i +pyws66i==1.1 + # homeassistant.components.xeoma pyxeoma==1.4.1 @@ -2050,7 +2065,7 @@ raincloudy==0.0.7 raspyrfm-client==1.2.8 # homeassistant.components.rainmachine -regenmaschine==2022.01.0 +regenmaschine==2022.05.1 # homeassistant.components.renault renault-api==0.1.11 @@ -2135,7 +2150,7 @@ sendgrid==6.8.2 sense_energy==0.10.4 # homeassistant.components.sentry -sentry-sdk==1.5.10 +sentry-sdk==1.5.12 # homeassistant.components.sharkiq sharkiq==0.0.1 @@ -2153,7 +2168,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==2022.05.1 +simplisafe-python==2022.05.2 # homeassistant.components.sisyphus sisyphus-control==3.1.2 @@ -2208,7 +2223,7 @@ spotipy==2.19.0 # homeassistant.components.recorder # homeassistant.components.sql -sqlalchemy==1.4.36 +sqlalchemy==1.4.37 # homeassistant.components.srp_energy srpenergy==1.3.6 @@ -2331,7 +2346,7 @@ ttls==1.4.3 tuya-iot-py-sdk==0.6.6 # homeassistant.components.twentemilieu -twentemilieu==0.6.0 +twentemilieu==0.6.1 # homeassistant.components.twilio twilio==6.32.0 @@ -2339,14 +2354,11 @@ twilio==6.32.0 # homeassistant.components.twitch twitchAPI==2.5.2 -# homeassistant.components.rainforest_eagle -uEagle==0.0.2 - # homeassistant.components.ukraine_alarm uasiren==0.0.1 # homeassistant.components.unifiprotect -unifi-discovery==1.1.2 +unifi-discovery==1.1.3 # homeassistant.components.unifiled unifiled==0.11 @@ -2375,7 +2387,7 @@ vallox-websocket-api==2.11.0 vehicle==0.4.0 # homeassistant.components.velbus -velbus-aio==2022.2.4 +velbus-aio==2022.5.1 # homeassistant.components.venstar venstarcolortouch==0.15 @@ -2396,7 +2408,7 @@ vsure==1.7.3 vtjp==0.1.14 # homeassistant.components.vulcan -vulcan-api==2.0.3 +vulcan-api==2.1.1 # homeassistant.components.vultr vultr==0.1.2 @@ -2412,7 +2424,7 @@ wallbox==0.4.4 waqiasync==1.0.0 # homeassistant.components.folder_watcher -watchdog==2.1.7 +watchdog==2.1.8 # homeassistant.components.waterfurnace waterfurnace==1.1.0 @@ -2448,7 +2460,7 @@ xbox-webapi==2.0.11 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.21.2 +xknx==0.21.3 # homeassistant.components.bluesound # homeassistant.components.fritz @@ -2465,7 +2477,7 @@ xs1-api-client==3.0.0 yalesmartalarmclient==0.3.8 # homeassistant.components.august -yalexs==1.1.23 +yalexs==1.1.25 # homeassistant.components.yeelight yeelight==0.7.10 @@ -2473,6 +2485,9 @@ yeelight==0.7.10 # homeassistant.components.yeelightsunflower yeelightsunflower==0.0.10 +# homeassistant.components.yolink +yolink-api==0.0.5 + # homeassistant.components.youless youless-api==0.16 @@ -2483,10 +2498,10 @@ youtube_dl==2021.12.17 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.38.5 +zeroconf==0.38.6 # homeassistant.components.zha -zha-quirks==0.0.73 +zha-quirks==0.0.75 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 @@ -2513,7 +2528,7 @@ zigpy==0.45.1 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.36.1 +zwave-js-server-python==0.37.1 # homeassistant.components.zwave_me zwave_me_ws==0.2.4 diff --git a/requirements_test.txt b/requirements_test.txt index 31a87356fcc..7e4e29de339 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,12 +8,12 @@ -c homeassistant/package_constraints.txt -r requirements_test_pre_commit.txt codecov==2.1.12 -coverage==6.3.2 +coverage==6.4 freezegun==1.2.1 mock-open==1.4.0 -mypy==0.942 -pre-commit==2.17.0 -pylint==2.13.7 +mypy==0.960 +pre-commit==2.19.0 +pylint==2.13.9 pipdeptree==2.2.1 pylint-strict-informational==0.1 pytest-aiohttp==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 192a5db2d40..b5767c1e468 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.adax -Adax-local==0.1.3 +Adax-local==0.1.4 # homeassistant.components.homekit HAP-python==4.4.0 @@ -40,7 +40,7 @@ PyTransportNSW==0.1.1 PyTurboJPEG==1.6.6 # homeassistant.components.vicare -PyViCare==2.16.1 +PyViCare==2.16.2 # homeassistant.components.xiaomi_aqara PyXiaomiGateway==0.13.4 @@ -94,7 +94,7 @@ aio_geojson_nsw_rfs_incidents==0.4 aio_georss_gdacs==0.7 # homeassistant.components.airzone -aioairzone==0.4.3 +aioairzone==0.4.4 # homeassistant.components.ambient_station aioambient==2021.11.0 @@ -108,6 +108,9 @@ aioasuswrt==1.4.0 # homeassistant.components.azure_devops aioazuredevops==1.3.5 +# homeassistant.components.baf +aiobafi6==0.3.0 + # homeassistant.components.aws aiobotocore==2.1.0 @@ -128,7 +131,7 @@ aioeagle==1.1.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==10.8.2 +aioesphomeapi==10.10.0 # homeassistant.components.flo aioflo==2021.11.0 @@ -152,9 +155,6 @@ aiohttp_cors==0.7.0 # homeassistant.components.hue aiohue==4.4.1 -# homeassistant.components.homewizard -aiohwenergy==0.8.0 - # homeassistant.components.apache_kafka aiokafka==0.6.0 @@ -192,7 +192,7 @@ aiopvpc==3.0.0 aiopyarr==22.2.2 # homeassistant.components.qnap_qsw -aioqsw==0.0.5 +aioqsw==0.0.8 # homeassistant.components.recollect_waste aiorecollect==1.0.8 @@ -248,6 +248,9 @@ airthings_cloud==0.1.0 # homeassistant.components.airtouch4 airtouch4pyapi==1.0.5 +# homeassistant.components.aladdin_connect +aladdin_connect==0.4 + # homeassistant.components.ambee ambee==0.4.0 @@ -261,7 +264,7 @@ ambiclimate==0.2.1 androidtv[async]==0.0.67 # homeassistant.components.apprise -apprise==0.9.7 +apprise==0.9.8.3 # homeassistant.components.aprs aprslib==0.7.0 @@ -275,7 +278,7 @@ arcam-fmj==0.12.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.29.0 +async-upnp-client==0.30.1 # homeassistant.components.sleepiq asyncsleepiq==1.2.3 @@ -303,10 +306,10 @@ base36==0.1.1 beautifulsoup4==4.11.1 # homeassistant.components.zha -bellows==0.29.0 +bellows==0.30.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.8.12 +bimmer_connected==0.9.3 # homeassistant.components.blebox blebox_uniapi==1.3.3 @@ -315,7 +318,7 @@ blebox_uniapi==1.3.3 blinkpy==0.19.0 # homeassistant.components.bond -bond-api==0.1.16 +bond-async==0.1.20 # homeassistant.components.bosch_shc boschshcpy==0.2.30 @@ -324,7 +327,7 @@ boschshcpy==0.2.30 bravia-tv==1.0.11 # homeassistant.components.broadlink -broadlink==0.18.1 +broadlink==0.18.2 # homeassistant.components.brother brother==1.1.0 @@ -339,7 +342,7 @@ bsblan==0.5.0 buienradar==1.0.5 # homeassistant.components.caldav -caldav==0.8.2 +caldav==0.9.0 # homeassistant.components.co2signal co2signal==0.4.2 @@ -394,10 +397,10 @@ deluge-client==1.7.1 denonavr==0.10.11 # homeassistant.components.devolo_home_control -devolo-home-control-api==0.18.1 +devolo-home-control-api==0.18.2 # homeassistant.components.devolo_home_network -devolo-plc-api==0.7.1 +devolo-plc-api==0.8.0 # homeassistant.components.directv directv==0.4.0 @@ -409,16 +412,19 @@ discovery30303==0.2.1 doorbirdpy==2.1.0 # homeassistant.components.dsmr -dsmr_parser==0.32 +dsmr_parser==0.33 # homeassistant.components.dynalite dynalite_devices==0.1.46 +# homeassistant.components.rainforest_eagle +eagle100==0.1.1 + # homeassistant.components.elgato elgato==3.0.0 # homeassistant.components.elkm1 -elkm1-lib==1.3.5 +elkm1-lib==2.0.0 # homeassistant.components.elmax elmax_api==0.0.2 @@ -460,7 +466,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.2 # homeassistant.components.flux_led -flux_led==0.28.28 +flux_led==0.28.30 # homeassistant.components.homekit # homeassistant.components.recorder @@ -486,7 +492,10 @@ gTTS==2.2.4 garages-amsterdam==3.0.0 # homeassistant.components.google -gcal-sync==0.7.1 +gcal-sync==0.9.0 + +# homeassistant.components.geocaching +geocachingapi==0.2.1 # homeassistant.components.usgs_earthquakes_feed geojson_client==0.6 @@ -562,7 +571,7 @@ hangups==0.4.18 hass-nabucasa==0.54.0 # homeassistant.components.tasmota -hatasmota==0.4.1 +hatasmota==0.5.1 # homeassistant.components.jewish_calendar hdate==0.10.4 @@ -580,7 +589,7 @@ hole==0.7.0 holidays==0.13 # homeassistant.components.frontend -home-assistant-frontend==20220504.1 +home-assistant-frontend==20220531.0 # homeassistant.components.home_connect homeconnect==0.7.0 @@ -595,7 +604,7 @@ homepluscontrol==0.0.5 httplib2==0.20.4 # homeassistant.components.huawei_lte -huawei-lte-api==1.4.18 +huawei-lte-api==1.6.0 # homeassistant.components.huisbaasje huisbaasje-client==0.1.0 @@ -645,6 +654,9 @@ kostal_plenticore==0.2.0 # homeassistant.components.kraken krakenex==2.1.0 +# homeassistant.components.laundrify +laundrify_aio==1.1.2 + # homeassistant.components.foscam libpyfoscam==1.0 @@ -663,6 +675,9 @@ lru-dict==1.1.7 # homeassistant.components.luftdaten luftdaten==0.7.2 +# homeassistant.components.scrape +lxml==4.8.0 + # homeassistant.components.nmap_tracker mac-vendor-lookup==0.1.11 @@ -697,10 +712,10 @@ millheater==0.9.0 minio==5.0.10 # homeassistant.components.moehlenhoff_alpha2 -moehlenhoff-alpha2==1.1.2 +moehlenhoff-alpha2==1.2.1 # homeassistant.components.motion_blinds -motionblinds==0.6.5 +motionblinds==0.6.8 # homeassistant.components.motioneye motioneye-client==0.3.12 @@ -730,7 +745,7 @@ netmap==0.7.0.2 nettigo-air-monitor==1.2.4 # homeassistant.components.nexia -nexia==0.9.13 +nexia==1.0.1 # homeassistant.components.discord nextcord==2.0.0a8 @@ -809,9 +824,6 @@ pexpect==4.6.0 # homeassistant.components.modem_callerid phone_modem==0.1.1 -# homeassistant.components.onewire -pi1wire==0.1.0 - # homeassistant.components.pilight pilight==0.1.1 @@ -823,10 +835,10 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==9.1.0 +pillow==9.1.1 # homeassistant.components.plex -plexapi==4.10.1 +plexapi==4.11.2 # homeassistant.components.plex plexauth==0.0.6 @@ -835,7 +847,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.17.3 +plugwise==0.18.5 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 @@ -893,7 +905,7 @@ pyMetEireann==2021.8.0 pyMetno==0.9.0 # homeassistant.components.rfxtrx -pyRFXtrx==0.28.0 +pyRFXtrx==0.29.0 # homeassistant.components.tibber pyTibber==0.22.3 @@ -938,7 +950,7 @@ pybotvac==0.0.23 pycfdns==1.2.2 # homeassistant.components.cast -pychromecast==12.1.2 +pychromecast==12.1.3 # homeassistant.components.climacell pyclimacell==0.18.2 @@ -971,7 +983,7 @@ pyeverlights==0.1.0 pyevilgenius==2.0.0 # homeassistant.components.ezviz -pyezviz==0.2.0.6 +pyezviz==0.2.0.8 # homeassistant.components.fido pyfido==2.1.1 @@ -1017,7 +1029,7 @@ pyhaversion==22.4.1 pyheos==0.7.2 # homeassistant.components.hive -pyhiveapi==0.4.2 +pyhiveapi==0.5.4 # homeassistant.components.homematic pyhomematic==0.1.77 @@ -1025,6 +1037,9 @@ pyhomematic==0.1.77 # homeassistant.components.ialarm pyialarm==1.9.0 +# homeassistant.components.ialarm_xr +pyialarmxr==1.0.18 + # homeassistant.components.icloud pyicloud==1.0.0 @@ -1250,6 +1265,9 @@ python-ecobee-api==0.2.14 # homeassistant.components.darksky python-forecastio==1.4.0 +# homeassistant.components.homewizard +python-homewizard-energy==1.0.3 + # homeassistant.components.izone python-izone==1.2.3 @@ -1298,13 +1316,13 @@ pytradfri[async]==9.0.0 # homeassistant.components.trafikverket_ferry # homeassistant.components.trafikverket_train # homeassistant.components.trafikverket_weatherstation -pytrafikverket==0.1.6.2 +pytrafikverket==0.2.0.1 # homeassistant.components.usb pyudev==0.22.0 # homeassistant.components.unifiprotect -pyunifiprotect==3.5.1 +pyunifiprotect==3.6.0 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 @@ -1325,7 +1343,7 @@ pyvolumio==0.1.5 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==0.7.0 +pywemo==0.8.1 # homeassistant.components.wilight pywilight==0.0.70 @@ -1333,6 +1351,9 @@ pywilight==0.0.70 # homeassistant.components.wiz pywizlight==0.5.13 +# homeassistant.components.ws66i +pyws66i==1.1 + # homeassistant.components.zerproc pyzerproc==0.4.8 @@ -1343,7 +1364,7 @@ rachiopy==1.0.3 radios==0.1.1 # homeassistant.components.rainmachine -regenmaschine==2022.01.0 +regenmaschine==2022.05.1 # homeassistant.components.renault renault-api==0.1.11 @@ -1395,7 +1416,7 @@ securetar==2022.2.0 sense_energy==0.10.4 # homeassistant.components.sentry -sentry-sdk==1.5.10 +sentry-sdk==1.5.12 # homeassistant.components.sharkiq sharkiq==0.0.1 @@ -1404,7 +1425,7 @@ sharkiq==0.0.1 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==2022.05.1 +simplisafe-python==2022.05.2 # homeassistant.components.slack slackclient==2.5.0 @@ -1444,7 +1465,7 @@ spotipy==2.19.0 # homeassistant.components.recorder # homeassistant.components.sql -sqlalchemy==1.4.36 +sqlalchemy==1.4.37 # homeassistant.components.srp_energy srpenergy==1.3.6 @@ -1513,7 +1534,7 @@ ttls==1.4.3 tuya-iot-py-sdk==0.6.6 # homeassistant.components.twentemilieu -twentemilieu==0.6.0 +twentemilieu==0.6.1 # homeassistant.components.twilio twilio==6.32.0 @@ -1521,14 +1542,11 @@ twilio==6.32.0 # homeassistant.components.twitch twitchAPI==2.5.2 -# homeassistant.components.rainforest_eagle -uEagle==0.0.2 - # homeassistant.components.ukraine_alarm uasiren==0.0.1 # homeassistant.components.unifiprotect -unifi-discovery==1.1.2 +unifi-discovery==1.1.3 # homeassistant.components.upb upb_lib==0.4.12 @@ -1551,7 +1569,7 @@ vallox-websocket-api==2.11.0 vehicle==0.4.0 # homeassistant.components.velbus -velbus-aio==2022.2.4 +velbus-aio==2022.5.1 # homeassistant.components.venstar venstarcolortouch==0.15 @@ -1563,7 +1581,7 @@ vilfo-api-client==0.3.2 vsure==1.7.3 # homeassistant.components.vulcan -vulcan-api==2.0.3 +vulcan-api==2.1.1 # homeassistant.components.vultr vultr==0.1.2 @@ -1576,7 +1594,7 @@ wakeonlan==2.0.1 wallbox==0.4.4 # homeassistant.components.folder_watcher -watchdog==2.1.7 +watchdog==2.1.8 # homeassistant.components.whirlpool whirlpool-sixth-sense==0.15.1 @@ -1600,7 +1618,7 @@ wolf_smartset==0.1.11 xbox-webapi==2.0.11 # homeassistant.components.knx -xknx==0.21.2 +xknx==0.21.3 # homeassistant.components.bluesound # homeassistant.components.fritz @@ -1614,19 +1632,22 @@ xmltodict==0.12.0 yalesmartalarmclient==0.3.8 # homeassistant.components.august -yalexs==1.1.23 +yalexs==1.1.25 # homeassistant.components.yeelight yeelight==0.7.10 +# homeassistant.components.yolink +yolink-api==0.0.5 + # homeassistant.components.youless youless-api==0.16 # homeassistant.components.zeroconf -zeroconf==0.38.5 +zeroconf==0.38.6 # homeassistant.components.zha -zha-quirks==0.0.73 +zha-quirks==0.0.75 # homeassistant.components.zha zigpy-deconz==0.16.0 @@ -1644,7 +1665,7 @@ zigpy-znp==0.7.0 zigpy==0.45.1 # homeassistant.components.zwave_js -zwave-js-server-python==0.36.1 +zwave-js-server-python==0.37.1 # homeassistant.components.zwave_me zwave_me_ws==0.2.4 diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index bed60c11c53..047dcbf90ad 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -12,5 +12,5 @@ mccabe==0.6.1 pycodestyle==2.8.0 pydocstyle==6.1.1 pyflakes==2.4.0 -pyupgrade==2.32.0 +pyupgrade==2.32.1 yamllint==1.26.3 diff --git a/rootfs/etc/services.d/home-assistant/finish b/rootfs/etc/services.d/home-assistant/finish old mode 100644 new mode 100755 index 3691583ec81..115d8352618 --- a/rootfs/etc/services.d/home-assistant/finish +++ b/rootfs/etc/services.d/home-assistant/finish @@ -1,24 +1,30 @@ -#!/usr/bin/execlineb -S1 +#!/usr/bin/env bashio # ============================================================================== # Take down the S6 supervision tree when Home Assistant fails # ============================================================================== -define HA_RESTART_EXIT_CODE 100 -define SIGNAL_EXIT_CODE 256 -define SIGTERM 15 +declare RESTART_EXIT_CODE 100 +declare SIGNAL_EXIT_CODE 256 +declare SIGTERM 15 +declare APP_EXIT_CODE=${1} +declare SYS_EXIT_CODE=${2+x} +declare NEW_EXIT_CODE= -foreground { s6-echo "[finish] process exit code ${1}" } +bashio::log.info "Home Assistant Core finish process exit code ${1}" -if { s6-test ${1} -ne ${HA_RESTART_EXIT_CODE} } -ifelse { s6-test ${1} -eq ${SIGNAL_EXIT_CODE} } { - # Process terminated by a signal - define signal ${2} - foreground { s6-echo "[finish] process received signal ${signal}" } - backtick -n new_exit_code { s6-expr 128 + ${signal} } - importas -ui new_exit_code new_exit_code - foreground { redirfd -w 1 /var/run/s6/env-stage3/S6_STAGE2_EXITED s6-echo -n -- ${new_exit_code} } - if { s6-test ${signal} -ne ${SIGTERM} } - s6-svscanctl -t /var/run/s6/services -} +if [[ ${APP_EXIT_CODE} -eq ${RESTART_EXIT_CODE} ]]; then + exit 0 +elif [[ ${APP_EXIT_CODE} -eq ${SIGNAL_EXIT_CODE} ]]; then + bashio::log.info "Home Assistant Core finish process received signal ${APP_EXIT_CODE}" -foreground { redirfd -w 1 /var/run/s6/env-stage3/S6_STAGE2_EXITED s6-echo -n -- ${1} } -s6-svscanctl -t /var/run/s6/services + NEW_EXIT_CODE=$((128 + SYS_EXIT_CODE)) + echo ${NEW_EXIT_CODE} > /run/s6-linux-init-container-results/exitcode + + if [[ ${NEW_EXIT_CODE} -eq ${SIGTERM} ]]; then + /run/s6/basedir/bin/halt + fi +else + bashio::log.info "Home Assistant Core service shutdown" + + echo ${APP_EXIT_CODE} > /run/s6-linux-init-container-results/exitcode + /run/s6/basedir/bin/halt +fi diff --git a/rootfs/etc/services.d/home-assistant/run b/rootfs/etc/services.d/home-assistant/run old mode 100644 new mode 100755 index 4fcbcce417a..40ec07c1543 --- a/rootfs/etc/services.d/home-assistant/run +++ b/rootfs/etc/services.d/home-assistant/run @@ -8,5 +8,6 @@ cd /config || bashio::exit.nok "Can't find config folder!" # Enable mimalloc for Home Assistant Core, unless disabled if [[ -z "${DISABLE_JEMALLOC+x}" ]]; then export LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" + export MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" fi exec python3 -m homeassistant --config /config diff --git a/rootfs/init b/rootfs/init index 7bea7ed88a9..0aa246c33ab 100755 --- a/rootfs/init +++ b/rootfs/init @@ -1,23 +1,49 @@ -#!/bin/execlineb -S0 +#!/bin/sh -e -## -## load default PATH (the same that Docker includes if not provided) if it doesn't exist, -## then go ahead with stage1. -## this was motivated due to this issue: -## - https://github.com/just-containers/s6-overlay/issues/108 -## +# This is the first program launched at container start. +# We don't know where our binaries are and we cannot guarantee +# that the default PATH can access them. +# So this script needs to be entirely self-contained until it has +# at least /command, /usr/bin and /bin in its PATH. -/bin/importas -D /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PATH PATH -export PATH ${PATH} - -## -## Skip further init if the user has a given CMD. -## This is to prevent Home Assistant from starting twice if the user -## decided to override/start via the CMD. -## - -ifelse { s6-test $# -ne 0 } -{ - $@ +addpath () { + x="$1" + IFS=: + set -- $PATH + IFS= + while test "$#" -gt 0 ; do + if test "$1" = "$x" ; then + return + fi + shift + done + PATH="${x}:$PATH" } -/etc/s6/init/init-stage1 $@ \ No newline at end of file + +if test -z "$PATH" ; then + PATH=/bin +fi + +addpath /bin +addpath /usr/bin +addpath /command +export PATH + +# Now we're good: s6-overlay-suexec is accessible via PATH, as are +# all our binaries. + +# Skip further init if the user has a given CMD. +# This is to prevent Home Assistant from starting twice if the user +# decided to override/start via the CMD. +if test $# -ne 0 ; then + exec "$@" +fi + +# Run preinit as root, then run stage0 as the container's user (can be +# root, can be a normal user). + +exec s6-overlay-suexec \ + ' /package/admin/s6-overlay/libexec/preinit' \ + '' \ + /package/admin/s6-overlay/libexec/stage0 \ + "$@" diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 1e93b8bba35..0fd31430aa4 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -32,7 +32,6 @@ COMMENT_REQUIREMENTS = ( "python-gammu", "python-lirc", "pyuserinput", - "RPi.GPIO", "tensorflow", "tf-models-official", ) @@ -67,8 +66,8 @@ httplib2>=0.19.0 # gRPC is an implicit dependency that we want to make explicit so we manage # upgrades intentionally. It is a large package to build from source and we # want to ensure we have wheels built. -grpcio==1.45.0 -grpcio-status==1.45.0 +grpcio==1.46.1 +grpcio-status==1.46.1 # libcst >=0.4.0 requires a newer Rust than we currently have available, # thus our wheels builds fail. This pins it to the last working version, @@ -95,9 +94,9 @@ regex==2021.8.28 # these requirements are quite loose. As the entire stack has some outstanding issues, and # even newer versions seem to introduce new issues, it's useful for us to pin all these # requirements so we can directly link HA versions to these library versions. -anyio==3.5.0 +anyio==3.6.1 h11==0.12.0 -httpcore==0.14.7 +httpcore==0.15.0 # Ensure we have a hyperframe version that works in Python 3.10 # 5.2.0 fixed a collections abc deprecation @@ -123,6 +122,10 @@ authlib<1.0 # Pin backoff for compatibility until most libraries have been updated # https://github.com/home-assistant/core/pull/70817 backoff<2.0 + +# Breaking change in version +# https://github.com/samuelcolvin/pydantic/issues/4092 +pydantic!=1.9.1 """ IGNORE_PRE_COMMIT_HOOK_ID = ( diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py index c6a9799a502..889cad2a497 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -5,6 +5,7 @@ import sys from time import monotonic from . import ( + application_credentials, codeowners, config_flow, coverage, @@ -25,6 +26,7 @@ from . import ( from .model import Config, Integration INTEGRATION_PLUGINS = [ + application_credentials, codeowners, config_flow, dependencies, diff --git a/script/hassfest/application_credentials.py b/script/hassfest/application_credentials.py new file mode 100644 index 00000000000..48d812dba02 --- /dev/null +++ b/script/hassfest/application_credentials.py @@ -0,0 +1,63 @@ +"""Generate application_credentials data.""" +from __future__ import annotations + +import json + +from .model import Config, Integration + +BASE = """ +\"\"\"Automatically generated by hassfest. + +To update, run python3 -m script.hassfest +\"\"\" + +# fmt: off + +APPLICATION_CREDENTIALS = {} +""".strip() + + +def generate_and_validate(integrations: dict[str, Integration], config: Config) -> str: + """Validate and generate application_credentials data.""" + + match_list = [] + + for domain in sorted(integrations): + integration = integrations[domain] + application_credentials_file = integration.path / "application_credentials.py" + if not application_credentials_file.is_file(): + continue + + match_list.append(domain) + + return BASE.format(json.dumps(match_list, indent=4)) + + +def validate(integrations: dict[str, Integration], config: Config) -> None: + """Validate application_credentials data.""" + application_credentials_path = ( + config.root / "homeassistant/generated/application_credentials.py" + ) + config.cache["application_credentials"] = content = generate_and_validate( + integrations, config + ) + + if config.specific_integrations: + return + + if application_credentials_path.read_text(encoding="utf-8").strip() != content: + config.add_error( + "application_credentials", + "File application_credentials.py is not up to date. Run python3 -m script.hassfest", + fixable=True, + ) + + +def generate(integrations: dict[str, Integration], config: Config): + """Generate application_credentials data.""" + application_credentials_path = ( + config.root / "homeassistant/generated/application_credentials.py" + ) + application_credentials_path.write_text( + f"{config.cache['application_credentials']}\n", encoding="utf-8" + ) diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index ca9acedd515..7f2e8e0d477 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -36,6 +36,7 @@ SUPPORTED_IOT_CLASSES = [ NO_IOT_CLASS = [ *{platform.value for platform in Platform}, "api", + "application_credentials", "auth", "automation", "blueprint", @@ -51,6 +52,8 @@ NO_IOT_CLASS = [ "downloader", "ffmpeg", "frontend", + "hardkernel", + "hardware", "history", "homeassistant", "image", @@ -75,6 +78,7 @@ NO_IOT_CLASS = [ "profiler", "proxy", "python_script", + "raspberry_pi", "safe_mode", "script", "search", @@ -152,7 +156,7 @@ MANIFEST_SCHEMA = vol.Schema( { vol.Required("domain"): str, vol.Required("name"): str, - vol.Optional("integration_type"): "helper", + vol.Optional("integration_type"): vol.In(["hardware", "helper"]), vol.Optional("config_flow"): bool, vol.Optional("mqtt"): [str], vol.Optional("zeroconf"): [ diff --git a/script/hassfest/model.py b/script/hassfest/model.py index 2a6ea9ca85f..fc38e1db592 100644 --- a/script/hassfest/model.py +++ b/script/hassfest/model.py @@ -98,9 +98,9 @@ class Integration: return self.manifest.get("quality_scale") @property - def config_flow(self) -> str: + def config_flow(self) -> bool: """Return if the integration has a config flow.""" - return self.manifest.get("config_flow") + return self.manifest.get("config_flow", False) @property def requirements(self) -> list[str]: diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index cf3ef92fbdb..0b705fab983 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -199,7 +199,11 @@ IGNORED_MODULES: Final[list[str]] = [ # Component modules which should set no_implicit_reexport = true. NO_IMPLICIT_REEXPORT_MODULES: set[str] = { "homeassistant.components", + "homeassistant.components.application_credentials.*", "homeassistant.components.diagnostics.*", + "homeassistant.components.spotify.*", + "homeassistant.components.stream.*", + "homeassistant.components.update.*", } HEADER: Final = """ @@ -221,6 +225,8 @@ GENERAL_SETTINGS: Final[dict[str, str]] = { "warn_unused_configs": "true", "warn_unused_ignores": "true", "enable_error_code": "ignore-without-code", + # Strict_concatenate breaks passthrough ParamSpec typing + "strict_concatenate": "false", } # This is basically the list of checks which is enabled for "strict=true". @@ -361,7 +367,9 @@ def generate_and_validate(config: Config) -> str: if strict_module in NO_IMPLICIT_REEXPORT_MODULES: mypy_config.set(strict_section, "no_implicit_reexport", "true") - for reexport_module in NO_IMPLICIT_REEXPORT_MODULES.difference(strict_modules): + for reexport_module in sorted( + NO_IMPLICIT_REEXPORT_MODULES.difference(strict_modules) + ): reexport_section = f"mypy-{reexport_module}" mypy_config.add_section(reexport_section) mypy_config.set(reexport_section, "no_implicit_reexport", "true") diff --git a/script/scaffold/generate.py b/script/scaffold/generate.py index a86c4e3a015..b7e4c58d1a1 100644 --- a/script/scaffold/generate.py +++ b/script/scaffold/generate.py @@ -173,7 +173,7 @@ def _custom_tasks(template, info: Info) -> None: ) elif template == "config_flow_oauth2": - info.update_manifest(config_flow=True, dependencies=["auth"]) + info.update_manifest(config_flow=True, dependencies=["application_credentials"]) info.update_strings( config={ "step": { @@ -188,6 +188,7 @@ def _custom_tasks(template, info: Info) -> None: "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%]", + "user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]", }, "create_entry": { "default": "[%key:common::config_flow::create_entry::authenticated%]" diff --git a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py index b580e609bba..24dcee48ccd 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py @@ -1,60 +1,19 @@ """The NEW_NAME integration.""" from __future__ import annotations -import voluptuous as vol - from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, Platform +from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import ( - aiohttp_client, - config_entry_oauth2_flow, - config_validation as cv, -) -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow -from . import api, config_flow -from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN - -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, -) +from . import api +from .const import DOMAIN # TODO List the platforms that you want to support. # For your initial PR, limit it to 1 platform. PLATFORMS: list[Platform] = [Platform.LIGHT] -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the NEW_NAME component.""" - 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], - OAUTH2_AUTHORIZE, - OAUTH2_TOKEN, - ), - ) - - return True - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up NEW_NAME from a config entry.""" implementation = ( diff --git a/script/scaffold/templates/config_flow_oauth2/integration/application_credentials.py b/script/scaffold/templates/config_flow_oauth2/integration/application_credentials.py new file mode 100644 index 00000000000..51ef70b1885 --- /dev/null +++ b/script/scaffold/templates/config_flow_oauth2/integration/application_credentials.py @@ -0,0 +1,16 @@ +"""application_credentials platform the NEW_NAME integration.""" + +from homeassistant.components.application_credentials import AuthorizationServer +from homeassistant.core import HomeAssistant + +# TODO Update with your own urls +OAUTH2_AUTHORIZE = "https://www.example.com/auth/authorize" +OAUTH2_TOKEN = "https://www.example.com/auth/token" + + +async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer: + """Return authorization server.""" + return AuthorizationServer( + authorize_url=OAUTH2_AUTHORIZE, + token_url=OAUTH2_TOKEN, + ) diff --git a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py index 3ed8d9d293f..bc087119c6e 100644 --- a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py @@ -1,35 +1,46 @@ """Test the NEW_NAME config flow.""" + from unittest.mock import patch -from homeassistant import config_entries, setup +import pytest + +from homeassistant import config_entries from homeassistant.components.NEW_DOMAIN.const import ( DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN, ) +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow +from homeassistant.setup import async_setup_component CLIENT_ID = "1234" CLIENT_SECRET = "5678" +@pytest.fixture +async def setup_credentials(hass: HomeAssistant) -> None: + """Fixture to setup credentials.""" + assert await async_setup_component(hass, "application_credentials", {}) + await async_import_client_credential( + hass, + DOMAIN, + ClientCredential(CLIENT_ID, CLIENT_SECRET), + ) + + async def test_full_flow( hass: HomeAssistant, hass_client_no_auth, aioclient_mock, current_request_with_host, + setup_credentials, ) -> None: """Check full flow.""" - assert await setup.async_setup_component( - hass, - "NEW_DOMAIN", - { - "NEW_DOMAIN": {"client_id": CLIENT_ID, "client_secret": CLIENT_SECRET}, - "http": {"base_url": "https://example.com"}, - }, - ) - result = await hass.config_entries.flow.async_init( "NEW_DOMAIN", context={"source": config_entries.SOURCE_USER} ) diff --git a/script/scaffold/templates/device_action/integration/device_action.py b/script/scaffold/templates/device_action/integration/device_action.py index 5eb5249211b..a9d77853e55 100644 --- a/script/scaffold/templates/device_action/integration/device_action.py +++ b/script/scaffold/templates/device_action/integration/device_action.py @@ -33,7 +33,7 @@ async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for NEW_NAME devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) actions = [] # TODO Read this comment and remove it. diff --git a/script/scaffold/templates/device_condition/integration/device_condition.py b/script/scaffold/templates/device_condition/integration/device_condition.py index 6f129289af8..cc5ad765885 100644 --- a/script/scaffold/templates/device_condition/integration/device_condition.py +++ b/script/scaffold/templates/device_condition/integration/device_condition.py @@ -35,7 +35,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for NEW_NAME devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) conditions = [] # Get all the integrations entities for this device diff --git a/script/scaffold/templates/device_trigger/integration/device_trigger.py b/script/scaffold/templates/device_trigger/integration/device_trigger.py index 9082d27953a..a03e27394e2 100644 --- a/script/scaffold/templates/device_trigger/integration/device_trigger.py +++ b/script/scaffold/templates/device_trigger/integration/device_trigger.py @@ -41,7 +41,7 @@ async def async_get_triggers( hass: HomeAssistant, device_id: str ) -> list[dict[str, Any]]: """List device triggers for NEW_NAME devices.""" - registry = await entity_registry.async_get_registry(hass) + registry = entity_registry.async_get(hass) triggers = [] # TODO Read this comment and remove it. diff --git a/script/translations/clean.py b/script/translations/clean.py index 63281b36229..0dcf40941ef 100644 --- a/script/translations/clean.py +++ b/script/translations/clean.py @@ -65,7 +65,7 @@ def find_frontend(): raise ExitApp(f"Unable to find frontend at {FRONTEND_DIR}") source = FRONTEND_DIR / "src/translations/en.json" - translated = FRONTEND_DIR / "translations/en.json" + translated = FRONTEND_DIR / "translations/frontend/en.json" missing_keys = [] find_extra( @@ -91,6 +91,8 @@ def run(): print("No missing translations!") return 0 + print(f"Found {len(missing_keys)} extra keys") + # We can't query too many keys at once, so limit the number to 50. for i in range(0, len(missing_keys), 50): chunk = missing_keys[i : i + 50] @@ -100,11 +102,13 @@ def run(): print( f"Lookin up key in Lokalise returns {len(key_data)} results, expected {len(chunk)}" ) - return 1 - print(f"Deleting {len(chunk)} keys:") - for key in chunk: - print(" -", key) + if not key_data: + continue + + print(f"Deleting {len(key_data)} keys:") + for key in key_data: + print(" -", key["key_name"]["web"]) print() while input("Type YES to delete these keys: ") != "YES": pass diff --git a/script/translations/download.py b/script/translations/download.py index eab9c40370e..6d4ce91263a 100755 --- a/script/translations/download.py +++ b/script/translations/download.py @@ -70,7 +70,7 @@ def get_component_path(lang, component): return os.path.join( "homeassistant", "components", component, "translations", f"{lang}.json" ) - raise ExitApp(f"Integration {component} not found under homeassistant/components/") + return None def get_platform_path(lang, component, platform): @@ -98,7 +98,11 @@ def save_language_translations(lang, translations): for component, component_translations in components.items(): base_translations = get_component_translations(component_translations) if base_translations: - path = get_component_path(lang, component) + if (path := get_component_path(lang, component)) is None: + print( + f"Skipping {lang} for {component}, as the integration doesn't seem to exist." + ) + continue os.makedirs(os.path.dirname(path), exist_ok=True) save_json(path, base_translations) diff --git a/setup.cfg b/setup.cfg index ab8af3d941b..7f1e739803b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,34 +1,8 @@ [metadata] -name = homeassistant -version = 2022.5.5 -author = The Home Assistant Authors -author_email = hello@home-assistant.io -license = Apache-2.0 -platforms = any -description = Open-source home automation platform running on Python 3. -long_description = file: README.rst -long_description_content_type = text/x-rst -keywords = home, automation +version = 2022.6.0 url = https://www.home-assistant.io/ -project_urls = - Source Code = https://github.com/home-assistant/core - Bug Reports = https://github.com/home-assistant/core/issues - Docs: Dev = https://developers.home-assistant.io/ - Discord = https://discordapp.com/invite/c5DvZ4e - Forum = https://community.home-assistant.io/ -classifier = - Development Status :: 4 - Beta - Intended Audience :: End Users/Desktop - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python :: 3.9 - Topic :: Home Automation [options] -packages = find: -zip_safe = False -include_package_data = True python_requires = >=3.9.0 install_requires = aiohttp==3.8.1 @@ -36,19 +10,19 @@ install_requires = async_timeout==4.0.2 attrs==21.2.0 atomicwrites==1.4.0 - awesomeversion==22.2.0 + awesomeversion==22.5.2 bcrypt==3.1.7 certifi>=2021.5.30 ciso8601==2.2.0 # When bumping httpx, please check the version pins of # httpcore, anyio, and h11 in gen_requirements_all - httpx==0.22.0 + httpx==0.23.0 ifaddr==0.1.7 - jinja2==3.1.1 - PyJWT==2.3.0 + jinja2==3.1.2 + PyJWT==2.4.0 # PyJWT has loose dependency. We want the latest one. cryptography==36.0.2 - pip>=21.0,<22.1 + pip>=21.0,<22.2 python-slugify==4.0.1 pyyaml==6.0 requests==2.27.1 @@ -57,14 +31,6 @@ install_requires = voluptuous-serialize==2.5.0 yarl==1.7.2 -[options.packages.find] -include = - homeassistant* - -[options.entry_points] -console_scripts = - hass = homeassistant.__main__:main - [flake8] exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build max-complexity = 25 diff --git a/tests/auth/test_auth_store.py b/tests/auth/test_auth_store.py index 0c650adba3c..e52a0dc88f5 100644 --- a/tests/auth/test_auth_store.py +++ b/tests/auth/test_auth_store.py @@ -236,9 +236,9 @@ async def test_loading_race_condition(hass): """Test only one storage load called when concurrent loading occurred .""" store = auth_store.AuthStore(hass) with patch( - "homeassistant.helpers.entity_registry.async_get_registry" + "homeassistant.helpers.entity_registry.async_get" ) as mock_ent_registry, patch( - "homeassistant.helpers.device_registry.async_get_registry" + "homeassistant.helpers.device_registry.async_get" ) as mock_dev_registry, patch( "homeassistant.helpers.storage.Store.async_load", return_value=None ) as mock_load: diff --git a/tests/common.py b/tests/common.py index 73625bcfe07..bd0b828737b 100644 --- a/tests/common.py +++ b/tests/common.py @@ -905,10 +905,9 @@ def init_recorder_component(hass, add_config=None): if recorder.CONF_COMMIT_INTERVAL not in config: config[recorder.CONF_COMMIT_INTERVAL] = 0 - with patch( - "homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", - True, - ), patch("homeassistant.components.recorder.migration.migrate_schema"): + with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( + "homeassistant.components.recorder.migration.migrate_schema" + ): assert setup_component(hass, recorder.DOMAIN, {recorder.DOMAIN: config}) assert recorder.DOMAIN in hass.config.components _LOGGER.info( diff --git a/tests/components/aemet/test_weather.py b/tests/components/aemet/test_weather.py index d1f1889c807..809b61e0bda 100644 --- a/tests/components/aemet/test_weather.py +++ b/tests/components/aemet/test_weather.py @@ -42,10 +42,10 @@ async def test_aemet_weather(hass): assert state.state == ATTR_CONDITION_SNOWY assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 99.0 - assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1004.4 + assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 100440.0 assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == -0.7 assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 90.0 - assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 15 + assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 4.17 forecast = state.attributes.get(ATTR_FORECAST)[0] assert forecast.get(ATTR_FORECAST_CONDITION) == ATTR_CONDITION_PARTLYCLOUDY assert forecast.get(ATTR_FORECAST_PRECIPITATION) is None @@ -57,7 +57,7 @@ async def test_aemet_weather(hass): == dt_util.parse_datetime("2021-01-10 00:00:00+00:00").isoformat() ) assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 45.0 - assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 20 + assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 5.56 state = hass.states.get("weather.aemet_hourly") assert state is None diff --git a/tests/components/airzone/test_binary_sensor.py b/tests/components/airzone/test_binary_sensor.py index d6a3fdcb115..138801d1dc0 100644 --- a/tests/components/airzone/test_binary_sensor.py +++ b/tests/components/airzone/test_binary_sensor.py @@ -13,6 +13,11 @@ async def test_airzone_create_binary_sensors(hass: HomeAssistant) -> None: await async_init_integration(hass) + # Systems + state = hass.states.get("binary_sensor.system_1_problem") + assert state.state == STATE_OFF + + # Zones state = hass.states.get("binary_sensor.despacho_air_demand") assert state.state == STATE_OFF diff --git a/tests/components/airzone/test_diagnostics.py b/tests/components/airzone/test_diagnostics.py index 7425677876f..4f7e2f61a48 100644 --- a/tests/components/airzone/test_diagnostics.py +++ b/tests/components/airzone/test_diagnostics.py @@ -1,20 +1,30 @@ """The diagnostics tests for the Airzone platform.""" +from unittest.mock import patch + from aioairzone.const import ( + API_DATA, + API_MAC, + API_SYSTEM_ID, + API_SYSTEMS, + API_WIFI_RSSI, AZD_ID, AZD_MASTER, AZD_SYSTEM, AZD_SYSTEMS, AZD_ZONES, AZD_ZONES_NUM, + RAW_HVAC, + RAW_WEBSERVER, ) from aiohttp import ClientSession from homeassistant.components.airzone.const import DOMAIN +from homeassistant.components.diagnostics.const import REDACTED from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant -from .util import CONFIG, async_init_integration +from .util import CONFIG, HVAC_MOCK, HVAC_WEBSERVER_MOCK, async_init_integration from tests.components.diagnostics import get_diagnostics_for_config_entry @@ -27,31 +37,55 @@ async def test_config_entry_diagnostics( assert hass.data[DOMAIN] config_entry = hass.config_entries.async_entries(DOMAIN)[0] + with patch( + "homeassistant.components.airzone.AirzoneLocalApi.raw_data", + return_value={ + RAW_HVAC: HVAC_MOCK, + RAW_WEBSERVER: HVAC_WEBSERVER_MOCK, + }, + ): + diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) + assert ( + diag["api_data"][RAW_HVAC][API_SYSTEMS][0][API_DATA][0].items() + >= { + API_SYSTEM_ID: HVAC_MOCK[API_SYSTEMS][0][API_DATA][0][API_SYSTEM_ID], + }.items() + ) - assert diag["info"][CONF_HOST] == CONFIG[CONF_HOST] - assert diag["info"][CONF_PORT] == CONFIG[CONF_PORT] + assert ( + diag["api_data"][RAW_WEBSERVER].items() + >= { + API_MAC: REDACTED, + API_WIFI_RSSI: HVAC_WEBSERVER_MOCK[API_WIFI_RSSI], + }.items() + ) - assert diag["data"][AZD_SYSTEMS]["1"][AZD_ID] == 1 - assert diag["data"][AZD_SYSTEMS]["1"][AZD_ZONES_NUM] == 5 + assert ( + diag["config_entry"].items() + >= { + "data": { + CONF_HOST: CONFIG[CONF_HOST], + CONF_PORT: CONFIG[CONF_PORT], + }, + "domain": DOMAIN, + "unique_id": REDACTED, + }.items() + ) - assert diag["data"][AZD_ZONES]["1:1"][AZD_ID] == 1 - assert diag["data"][AZD_ZONES]["1:1"][AZD_MASTER] == 1 - assert diag["data"][AZD_ZONES]["1:1"][AZD_SYSTEM] == 1 + assert ( + diag["coord_data"][AZD_SYSTEMS]["1"].items() + >= { + AZD_ID: 1, + AZD_ZONES_NUM: 5, + }.items() + ) - assert diag["data"][AZD_ZONES]["1:2"][AZD_ID] == 2 - assert diag["data"][AZD_ZONES]["1:2"][AZD_MASTER] == 0 - assert diag["data"][AZD_ZONES]["1:2"][AZD_SYSTEM] == 1 - - assert diag["data"][AZD_ZONES]["1:3"][AZD_ID] == 3 - assert diag["data"][AZD_ZONES]["1:3"][AZD_MASTER] == 0 - assert diag["data"][AZD_ZONES]["1:3"][AZD_SYSTEM] == 1 - - assert diag["data"][AZD_ZONES]["1:4"][AZD_ID] == 4 - assert diag["data"][AZD_ZONES]["1:4"][AZD_MASTER] == 0 - assert diag["data"][AZD_ZONES]["1:4"][AZD_SYSTEM] == 1 - - assert diag["data"][AZD_ZONES]["1:5"][AZD_ID] == 5 - assert diag["data"][AZD_ZONES]["1:5"][AZD_MASTER] == 0 - assert diag["data"][AZD_ZONES]["1:5"][AZD_SYSTEM] == 1 + assert ( + diag["coord_data"][AZD_ZONES]["1:1"].items() + >= { + AZD_ID: 1, + AZD_MASTER: True, + AZD_SYSTEM: 1, + }.items() + ) diff --git a/tests/components/airzone/test_sensor.py b/tests/components/airzone/test_sensor.py index c68be2abbab..bd57129cae0 100644 --- a/tests/components/airzone/test_sensor.py +++ b/tests/components/airzone/test_sensor.py @@ -1,15 +1,24 @@ """The sensor tests for the Airzone platform.""" +from unittest.mock import AsyncMock + from homeassistant.core import HomeAssistant from .util import async_init_integration -async def test_airzone_create_sensors(hass: HomeAssistant) -> None: +async def test_airzone_create_sensors( + hass: HomeAssistant, entity_registry_enabled_by_default: AsyncMock +) -> None: """Test creation of sensors.""" await async_init_integration(hass) + # WebServer + state = hass.states.get("sensor.webserver_rssi") + assert state.state == "-42" + + # Zones state = hass.states.get("sensor.despacho_temperature") assert state.state == "21.2" diff --git a/tests/components/airzone/util.py b/tests/components/airzone/util.py index 52f15dd1476..6b81c493eb6 100644 --- a/tests/components/airzone/util.py +++ b/tests/components/airzone/util.py @@ -19,9 +19,12 @@ from aioairzone.const import ( API_MODES, API_NAME, API_ON, + API_POWER, API_ROOM_TEMP, API_SET_POINT, + API_SYSTEM_FIRMWARE, API_SYSTEM_ID, + API_SYSTEM_TYPE, API_SYSTEMS, API_THERMOS_FIRMWARE, API_THERMOS_RADIO, @@ -31,7 +34,6 @@ from aioairzone.const import ( API_WIFI_RSSI, API_ZONE_ID, ) -from aioairzone.exceptions import InvalidMethod, SystemOutOfRange from homeassistant.components.airzone import DOMAIN from homeassistant.const import CONF_HOST, CONF_ID, CONF_PORT @@ -178,6 +180,17 @@ HVAC_MOCK = { ] } +HVAC_SYSTEMS_MOCK = { + API_SYSTEMS: [ + { + API_SYSTEM_ID: 1, + API_POWER: 0, + API_SYSTEM_FIRMWARE: "3.31", + API_SYSTEM_TYPE: 1, + } + ] +} + HVAC_WEBSERVER_MOCK = { API_MAC: "11:22:33:44:55:66", API_WIFI_CHANNEL: 6, @@ -202,10 +215,10 @@ async def async_init_integration( return_value=HVAC_MOCK, ), patch( "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - side_effect=SystemOutOfRange, + return_value=HVAC_SYSTEMS_MOCK, ), patch( "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - side_effect=InvalidMethod, + return_value=HVAC_WEBSERVER_MOCK, ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/aladdin_connect/__init__.py b/tests/components/aladdin_connect/__init__.py new file mode 100644 index 00000000000..6e108ed88df --- /dev/null +++ b/tests/components/aladdin_connect/__init__.py @@ -0,0 +1 @@ +"""The tests for Aladdin Connect platforms.""" diff --git a/tests/components/aladdin_connect/test_config_flow.py b/tests/components/aladdin_connect/test_config_flow.py new file mode 100644 index 00000000000..899aa0a7e55 --- /dev/null +++ b/tests/components/aladdin_connect/test_config_flow.py @@ -0,0 +1,241 @@ +"""Test the Aladdin Connect config flow.""" +from unittest.mock import patch + +from homeassistant import config_entries +from homeassistant.components.aladdin_connect.const import DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) + +from tests.common import MockConfigEntry + + +async def test_form(hass: HomeAssistant) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + with patch( + "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient.login", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "Aladdin Connect" + assert result2["data"] == { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_failed_auth(hass: HomeAssistant) -> None: + """Test we handle failed authentication error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient.login", + return_value=False, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "invalid_auth"} + + with patch( + "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient.login", + return_value=False, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_already_configured(hass): + """Test we handle already configured error.""" + mock_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"}, + unique_id="test-username", + ) + mock_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == config_entries.SOURCE_USER + + with patch( + "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient.login", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "abort" + assert result2["reason"] == "already_configured" + + +async def test_import_flow_success(hass: HomeAssistant) -> None: + """Test a successful import of yaml.""" + + with patch( + "homeassistant.components.aladdin_connect.cover.async_setup_platform", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient.login", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.cover.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_USERNAME: "test-user", + CONF_PASSWORD: "test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "Aladdin Connect" + assert result2["data"] == { + CONF_USERNAME: "test-user", + CONF_PASSWORD: "test-password", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_reauth_flow(hass: HomeAssistant) -> None: + """Test a successful reauth flow.""" + + mock_entry = MockConfigEntry( + domain=DOMAIN, + data={"username": "test-username", "password": "test-password"}, + unique_id="test-username", + ) + mock_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "unique_id": mock_entry.unique_id, + "entry_id": mock_entry.entry_id, + }, + data={"username": "test-username", "password": "new-password"}, + ) + + assert result["step_id"] == "reauth_confirm" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.aladdin_connect.cover.async_setup_platform", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient.login", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.cover.async_setup_entry", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_PASSWORD: "new-password"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_ABORT + assert result2["reason"] == "reauth_successful" + assert mock_entry.data == { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "new-password", + } + + +async def test_reauth_flow_auth_error(hass: HomeAssistant) -> None: + """Test an authorization error reauth flow.""" + + mock_entry = MockConfigEntry( + domain=DOMAIN, + data={"username": "test-username", "password": "test-password"}, + unique_id="test-username", + ) + mock_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "unique_id": mock_entry.unique_id, + "entry_id": mock_entry.entry_id, + }, + data={"username": "test-username", "password": "new-password"}, + ) + + assert result["step_id"] == "reauth_confirm" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.aladdin_connect.cover.async_setup_platform", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient.login", + return_value=False, + ), patch( + "homeassistant.components.aladdin_connect.cover.async_setup_entry", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_PASSWORD: "new-password"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "invalid_auth"} diff --git a/tests/components/aladdin_connect/test_cover.py b/tests/components/aladdin_connect/test_cover.py new file mode 100644 index 00000000000..c1571ed9fa2 --- /dev/null +++ b/tests/components/aladdin_connect/test_cover.py @@ -0,0 +1,250 @@ +"""Test the Aladdin Connect Cover.""" +from unittest.mock import patch + +import pytest + +from homeassistant.components.aladdin_connect.const import DOMAIN +from homeassistant.components.cover import DOMAIN as COVER_DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ( + CONF_PASSWORD, + CONF_USERNAME, + STATE_CLOSED, + STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, +) +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry + +YAML_CONFIG = {"username": "test-user", "password": "test-password"} + +DEVICE_CONFIG_OPEN = { + "device_id": 533255, + "door_number": 1, + "name": "home", + "status": "open", + "link_status": "Connected", +} + +DEVICE_CONFIG_OPENING = { + "device_id": 533255, + "door_number": 1, + "name": "home", + "status": "opening", + "link_status": "Connected", +} + +DEVICE_CONFIG_CLOSED = { + "device_id": 533255, + "door_number": 1, + "name": "home", + "status": "closed", + "link_status": "Connected", +} + +DEVICE_CONFIG_CLOSING = { + "device_id": 533255, + "door_number": 1, + "name": "home", + "status": "closing", + "link_status": "Connected", +} + +DEVICE_CONFIG_DISCONNECTED = { + "device_id": 533255, + "door_number": 1, + "name": "home", + "status": "open", + "link_status": "Disconnected", +} + +DEVICE_CONFIG_BAD = { + "device_id": 533255, + "door_number": 1, + "name": "home", + "status": "open", +} +DEVICE_CONFIG_BAD_NO_DOOR = { + "device_id": 533255, + "door_number": 2, + "name": "home", + "status": "open", + "link_status": "Disconnected", +} + + +async def test_setup_get_doors_errors(hass: HomeAssistant) -> None: + """Test component setup Get Doors Errors.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=YAML_CONFIG, + unique_id="test-id", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", + return_value=None, + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) is True + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + + +async def test_setup_login_error(hass: HomeAssistant) -> None: + """Test component setup Login Errors.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=YAML_CONFIG, + unique_id="test-id", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login", + return_value=False, + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) is False + + +async def test_setup_component_noerror(hass: HomeAssistant) -> None: + """Test component setup No Error.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=YAML_CONFIG, + unique_id="test-id", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login", + return_value=True, + ): + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ConfigEntryState.LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +async def test_cover_operation(hass: HomeAssistant) -> None: + """Test component setup open cover, close cover.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=YAML_CONFIG, + unique_id="test-id", + ) + config_entry.add_to_hass(hass) + + assert await async_setup_component(hass, "homeassistant", {}) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", + return_value=[DEVICE_CONFIG_OPEN], + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ConfigEntryState.LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert COVER_DOMAIN in hass.config.components + + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.open_door", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", + return_value=[DEVICE_CONFIG_OPEN], + ): + await hass.services.async_call( + "cover", "open_cover", {"entity_id": "cover.home"}, blocking=True + ) + assert hass.states.get("cover.home").state == STATE_OPEN + + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.close_door", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", + return_value=[DEVICE_CONFIG_CLOSED], + ): + await hass.services.async_call( + "cover", "close_cover", {"entity_id": "cover.home"}, blocking=True + ) + assert hass.states.get("cover.home").state == STATE_CLOSED + + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", + return_value=[DEVICE_CONFIG_OPENING], + ): + await hass.services.async_call( + "homeassistant", "update_entity", {"entity_id": "cover.home"}, blocking=True + ) + assert hass.states.get("cover.home").state == STATE_OPENING + + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", + return_value=[DEVICE_CONFIG_CLOSING], + ): + await hass.services.async_call( + "homeassistant", "update_entity", {"entity_id": "cover.home"}, blocking=True + ) + assert hass.states.get("cover.home").state == STATE_CLOSING + + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", + return_value=[DEVICE_CONFIG_BAD], + ): + await hass.services.async_call( + "homeassistant", "update_entity", {"entity_id": "cover.home"}, blocking=True + ) + assert hass.states.get("cover.home").state + + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", + return_value=[DEVICE_CONFIG_BAD_NO_DOOR], + ): + await hass.services.async_call( + "homeassistant", "update_entity", {"entity_id": "cover.home"}, blocking=True + ) + assert hass.states.get("cover.home").state + + +async def test_yaml_import(hass: HomeAssistant, caplog: pytest.LogCaptureFixture): + """Test setup YAML import.""" + assert COVER_DOMAIN not in hass.config.components + + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login", + return_value=True, + ), patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", + return_value=[DEVICE_CONFIG_CLOSED], + ): + await async_setup_component( + hass, + COVER_DOMAIN, + { + COVER_DOMAIN: { + "platform": DOMAIN, + "username": "test-user", + "password": "test-password", + } + }, + ) + await hass.async_block_till_done() + assert hass.config_entries.async_entries(DOMAIN) + assert "Configuring Aladdin Connect through yaml is deprecated" in caplog.text + + assert hass.config_entries.async_entries(DOMAIN) + config_data = hass.config_entries.async_entries(DOMAIN)[0].data + assert config_data[CONF_USERNAME] == "test-user" + assert config_data[CONF_PASSWORD] == "test-password" diff --git a/tests/components/aladdin_connect/test_init.py b/tests/components/aladdin_connect/test_init.py new file mode 100644 index 00000000000..0ba9b317dfb --- /dev/null +++ b/tests/components/aladdin_connect/test_init.py @@ -0,0 +1,52 @@ +"""Test for Aladdin Connect init logic.""" +from unittest.mock import patch + +from homeassistant.components.aladdin_connect.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + +YAML_CONFIG = {"username": "test-user", "password": "test-password"} + + +async def test_entry_password_fail(hass: HomeAssistant): + """Test password fail during entry.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={"username": "test-user", "password": "test-password"}, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login", + return_value=False, + ): + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert entry.state is ConfigEntryState.SETUP_ERROR + + +async def test_load_and_unload(hass: HomeAssistant) -> None: + """Test loading and unloading Aladdin Connect entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=YAML_CONFIG, + unique_id="test-id", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login", + return_value=True, + ): + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ConfigEntryState.LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + assert await config_entry.async_unload(hass) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.NOT_LOADED diff --git a/tests/components/aladdin_connect/test_model.py b/tests/components/aladdin_connect/test_model.py new file mode 100644 index 00000000000..e802ae53b74 --- /dev/null +++ b/tests/components/aladdin_connect/test_model.py @@ -0,0 +1,18 @@ +"""Test the Aladdin Connect model class.""" +from homeassistant.components.aladdin_connect.model import DoorDevice +from homeassistant.core import HomeAssistant + + +async def test_model(hass: HomeAssistant) -> None: + """Test model for Aladdin Connect Model.""" + test_values = { + "device_id": "1", + "door_number": "2", + "name": "my door", + "status": "good", + } + result2 = DoorDevice(test_values) + assert result2["device_id"] == "1" + assert result2["door_number"] == "2" + assert result2["name"] == "my door" + assert result2["status"] == "good" diff --git a/tests/components/alexa/test_init.py b/tests/components/alexa/test_init.py index eac4b32e5ba..9dc47da6256 100644 --- a/tests/components/alexa/test_init.py +++ b/tests/components/alexa/test_init.py @@ -1,9 +1,8 @@ """Tests for alexa.""" -from homeassistant.components import logbook from homeassistant.components.alexa.const import EVENT_ALEXA_SMART_HOME from homeassistant.setup import async_setup_component -from tests.components.logbook.test_init import MockLazyEventPartialState +from tests.components.logbook.common import MockRow, mock_humanify async def test_humanify_alexa_event(hass): @@ -12,40 +11,35 @@ async def test_humanify_alexa_event(hass): await async_setup_component(hass, "alexa", {}) await async_setup_component(hass, "logbook", {}) hass.states.async_set("light.kitchen", "on", {"friendly_name": "Kitchen Light"}) - entity_attr_cache = logbook.EntityAttributeCache(hass) - results = list( - logbook.humanify( - hass, - [ - MockLazyEventPartialState( - EVENT_ALEXA_SMART_HOME, - {"request": {"namespace": "Alexa.Discovery", "name": "Discover"}}, - ), - MockLazyEventPartialState( - EVENT_ALEXA_SMART_HOME, - { - "request": { - "namespace": "Alexa.PowerController", - "name": "TurnOn", - "entity_id": "light.kitchen", - } - }, - ), - MockLazyEventPartialState( - EVENT_ALEXA_SMART_HOME, - { - "request": { - "namespace": "Alexa.PowerController", - "name": "TurnOn", - "entity_id": "light.non_existing", - } - }, - ), - ], - entity_attr_cache, - {}, - ) + results = mock_humanify( + hass, + [ + MockRow( + EVENT_ALEXA_SMART_HOME, + {"request": {"namespace": "Alexa.Discovery", "name": "Discover"}}, + ), + MockRow( + EVENT_ALEXA_SMART_HOME, + { + "request": { + "namespace": "Alexa.PowerController", + "name": "TurnOn", + "entity_id": "light.kitchen", + } + }, + ), + MockRow( + EVENT_ALEXA_SMART_HOME, + { + "request": { + "namespace": "Alexa.PowerController", + "name": "TurnOn", + "entity_id": "light.non_existing", + } + }, + ), + ], ) event1, event2, event3 = results diff --git a/tests/components/androidtv/patchers.py b/tests/components/androidtv/patchers.py index 7cc14bbd7b5..31e9a9c82c3 100644 --- a/tests/components/androidtv/patchers.py +++ b/tests/components/androidtv/patchers.py @@ -1,8 +1,15 @@ """Define patches used for androidtv tests.""" -from unittest.mock import mock_open, patch +from unittest.mock import patch from androidtv.constants import CMD_DEVICE_PROPERTIES, CMD_MAC_ETH0, CMD_MAC_WLAN0 +from homeassistant.components.androidtv.const import ( + DEFAULT_ADB_SERVER_PORT, + DEVICE_ANDROIDTV, + DEVICE_FIRETV, +) + +ADB_SERVER_HOST = "127.0.0.1" KEY_PYTHON = "python" KEY_SERVER = "server" @@ -36,7 +43,7 @@ class AdbDeviceTcpAsyncFake: class ClientAsyncFakeSuccess: """A fake of the `ClientAsync` class when the connection and shell commands succeed.""" - def __init__(self, host="127.0.0.1", port=5037): + def __init__(self, host=ADB_SERVER_HOST, port=DEFAULT_ADB_SERVER_PORT): """Initialize a `ClientAsyncFakeSuccess` instance.""" self._devices = [] @@ -50,7 +57,7 @@ class ClientAsyncFakeSuccess: class ClientAsyncFakeFail: """A fake of the `ClientAsync` class when the connection and shell commands fail.""" - def __init__(self, host="127.0.0.1", port=5037): + def __init__(self, host=ADB_SERVER_HOST, port=DEFAULT_ADB_SERVER_PORT): """Initialize a `ClientAsyncFakeFail` instance.""" self._devices = [] @@ -143,17 +150,34 @@ def patch_shell(response=None, error=False, mac_eth=False): } -PATCH_ADB_DEVICE_TCP = patch( - "androidtv.adb_manager.adb_manager_async.AdbDeviceTcpAsync", AdbDeviceTcpAsyncFake -) -PATCH_ANDROIDTV_OPEN = patch( - "homeassistant.components.androidtv.media_player.open", mock_open() -) -PATCH_KEYGEN = patch("homeassistant.components.androidtv.keygen") -PATCH_SIGNER = patch( - "homeassistant.components.androidtv.ADBPythonSync.load_adbkey", - return_value="signer for testing", -) +def patch_androidtv_update( + state, + current_app, + running_apps, + device, + is_volume_muted, + volume_level, + hdmi_input, +): + """Patch the `AndroidTV.update()` method.""" + return { + DEVICE_ANDROIDTV: patch( + "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", + return_value=( + state, + current_app, + running_apps, + device, + is_volume_muted, + volume_level, + hdmi_input, + ), + ), + DEVICE_FIRETV: patch( + "androidtv.firetv.firetv_async.FireTVAsync.update", + return_value=(state, current_app, running_apps, hdmi_input), + ), + } def isfile(filepath): @@ -161,32 +185,12 @@ def isfile(filepath): return filepath.endswith("adbkey") -def patch_firetv_update(state, current_app, running_apps, hdmi_input): - """Patch the `FireTV.update()` method.""" - return patch( - "androidtv.firetv.firetv_async.FireTVAsync.update", - return_value=(state, current_app, running_apps, hdmi_input), - ) - - -def patch_androidtv_update( - state, current_app, running_apps, device, is_volume_muted, volume_level, hdmi_input -): - """Patch the `AndroidTV.update()` method.""" - return patch( - "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", - return_value=( - state, - current_app, - running_apps, - device, - is_volume_muted, - volume_level, - hdmi_input, - ), - ) - - +PATCH_SETUP_ENTRY = patch( + "homeassistant.components.androidtv.async_setup_entry", + return_value=True, +) +PATCH_ACCESS = patch("homeassistant.components.androidtv.os.access", return_value=True) +PATCH_ISFILE = patch("homeassistant.components.androidtv.os.path.isfile", isfile) PATCH_LAUNCH_APP = patch("androidtv.basetv.basetv_async.BaseTVAsync.launch_app") PATCH_STOP_APP = patch("androidtv.basetv.basetv_async.BaseTVAsync.stop_app") diff --git a/tests/components/androidtv/test_config_flow.py b/tests/components/androidtv/test_config_flow.py index aca308fd0e5..d5301f7ada3 100644 --- a/tests/components/androidtv/test_config_flow.py +++ b/tests/components/androidtv/test_config_flow.py @@ -36,7 +36,7 @@ from homeassistant.components.androidtv.const import ( from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_DEVICE_CLASS, CONF_HOST, CONF_PORT -from .patchers import isfile +from .patchers import PATCH_ACCESS, PATCH_ISFILE, PATCH_SETUP_ENTRY from tests.common import MockConfigEntry @@ -66,16 +66,6 @@ CONFIG_ADB_SERVER = { CONNECT_METHOD = ( "homeassistant.components.androidtv.config_flow.async_connect_androidtv" ) -PATCH_ACCESS = patch( - "homeassistant.components.androidtv.config_flow.os.access", return_value=True -) -PATCH_ISFILE = patch( - "homeassistant.components.androidtv.config_flow.os.path.isfile", isfile -) -PATCH_SETUP_ENTRY = patch( - "homeassistant.components.androidtv.async_setup_entry", - return_value=True, -) class MockConfigDevice: diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index 7496f973631..73f8de55cc9 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -1,8 +1,7 @@ """The tests for the androidtv platform.""" import base64 -import copy import logging -from unittest.mock import patch +from unittest.mock import Mock, patch from androidtv.constants import APPS as ANDROIDTV_APPS, KEYS from androidtv.exceptions import LockNotAcquiredException @@ -14,6 +13,8 @@ from homeassistant.components.androidtv.const import ( CONF_ADBKEY, CONF_APPS, CONF_EXCLUDE_UNNAMED_APPS, + CONF_SCREENCAP, + CONF_STATE_DETECTION_RULES, CONF_TURN_OFF_COMMAND, CONF_TURN_ON_COMMAND, DEFAULT_ADB_SERVER_PORT, @@ -52,6 +53,7 @@ from homeassistant.components.media_player import ( SERVICE_VOLUME_UP, ) from homeassistant.components.websocket_api.const import TYPE_RESULT +from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ( ATTR_COMMAND, ATTR_ENTITY_ID, @@ -72,20 +74,22 @@ from . import patchers from tests.common import MockConfigEntry -CONF_OPTIONS = "options" HOST = "127.0.0.1" + +ADB_PATCH_KEY = "patch_key" TEST_ENTITY_NAME = "entity_name" -PATCH_ACCESS = patch("homeassistant.components.androidtv.os.access", return_value=True) -PATCH_ISFILE = patch( - "homeassistant.components.androidtv.os.path.isfile", patchers.isfile -) +MSG_RECONNECT = { + patchers.KEY_PYTHON: f"ADB connection to {HOST}:{DEFAULT_PORT} successfully established", + patchers.KEY_SERVER: f"ADB connection to {HOST}:{DEFAULT_PORT} via ADB server {patchers.ADB_SERVER_HOST}:{DEFAULT_ADB_SERVER_PORT} successfully established", +} SHELL_RESPONSE_OFF = "" SHELL_RESPONSE_STANDBY = "1" # Android TV device with Python ADB implementation CONFIG_ANDROIDTV_PYTHON_ADB = { + ADB_PATCH_KEY: patchers.KEY_PYTHON, TEST_ENTITY_NAME: f"{PREFIX_ANDROIDTV} {HOST}", DOMAIN: { CONF_HOST: HOST, @@ -96,6 +100,7 @@ CONFIG_ANDROIDTV_PYTHON_ADB = { # Android TV device with Python ADB implementation imported from YAML CONFIG_ANDROIDTV_PYTHON_ADB_YAML = { + ADB_PATCH_KEY: patchers.KEY_PYTHON, TEST_ENTITY_NAME: "ADB yaml import", DOMAIN: { CONF_NAME: "ADB yaml import", @@ -103,20 +108,32 @@ CONFIG_ANDROIDTV_PYTHON_ADB_YAML = { }, } +# Android TV device with Python ADB implementation with custom adbkey +CONFIG_ANDROIDTV_PYTHON_ADB_KEY = { + ADB_PATCH_KEY: patchers.KEY_PYTHON, + TEST_ENTITY_NAME: CONFIG_ANDROIDTV_PYTHON_ADB[TEST_ENTITY_NAME], + DOMAIN: { + **CONFIG_ANDROIDTV_PYTHON_ADB[DOMAIN], + CONF_ADBKEY: "user_provided_adbkey", + }, +} + # Android TV device with ADB server CONFIG_ANDROIDTV_ADB_SERVER = { + ADB_PATCH_KEY: patchers.KEY_SERVER, TEST_ENTITY_NAME: f"{PREFIX_ANDROIDTV} {HOST}", DOMAIN: { CONF_HOST: HOST, CONF_PORT: DEFAULT_PORT, CONF_DEVICE_CLASS: DEVICE_ANDROIDTV, - CONF_ADB_SERVER_IP: HOST, + CONF_ADB_SERVER_IP: patchers.ADB_SERVER_HOST, CONF_ADB_SERVER_PORT: DEFAULT_ADB_SERVER_PORT, }, } # Fire TV device with Python ADB implementation CONFIG_FIRETV_PYTHON_ADB = { + ADB_PATCH_KEY: patchers.KEY_PYTHON, TEST_ENTITY_NAME: f"{PREFIX_FIRETV} {HOST}", DOMAIN: { CONF_HOST: HOST, @@ -127,26 +144,59 @@ CONFIG_FIRETV_PYTHON_ADB = { # Fire TV device with ADB server CONFIG_FIRETV_ADB_SERVER = { + ADB_PATCH_KEY: patchers.KEY_SERVER, TEST_ENTITY_NAME: f"{PREFIX_FIRETV} {HOST}", DOMAIN: { CONF_HOST: HOST, CONF_PORT: DEFAULT_PORT, CONF_DEVICE_CLASS: DEVICE_FIRETV, - CONF_ADB_SERVER_IP: HOST, + CONF_ADB_SERVER_IP: patchers.ADB_SERVER_HOST, CONF_ADB_SERVER_PORT: DEFAULT_ADB_SERVER_PORT, }, } +CONFIG_ANDROIDTV_DEFAULT = CONFIG_ANDROIDTV_PYTHON_ADB +CONFIG_FIRETV_DEFAULT = CONFIG_FIRETV_PYTHON_ADB + + +@pytest.fixture(autouse=True) +def adb_device_tcp_fixture() -> None: + """Patch ADB Device TCP.""" + with patch( + "androidtv.adb_manager.adb_manager_async.AdbDeviceTcpAsync", + patchers.AdbDeviceTcpAsyncFake, + ): + yield + + +@pytest.fixture(autouse=True) +def load_adbkey_fixture() -> None: + """Patch load_adbkey.""" + with patch( + "homeassistant.components.androidtv.ADBPythonSync.load_adbkey", + return_value="signer for testing", + ): + yield + + +@pytest.fixture(autouse=True) +def keygen_fixture() -> None: + """Patch keygen.""" + with patch( + "homeassistant.components.androidtv.keygen", + return_value=Mock(), + ): + yield + def _setup(config): """Perform common setup tasks for the tests.""" - patch_key = "server" if CONF_ADB_SERVER_IP in config[DOMAIN] else "python" + patch_key = config[ADB_PATCH_KEY] entity_id = f"{MP_DOMAIN}.{slugify(config[TEST_ENTITY_NAME])}" config_entry = MockConfigEntry( domain=DOMAIN, data=config[DOMAIN], unique_id="a1:b1:c1:d1:e1:f1", - options=config[DOMAIN].get(CONF_OPTIONS), ) return patch_key, entity_id, config_entry @@ -173,11 +223,9 @@ async def test_reconnect(hass, caplog, config): patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ - patch_key - ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -191,7 +239,7 @@ async def test_reconnect(hass, caplog, config): with patchers.patch_connect(False)[patch_key], patchers.patch_shell(error=True)[ patch_key - ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + ]: for _ in range(5): await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) @@ -205,23 +253,13 @@ async def test_reconnect(hass, caplog, config): caplog.set_level(logging.DEBUG) with patchers.patch_connect(True)[patch_key], patchers.patch_shell( SHELL_RESPONSE_STANDBY - )[patch_key], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + )[patch_key]: await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_STANDBY - - if patch_key == "python": - assert ( - "ADB connection to 127.0.0.1:5555 successfully established" - in caplog.record_tuples[2] - ) - else: - assert ( - "ADB connection to 127.0.0.1:5555 via ADB server 127.0.0.1:5037 successfully established" - in caplog.record_tuples[2] - ) + assert MSG_RECONNECT[patch_key] in caplog.record_tuples[2] @pytest.mark.parametrize( @@ -241,11 +279,9 @@ async def test_adb_shell_returns_none(hass, config): patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ - patch_key - ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -256,7 +292,7 @@ async def test_adb_shell_returns_none(hass, config): with patchers.patch_shell(None)[patch_key], patchers.patch_shell(error=True)[ patch_key - ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + ]: await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None @@ -265,16 +301,12 @@ async def test_adb_shell_returns_none(hass, config): async def test_setup_with_adbkey(hass): """Test that setup succeeds when using an ADB key.""" - config = copy.deepcopy(CONFIG_ANDROIDTV_PYTHON_ADB) - config[DOMAIN][CONF_ADBKEY] = hass.config.path("user_provided_adbkey") - patch_key, entity_id, config_entry = _setup(config) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_PYTHON_ADB_KEY) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ - patch_key - ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER, PATCH_ISFILE, PATCH_ACCESS: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key], patchers.PATCH_ISFILE: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -285,30 +317,26 @@ async def test_setup_with_adbkey(hass): @pytest.mark.parametrize( - "config0", + "config", [ - CONFIG_ANDROIDTV_ADB_SERVER, - CONFIG_FIRETV_ADB_SERVER, + CONFIG_ANDROIDTV_DEFAULT, + CONFIG_FIRETV_DEFAULT, ], ) -async def test_sources(hass, config0): +async def test_sources(hass, config): """Test that sources (i.e., apps) are handled correctly for Android TV and Fire TV devices.""" - config = copy.deepcopy(config0) - config[DOMAIN].setdefault(CONF_OPTIONS, {}).update( - { - CONF_APPS: { - "com.app.test1": "TEST 1", - "com.app.test3": None, - "com.app.test4": SHELL_RESPONSE_OFF, - } - } - ) + conf_apps = { + "com.app.test1": "TEST 1", + "com.app.test3": None, + "com.app.test4": SHELL_RESPONSE_OFF, + } patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) + hass.config_entries.async_update_entry(config_entry, options={CONF_APPS: conf_apps}) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -317,25 +345,17 @@ async def test_sources(hass, config0): assert state is not None assert state.state == STATE_OFF - if config[DOMAIN].get(CONF_DEVICE_CLASS) != DEVICE_FIRETV: - patch_update = patchers.patch_androidtv_update( - "playing", - "com.app.test1", - ["com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4"], - "hdmi", - False, - 1, - "HW5", - ) - else: - patch_update = patchers.patch_firetv_update( - "playing", - "com.app.test1", - ["com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4"], - "HW5", - ) + patch_update = patchers.patch_androidtv_update( + "playing", + "com.app.test1", + ["com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4"], + "hdmi", + False, + 1, + "HW5", + ) - with patch_update: + with patch_update[config[DOMAIN][CONF_DEVICE_CLASS]]: await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None @@ -343,25 +363,17 @@ async def test_sources(hass, config0): assert state.attributes["source"] == "TEST 1" assert sorted(state.attributes["source_list"]) == ["TEST 1", "com.app.test2"] - if config[DOMAIN].get(CONF_DEVICE_CLASS) != DEVICE_FIRETV: - patch_update = patchers.patch_androidtv_update( - "playing", - "com.app.test2", - ["com.app.test2", "com.app.test1", "com.app.test3", "com.app.test4"], - "hdmi", - True, - 0, - "HW5", - ) - else: - patch_update = patchers.patch_firetv_update( - "playing", - "com.app.test2", - ["com.app.test2", "com.app.test1", "com.app.test3", "com.app.test4"], - "HW5", - ) + patch_update = patchers.patch_androidtv_update( + "playing", + "com.app.test2", + ["com.app.test2", "com.app.test1", "com.app.test3", "com.app.test4"], + "hdmi", + True, + 0, + "HW5", + ) - with patch_update: + with patch_update[config[DOMAIN][CONF_DEVICE_CLASS]]: await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None @@ -370,24 +382,29 @@ async def test_sources(hass, config0): assert sorted(state.attributes["source_list"]) == ["TEST 1", "com.app.test2"] -async def _test_exclude_sources(hass, config0, expected_sources): +@pytest.mark.parametrize( + ["config", "expected_sources"], + [ + (CONFIG_ANDROIDTV_DEFAULT, ["TEST 1"]), + (CONFIG_FIRETV_DEFAULT, ["TEST 1"]), + ], +) +async def test_exclude_sources(hass, config, expected_sources): """Test that sources (i.e., apps) are handled correctly when the `exclude_unnamed_apps` config parameter is provided.""" - config = copy.deepcopy(config0) - config[DOMAIN].setdefault(CONF_OPTIONS, {}).update( - { - CONF_APPS: { - "com.app.test1": "TEST 1", - "com.app.test3": None, - "com.app.test4": SHELL_RESPONSE_OFF, - } - } - ) + conf_apps = { + "com.app.test1": "TEST 1", + "com.app.test3": None, + "com.app.test4": SHELL_RESPONSE_OFF, + } patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) + hass.config_entries.async_update_entry( + config_entry, options={CONF_EXCLUDE_UNNAMED_APPS: True, CONF_APPS: conf_apps} + ) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -396,37 +413,23 @@ async def _test_exclude_sources(hass, config0, expected_sources): assert state is not None assert state.state == STATE_OFF - if config[DOMAIN].get(CONF_DEVICE_CLASS) != DEVICE_FIRETV: - patch_update = patchers.patch_androidtv_update( - "playing", + patch_update = patchers.patch_androidtv_update( + "playing", + "com.app.test1", + [ "com.app.test1", - [ - "com.app.test1", - "com.app.test2", - "com.app.test3", - "com.app.test4", - "com.app.test5", - ], - "hdmi", - False, - 1, - "HW5", - ) - else: - patch_update = patchers.patch_firetv_update( - "playing", - "com.app.test1", - [ - "com.app.test1", - "com.app.test2", - "com.app.test3", - "com.app.test4", - "com.app.test5", - ], - "HW5", - ) + "com.app.test2", + "com.app.test3", + "com.app.test4", + "com.app.test5", + ], + "hdmi", + False, + 1, + "HW5", + ) - with patch_update: + with patch_update[config[DOMAIN][CONF_DEVICE_CLASS]]: await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None @@ -434,41 +437,18 @@ async def _test_exclude_sources(hass, config0, expected_sources): assert state.attributes["source"] == "TEST 1" assert sorted(state.attributes["source_list"]) == expected_sources - return True - -async def test_androidtv_exclude_sources(hass): - """Test that sources (i.e., apps) are handled correctly for Android TV devices when the `exclude_unnamed_apps` config parameter is provided as true.""" - config = copy.deepcopy(CONFIG_ANDROIDTV_ADB_SERVER) - config[DOMAIN][CONF_OPTIONS] = {CONF_EXCLUDE_UNNAMED_APPS: True} - assert await _test_exclude_sources(hass, config, ["TEST 1"]) - - -async def test_firetv_exclude_sources(hass): - """Test that sources (i.e., apps) are handled correctly for Fire TV devices when the `exclude_unnamed_apps` config parameter is provided as true.""" - config = copy.deepcopy(CONFIG_FIRETV_ADB_SERVER) - config[DOMAIN][CONF_OPTIONS] = {CONF_EXCLUDE_UNNAMED_APPS: True} - assert await _test_exclude_sources(hass, config, ["TEST 1"]) - - -async def _test_select_source(hass, config0, source, expected_arg, method_patch): +async def _test_select_source( + hass, config, conf_apps, source, expected_arg, method_patch +): """Test that the methods for launching and stopping apps are called correctly when selecting a source.""" - config = copy.deepcopy(config0) - config[DOMAIN].setdefault(CONF_OPTIONS, {}).update( - { - CONF_APPS: { - "com.app.test1": "TEST 1", - "com.app.test3": None, - "com.youtube.test": "YouTube", - } - } - ) patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) + hass.config_entries.async_update_entry(config_entry, options={CONF_APPS: conf_apps}) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -477,213 +457,87 @@ async def _test_select_source(hass, config0, source, expected_arg, method_patch) assert state is not None assert state.state == STATE_OFF - with method_patch as method_patch_: + with method_patch as method_patch_used: await hass.services.async_call( MP_DOMAIN, SERVICE_SELECT_SOURCE, {ATTR_ENTITY_ID: entity_id, ATTR_INPUT_SOURCE: source}, blocking=True, ) - method_patch_.assert_called_with(expected_arg) - - return True + method_patch_used.assert_called_with(expected_arg) -async def test_androidtv_select_source_launch_app_id(hass): - """Test that an app can be launched using its app ID.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "com.app.test1", - "com.app.test1", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_androidtv_select_source_launch_app_name(hass): - """Test that an app can be launched using its friendly name.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "TEST 1", - "com.app.test1", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_androidtv_select_source_launch_app_id_no_name(hass): - """Test that an app can be launched using its app ID when it has no friendly name.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "com.app.test2", - "com.app.test2", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_androidtv_select_source_launch_app_hidden(hass): - """Test that an app can be launched using its app ID when it is hidden from the sources list.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "com.app.test3", - "com.app.test3", - patchers.PATCH_LAUNCH_APP, +@pytest.mark.parametrize( + ["source", "expected_arg", "method_patch"], + [ + ("com.app.test1", "com.app.test1", patchers.PATCH_LAUNCH_APP), + ("TEST 1", "com.app.test1", patchers.PATCH_LAUNCH_APP), + ("com.app.test2", "com.app.test2", patchers.PATCH_LAUNCH_APP), + ("com.app.test3", "com.app.test3", patchers.PATCH_LAUNCH_APP), + ("!com.app.test1", "com.app.test1", patchers.PATCH_STOP_APP), + ("!TEST 1", "com.app.test1", patchers.PATCH_STOP_APP), + ("!com.app.test2", "com.app.test2", patchers.PATCH_STOP_APP), + ("!com.app.test3", "com.app.test3", patchers.PATCH_STOP_APP), + ], +) +async def test_select_source_androidtv(hass, source, expected_arg, method_patch): + """Test that an app can be launched for AndroidTV.""" + conf_apps = { + "com.app.test1": "TEST 1", + "com.app.test3": None, + } + await _test_select_source( + hass, CONFIG_ANDROIDTV_DEFAULT, conf_apps, source, expected_arg, method_patch ) async def test_androidtv_select_source_overridden_app_name(hass): """Test that when an app name is overridden via the `apps` configuration parameter, the app is launched correctly.""" # Evidence that the default YouTube app ID will be overridden + conf_apps = { + "com.youtube.test": "YouTube", + } assert "YouTube" in ANDROIDTV_APPS.values() assert "com.youtube.test" not in ANDROIDTV_APPS - assert await _test_select_source( + await _test_select_source( hass, - CONFIG_ANDROIDTV_ADB_SERVER, + CONFIG_ANDROIDTV_PYTHON_ADB, + conf_apps, "YouTube", "com.youtube.test", patchers.PATCH_LAUNCH_APP, ) -async def test_androidtv_select_source_stop_app_id(hass): - """Test that an app can be stopped using its app ID.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "!com.app.test1", - "com.app.test1", - patchers.PATCH_STOP_APP, - ) - - -async def test_androidtv_select_source_stop_app_name(hass): - """Test that an app can be stopped using its friendly name.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "!TEST 1", - "com.app.test1", - patchers.PATCH_STOP_APP, - ) - - -async def test_androidtv_select_source_stop_app_id_no_name(hass): - """Test that an app can be stopped using its app ID when it has no friendly name.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "!com.app.test2", - "com.app.test2", - patchers.PATCH_STOP_APP, - ) - - -async def test_androidtv_select_source_stop_app_hidden(hass): - """Test that an app can be stopped using its app ID when it is hidden from the sources list.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "!com.app.test3", - "com.app.test3", - patchers.PATCH_STOP_APP, - ) - - -async def test_firetv_select_source_launch_app_id(hass): - """Test that an app can be launched using its app ID.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "com.app.test1", - "com.app.test1", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_firetv_select_source_launch_app_name(hass): - """Test that an app can be launched using its friendly name.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "TEST 1", - "com.app.test1", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_firetv_select_source_launch_app_id_no_name(hass): - """Test that an app can be launched using its app ID when it has no friendly name.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "com.app.test2", - "com.app.test2", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_firetv_select_source_launch_app_hidden(hass): - """Test that an app can be launched using its app ID when it is hidden from the sources list.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "com.app.test3", - "com.app.test3", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_firetv_select_source_stop_app_id(hass): - """Test that an app can be stopped using its app ID.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "!com.app.test1", - "com.app.test1", - patchers.PATCH_STOP_APP, - ) - - -async def test_firetv_select_source_stop_app_name(hass): - """Test that an app can be stopped using its friendly name.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "!TEST 1", - "com.app.test1", - patchers.PATCH_STOP_APP, - ) - - -async def test_firetv_select_source_stop_app_id_no_name(hass): - """Test that an app can be stopped using its app ID when it has no friendly name.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "!com.app.test2", - "com.app.test2", - patchers.PATCH_STOP_APP, - ) - - -async def test_firetv_select_source_stop_hidden(hass): - """Test that an app can be stopped using its app ID when it is hidden from the sources list.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "!com.app.test3", - "com.app.test3", - patchers.PATCH_STOP_APP, +@pytest.mark.parametrize( + ["source", "expected_arg", "method_patch"], + [ + ("com.app.test1", "com.app.test1", patchers.PATCH_LAUNCH_APP), + ("TEST 1", "com.app.test1", patchers.PATCH_LAUNCH_APP), + ("com.app.test2", "com.app.test2", patchers.PATCH_LAUNCH_APP), + ("com.app.test3", "com.app.test3", patchers.PATCH_LAUNCH_APP), + ("!com.app.test1", "com.app.test1", patchers.PATCH_STOP_APP), + ("!TEST 1", "com.app.test1", patchers.PATCH_STOP_APP), + ("!com.app.test2", "com.app.test2", patchers.PATCH_STOP_APP), + ("!com.app.test3", "com.app.test3", patchers.PATCH_STOP_APP), + ], +) +async def test_select_source_firetv(hass, source, expected_arg, method_patch): + """Test that an app can be launched for FireTV.""" + conf_apps = { + "com.app.test1": "TEST 1", + "com.app.test3": None, + } + await _test_select_source( + hass, CONFIG_FIRETV_DEFAULT, conf_apps, source, expected_arg, method_patch ) @pytest.mark.parametrize( "config", [ - CONFIG_ANDROIDTV_PYTHON_ADB, - CONFIG_FIRETV_PYTHON_ADB, + CONFIG_ANDROIDTV_DEFAULT, + CONFIG_FIRETV_DEFAULT, ], ) async def test_setup_fail(hass, config): @@ -691,11 +545,9 @@ async def test_setup_fail(hass, config): patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(False)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ - patch_key - ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + with patchers.patch_connect(False)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) is False await hass.async_block_till_done() @@ -706,14 +558,14 @@ async def test_setup_fail(hass, config): async def test_adb_command(hass): """Test sending a command via the `androidtv.adb_command` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) command = "test command" response = "test response" - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -735,14 +587,14 @@ async def test_adb_command(hass): async def test_adb_command_unicode_decode_error(hass): """Test sending a command via the `androidtv.adb_command` service that raises a UnicodeDecodeError exception.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) command = "test command" response = b"test response" - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -764,14 +616,14 @@ async def test_adb_command_unicode_decode_error(hass): async def test_adb_command_key(hass): """Test sending a key command via the `androidtv.adb_command` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) command = "HOME" response = None - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -793,14 +645,14 @@ async def test_adb_command_key(hass): async def test_adb_command_get_properties(hass): """Test sending the "GET_PROPERTIES" command via the `androidtv.adb_command` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) command = "GET_PROPERTIES" response = {"test key": "test value"} - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -823,13 +675,13 @@ async def test_adb_command_get_properties(hass): async def test_learn_sendevent(hass): """Test the `androidtv.learn_sendevent` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) response = "sendevent 1 2 3 4" - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -852,12 +704,12 @@ async def test_learn_sendevent(hass): async def test_update_lock_not_acquired(hass): """Test that the state does not get updated when a `LockNotAcquiredException` is raised.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -885,14 +737,14 @@ async def test_update_lock_not_acquired(hass): async def test_download(hass): """Test the `androidtv.download` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) device_path = "device/path" local_path = "local/path" - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -931,14 +783,14 @@ async def test_download(hass): async def test_upload(hass): """Test the `androidtv.upload` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) device_path = "device/path" local_path = "local/path" - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -977,12 +829,12 @@ async def test_upload(hass): async def test_androidtv_volume_set(hass): """Test setting the volume for an Android TV device.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1004,12 +856,12 @@ async def test_get_image(hass, hass_ws_client): This is based on `test_get_image` in tests/components/media_player/test_init.py. """ - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1035,7 +887,7 @@ async def test_get_image(hass, hass_ws_client): with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_screencap", - side_effect=RuntimeError, + side_effect=ConnectionResetError, ): await client.send_json( {"id": 6, "type": "media_player_thumbnail", "entity_id": entity_id} @@ -1049,6 +901,39 @@ async def test_get_image(hass, hass_ws_client): assert state.state == STATE_UNAVAILABLE +async def test_get_image_disabled(hass, hass_ws_client): + """Test taking a screen capture with screencap option disabled. + + This is based on `test_get_image` in tests/components/media_player/test_init.py. + """ + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) + config_entry.add_to_hass(hass) + hass.config_entries.async_update_entry( + config_entry, options={CONF_SCREENCAP: False} + ) + + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + with patchers.patch_shell("11")[patch_key]: + await async_update_entity(hass, entity_id) + + client = await hass_ws_client(hass) + + with patch( + "androidtv.basetv.basetv_async.BaseTVAsync.adb_screencap", return_value=b"image" + ) as screen_cap: + await client.send_json( + {"id": 5, "type": "media_player_thumbnail", "entity_id": entity_id} + ) + + await client.receive_json() + assert not screen_cap.called + + async def _test_service( hass, entity_id, @@ -1081,10 +966,10 @@ async def _test_service( async def test_services_androidtv(hass): """Test media player services for an Android TV device.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: + with patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1122,15 +1007,17 @@ async def test_services_androidtv(hass): async def test_services_firetv(hass): """Test media player services for a Fire TV device.""" - config = copy.deepcopy(CONFIG_FIRETV_ADB_SERVER) - config[DOMAIN][CONF_OPTIONS] = { - CONF_TURN_OFF_COMMAND: "test off", - CONF_TURN_ON_COMMAND: "test on", - } - patch_key, entity_id, config_entry = _setup(config) + patch_key, entity_id, config_entry = _setup(CONFIG_FIRETV_DEFAULT) config_entry.add_to_hass(hass) + hass.config_entries.async_update_entry( + config_entry, + options={ + CONF_TURN_OFF_COMMAND: "test off", + CONF_TURN_ON_COMMAND: "test on", + }, + ) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: + with patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1143,10 +1030,10 @@ async def test_services_firetv(hass): async def test_volume_mute(hass): """Test the volume mute service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: + with patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1186,18 +1073,16 @@ async def test_volume_mute(hass): async def test_connection_closed_on_ha_stop(hass): """Test that the ADB socket connection is closed when HA stops.""" - patch_key, _, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, _, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - with patch( - "androidtv.androidtv.androidtv_async.AndroidTVAsync.adb_close" - ) as adb_close: + with patch("androidtv.basetv.basetv_async.BaseTVAsync.adb_close") as adb_close: hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() assert adb_close.called @@ -1208,14 +1093,12 @@ async def test_exception(hass): HA will attempt to reconnect on the next update. """ - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_PYTHON_ADB) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ - patch_key - ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1236,3 +1119,38 @@ async def test_exception(hass): state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF + + +async def test_options_reload(hass): + """Test changing an option that will cause integration reload.""" + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) + config_entry.add_to_hass(hass) + + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + await async_update_entity(hass, entity_id) + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_OFF + + with patchers.PATCH_SETUP_ENTRY as setup_entry_call: + # change an option that not require integration reload + hass.config_entries.async_update_entry( + config_entry, options={CONF_SCREENCAP: False} + ) + await hass.async_block_till_done() + + assert not setup_entry_call.called + + # change an option that require integration reload + hass.config_entries.async_update_entry( + config_entry, options={CONF_STATE_DETECTION_RULES: {}} + ) + await hass.async_block_till_done() + + assert setup_entry_call.called + assert config_entry.state is ConfigEntryState.LOADED diff --git a/tests/components/application_credentials/__init__.py b/tests/components/application_credentials/__init__.py new file mode 100644 index 00000000000..36933b9ccfb --- /dev/null +++ b/tests/components/application_credentials/__init__.py @@ -0,0 +1 @@ +"""Tests for the Application Credentials integration.""" diff --git a/tests/components/application_credentials/test_init.py b/tests/components/application_credentials/test_init.py new file mode 100644 index 00000000000..b89a60f42e4 --- /dev/null +++ b/tests/components/application_credentials/test_init.py @@ -0,0 +1,749 @@ +"""Test the Developer Credentials integration.""" + +from __future__ import annotations + +from collections.abc import Callable, Generator +import logging +from typing import Any +from unittest.mock import AsyncMock, Mock, patch + +from aiohttp import ClientWebSocketResponse +import pytest + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components.application_credentials import ( + CONF_AUTH_DOMAIN, + DEFAULT_IMPORT_NAME, + DOMAIN, + AuthImplementation, + AuthorizationServer, + ClientCredential, + async_import_client_credential, +) +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_DOMAIN, + CONF_NAME, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow +from homeassistant.setup import async_setup_component + +from tests.common import mock_platform + +CLIENT_ID = "some-client-id" +CLIENT_SECRET = "some-client-secret" +DEVELOPER_CREDENTIAL = ClientCredential(CLIENT_ID, CLIENT_SECRET) +NAMED_CREDENTIAL = ClientCredential(CLIENT_ID, CLIENT_SECRET, "Name") +ID = "fake_integration_some_client_id" +AUTHORIZE_URL = "https://example.com/auth" +TOKEN_URL = "https://example.com/oauth2/v4/token" +REFRESH_TOKEN = "mock-refresh-token" +ACCESS_TOKEN = "mock-access-token" +NAME = "Name" + +TEST_DOMAIN = "fake_integration" + + +@pytest.fixture +async def authorization_server() -> AuthorizationServer: + """Fixture AuthorizationServer for mock application_credentials integration.""" + return AuthorizationServer(AUTHORIZE_URL, TOKEN_URL) + + +@pytest.fixture +async def config_credential() -> ClientCredential | None: + """Fixture ClientCredential for mock application_credentials integration.""" + return None + + +@pytest.fixture +async def import_config_credential( + hass: HomeAssistant, config_credential: ClientCredential +) -> None: + """Fixture to import the yaml based credential.""" + await async_import_client_credential(hass, TEST_DOMAIN, config_credential) + + +async def setup_application_credentials_integration( + hass: HomeAssistant, + domain: str, + authorization_server: AuthorizationServer, +) -> None: + """Set up a fake application_credentials integration.""" + hass.config.components.add(domain) + mock_platform_impl = Mock( + async_get_authorization_server=AsyncMock(return_value=authorization_server), + ) + del mock_platform_impl.async_get_auth_implementation # return False on hasattr + mock_platform( + hass, + f"{domain}.application_credentials", + mock_platform_impl, + ) + + +@pytest.fixture(autouse=True) +async def mock_application_credentials_integration( + hass: HomeAssistant, + authorization_server: AuthorizationServer, +): + """Mock a application_credentials integration.""" + assert await async_setup_component(hass, "application_credentials", {}) + await setup_application_credentials_integration( + hass, TEST_DOMAIN, authorization_server + ) + + +class FakeConfigFlow(config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN): + """Config flow used during tests.""" + + DOMAIN = TEST_DOMAIN + + @property + def logger(self) -> logging.Logger: + """Return logger.""" + return logging.getLogger(__name__) + + +@pytest.fixture(autouse=True) +def config_flow_handler( + hass: HomeAssistant, current_request_with_host: Any +) -> Generator[FakeConfigFlow, None, None]: + """Fixture for a test config flow.""" + mock_platform(hass, f"{TEST_DOMAIN}.config_flow") + with patch.dict(config_entries.HANDLERS, {TEST_DOMAIN: FakeConfigFlow}): + yield FakeConfigFlow + + +class OAuthFixture: + """Fixture to facilitate testing an OAuth flow.""" + + def __init__(self, hass, hass_client, aioclient_mock): + """Initialize OAuthFixture.""" + self.hass = hass + self.hass_client = hass_client + self.aioclient_mock = aioclient_mock + self.client_id = CLIENT_ID + self.title = CLIENT_ID + + async def complete_external_step( + self, result: data_entry_flow.FlowResult + ) -> data_entry_flow.FlowResult: + """Fixture method to complete the OAuth flow and return the completed result.""" + client = await self.hass_client() + state = config_entry_oauth2_flow._encode_jwt( + self.hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + assert result["url"] == ( + f"{AUTHORIZE_URL}?response_type=code&client_id={self.client_id}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + self.aioclient_mock.post( + TOKEN_URL, + json={ + "refresh_token": REFRESH_TOKEN, + "access_token": ACCESS_TOKEN, + "type": "bearer", + "expires_in": 60, + }, + ) + + result = await self.hass.config_entries.flow.async_configure(result["flow_id"]) + assert result.get("type") == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result.get("title") == self.title + assert "data" in result + assert "token" in result["data"] + return result + + +@pytest.fixture +async def oauth_fixture( + hass: HomeAssistant, hass_client_no_auth: Any, aioclient_mock: Any +) -> OAuthFixture: + """Fixture for testing the OAuth flow.""" + return OAuthFixture(hass, hass_client_no_auth, aioclient_mock) + + +class Client: + """Test client with helper methods for application credentials websocket.""" + + def __init__(self, client): + """Initialize Client.""" + self.client = client + self.id = 0 + + async def cmd(self, cmd: str, payload: dict[str, Any] = None) -> dict[str, Any]: + """Send a command and receive the json result.""" + self.id += 1 + await self.client.send_json( + { + "id": self.id, + "type": f"{DOMAIN}/{cmd}", + **(payload if payload is not None else {}), + } + ) + resp = await self.client.receive_json() + assert resp.get("id") == self.id + return resp + + async def cmd_result(self, cmd: str, payload: dict[str, Any] = None) -> Any: + """Send a command and parse the result.""" + resp = await self.cmd(cmd, payload) + assert resp.get("success") + assert resp.get("type") == "result" + return resp.get("result") + + +ClientFixture = Callable[[], Client] + + +@pytest.fixture +async def ws_client( + hass_ws_client: Callable[[...], ClientWebSocketResponse] +) -> ClientFixture: + """Fixture for creating the test websocket client.""" + + async def create_client() -> Client: + ws_client = await hass_ws_client() + return Client(ws_client) + + return create_client + + +async def test_websocket_list_empty(ws_client: ClientFixture): + """Test websocket list command.""" + client = await ws_client() + assert await client.cmd_result("list") == [] + + +async def test_websocket_create(ws_client: ClientFixture): + """Test websocket create command.""" + client = await ws_client() + result = await client.cmd_result( + "create", + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + ) + assert result == { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + "id": ID, + } + + result = await client.cmd_result("list") + assert result == [ + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + "id": ID, + } + ] + + +async def test_websocket_create_invalid_domain(ws_client: ClientFixture): + """Test websocket create command.""" + client = await ws_client() + resp = await client.cmd( + "create", + { + CONF_DOMAIN: "other-domain", + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + ) + assert not resp.get("success") + assert "error" in resp + assert resp["error"].get("code") == "invalid_format" + assert ( + resp["error"].get("message") + == "No application_credentials platform for other-domain" + ) + + +async def test_websocket_update_not_supported(ws_client: ClientFixture): + """Test websocket update command in unsupported.""" + client = await ws_client() + result = await client.cmd_result( + "create", + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + ) + assert result == { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + "id": ID, + } + + resp = await client.cmd("update", {"application_credentials_id": ID}) + assert not resp.get("success") + assert "error" in resp + assert resp["error"].get("code") == "invalid_format" + assert resp["error"].get("message") == "Updates not supported" + + +async def test_websocket_delete(ws_client: ClientFixture): + """Test websocket delete command.""" + client = await ws_client() + + await client.cmd_result( + "create", + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + ) + assert await client.cmd_result("list") == [ + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + "id": ID, + } + ] + + await client.cmd_result("delete", {"application_credentials_id": ID}) + assert await client.cmd_result("list") == [] + + +async def test_websocket_delete_item_not_found(ws_client: ClientFixture): + """Test websocket delete command.""" + client = await ws_client() + + resp = await client.cmd("delete", {"application_credentials_id": ID}) + assert not resp.get("success") + assert "error" in resp + assert resp["error"].get("code") == "not_found" + assert ( + resp["error"].get("message") + == f"Unable to find application_credentials_id {ID}" + ) + + +@pytest.mark.parametrize("config_credential", [DEVELOPER_CREDENTIAL]) +async def test_websocket_import_config( + ws_client: ClientFixture, + config_credential: ClientCredential, + import_config_credential: Any, +): + """Test websocket list command for an imported credential.""" + client = await ws_client() + + # Imported creds returned from websocket + assert await client.cmd_result("list") == [ + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + "id": ID, + CONF_AUTH_DOMAIN: TEST_DOMAIN, + CONF_NAME: DEFAULT_IMPORT_NAME, + } + ] + + # Imported credential can be deleted + await client.cmd_result("delete", {"application_credentials_id": ID}) + assert await client.cmd_result("list") == [] + + +@pytest.mark.parametrize("config_credential", [DEVELOPER_CREDENTIAL]) +async def test_import_duplicate_credentials( + hass: HomeAssistant, + ws_client: ClientFixture, + config_credential: ClientCredential, + import_config_credential: Any, +): + """Exercise duplicate credentials are ignored.""" + + # Import the test credential again and verify it is not imported twice + await async_import_client_credential(hass, TEST_DOMAIN, DEVELOPER_CREDENTIAL) + client = await ws_client() + assert await client.cmd_result("list") == [ + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + "id": ID, + CONF_AUTH_DOMAIN: TEST_DOMAIN, + CONF_NAME: DEFAULT_IMPORT_NAME, + } + ] + + +@pytest.mark.parametrize("config_credential", [NAMED_CREDENTIAL]) +async def test_import_named_credential( + ws_client: ClientFixture, + config_credential: ClientCredential, + import_config_credential: Any, +): + """Test websocket list command for an imported credential.""" + client = await ws_client() + + # Imported creds returned from websocket + assert await client.cmd_result("list") == [ + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + "id": ID, + CONF_AUTH_DOMAIN: TEST_DOMAIN, + CONF_NAME: NAME, + } + ] + + +async def test_config_flow_no_credentials(hass): + """Test config flow base case with no credentials registered.""" + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT + assert result.get("reason") == "missing_configuration" + + +async def test_config_flow_other_domain( + hass: HomeAssistant, + ws_client: ClientFixture, + authorization_server: AuthorizationServer, +): + """Test config flow ignores credentials for another domain.""" + await setup_application_credentials_integration( + hass, + "other_domain", + authorization_server, + ) + client = await ws_client() + await client.cmd_result( + "create", + { + CONF_DOMAIN: "other_domain", + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + ) + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT + assert result.get("reason") == "missing_configuration" + + +async def test_config_flow( + hass: HomeAssistant, + ws_client: ClientFixture, + oauth_fixture: OAuthFixture, +): + """Test config flow with application credential registered.""" + client = await ws_client() + + await client.cmd_result( + "create", + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + ) + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + result = await oauth_fixture.complete_external_step(result) + assert ( + result["data"].get("auth_implementation") == "fake_integration_some_client_id" + ) + + # Verify it is not possible to delete an in-use config entry + resp = await client.cmd("delete", {"application_credentials_id": ID}) + assert not resp.get("success") + assert "error" in resp + assert resp["error"].get("code") == "unknown_error" + assert ( + resp["error"].get("message") + == "Cannot delete credential in use by integration fake_integration" + ) + + +async def test_config_flow_multiple_entries( + hass: HomeAssistant, + ws_client: ClientFixture, + oauth_fixture: OAuthFixture, +): + """Test config flow with multiple application credentials registered.""" + client = await ws_client() + + await client.cmd_result( + "create", + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + ) + await client.cmd_result( + "create", + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID + "2", + CONF_CLIENT_SECRET: CLIENT_SECRET + "2", + }, + ) + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_FORM + assert result.get("step_id") == "pick_implementation" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"implementation": "fake_integration_some_client_id2"}, + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + oauth_fixture.client_id = CLIENT_ID + "2" + oauth_fixture.title = CLIENT_ID + "2" + result = await oauth_fixture.complete_external_step(result) + assert ( + result["data"].get("auth_implementation") == "fake_integration_some_client_id2" + ) + + +async def test_config_flow_create_delete_credential( + hass: HomeAssistant, + ws_client: ClientFixture, + oauth_fixture: OAuthFixture, +): + """Test adding and deleting a credential unregisters from the config flow.""" + client = await ws_client() + + await client.cmd_result( + "create", + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + ) + await client.cmd("delete", {"application_credentials_id": ID}) + + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT + assert result.get("reason") == "missing_configuration" + + +@pytest.mark.parametrize("config_credential", [DEVELOPER_CREDENTIAL]) +async def test_config_flow_with_config_credential( + hass, + hass_client_no_auth, + aioclient_mock, + oauth_fixture, + config_credential, + import_config_credential, +): + """Test config flow with application credential registered.""" + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + oauth_fixture.title = DEFAULT_IMPORT_NAME + result = await oauth_fixture.complete_external_step(result) + # Uses the imported auth domain for compatibility + assert result["data"].get("auth_implementation") == TEST_DOMAIN + + +@pytest.mark.parametrize("mock_application_credentials_integration", [None]) +async def test_import_without_setup(hass, config_credential): + """Test import of credentials without setting up the integration.""" + + with pytest.raises(ValueError): + await async_import_client_credential(hass, TEST_DOMAIN, config_credential) + + # Config flow does not have authentication + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT + assert result.get("reason") == "missing_configuration" + + +@pytest.mark.parametrize("mock_application_credentials_integration", [None]) +async def test_websocket_without_platform( + hass: HomeAssistant, ws_client: ClientFixture +): + """Test an integration without the application credential platform.""" + assert await async_setup_component(hass, "application_credentials", {}) + hass.config.components.add(TEST_DOMAIN) + + client = await ws_client() + resp = await client.cmd( + "create", + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + ) + assert not resp.get("success") + assert "error" in resp + assert resp["error"].get("code") == "invalid_format" + + # Config flow does not have authentication + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT + assert result.get("reason") == "missing_configuration" + + +@pytest.mark.parametrize("mock_application_credentials_integration", [None]) +async def test_websocket_without_authorization_server( + hass: HomeAssistant, ws_client: ClientFixture +): + """Test platform with incorrect implementation.""" + assert await async_setup_component(hass, "application_credentials", {}) + hass.config.components.add(TEST_DOMAIN) + + # Platform does not implemenent async_get_authorization_server + platform = Mock() + del platform.async_get_authorization_server + del platform.async_get_auth_implementation + mock_platform( + hass, + f"{TEST_DOMAIN}.application_credentials", + platform, + ) + + client = await ws_client() + resp = await client.cmd( + "create", + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + ) + assert not resp.get("success") + assert "error" in resp + assert resp["error"].get("code") == "invalid_format" + + # Config flow does not have authentication + with pytest.raises(ValueError): + await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + +@pytest.mark.parametrize("config_credential", [DEVELOPER_CREDENTIAL]) +async def test_platform_with_auth_implementation( + hass, + hass_client_no_auth, + aioclient_mock, + oauth_fixture, + config_credential, + import_config_credential, + authorization_server, +): + """Test config flow with custom OAuth2 implementation.""" + + assert await async_setup_component(hass, "application_credentials", {}) + hass.config.components.add(TEST_DOMAIN) + + async def get_auth_impl( + hass: HomeAssistant, auth_domain: str, credential: ClientCredential + ) -> config_entry_oauth2_flow.AbstractOAuth2Implementation: + return AuthImplementation(hass, auth_domain, credential, authorization_server) + + mock_platform_impl = Mock( + async_get_auth_implementation=get_auth_impl, + ) + del mock_platform_impl.async_get_authorization_server + mock_platform( + hass, + f"{TEST_DOMAIN}.application_credentials", + mock_platform_impl, + ) + + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + oauth_fixture.title = DEFAULT_IMPORT_NAME + result = await oauth_fixture.complete_external_step(result) + # Uses the imported auth domain for compatibility + assert result["data"].get("auth_implementation") == TEST_DOMAIN + + +async def test_websocket_integration_list(ws_client: ClientFixture): + """Test websocket integration list command.""" + client = await ws_client() + with patch( + "homeassistant.loader.APPLICATION_CREDENTIALS", ["example1", "example2"] + ): + assert await client.cmd_result("config") == { + "domains": ["example1", "example2"] + } + + +async def test_name( + hass: HomeAssistant, ws_client: ClientFixture, oauth_fixture: OAuthFixture +): + """Test a credential with a name set.""" + client = await ws_client() + result = await client.cmd_result( + "create", + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_NAME: NAME, + }, + ) + assert result == { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_NAME: NAME, + "id": ID, + } + + result = await client.cmd_result("list") + assert result == [ + { + CONF_DOMAIN: TEST_DOMAIN, + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_NAME: NAME, + "id": ID, + } + ] + + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + oauth_fixture.title = NAME + result = await oauth_fixture.complete_external_step(result) + assert ( + result["data"].get("auth_implementation") == "fake_integration_some_client_id" + ) diff --git a/tests/components/arcam_fmj/test_media_player.py b/tests/components/arcam_fmj/test_media_player.py index 81f6a5a935f..d3b221a05fd 100644 --- a/tests/components/arcam_fmj/test_media_player.py +++ b/tests/components/arcam_fmj/test_media_player.py @@ -1,6 +1,6 @@ """Tests for arcam fmj receivers.""" from math import isclose -from unittest.mock import ANY, MagicMock, PropertyMock, patch +from unittest.mock import ANY, PropertyMock, patch from arcam.fmj import DecodeMode2CH, DecodeModeMCH, SourceCodes import pytest @@ -303,22 +303,12 @@ async def test_added_to_hass(player, state): SIGNAL_CLIENT_STOPPED, ) - connectors = {} + with patch( + "homeassistant.components.arcam_fmj.media_player.async_dispatcher_connect" + ) as connect: + await player.async_added_to_hass() - def _connect(signal, fun): - connectors[signal] = fun - - player.hass = MagicMock() - player.hass.helpers.dispatcher.async_dispatcher_connect.side_effects = _connect - - await player.async_added_to_hass() state.start.assert_called_with() - player.hass.helpers.dispatcher.async_dispatcher_connect.assert_any_call( - SIGNAL_CLIENT_DATA, ANY - ) - player.hass.helpers.dispatcher.async_dispatcher_connect.assert_any_call( - SIGNAL_CLIENT_STARTED, ANY - ) - player.hass.helpers.dispatcher.async_dispatcher_connect.assert_any_call( - SIGNAL_CLIENT_STOPPED, ANY - ) + connect.assert_any_call(player.hass, SIGNAL_CLIENT_DATA, ANY) + connect.assert_any_call(player.hass, SIGNAL_CLIENT_STARTED, ANY) + connect.assert_any_call(player.hass, SIGNAL_CLIENT_STOPPED, ANY) diff --git a/tests/components/asuswrt/test_config_flow.py b/tests/components/asuswrt/test_config_flow.py index a1a37d25460..8475bd48f9a 100644 --- a/tests/components/asuswrt/test_config_flow.py +++ b/tests/components/asuswrt/test_config_flow.py @@ -28,6 +28,7 @@ from tests.common import MockConfigEntry HOST = "myrouter.asuswrt.com" IP_ADDRESS = "192.168.1.1" +MAC_ADDR = "a1:b1:c1:d1:e1:f1" SSH_KEY = "1234" CONFIG_DATA = { @@ -39,19 +40,44 @@ CONFIG_DATA = { CONF_MODE: "ap", } +PATCH_GET_HOST = patch( + "homeassistant.components.asuswrt.config_flow.socket.gethostbyname", + return_value=IP_ADDRESS, +) + +PATCH_SETUP_ENTRY = patch( + "homeassistant.components.asuswrt.async_setup_entry", + return_value=True, +) + + +@pytest.fixture(name="mock_unique_id") +def mock_unique_id_fixture(): + """Mock returned unique id.""" + return {} + @pytest.fixture(name="connect") -def mock_controller_connect(): +def mock_controller_connect(mock_unique_id): """Mock a successful connection.""" with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: service_mock.return_value.connection.async_connect = AsyncMock() service_mock.return_value.is_connected = True service_mock.return_value.connection.disconnect = Mock() + service_mock.return_value.async_get_nvram = AsyncMock( + return_value=mock_unique_id + ) yield service_mock -async def test_user(hass, connect): +@pytest.mark.usefixtures("connect") +@pytest.mark.parametrize( + "unique_id", + [{}, {"label_mac": MAC_ADDR}], +) +async def test_user(hass, mock_unique_id, unique_id): """Test user config.""" + mock_unique_id.update(unique_id) flow_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER, "show_advanced_options": True} ) @@ -59,15 +85,10 @@ async def test_user(hass, connect): assert flow_result["step_id"] == "user" # test with all provided - with patch( - "homeassistant.components.asuswrt.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.asuswrt.config_flow.socket.gethostbyname", - return_value=IP_ADDRESS, - ): + with PATCH_GET_HOST, PATCH_SETUP_ENTRY as mock_setup_entry: result = await hass.config_entries.flow.async_configure( - flow_result["flow_id"], user_input=CONFIG_DATA + flow_result["flow_id"], + user_input=CONFIG_DATA, ) await hass.async_block_till_done() @@ -78,10 +99,17 @@ async def test_user(hass, connect): assert len(mock_setup_entry.mock_calls) == 1 -async def test_error_no_password_ssh(hass): - """Test we abort if component is already setup.""" +@pytest.mark.parametrize( + ["config", "error"], + [ + ({CONF_PASSWORD: None}, "pwd_or_ssh"), + ({CONF_SSH_KEY: SSH_KEY}, "pwd_and_ssh"), + ], +) +async def test_error_wrong_password_ssh(hass, config, error): + """Test we abort for wrong password and ssh file combination.""" config_data = CONFIG_DATA.copy() - config_data.pop(CONF_PASSWORD) + config_data.update(config) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER, "show_advanced_options": True}, @@ -89,36 +117,27 @@ async def test_error_no_password_ssh(hass): ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "pwd_or_ssh"} - - -async def test_error_both_password_ssh(hass): - """Test we abort if component is already setup.""" - config_data = CONFIG_DATA.copy() - config_data[CONF_SSH_KEY] = SSH_KEY - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER, "show_advanced_options": True}, - data=config_data, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "pwd_and_ssh"} + assert result["errors"] == {"base": error} async def test_error_invalid_ssh(hass): - """Test we abort if component is already setup.""" + """Test we abort if invalid ssh file is provided.""" config_data = CONFIG_DATA.copy() config_data.pop(CONF_PASSWORD) config_data[CONF_SSH_KEY] = SSH_KEY - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER, "show_advanced_options": True}, - data=config_data, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "ssh_not_file"} + with patch( + "homeassistant.components.asuswrt.config_flow.os.path.isfile", + return_value=False, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER, "show_advanced_options": True}, + data=config_data, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "ssh_not_file"} async def test_error_invalid_host(hass): @@ -137,60 +156,96 @@ async def test_error_invalid_host(hass): assert result["errors"] == {"base": "invalid_host"} -async def test_abort_if_already_setup(hass): - """Test we abort if component is already setup.""" +async def test_abort_if_not_unique_id_setup(hass): + """Test we abort if component without uniqueid is already setup.""" MockConfigEntry( domain=DOMAIN, data=CONFIG_DATA, ).add_to_hass(hass) - with patch( - "homeassistant.components.asuswrt.config_flow.socket.gethostbyname", - return_value=IP_ADDRESS, - ): - # Should fail, same HOST (flow) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=CONFIG_DATA, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "no_unique_id" + + +@pytest.mark.usefixtures("connect") +async def test_update_uniqueid_exist(hass, mock_unique_id): + """Test we update entry if uniqueid is already configured.""" + mock_unique_id.update({"label_mac": MAC_ADDR}) + existing_entry = MockConfigEntry( + domain=DOMAIN, + data={**CONFIG_DATA, CONF_HOST: "10.10.10.10"}, + unique_id=MAC_ADDR, + ) + existing_entry.add_to_hass(hass) + + # test with all provided + with PATCH_GET_HOST, PATCH_SETUP_ENTRY: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER, "show_advanced_options": True}, + data=CONFIG_DATA, + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == HOST + assert result["data"] == CONFIG_DATA + prev_entry = hass.config_entries.async_get_entry(existing_entry.entry_id) + assert not prev_entry + + +@pytest.mark.usefixtures("connect") +async def test_abort_invalid_unique_id(hass): + """Test we abort if uniqueid not available.""" + MockConfigEntry( + domain=DOMAIN, + data=CONFIG_DATA, + unique_id=MAC_ADDR, + ).add_to_hass(hass) + + with PATCH_GET_HOST: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=CONFIG_DATA, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "single_instance_allowed" + assert result["reason"] == "invalid_unique_id" -async def test_on_connect_failed(hass): +@pytest.mark.parametrize( + ["side_effect", "error"], + [ + (OSError, "cannot_connect"), + (TypeError, "unknown"), + (None, "cannot_connect"), + ], +) +async def test_on_connect_failed(hass, side_effect, error): """Test when we have errors connecting the router.""" flow_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER, "show_advanced_options": True}, ) - with patch("homeassistant.components.asuswrt.router.AsusWrt") as asus_wrt: - asus_wrt.return_value.connection.async_connect = AsyncMock() - asus_wrt.return_value.is_connected = False - result = await hass.config_entries.flow.async_configure( - flow_result["flow_id"], user_input=CONFIG_DATA - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "cannot_connect"} - - with patch("homeassistant.components.asuswrt.router.AsusWrt") as asus_wrt: - asus_wrt.return_value.connection.async_connect = AsyncMock(side_effect=OSError) - result = await hass.config_entries.flow.async_configure( - flow_result["flow_id"], user_input=CONFIG_DATA - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "cannot_connect"} - - with patch("homeassistant.components.asuswrt.router.AsusWrt") as asus_wrt: + with PATCH_GET_HOST, patch( + "homeassistant.components.asuswrt.router.AsusWrt" + ) as asus_wrt: asus_wrt.return_value.connection.async_connect = AsyncMock( - side_effect=TypeError + side_effect=side_effect ) + asus_wrt.return_value.is_connected = False + result = await hass.config_entries.flow.async_configure( flow_result["flow_id"], user_input=CONFIG_DATA ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "unknown"} + assert result["errors"] == {"base": error} async def test_options_flow(hass): @@ -202,7 +257,7 @@ async def test_options_flow(hass): ) config_entry.add_to_hass(hass) - with patch("homeassistant.components.asuswrt.async_setup_entry", return_value=True): + with PATCH_SETUP_ENTRY: await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() result = await hass.config_entries.options.async_init(config_entry.entry_id) diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 3e0c01071c1..483592302bf 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -7,7 +7,7 @@ import pytest from homeassistant.components import device_tracker, sensor from homeassistant.components.asuswrt.const import CONF_INTERFACE, DOMAIN -from homeassistant.components.asuswrt.sensor import DEFAULT_PREFIX +from homeassistant.components.asuswrt.router import DEFAULT_NAME from homeassistant.components.device_tracker.const import CONF_CONSIDER_HOME from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ( @@ -39,6 +39,8 @@ CONFIG_DATA = { CONF_MODE: "router", } +MAC_ADDR = "a1:b2:c3:d4:e5:f6" + MOCK_BYTES_TOTAL = [60000000000, 50000000000] MOCK_CURRENT_TRANSFER_RATES = [20000000, 10000000] MOCK_LOAD_AVG = [1.1, 1.2, 1.3] @@ -157,7 +159,7 @@ def mock_controller_connect_sens_fail(): yield service_mock -def _setup_entry(hass): +def _setup_entry(hass, unique_id=None): """Create mock config entry.""" entity_reg = er.async_get(hass) @@ -166,11 +168,11 @@ def _setup_entry(hass): domain=DOMAIN, data=CONFIG_DATA, options={CONF_CONSIDER_HOME: 60}, + unique_id=unique_id, ) # init variable - unique_id = DOMAIN - obj_prefix = slugify(DEFAULT_PREFIX) + obj_prefix = slugify(HOST if unique_id else DEFAULT_NAME) sensor_prefix = f"{sensor.DOMAIN}.{obj_prefix}" # Pre-enable the status sensor @@ -179,7 +181,7 @@ def _setup_entry(hass): entity_reg.async_get_or_create( sensor.DOMAIN, DOMAIN, - f"{unique_id} {DEFAULT_PREFIX} {sensor_name}", + f"{DOMAIN} {unique_id or DEFAULT_NAME} {sensor_name}", suggested_object_id=f"{obj_prefix}_{sensor_id}", disabled_by=None, ) @@ -202,15 +204,20 @@ def _setup_entry(hass): return config_entry, sensor_prefix +@pytest.mark.parametrize( + "entry_unique_id", + [None, MAC_ADDR], +) async def test_sensors( hass, connect, mock_devices, mock_available_temps, create_device_registry_devices, + entry_unique_id, ): """Test creating an AsusWRT sensor.""" - config_entry, sensor_prefix = _setup_entry(hass) + config_entry, sensor_prefix = _setup_entry(hass, entry_unique_id) config_entry.add_to_hass(hass) # initial devices setup diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py index f6d0695d97d..3c90d915966 100644 --- a/tests/components/auth/test_init.py +++ b/tests/components/auth/test_init.py @@ -81,6 +81,7 @@ async def test_login_new_user_and_trying_refresh_token(hass, aiohttp_client): assert ( await hass.auth.async_validate_access_token(tokens["access_token"]) is not None ) + assert tokens["ha_auth_provider"] == "insecure_example" # Use refresh token to get more tokens. resp = await client.post( diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 45f719a326f..bcbcf382892 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -6,7 +6,6 @@ from unittest.mock import Mock, patch import pytest -from homeassistant.components import logbook import homeassistant.components.automation as automation from homeassistant.components.automation import ( ATTR_SOURCE, @@ -53,7 +52,7 @@ from tests.common import ( async_mock_service, mock_restore_cache, ) -from tests.components.logbook.test_init import MockLazyEventPartialState +from tests.components.logbook.common import MockRow, mock_humanify @pytest.fixture @@ -1223,38 +1222,33 @@ async def test_logbook_humanify_automation_triggered_event(hass): hass.config.components.add("recorder") await async_setup_component(hass, automation.DOMAIN, {}) await async_setup_component(hass, "logbook", {}) - entity_attr_cache = logbook.EntityAttributeCache(hass) - event1, event2 = list( - logbook.humanify( - hass, - [ - MockLazyEventPartialState( - EVENT_AUTOMATION_TRIGGERED, - {ATTR_ENTITY_ID: "automation.hello", ATTR_NAME: "Hello Automation"}, - ), - MockLazyEventPartialState( - EVENT_AUTOMATION_TRIGGERED, - { - ATTR_ENTITY_ID: "automation.bye", - ATTR_NAME: "Bye Automation", - ATTR_SOURCE: "source of trigger", - }, - ), - ], - entity_attr_cache, - {}, - ) + event1, event2 = mock_humanify( + hass, + [ + MockRow( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_ENTITY_ID: "automation.hello", ATTR_NAME: "Hello Automation"}, + ), + MockRow( + EVENT_AUTOMATION_TRIGGERED, + { + ATTR_ENTITY_ID: "automation.bye", + ATTR_NAME: "Bye Automation", + ATTR_SOURCE: "source of trigger", + }, + ), + ], ) assert event1["name"] == "Hello Automation" assert event1["domain"] == "automation" - assert event1["message"] == "has been triggered" + assert event1["message"] == "triggered" assert event1["entity_id"] == "automation.hello" assert event2["name"] == "Bye Automation" assert event2["domain"] == "automation" - assert event2["message"] == "has been triggered by source of trigger" + assert event2["message"] == "triggered by source of trigger" assert event2["entity_id"] == "automation.bye" diff --git a/tests/components/automation/test_logbook.py b/tests/components/automation/test_logbook.py index e13ebdc17a1..1f726f478bd 100644 --- a/tests/components/automation/test_logbook.py +++ b/tests/components/automation/test_logbook.py @@ -1,9 +1,9 @@ """Test automation logbook.""" -from homeassistant.components import automation, logbook +from homeassistant.components import automation from homeassistant.core import Context from homeassistant.setup import async_setup_component -from tests.components.logbook.test_init import MockLazyEventPartialState +from tests.components.logbook.common import MockRow, mock_humanify async def test_humanify_automation_trigger_event(hass): @@ -11,44 +11,39 @@ async def test_humanify_automation_trigger_event(hass): hass.config.components.add("recorder") assert await async_setup_component(hass, "automation", {}) assert await async_setup_component(hass, "logbook", {}) - entity_attr_cache = logbook.EntityAttributeCache(hass) context = Context() - event1, event2 = list( - logbook.humanify( - hass, - [ - MockLazyEventPartialState( - automation.EVENT_AUTOMATION_TRIGGERED, - { - "name": "Bla", - "entity_id": "automation.bla", - "source": "state change of input_boolean.yo", - }, - context=context, - ), - MockLazyEventPartialState( - automation.EVENT_AUTOMATION_TRIGGERED, - { - "name": "Bla", - "entity_id": "automation.bla", - }, - context=context, - ), - ], - entity_attr_cache, - {}, - ) + event1, event2 = mock_humanify( + hass, + [ + MockRow( + automation.EVENT_AUTOMATION_TRIGGERED, + { + "name": "Bla", + "entity_id": "automation.bla", + "source": "state change of input_boolean.yo", + }, + context=context, + ), + MockRow( + automation.EVENT_AUTOMATION_TRIGGERED, + { + "name": "Bla", + "entity_id": "automation.bla", + }, + context=context, + ), + ], ) assert event1["name"] == "Bla" - assert event1["message"] == "has been triggered by state change of input_boolean.yo" + assert event1["message"] == "triggered by state change of input_boolean.yo" assert event1["source"] == "state change of input_boolean.yo" assert event1["context_id"] == context.id assert event1["entity_id"] == "automation.bla" assert event2["name"] == "Bla" - assert event2["message"] == "has been triggered" + assert event2["message"] == "triggered" assert event2["source"] is None assert event2["context_id"] == context.id assert event2["entity_id"] == "automation.bla" diff --git a/tests/components/baf/__init__.py b/tests/components/baf/__init__.py new file mode 100644 index 00000000000..4e435dc1a2e --- /dev/null +++ b/tests/components/baf/__init__.py @@ -0,0 +1,37 @@ +"""Tests for the Big Ass Fans integration.""" + + +import asyncio + +from aiobafi6 import Device + +MOCK_UUID = "1234" +MOCK_NAME = "Living Room Fan" + + +class MockBAFDevice(Device): + """A simple mock for a BAF Device.""" + + def __init__(self, async_wait_available_side_effect=None): + """Init simple mock.""" + self._async_wait_available_side_effect = async_wait_available_side_effect + + @property + def dns_sd_uuid(self): + """Mock the unique id.""" + return MOCK_UUID + + @property + def name(self): + """Mock the name of the device.""" + return MOCK_NAME + + async def async_wait_available(self): + """Mock async_wait_available.""" + if self._async_wait_available_side_effect: + raise self._async_wait_available_side_effect + return + + def async_run(self): + """Mock async_run.""" + return asyncio.Future() diff --git a/tests/components/baf/test_config_flow.py b/tests/components/baf/test_config_flow.py new file mode 100644 index 00000000000..77a83a9673b --- /dev/null +++ b/tests/components/baf/test_config_flow.py @@ -0,0 +1,200 @@ +"""Test the baf config flow.""" +import asyncio +from unittest.mock import patch + +from homeassistant import config_entries +from homeassistant.components import zeroconf +from homeassistant.components.baf.const import DOMAIN +from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) + +from . import MOCK_NAME, MOCK_UUID, MockBAFDevice + +from tests.common import MockConfigEntry + + +def _patch_device_config_flow(side_effect=None): + """Mock out the BAF Device object.""" + + def _create_mock_baf(*args, **kwargs): + return MockBAFDevice(side_effect) + + return patch("homeassistant.components.baf.config_flow.Device", _create_mock_baf) + + +async def test_form_user(hass): + """Test we get the user form.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with _patch_device_config_flow(), patch( + "homeassistant.components.baf.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_IP_ADDRESS: "127.0.0.1"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == MOCK_NAME + assert result2["data"] == {CONF_IP_ADDRESS: "127.0.0.1"} + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_cannot_connect(hass): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with _patch_device_config_flow(asyncio.TimeoutError): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_IP_ADDRESS: "127.0.0.1"}, + ) + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {CONF_IP_ADDRESS: "cannot_connect"} + + +async def test_form_unknown_exception(hass): + """Test we handle unknown exceptions.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with _patch_device_config_flow(Exception): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_IP_ADDRESS: "127.0.0.1"}, + ) + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} + + +async def test_zeroconf_discovery(hass): + """Test we can setup from zeroconf discovery.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=zeroconf.ZeroconfServiceInfo( + host="127.0.0.1", + addresses=["127.0.0.1"], + hostname="mock_hostname", + name="testfan", + port=None, + properties={"name": "My Fan", "model": "Haiku", "uuid": MOCK_UUID}, + type="mock_type", + ), + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + with patch( + "homeassistant.components.baf.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "My Fan" + assert result2["data"] == {CONF_IP_ADDRESS: "127.0.0.1"} + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_zeroconf_updates_existing_ip(hass): + """Test we can setup from zeroconf discovery.""" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_IP_ADDRESS: "127.0.0.2"}, unique_id=MOCK_UUID + ) + entry.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=zeroconf.ZeroconfServiceInfo( + host="127.0.0.1", + addresses=["127.0.0.1"], + hostname="mock_hostname", + name="testfan", + port=None, + properties={"name": "My Fan", "model": "Haiku", "uuid": MOCK_UUID}, + type="mock_type", + ), + ) + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert entry.data[CONF_IP_ADDRESS] == "127.0.0.1" + + +async def test_zeroconf_rejects_ipv6(hass): + """Test zeroconf discovery rejects ipv6.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=zeroconf.ZeroconfServiceInfo( + host="fd00::b27c:63bb:cc85:4ea0", + addresses=["fd00::b27c:63bb:cc85:4ea0"], + hostname="mock_hostname", + name="testfan", + port=None, + properties={"name": "My Fan", "model": "Haiku", "uuid": MOCK_UUID}, + type="mock_type", + ), + ) + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "ipv6_not_supported" + + +async def test_user_flow_is_not_blocked_by_discovery(hass): + """Test we can setup from the user flow when there is also a discovery.""" + discovery_result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=zeroconf.ZeroconfServiceInfo( + host="127.0.0.1", + addresses=["127.0.0.1"], + hostname="mock_hostname", + name="testfan", + port=None, + properties={"name": "My Fan", "model": "Haiku", "uuid": MOCK_UUID}, + type="mock_type", + ), + ) + assert discovery_result["type"] == RESULT_TYPE_FORM + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with _patch_device_config_flow(), patch( + "homeassistant.components.baf.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_IP_ADDRESS: "127.0.0.1"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == MOCK_NAME + assert result2["data"] == {CONF_IP_ADDRESS: "127.0.0.1"} + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/bayesian/test_binary_sensor.py b/tests/components/bayesian/test_binary_sensor.py index c2f289b0697..2f45e0e475e 100644 --- a/tests/components/bayesian/test_binary_sensor.py +++ b/tests/components/bayesian/test_binary_sensor.py @@ -16,6 +16,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import Context, callback +from homeassistant.helpers.event import async_track_state_change_event from homeassistant.setup import async_setup_component from tests.common import get_fixture_path @@ -709,8 +710,8 @@ async def test_template_triggers(hass): assert hass.states.get("binary_sensor.test_binary").state == STATE_OFF events = [] - hass.helpers.event.async_track_state_change_event( - "binary_sensor.test_binary", callback(lambda event: events.append(event)) + async_track_state_change_event( + hass, "binary_sensor.test_binary", callback(lambda event: events.append(event)) ) context = Context() @@ -748,8 +749,8 @@ async def test_state_triggers(hass): assert hass.states.get("binary_sensor.test_binary").state == STATE_OFF events = [] - hass.helpers.event.async_track_state_change_event( - "binary_sensor.test_binary", callback(lambda event: events.append(event)) + async_track_state_change_event( + hass, "binary_sensor.test_binary", callback(lambda event: events.append(event)) ) context = Context() diff --git a/tests/components/bmw_connected_drive/__init__.py b/tests/components/bmw_connected_drive/__init__.py index e1243fe2c0a..4774032b409 100644 --- a/tests/components/bmw_connected_drive/__init__.py +++ b/tests/components/bmw_connected_drive/__init__.py @@ -1 +1,28 @@ """Tests for the for the BMW Connected Drive integration.""" + +from homeassistant import config_entries +from homeassistant.components.bmw_connected_drive.const import ( + CONF_READ_ONLY, + DOMAIN as BMW_DOMAIN, +) +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME + +FIXTURE_USER_INPUT = { + CONF_USERNAME: "user@domain.com", + CONF_PASSWORD: "p4ssw0rd", + CONF_REGION: "rest_of_world", +} + +FIXTURE_CONFIG_ENTRY = { + "entry_id": "1", + "domain": BMW_DOMAIN, + "title": FIXTURE_USER_INPUT[CONF_USERNAME], + "data": { + CONF_USERNAME: FIXTURE_USER_INPUT[CONF_USERNAME], + CONF_PASSWORD: FIXTURE_USER_INPUT[CONF_PASSWORD], + CONF_REGION: FIXTURE_USER_INPUT[CONF_REGION], + }, + "options": {CONF_READ_ONLY: False}, + "source": config_entries.SOURCE_USER, + "unique_id": f"{FIXTURE_USER_INPUT[CONF_REGION]}-{FIXTURE_USER_INPUT[CONF_REGION]}", +} diff --git a/tests/components/bmw_connected_drive/test_config_flow.py b/tests/components/bmw_connected_drive/test_config_flow.py index 644da56a91d..d3c7c64dc99 100644 --- a/tests/components/bmw_connected_drive/test_config_flow.py +++ b/tests/components/bmw_connected_drive/test_config_flow.py @@ -1,35 +1,20 @@ """Test the for the BMW Connected Drive config flow.""" from unittest.mock import patch +from httpx import HTTPError + from homeassistant import config_entries, data_entry_flow from homeassistant.components.bmw_connected_drive.config_flow import DOMAIN from homeassistant.components.bmw_connected_drive.const import CONF_READ_ONLY -from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME +from homeassistant.const import CONF_USERNAME + +from . import FIXTURE_CONFIG_ENTRY, FIXTURE_USER_INPUT from tests.common import MockConfigEntry -FIXTURE_USER_INPUT = { - CONF_USERNAME: "user@domain.com", - CONF_PASSWORD: "p4ssw0rd", - CONF_REGION: "rest_of_world", -} FIXTURE_COMPLETE_ENTRY = FIXTURE_USER_INPUT.copy() FIXTURE_IMPORT_ENTRY = FIXTURE_USER_INPUT.copy() -FIXTURE_CONFIG_ENTRY = { - "entry_id": "1", - "domain": DOMAIN, - "title": FIXTURE_USER_INPUT[CONF_USERNAME], - "data": { - CONF_USERNAME: FIXTURE_USER_INPUT[CONF_USERNAME], - CONF_PASSWORD: FIXTURE_USER_INPUT[CONF_PASSWORD], - CONF_REGION: FIXTURE_USER_INPUT[CONF_REGION], - }, - "options": {CONF_READ_ONLY: False}, - "source": config_entries.SOURCE_USER, - "unique_id": f"{FIXTURE_USER_INPUT[CONF_REGION]}-{FIXTURE_USER_INPUT[CONF_REGION]}", -} - async def test_show_form(hass): """Test that the form is served with no input.""" @@ -48,8 +33,8 @@ async def test_connection_error(hass): pass with patch( - "bimmer_connected.account.ConnectedDriveAccount._get_oauth_token", - side_effect=OSError, + "bimmer_connected.api.authentication.MyBMWAuthentication.login", + side_effect=HTTPError("login failure"), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -65,7 +50,7 @@ async def test_connection_error(hass): async def test_full_user_flow_implementation(hass): """Test registering an integration and finishing flow works.""" with patch( - "bimmer_connected.account.ConnectedDriveAccount._get_vehicles", + "bimmer_connected.account.MyBMWAccount.get_vehicles", return_value=[], ), patch( "homeassistant.components.bmw_connected_drive.async_setup_entry", @@ -86,7 +71,7 @@ async def test_full_user_flow_implementation(hass): async def test_options_flow_implementation(hass): """Test config flow options.""" with patch( - "bimmer_connected.account.ConnectedDriveAccount._get_vehicles", + "bimmer_connected.account.MyBMWAccount.get_vehicles", return_value=[], ), patch( "homeassistant.components.bmw_connected_drive.async_setup_entry", @@ -104,13 +89,13 @@ async def test_options_flow_implementation(hass): result = await hass.config_entries.options.async_configure( result["flow_id"], - user_input={CONF_READ_ONLY: False}, + user_input={CONF_READ_ONLY: True}, ) await hass.async_block_till_done() assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"] == { - CONF_READ_ONLY: False, + CONF_READ_ONLY: True, } assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/bmw_connected_drive/test_init.py b/tests/components/bmw_connected_drive/test_init.py new file mode 100644 index 00000000000..70a64090203 --- /dev/null +++ b/tests/components/bmw_connected_drive/test_init.py @@ -0,0 +1,136 @@ +"""Test Axis component setup process.""" +from unittest.mock import patch + +import pytest + +from homeassistant.components.bmw_connected_drive.const import DOMAIN as BMW_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import FIXTURE_CONFIG_ENTRY + +from tests.common import MockConfigEntry + +VIN = "WBYYYYYYYYYYYYYYY" +VEHICLE_NAME = "i3 (+ REX)" +VEHICLE_NAME_SLUG = "i3_rex" + + +@pytest.mark.parametrize( + "entitydata,old_unique_id,new_unique_id", + [ + ( + { + "domain": SENSOR_DOMAIN, + "platform": BMW_DOMAIN, + "unique_id": f"{VIN}-charging_level_hv", + "suggested_object_id": f"{VEHICLE_NAME} charging_level_hv", + "disabled_by": None, + }, + f"{VIN}-charging_level_hv", + f"{VIN}-remaining_battery_percent", + ), + ( + { + "domain": SENSOR_DOMAIN, + "platform": BMW_DOMAIN, + "unique_id": f"{VIN}-remaining_range_total", + "suggested_object_id": f"{VEHICLE_NAME} remaining_range_total", + "disabled_by": None, + }, + f"{VIN}-remaining_range_total", + f"{VIN}-remaining_range_total", + ), + ], +) +async def test_migrate_unique_ids( + hass: HomeAssistant, + entitydata: dict, + old_unique_id: str, + new_unique_id: str, +) -> None: + """Test successful migration of entity unique_ids.""" + mock_config_entry = MockConfigEntry(**FIXTURE_CONFIG_ENTRY) + mock_config_entry.add_to_hass(hass) + + entity_registry = er.async_get(hass) + entity: er.RegistryEntry = entity_registry.async_get_or_create( + **entitydata, + config_entry=mock_config_entry, + ) + + assert entity.unique_id == old_unique_id + + with patch( + "bimmer_connected.account.MyBMWAccount.get_vehicles", + return_value=[], + ): + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + entity_migrated = entity_registry.async_get(entity.entity_id) + assert entity_migrated + assert entity_migrated.unique_id == new_unique_id + + +@pytest.mark.parametrize( + "entitydata,old_unique_id,new_unique_id", + [ + ( + { + "domain": SENSOR_DOMAIN, + "platform": BMW_DOMAIN, + "unique_id": f"{VIN}-charging_level_hv", + "suggested_object_id": f"{VEHICLE_NAME} charging_level_hv", + "disabled_by": None, + }, + f"{VIN}-charging_level_hv", + f"{VIN}-remaining_battery_percent", + ), + ], +) +async def test_dont_migrate_unique_ids( + hass: HomeAssistant, + entitydata: dict, + old_unique_id: str, + new_unique_id: str, +) -> None: + """Test successful migration of entity unique_ids.""" + mock_config_entry = MockConfigEntry(**FIXTURE_CONFIG_ENTRY) + mock_config_entry.add_to_hass(hass) + + entity_registry = er.async_get(hass) + + # create existing entry with new_unique_id + existing_entity = entity_registry.async_get_or_create( + SENSOR_DOMAIN, + BMW_DOMAIN, + unique_id=f"{VIN}-remaining_battery_percent", + suggested_object_id=f"{VEHICLE_NAME} remaining_battery_percent", + config_entry=mock_config_entry, + ) + + entity: er.RegistryEntry = entity_registry.async_get_or_create( + **entitydata, + config_entry=mock_config_entry, + ) + + assert entity.unique_id == old_unique_id + + with patch( + "bimmer_connected.account.MyBMWAccount.get_vehicles", + return_value=[], + ): + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + entity_migrated = entity_registry.async_get(entity.entity_id) + assert entity_migrated + assert entity_migrated.unique_id == old_unique_id + + entity_not_changed = entity_registry.async_get(existing_entity.entity_id) + assert entity_not_changed + assert entity_not_changed.unique_id == new_unique_id + + assert entity_migrated != entity_not_changed diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index 9c53c0afb8b..4b45a4016c0 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -8,7 +8,7 @@ from typing import Any from unittest.mock import MagicMock, patch from aiohttp.client_exceptions import ClientResponseError -from bond_api import DeviceType +from bond_async import DeviceType from homeassistant import core from homeassistant.components.bond.const import DOMAIN as BOND_DOMAIN diff --git a/tests/components/bond/test_button.py b/tests/components/bond/test_button.py index ee6e98b8462..4411b25657b 100644 --- a/tests/components/bond/test_button.py +++ b/tests/components/bond/test_button.py @@ -1,6 +1,6 @@ """Tests for the Bond button device.""" -from bond_api import Action, DeviceType +from bond_async import Action, DeviceType from homeassistant import core from homeassistant.components.bond.button import STEP_SIZE diff --git a/tests/components/bond/test_cover.py b/tests/components/bond/test_cover.py index 7a27617d607..ccb44402a3e 100644 --- a/tests/components/bond/test_cover.py +++ b/tests/components/bond/test_cover.py @@ -1,18 +1,25 @@ """Tests for the Bond cover device.""" from datetime import timedelta -from bond_api import Action, DeviceType +from bond_async import Action, DeviceType from homeassistant import core -from homeassistant.components.cover import DOMAIN as COVER_DOMAIN, STATE_CLOSED +from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, + ATTR_POSITION, + DOMAIN as COVER_DOMAIN, + STATE_CLOSED, +) from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER_TILT, SERVICE_OPEN_COVER, SERVICE_OPEN_COVER_TILT, + SERVICE_SET_COVER_POSITION, SERVICE_STOP_COVER, SERVICE_STOP_COVER_TILT, + STATE_OPEN, STATE_UNKNOWN, ) from homeassistant.helpers import entity_registry as er @@ -38,6 +45,15 @@ def shades(name: str): } +def shades_with_position(name: str): + """Create motorized shades that supports set position.""" + return { + "name": name, + "type": DeviceType.MOTORIZED_SHADES, + "actions": [Action.OPEN, Action.CLOSE, Action.HOLD, Action.SET_POSITION], + } + + def tilt_only_shades(name: str): """Create motorized shades that only tilt.""" return { @@ -236,3 +252,64 @@ async def test_cover_available(hass: core.HomeAssistant): await help_test_entity_available( hass, COVER_DOMAIN, shades("name-1"), "cover.name_1" ) + + +async def test_set_position_cover(hass: core.HomeAssistant): + """Tests that set position cover command delegates to API.""" + await setup_platform( + hass, + COVER_DOMAIN, + shades_with_position("name-1"), + bond_device_id="test-device-id", + ) + + with patch_bond_action() as mock_hold, patch_bond_device_state( + return_value={"position": 0, "open": 1} + ): + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_POSITION, + {ATTR_ENTITY_ID: "cover.name_1", ATTR_POSITION: 100}, + blocking=True, + ) + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + mock_hold.assert_called_once_with("test-device-id", Action.set_position(0)) + entity_state = hass.states.get("cover.name_1") + assert entity_state.state == STATE_OPEN + assert entity_state.attributes[ATTR_CURRENT_POSITION] == 100 + + with patch_bond_action() as mock_hold, patch_bond_device_state( + return_value={"position": 100, "open": 0} + ): + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_POSITION, + {ATTR_ENTITY_ID: "cover.name_1", ATTR_POSITION: 0}, + blocking=True, + ) + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + mock_hold.assert_called_once_with("test-device-id", Action.set_position(100)) + entity_state = hass.states.get("cover.name_1") + assert entity_state.state == STATE_CLOSED + assert entity_state.attributes[ATTR_CURRENT_POSITION] == 0 + + with patch_bond_action() as mock_hold, patch_bond_device_state( + return_value={"position": 40, "open": 1} + ): + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_POSITION, + {ATTR_ENTITY_ID: "cover.name_1", ATTR_POSITION: 60}, + blocking=True, + ) + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + mock_hold.assert_called_once_with("test-device-id", Action.set_position(40)) + entity_state = hass.states.get("cover.name_1") + assert entity_state.state == STATE_OPEN + assert entity_state.attributes[ATTR_CURRENT_POSITION] == 60 diff --git a/tests/components/bond/test_entity.py b/tests/components/bond/test_entity.py index 122e9c2f04e..9245f4513ed 100644 --- a/tests/components/bond/test_entity.py +++ b/tests/components/bond/test_entity.py @@ -3,7 +3,8 @@ import asyncio from datetime import timedelta from unittest.mock import patch -from bond_api import BPUPSubscriptions, DeviceType +from bond_async import BPUPSubscriptions, DeviceType +from bond_async.bpup import BPUP_ALIVE_TIMEOUT from homeassistant import core from homeassistant.components import fan @@ -44,24 +45,47 @@ async def test_bpup_goes_offline_and_recovers_same_entity(hass: core.HomeAssista bpup_subs.notify( { "s": 200, - "t": "bond/test-device-id/update", + "t": "devices/test-device-id/state", "b": {"power": 1, "speed": 3, "direction": 0}, } ) await hass.async_block_till_done() assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 100 + # Send a message for the wrong device to make sure its ignored + # we should never get this callback + bpup_subs.notify( + { + "s": 200, + "t": "devices/other-device-id/state", + "b": {"power": 1, "speed": 1, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 100 + + # Test we ignore messages for the wrong topic + bpup_subs.notify( + { + "s": 200, + "t": "devices/test-device-id/other_topic", + "b": {"power": 1, "speed": 1, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 100 + bpup_subs.notify( { "s": 200, - "t": "bond/test-device-id/update", + "t": "devices/test-device-id/state", "b": {"power": 1, "speed": 1, "direction": 0}, } ) await hass.async_block_till_done() assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 33 - bpup_subs.last_message_time = 0 + bpup_subs.last_message_time = -BPUP_ALIVE_TIMEOUT with patch_bond_device_state(side_effect=asyncio.TimeoutError): async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) await hass.async_block_till_done() @@ -75,7 +99,7 @@ async def test_bpup_goes_offline_and_recovers_same_entity(hass: core.HomeAssista bpup_subs.notify( { "s": 200, - "t": "bond/test-device-id/update", + "t": "devices/test-device-id/state", "b": {"power": 1, "speed": 2, "direction": 0}, } ) @@ -106,7 +130,7 @@ async def test_bpup_goes_offline_and_recovers_different_entity( bpup_subs.notify( { "s": 200, - "t": "bond/test-device-id/update", + "t": "devices/test-device-id/state", "b": {"power": 1, "speed": 3, "direction": 0}, } ) @@ -116,14 +140,14 @@ async def test_bpup_goes_offline_and_recovers_different_entity( bpup_subs.notify( { "s": 200, - "t": "bond/test-device-id/update", + "t": "devices/test-device-id/state", "b": {"power": 1, "speed": 1, "direction": 0}, } ) await hass.async_block_till_done() assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 33 - bpup_subs.last_message_time = 0 + bpup_subs.last_message_time = -BPUP_ALIVE_TIMEOUT with patch_bond_device_state(side_effect=asyncio.TimeoutError): async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) await hass.async_block_till_done() @@ -133,7 +157,7 @@ async def test_bpup_goes_offline_and_recovers_different_entity( bpup_subs.notify( { "s": 200, - "t": "bond/not-this-device-id/update", + "t": "devices/not-this-device-id/state", "b": {"power": 1, "speed": 2, "direction": 0}, } ) diff --git a/tests/components/bond/test_fan.py b/tests/components/bond/test_fan.py index 061e94595bf..7c860e68efc 100644 --- a/tests/components/bond/test_fan.py +++ b/tests/components/bond/test_fan.py @@ -4,7 +4,7 @@ from __future__ import annotations from datetime import timedelta from unittest.mock import call -from bond_api import Action, DeviceType, Direction +from bond_async import Action, DeviceType, Direction import pytest from homeassistant import core diff --git a/tests/components/bond/test_init.py b/tests/components/bond/test_init.py index 88615d98122..03eb490b65e 100644 --- a/tests/components/bond/test_init.py +++ b/tests/components/bond/test_init.py @@ -3,7 +3,7 @@ import asyncio from unittest.mock import MagicMock, Mock from aiohttp import ClientConnectionError, ClientResponseError -from bond_api import DeviceType +from bond_async import DeviceType import pytest from homeassistant.components.bond.const import DOMAIN diff --git a/tests/components/bond/test_light.py b/tests/components/bond/test_light.py index 6556c25efe2..c7d8f195423 100644 --- a/tests/components/bond/test_light.py +++ b/tests/components/bond/test_light.py @@ -1,7 +1,7 @@ """Tests for the Bond light device.""" from datetime import timedelta -from bond_api import Action, DeviceType +from bond_async import Action, DeviceType import pytest from homeassistant import core diff --git a/tests/components/bond/test_switch.py b/tests/components/bond/test_switch.py index 619eac69e71..b63bad2d431 100644 --- a/tests/components/bond/test_switch.py +++ b/tests/components/bond/test_switch.py @@ -1,7 +1,7 @@ """Tests for the Bond switch device.""" from datetime import timedelta -from bond_api import Action, DeviceType +from bond_async import Action, DeviceType import pytest from homeassistant import core diff --git a/tests/components/caldav/test_calendar.py b/tests/components/caldav/test_calendar.py index 2131eebe997..f35fa609e4a 100644 --- a/tests/components/caldav/test_calendar.py +++ b/tests/components/caldav/test_calendar.py @@ -937,5 +937,7 @@ async def test_get_events_custom_calendars(hass, calendar, get_api_events): "end": {"dateTime": "2017-11-27T10:00:00-08:00"}, "start": {"dateTime": "2017-11-27T09:00:00-08:00"}, "summary": "This is a normal event", + "location": "Hamburg", + "description": "Surprisingly rainy", } ] diff --git a/tests/components/calendar/test_init.py b/tests/components/calendar/test_init.py index 26f9320a196..0a91f58b0b2 100644 --- a/tests/components/calendar/test_init.py +++ b/tests/components/calendar/test_init.py @@ -57,3 +57,5 @@ async def test_events_http_api_shim(hass, hass_client): assert response.status == HTTPStatus.OK events = await response.json() assert events[0]["summary"] == "Future Event" + assert events[0]["description"] == "Future Description" + assert events[0]["location"] == "Future Location" diff --git a/tests/components/calendar/test_trigger.py b/tests/components/calendar/test_trigger.py index 65655bc283c..a3b5bacca59 100644 --- a/tests/components/calendar/test_trigger.py +++ b/tests/components/calendar/test_trigger.py @@ -126,13 +126,15 @@ async def setup_calendar(hass: HomeAssistant, fake_schedule: FakeSchedule) -> No await hass.async_block_till_done() -async def create_automation(hass: HomeAssistant, event_type: str) -> None: +async def create_automation(hass: HomeAssistant, event_type: str, offset=None) -> None: """Register an automation.""" trigger_data = { "platform": calendar.DOMAIN, "entity_id": CALENDAR_ENTITY_ID, "event": event_type, } + if offset: + trigger_data["offset"] = offset assert await async_setup_component( hass, automation.DOMAIN, @@ -178,7 +180,43 @@ async def test_event_start_trigger(hass, calls, fake_schedule): assert len(calls()) == 0 await fake_schedule.fire_until( - datetime.datetime.fromisoformat("2022-04-19 11:15:00+00:00") + datetime.datetime.fromisoformat("2022-04-19 11:15:00+00:00"), + ) + assert calls() == [ + { + "platform": "calendar", + "event": EVENT_START, + "calendar_event": event_data, + } + ] + + +@pytest.mark.parametrize( + "offset_str, offset_delta", + [ + ("-01:00", datetime.timedelta(hours=-1)), + ("+01:00", datetime.timedelta(hours=1)), + ], +) +async def test_event_start_trigger_with_offset( + hass, calls, fake_schedule, offset_str, offset_delta +): + """Test the a calendar trigger based on start time with an offset.""" + event_data = fake_schedule.create_event( + start=datetime.datetime.fromisoformat("2022-04-19 12:00:00+00:00"), + end=datetime.datetime.fromisoformat("2022-04-19 12:30:00+00:00"), + ) + await create_automation(hass, EVENT_START, offset=offset_str) + + # No calls yet + await fake_schedule.fire_until( + datetime.datetime.fromisoformat("2022-04-19 11:55:00+00:00") + offset_delta, + ) + assert len(calls()) == 0 + + # Event has started w/ offset + await fake_schedule.fire_until( + datetime.datetime.fromisoformat("2022-04-19 12:05:00+00:00") + offset_delta, ) assert calls() == [ { @@ -216,6 +254,42 @@ async def test_event_end_trigger(hass, calls, fake_schedule): ] +@pytest.mark.parametrize( + "offset_str, offset_delta", + [ + ("-01:00", datetime.timedelta(hours=-1)), + ("+01:00", datetime.timedelta(hours=1)), + ], +) +async def test_event_end_trigger_with_offset( + hass, calls, fake_schedule, offset_str, offset_delta +): + """Test the a calendar trigger based on end time with an offset.""" + event_data = fake_schedule.create_event( + start=datetime.datetime.fromisoformat("2022-04-19 12:00:00+00:00"), + end=datetime.datetime.fromisoformat("2022-04-19 12:30:00+00:00"), + ) + await create_automation(hass, EVENT_END, offset=offset_str) + + # No calls yet + await fake_schedule.fire_until( + datetime.datetime.fromisoformat("2022-04-19 12:05:00+00:00") + offset_delta, + ) + assert len(calls()) == 0 + + # Event has started w/ offset + await fake_schedule.fire_until( + datetime.datetime.fromisoformat("2022-04-19 12:35:00+00:00") + offset_delta, + ) + assert calls() == [ + { + "platform": "calendar", + "event": EVENT_END, + "calendar_event": event_data, + } + ] + + async def test_calendar_trigger_with_no_events(hass, calls, fake_schedule): """Test a calendar trigger setup with no events.""" diff --git a/tests/components/camera/test_media_source.py b/tests/components/camera/test_media_source.py index f684d81a2b1..4134e9b1151 100644 --- a/tests/components/camera/test_media_source.py +++ b/tests/components/camera/test_media_source.py @@ -5,7 +5,7 @@ import pytest from homeassistant.components import media_source from homeassistant.components.camera.const import StreamType -from homeassistant.components.stream.const import FORMAT_CONTENT_TYPE +from homeassistant.components.stream import FORMAT_CONTENT_TYPE from homeassistant.setup import async_setup_component @@ -62,7 +62,7 @@ async def test_resolving(hass, mock_camera_hls): return_value="http://example.com/stream", ): item = await media_source.async_resolve_media( - hass, "media-source://camera/camera.demo_camera" + hass, "media-source://camera/camera.demo_camera", None ) assert item is not None assert item.url == "http://example.com/stream" @@ -74,7 +74,7 @@ async def test_resolving_errors(hass, mock_camera_hls): with pytest.raises(media_source.Unresolvable) as exc_info: await media_source.async_resolve_media( - hass, "media-source://camera/camera.demo_camera" + hass, "media-source://camera/camera.demo_camera", None ) assert str(exc_info.value) == "Stream integration not loaded" @@ -82,7 +82,7 @@ async def test_resolving_errors(hass, mock_camera_hls): with pytest.raises(media_source.Unresolvable) as exc_info: await media_source.async_resolve_media( - hass, "media-source://camera/camera.non_existing" + hass, "media-source://camera/camera.non_existing", None ) assert str(exc_info.value) == "Could not resolve media item: camera.non_existing" @@ -91,13 +91,13 @@ async def test_resolving_errors(hass, mock_camera_hls): new_callable=PropertyMock(return_value=StreamType.WEB_RTC), ): await media_source.async_resolve_media( - hass, "media-source://camera/camera.demo_camera" + hass, "media-source://camera/camera.demo_camera", None ) assert str(exc_info.value) == "Camera does not support MJPEG or HLS streaming." with pytest.raises(media_source.Unresolvable) as exc_info: await media_source.async_resolve_media( - hass, "media-source://camera/camera.demo_camera" + hass, "media-source://camera/camera.demo_camera", None ) assert ( str(exc_info.value) == "camera.demo_camera does not support play stream service" diff --git a/tests/components/cast/test_helpers.py b/tests/components/cast/test_helpers.py index d729d36a225..8ae73449b43 100644 --- a/tests/components/cast/test_helpers.py +++ b/tests/components/cast/test_helpers.py @@ -27,6 +27,11 @@ from tests.common import load_fixture "rthkaudio2.m3u8", "application/vnd.apple.mpegurl", ), + ( + "https://rthkaudio2-lh.akamaihd.net/i/radio2_1@355865/master.m3u8", + "rthkaudio2.m3u8", + None, + ), ), ) async def test_hls_playlist_supported(hass, aioclient_mock, url, fixture, content_type): @@ -38,11 +43,12 @@ async def test_hls_playlist_supported(hass, aioclient_mock, url, fixture, conten @pytest.mark.parametrize( - "url,fixture,expected_playlist", + "url,fixture,content_type,expected_playlist", ( ( "https://sverigesradio.se/topsy/direkt/209-hi-mp3.m3u", "209-hi-mp3.m3u", + "audio/x-mpegurl", [ PlaylistItem( length=["-1"], @@ -54,6 +60,7 @@ async def test_hls_playlist_supported(hass, aioclient_mock, url, fixture, conten ( "https://sverigesradio.se/topsy/direkt/209-hi-mp3.m3u", "209-hi-mp3_bad_extinf.m3u", + "audio/x-mpegurl", [ PlaylistItem( length=None, @@ -65,6 +72,7 @@ async def test_hls_playlist_supported(hass, aioclient_mock, url, fixture, conten ( "https://sverigesradio.se/topsy/direkt/209-hi-mp3.m3u", "209-hi-mp3_no_extinf.m3u", + "audio/x-mpegurl", [ PlaylistItem( length=None, @@ -76,6 +84,7 @@ async def test_hls_playlist_supported(hass, aioclient_mock, url, fixture, conten ( "http://sverigesradio.se/topsy/direkt/164-hi-aac.pls", "164-hi-aac.pls", + "audio/x-mpegurl", [ PlaylistItem( length="-1", @@ -86,9 +95,12 @@ async def test_hls_playlist_supported(hass, aioclient_mock, url, fixture, conten ), ), ) -async def test_parse_playlist(hass, aioclient_mock, url, fixture, expected_playlist): +async def test_parse_playlist( + hass, aioclient_mock, url, fixture, content_type, expected_playlist +): """Test playlist parsing of HLS playlist.""" - aioclient_mock.get(url, text=load_fixture(fixture, "cast")) + headers = {"content-type": content_type} + aioclient_mock.get(url, text=load_fixture(fixture, "cast"), headers=headers) playlist = await parse_playlist(hass, url) assert expected_playlist == playlist diff --git a/tests/components/cloud/test_binary_sensor.py b/tests/components/cloud/test_binary_sensor.py index c9c9d53981e..fb2ca562715 100644 --- a/tests/components/cloud/test_binary_sensor.py +++ b/tests/components/cloud/test_binary_sensor.py @@ -2,6 +2,8 @@ from unittest.mock import Mock, patch from homeassistant.components.cloud.const import DISPATCHER_REMOTE_UPDATE +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_setup_component @@ -13,9 +15,7 @@ async def test_remote_connection_sensor(hass): assert hass.states.get("binary_sensor.remote_ui") is None # Fake connection/discovery - await hass.helpers.discovery.async_load_platform( - "binary_sensor", "cloud", {}, {"cloud": {}} - ) + await async_load_platform(hass, "binary_sensor", "cloud", {}, {"cloud": {}}) # Mock test env cloud = hass.data["cloud"] = Mock() @@ -29,14 +29,14 @@ async def test_remote_connection_sensor(hass): with patch("homeassistant.components.cloud.binary_sensor.WAIT_UNTIL_CHANGE", 0): cloud.remote.is_connected = False cloud.remote.certificate = object() - hass.helpers.dispatcher.async_dispatcher_send(DISPATCHER_REMOTE_UPDATE, {}) + async_dispatcher_send(hass, DISPATCHER_REMOTE_UPDATE, {}) await hass.async_block_till_done() state = hass.states.get("binary_sensor.remote_ui") assert state.state == "off" cloud.remote.is_connected = True - hass.helpers.dispatcher.async_dispatcher_send(DISPATCHER_REMOTE_UPDATE, {}) + async_dispatcher_send(hass, DISPATCHER_REMOTE_UPDATE, {}) await hass.async_block_till_done() state = hass.states.get("binary_sensor.remote_ui") diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 6366eca4c6d..bb0d67fc306 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -12,7 +12,7 @@ from homeassistant.components.config import config_entries from homeassistant.config_entries import HANDLERS, ConfigFlow from homeassistant.core import callback from homeassistant.generated import config_flows -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_entry_flow, config_validation as cv from homeassistant.setup import async_setup_component from tests.common import ( @@ -68,9 +68,7 @@ async def test_get_entries(hass, client, clear_handlers): """Return options flow support for this handler.""" return True - hass.helpers.config_entry_flow.register_discovery_flow( - "comp2", "Comp 2", lambda: None - ) + config_entry_flow.register_discovery_flow("comp2", "Comp 2", lambda: None) entry = MockConfigEntry( domain="comp1", diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 8d3eb105896..b6a21fb9fa6 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -636,7 +636,7 @@ async def test_add_new_binary_sensor(hass, aioclient_mock, mock_deconz_websocket assert hass.states.get("binary_sensor.presence_sensor").state == STATE_OFF -async def test_add_new_binary_sensor_ignored( +async def test_add_new_binary_sensor_ignored_load_entities_on_service_call( hass, aioclient_mock, mock_deconz_websocket ): """Test that adding a new binary sensor is not allowed.""" @@ -683,3 +683,54 @@ async def test_add_new_binary_sensor_ignored( assert len(hass.states.async_all()) == 1 assert hass.states.get("binary_sensor.presence_sensor") + + +async def test_add_new_binary_sensor_ignored_load_entities_on_options_change( + hass, aioclient_mock, mock_deconz_websocket +): + """Test that adding a new binary sensor is not allowed.""" + sensor = { + "name": "Presence sensor", + "type": "ZHAPresence", + "state": {"presence": False}, + "config": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + } + event_added_sensor = { + "t": "event", + "e": "added", + "r": "sensors", + "id": "1", + "sensor": sensor, + } + + config_entry = await setup_deconz_integration( + hass, + aioclient_mock, + options={CONF_MASTER_GATEWAY: True, CONF_ALLOW_NEW_DEVICES: False}, + ) + + assert len(hass.states.async_all()) == 0 + + await mock_deconz_websocket(data=event_added_sensor) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + assert not hass.states.get("binary_sensor.presence_sensor") + + entity_registry = er.async_get(hass) + assert ( + len(async_entries_for_config_entry(entity_registry, config_entry.entry_id)) == 0 + ) + + aioclient_mock.clear_requests() + data = {"config": {}, "groups": {}, "lights": {}, "sensors": {"1": sensor}} + mock_deconz_request(aioclient_mock, config_entry.data, data) + + hass.config_entries.async_update_entry( + config_entry, options={CONF_ALLOW_NEW_DEVICES: True} + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + assert hass.states.get("binary_sensor.presence_sensor") diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index 5b04b459278..e697edd5a9a 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -22,7 +22,7 @@ from homeassistant.const import ( CONF_UNIQUE_ID, STATE_UNAVAILABLE, ) -from homeassistant.helpers.device_registry import async_entries_for_config_entry +from homeassistant.helpers import device_registry as dr from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -73,12 +73,13 @@ async def test_deconz_events(hass, aioclient_mock, mock_deconz_websocket): with patch.dict(DECONZ_WEB_REQUEST, data): config_entry = await setup_deconz_integration(hass, aioclient_mock) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) assert len(hass.states.async_all()) == 3 # 5 switches + 2 additional devices for deconz service and host assert ( - len(async_entries_for_config_entry(device_registry, config_entry.entry_id)) == 7 + len(dr.async_entries_for_config_entry(device_registry, config_entry.entry_id)) + == 7 ) assert hass.states.get("sensor.switch_2_battery").state == "100" assert hass.states.get("sensor.switch_3_battery").state == "100" @@ -267,12 +268,13 @@ async def test_deconz_alarm_events(hass, aioclient_mock, mock_deconz_websocket): with patch.dict(DECONZ_WEB_REQUEST, data): config_entry = await setup_deconz_integration(hass, aioclient_mock) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) assert len(hass.states.async_all()) == 4 # 1 alarm control device + 2 additional devices for deconz service and host assert ( - len(async_entries_for_config_entry(device_registry, config_entry.entry_id)) == 3 + len(dr.async_entries_for_config_entry(device_registry, config_entry.entry_id)) + == 3 ) captured_events = async_capture_events(hass, CONF_DECONZ_ALARM_EVENT) @@ -435,9 +437,10 @@ async def test_deconz_events_bad_unique_id(hass, aioclient_mock, mock_deconz_web with patch.dict(DECONZ_WEB_REQUEST, data): config_entry = await setup_deconz_integration(hass, aioclient_mock) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) assert len(hass.states.async_all()) == 1 assert ( - len(async_entries_for_config_entry(device_registry, config_entry.entry_id)) == 2 + len(dr.async_entries_for_config_entry(device_registry, config_entry.entry_id)) + == 2 ) diff --git a/tests/components/deconz/test_logbook.py b/tests/components/deconz/test_logbook.py index bfd5126c9db..9ba0799d04e 100644 --- a/tests/components/deconz/test_logbook.py +++ b/tests/components/deconz/test_logbook.py @@ -2,7 +2,6 @@ from unittest.mock import patch -from homeassistant.components import logbook from homeassistant.components.deconz.const import CONF_GESTURE, DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.deconz_event import ( CONF_DECONZ_ALARM_EVENT, @@ -16,12 +15,13 @@ from homeassistant.const import ( CONF_UNIQUE_ID, STATE_ALARM_ARMED_AWAY, ) +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from homeassistant.util import slugify from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration -from tests.components.logbook.test_init import MockLazyEventPartialState +from tests.components.logbook.common import MockRow, mock_humanify async def test_humanifying_deconz_alarm_event(hass, aioclient_mock): @@ -57,7 +57,7 @@ async def test_humanifying_deconz_alarm_event(hass, aioclient_mock): with patch.dict(DECONZ_WEB_REQUEST, data): await setup_deconz_integration(hass, aioclient_mock) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) keypad_event_id = slugify(data["sensors"]["1"]["name"]) keypad_serial = data["sensors"]["1"]["uniqueid"].split("-", 1)[0] @@ -67,31 +67,26 @@ async def test_humanifying_deconz_alarm_event(hass, aioclient_mock): hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) - entity_attr_cache = logbook.EntityAttributeCache(hass) - events = list( - logbook.humanify( - hass, - [ - MockLazyEventPartialState( - CONF_DECONZ_ALARM_EVENT, - { - CONF_CODE: 1234, - CONF_DEVICE_ID: keypad_entry.id, - CONF_EVENT: STATE_ALARM_ARMED_AWAY, - CONF_ID: keypad_event_id, - CONF_UNIQUE_ID: keypad_serial, - }, - ), - ], - entity_attr_cache, - {}, - ) + events = mock_humanify( + hass, + [ + MockRow( + CONF_DECONZ_ALARM_EVENT, + { + CONF_CODE: 1234, + CONF_DEVICE_ID: keypad_entry.id, + CONF_EVENT: STATE_ALARM_ARMED_AWAY, + CONF_ID: keypad_event_id, + CONF_UNIQUE_ID: keypad_serial, + }, + ), + ], ) assert events[0]["name"] == "Keypad" assert events[0]["domain"] == "deconz" - assert events[0]["message"] == "fired event 'armed_away'." + assert events[0]["message"] == "fired event 'armed_away'" async def test_humanifying_deconz_event(hass, aioclient_mock): @@ -133,7 +128,7 @@ async def test_humanifying_deconz_event(hass, aioclient_mock): with patch.dict(DECONZ_WEB_REQUEST, data): await setup_deconz_integration(hass, aioclient_mock) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) switch_event_id = slugify(data["sensors"]["1"]["name"]) switch_serial = data["sensors"]["1"]["uniqueid"].split("-", 1)[0] @@ -161,84 +156,79 @@ async def test_humanifying_deconz_event(hass, aioclient_mock): hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) - entity_attr_cache = logbook.EntityAttributeCache(hass) - events = list( - logbook.humanify( - hass, - [ - # Event without matching device trigger - MockLazyEventPartialState( - CONF_DECONZ_EVENT, - { - CONF_DEVICE_ID: switch_entry.id, - CONF_EVENT: 2000, - CONF_ID: switch_event_id, - CONF_UNIQUE_ID: switch_serial, - }, - ), - # Event with matching device trigger - MockLazyEventPartialState( - CONF_DECONZ_EVENT, - { - CONF_DEVICE_ID: hue_remote_entry.id, - CONF_EVENT: 2001, - CONF_ID: hue_remote_event_id, - CONF_UNIQUE_ID: hue_remote_serial, - }, - ), - # Gesture with matching device trigger - MockLazyEventPartialState( - CONF_DECONZ_EVENT, - { - CONF_DEVICE_ID: xiaomi_cube_entry.id, - CONF_GESTURE: 1, - CONF_ID: xiaomi_cube_event_id, - CONF_UNIQUE_ID: xiaomi_cube_serial, - }, - ), - # Unsupported device trigger - MockLazyEventPartialState( - CONF_DECONZ_EVENT, - { - CONF_DEVICE_ID: xiaomi_cube_entry.id, - CONF_GESTURE: "unsupported_gesture", - CONF_ID: xiaomi_cube_event_id, - CONF_UNIQUE_ID: xiaomi_cube_serial, - }, - ), - # Unknown event - MockLazyEventPartialState( - CONF_DECONZ_EVENT, - { - CONF_DEVICE_ID: faulty_entry.id, - "unknown_event": None, - CONF_ID: faulty_event_id, - CONF_UNIQUE_ID: faulty_serial, - }, - ), - ], - entity_attr_cache, - {}, - ) + events = mock_humanify( + hass, + [ + # Event without matching device trigger + MockRow( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: switch_entry.id, + CONF_EVENT: 2000, + CONF_ID: switch_event_id, + CONF_UNIQUE_ID: switch_serial, + }, + ), + # Event with matching device trigger + MockRow( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: hue_remote_entry.id, + CONF_EVENT: 2001, + CONF_ID: hue_remote_event_id, + CONF_UNIQUE_ID: hue_remote_serial, + }, + ), + # Gesture with matching device trigger + MockRow( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: xiaomi_cube_entry.id, + CONF_GESTURE: 1, + CONF_ID: xiaomi_cube_event_id, + CONF_UNIQUE_ID: xiaomi_cube_serial, + }, + ), + # Unsupported device trigger + MockRow( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: xiaomi_cube_entry.id, + CONF_GESTURE: "unsupported_gesture", + CONF_ID: xiaomi_cube_event_id, + CONF_UNIQUE_ID: xiaomi_cube_serial, + }, + ), + # Unknown event + MockRow( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: faulty_entry.id, + "unknown_event": None, + CONF_ID: faulty_event_id, + CONF_UNIQUE_ID: faulty_serial, + }, + ), + ], ) assert events[0]["name"] == "Switch 1" assert events[0]["domain"] == "deconz" - assert events[0]["message"] == "fired event '2000'." + assert events[0]["message"] == "fired event '2000'" assert events[1]["name"] == "Hue remote" assert events[1]["domain"] == "deconz" - assert events[1]["message"] == "'Long press' event for 'Dim up' was fired." + assert events[1]["message"] == "'Long press' event for 'Dim up' was fired" assert events[2]["name"] == "Xiaomi cube" assert events[2]["domain"] == "deconz" - assert events[2]["message"] == "fired event 'Shake'." + assert events[2]["message"] == "fired event 'Shake'" assert events[3]["name"] == "Xiaomi cube" assert events[3]["domain"] == "deconz" - assert events[3]["message"] == "fired event 'unsupported_gesture'." + assert events[3]["message"] == "fired event 'unsupported_gesture'" assert events[4]["name"] == "Faulty event" assert events[4]["domain"] == "deconz" - assert events[4]["message"] == "fired an unknown event." + assert events[4]["message"] == "fired an unknown event" diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index 590ccee25d5..658e11da906 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -337,7 +337,7 @@ TEST_DATA = [ "state": "5.0", "entity_category": None, "device_class": SensorDeviceClass.ILLUMINANCE, - "state_class": None, + "state_class": SensorStateClass.MEASUREMENT, "attributes": { "on": True, "dark": True, @@ -345,6 +345,7 @@ TEST_DATA = [ "unit_of_measurement": "lx", "device_class": "illuminance", "friendly_name": "Motion sensor 4", + "state_class": "measurement", }, "websocket_event": {"state": {"lightlevel": 1000}}, "next_state": "1.3", diff --git a/tests/components/deluge/__init__.py b/tests/components/deluge/__init__.py index 47339f8dfd5..4efbe04cf52 100644 --- a/tests/components/deluge/__init__.py +++ b/tests/components/deluge/__init__.py @@ -5,14 +5,7 @@ from homeassistant.components.deluge.const import ( DEFAULT_RPC_PORT, DEFAULT_WEB_PORT, ) -from homeassistant.const import ( - CONF_HOST, - CONF_MONITORED_VARIABLES, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, - CONF_USERNAME, -) +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME CONF_DATA = { CONF_HOST: "1.2.3.4", @@ -21,12 +14,3 @@ CONF_DATA = { CONF_PORT: DEFAULT_RPC_PORT, CONF_WEB_PORT: DEFAULT_WEB_PORT, } - -IMPORT_DATA = { - CONF_HOST: "1.2.3.4", - CONF_NAME: "Deluge Torrent", - CONF_MONITORED_VARIABLES: ["current_status", "download_speed", "upload_speed"], - CONF_USERNAME: "user", - CONF_PASSWORD: "password", - CONF_PORT: DEFAULT_RPC_PORT, -} diff --git a/tests/components/deluge/test_config_flow.py b/tests/components/deluge/test_config_flow.py index 56dbac55674..b56c717e635 100644 --- a/tests/components/deluge/test_config_flow.py +++ b/tests/components/deluge/test_config_flow.py @@ -4,7 +4,7 @@ from unittest.mock import patch import pytest from homeassistant.components.deluge.const import DEFAULT_NAME, DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH, SOURCE_USER +from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER from homeassistant.const import CONF_SOURCE from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import ( @@ -13,7 +13,7 @@ from homeassistant.data_entry_flow import ( RESULT_TYPE_FORM, ) -from . import CONF_DATA, IMPORT_DATA +from . import CONF_DATA from tests.common import MockConfigEntry @@ -102,33 +102,6 @@ async def test_flow_user_unknown_error(hass: HomeAssistant, unknown_error): assert result["errors"] == {"base": "unknown"} -async def test_flow_import(hass: HomeAssistant, api): - """Test import step.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_IMPORT}, data=IMPORT_DATA - ) - - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "Deluge Torrent" - assert result["data"] == CONF_DATA - - -async def test_flow_import_already_configured(hass: HomeAssistant, api): - """Test import step already configured.""" - entry = MockConfigEntry( - domain=DOMAIN, - data=CONF_DATA, - ) - - entry.add_to_hass(hass) - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_IMPORT}, data=IMPORT_DATA - ) - - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - - async def test_flow_reauth(hass: HomeAssistant, api): """Test reauth step.""" entry = MockConfigEntry( diff --git a/tests/components/devolo_home_control/mocks.py b/tests/components/devolo_home_control/mocks.py index 79bf94b8fc3..b43cb77ad71 100644 --- a/tests/components/devolo_home_control/mocks.py +++ b/tests/components/devolo_home_control/mocks.py @@ -8,6 +8,9 @@ from devolo_home_control_api.homecontrol import HomeControl from devolo_home_control_api.properties.binary_sensor_property import ( BinarySensorProperty, ) +from devolo_home_control_api.properties.multi_level_sensor_property import ( + MultiLevelSensorProperty, +) from devolo_home_control_api.properties.multi_level_switch_property import ( MultiLevelSwitchProperty, ) @@ -28,6 +31,31 @@ class BinarySensorPropertyMock(BinarySensorProperty): self.state = False +class MultiLevelSensorPropertyMock(MultiLevelSensorProperty): + """devolo Home Control multi level sensor mock.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize the mock.""" + self.element_uid = "Test" + self.sensor_type = "temperature" + self._unit = "°C" + self._value = 20 + self._logger = MagicMock() + + +class MultiLevelSwitchPropertyMock(MultiLevelSwitchProperty): + """devolo Home Control multi level switch mock.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize the mock.""" + self.element_uid = "Test" + self.min = 4 + self.max = 24 + self.switch_type = "temperature" + self._value = 20 + self._logger = MagicMock() + + class SirenPropertyMock(MultiLevelSwitchProperty): """devolo Home Control siren mock.""" @@ -84,6 +112,17 @@ class BinarySensorMockOverload(DeviceMock): self.binary_sensor_property["Overload"].sensor_type = "overload" +class ClimateMock(DeviceMock): + """devolo Home Control climate device mock.""" + + def __init__(self) -> None: + """Initialize the mock.""" + super().__init__() + self.device_model_uid = "devolo.model.Room:Thermostat" + self.multi_level_switch_property = {"Test": MultiLevelSwitchPropertyMock()} + self.multi_level_sensor_property = {"Test": MultiLevelSensorPropertyMock()} + + class RemoteControlMock(DeviceMock): """devolo Home Control remote control device mock.""" @@ -143,6 +182,19 @@ class HomeControlMockBinarySensor(HomeControlMock): self.publisher.unregister = MagicMock() +class HomeControlMockClimate(HomeControlMock): + """devolo Home Control gateway mock with climate devices.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize the mock.""" + super().__init__() + self.devices = { + "Test": ClimateMock(), + } + self.publisher = Publisher(self.devices.keys()) + self.publisher.unregister = MagicMock() + + class HomeControlMockRemoteControl(HomeControlMock): """devolo Home Control gateway mock with remote control device.""" diff --git a/tests/components/devolo_home_control/test_climate.py b/tests/components/devolo_home_control/test_climate.py new file mode 100644 index 00000000000..4fca1825459 --- /dev/null +++ b/tests/components/devolo_home_control/test_climate.py @@ -0,0 +1,81 @@ +"""Tests for the devolo Home Control climate.""" +from unittest.mock import patch + +from homeassistant.components.climate import DOMAIN +from homeassistant.components.climate.const import ( + ATTR_HVAC_MODE, + SERVICE_SET_TEMPERATURE, + HVACMode, +) +from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNAVAILABLE +from homeassistant.core import HomeAssistant + +from . import configure_integration +from .mocks import HomeControlMock, HomeControlMockClimate + + +async def test_climate(hass: HomeAssistant): + """Test setup and state change of a climate device.""" + entry = configure_integration(hass) + test_gateway = HomeControlMockClimate() + test_gateway.devices["Test"].value = 20 + with patch( + "homeassistant.components.devolo_home_control.HomeControl", + side_effect=[test_gateway, HomeControlMock()], + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.test") + assert state is not None + assert state.state == HVACMode.HEAT + assert state.attributes[ATTR_TEMPERATURE] == test_gateway.devices["Test"].value + + # Emulate websocket message: temperature changed + test_gateway.publisher.dispatch("Test", ("Test", 21.0)) + await hass.async_block_till_done() + state = hass.states.get(f"{DOMAIN}.test") + assert state.state == HVACMode.HEAT + assert state.attributes[ATTR_TEMPERATURE] == 21.0 + + # Test setting temperature + with patch( + "devolo_home_control_api.properties.multi_level_switch_property.MultiLevelSwitchProperty.set" + ) as set_value: + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: f"{DOMAIN}.test", + ATTR_HVAC_MODE: HVACMode.HEAT, + ATTR_TEMPERATURE: 20.0, + }, + blocking=True, + ) # In reality, this leads to a websocket message like already tested above + set_value.assert_called_once_with(20.0) + + # Emulate websocket message: device went offline + test_gateway.devices["Test"].status = 1 + test_gateway.publisher.dispatch("Test", ("Status", False, "status")) + await hass.async_block_till_done() + assert hass.states.get(f"{DOMAIN}.test").state == STATE_UNAVAILABLE + + +async def test_remove_from_hass(hass: HomeAssistant): + """Test removing entity.""" + entry = configure_integration(hass) + test_gateway = HomeControlMockClimate() + with patch( + "homeassistant.components.devolo_home_control.HomeControl", + side_effect=[test_gateway, HomeControlMock()], + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.test") + assert state is not None + await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + assert test_gateway.publisher.unregister.call_count == 2 diff --git a/tests/components/devolo_home_control/test_init.py b/tests/components/devolo_home_control/test_init.py index 311da47aac0..f12ea9486b4 100644 --- a/tests/components/devolo_home_control/test_init.py +++ b/tests/components/devolo_home_control/test_init.py @@ -1,13 +1,20 @@ """Tests for the devolo Home Control integration.""" +from collections.abc import Awaitable, Callable from unittest.mock import patch +from aiohttp import ClientWebSocketResponse from devolo_home_control_api.exceptions.gateway import GatewayOfflineError import pytest +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.devolo_home_control import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr +from homeassistant.setup import async_setup_component -from tests.components.devolo_home_control import configure_integration +from . import configure_integration +from .mocks import HomeControlMock, HomeControlMockBinarySensor async def test_setup_entry(hass: HomeAssistant, mock_zeroconf): @@ -53,3 +60,37 @@ async def test_unload_entry(hass: HomeAssistant): await hass.async_block_till_done() await hass.config_entries.async_unload(entry.entry_id) assert entry.state is ConfigEntryState.NOT_LOADED + + +async def test_remove_device( + hass: HomeAssistant, + hass_ws_client: Callable[[HomeAssistant], Awaitable[ClientWebSocketResponse]], +): + """Test removing a device.""" + assert await async_setup_component(hass, "config", {}) + entry = configure_integration(hass) + test_gateway = HomeControlMockBinarySensor() + with patch( + "homeassistant.components.devolo_home_control.HomeControl", + side_effect=[test_gateway, HomeControlMock()], + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + device_registry = dr.async_get(hass) + device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "Test")}) + assert device_entry + + client = await hass_ws_client(hass) + await client.send_json( + { + "id": 1, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry.entry_id, + "device_id": device_entry.id, + } + ) + response = await client.receive_json() + assert response["success"] + assert device_registry.async_get_device(identifiers={(DOMAIN, "Test")}) is None + assert hass.states.get(f"{BINARY_SENSOR_DOMAIN}.test") is None diff --git a/tests/components/devolo_home_network/__init__.py b/tests/components/devolo_home_network/__init__.py index 913193be3f7..1c10d7a59ef 100644 --- a/tests/components/devolo_home_network/__init__.py +++ b/tests/components/devolo_home_network/__init__.py @@ -28,5 +28,6 @@ def configure_integration(hass: HomeAssistant) -> MockConfigEntry: async def async_connect(self, session_instance: Any = None): """Give a mocked device the needed properties.""" + self.mac = DISCOVERY_INFO.properties["PlcMacAddress"] self.plcnet = PlcNetApi(IP, None, dataclasses.asdict(DISCOVERY_INFO)) self.device = DeviceApi(IP, None, dataclasses.asdict(DISCOVERY_INFO)) diff --git a/tests/components/devolo_home_network/const.py b/tests/components/devolo_home_network/const.py index 0e48833a78b..516a19f3421 100644 --- a/tests/components/devolo_home_network/const.py +++ b/tests/components/devolo_home_network/const.py @@ -62,6 +62,12 @@ NEIGHBOR_ACCESS_POINTS = { PLCNET = { "network": { + "devices": [ + { + "mac_address": "AA:BB:CC:DD:EE:FF", + "attached_to_router": False, + } + ], "data_rates": [ { "mac_address_from": "AA:BB:CC:DD:EE:FF", @@ -70,6 +76,17 @@ PLCNET = { "tx_rate": 0.0, }, ], - "devices": [], + } +} + +PLCNET_ATTACHED = { + "network": { + "devices": [ + { + "mac_address": "AA:BB:CC:DD:EE:FF", + "attached_to_router": True, + } + ], + "data_rates": [], } } diff --git a/tests/components/devolo_home_network/test_binary_sensor.py b/tests/components/devolo_home_network/test_binary_sensor.py new file mode 100644 index 00000000000..564e6e5ade6 --- /dev/null +++ b/tests/components/devolo_home_network/test_binary_sensor.py @@ -0,0 +1,77 @@ +"""Tests for the devolo Home Network sensors.""" +from unittest.mock import AsyncMock, patch + +from devolo_plc_api.exceptions.device import DeviceUnavailable +import pytest + +from homeassistant.components.binary_sensor import DOMAIN +from homeassistant.components.devolo_home_network.const import ( + CONNECTED_TO_ROUTER, + LONG_UPDATE_INTERVAL, +) +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry +from homeassistant.helpers.entity import EntityCategory +from homeassistant.util import dt + +from . import configure_integration +from .const import PLCNET_ATTACHED + +from tests.common import async_fire_time_changed + + +@pytest.mark.usefixtures("mock_device") +async def test_binary_sensor_setup(hass: HomeAssistant): + """Test default setup of the binary sensor component.""" + entry = configure_integration(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get(f"{DOMAIN}.{CONNECTED_TO_ROUTER}") is None + + await hass.config_entries.async_unload(entry.entry_id) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default", "mock_device") +async def test_update_attached_to_router(hass: HomeAssistant): + """Test state change of a attached_to_router binary sensor device.""" + state_key = f"{DOMAIN}.{CONNECTED_TO_ROUTER}" + entry = configure_integration(hass) + + er = entity_registry.async_get(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(state_key) + assert state is not None + assert state.state == STATE_OFF + + assert er.async_get(state_key).entity_category == EntityCategory.DIAGNOSTIC + + # Emulate device failure + with patch( + "devolo_plc_api.plcnet_api.plcnetapi.PlcNetApi.async_get_network_overview", + side_effect=DeviceUnavailable, + ): + async_fire_time_changed(hass, dt.utcnow() + LONG_UPDATE_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get(state_key) + assert state is not None + assert state.state == STATE_UNAVAILABLE + + # Emulate state change + with patch( + "devolo_plc_api.plcnet_api.plcnetapi.PlcNetApi.async_get_network_overview", + new=AsyncMock(return_value=PLCNET_ATTACHED), + ): + async_fire_time_changed(hass, dt.utcnow() + LONG_UPDATE_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get(state_key) + assert state is not None + assert state.state == STATE_ON + + await hass.config_entries.async_unload(entry.entry_id) diff --git a/tests/components/devolo_home_network/test_sensor.py b/tests/components/devolo_home_network/test_sensor.py index a7b1be5f07d..582d6533802 100644 --- a/tests/components/devolo_home_network/test_sensor.py +++ b/tests/components/devolo_home_network/test_sensor.py @@ -21,7 +21,6 @@ from tests.common import async_fire_time_changed @pytest.mark.usefixtures("mock_device") -@pytest.mark.usefixtures("mock_zeroconf") async def test_sensor_setup(hass: HomeAssistant): """Test default setup of the sensor component.""" entry = configure_integration(hass) @@ -36,7 +35,6 @@ async def test_sensor_setup(hass: HomeAssistant): @pytest.mark.usefixtures("mock_device") -@pytest.mark.usefixtures("mock_zeroconf") async def test_update_connected_wifi_clients(hass: HomeAssistant): """Test state change of a connected_wifi_clients sensor device.""" state_key = f"{DOMAIN}.connected_wifi_clients" @@ -73,85 +71,75 @@ async def test_update_connected_wifi_clients(hass: HomeAssistant): await hass.config_entries.async_unload(entry.entry_id) -@pytest.mark.usefixtures("mock_device") -@pytest.mark.usefixtures("mock_zeroconf") +@pytest.mark.usefixtures("entity_registry_enabled_by_default", "mock_device") async def test_update_neighboring_wifi_networks(hass: HomeAssistant): """Test state change of a neighboring_wifi_networks sensor device.""" state_key = f"{DOMAIN}.neighboring_wifi_networks" entry = configure_integration(hass) + er = entity_registry.async_get(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(state_key) + assert state is not None + assert state.state == "1" + assert er.async_get(state_key).entity_category is EntityCategory.DIAGNOSTIC + + # Emulate device failure with patch( - "homeassistant.helpers.entity.Entity.entity_registry_enabled_default", - return_value=True, + "devolo_plc_api.device_api.deviceapi.DeviceApi.async_get_wifi_neighbor_access_points", + side_effect=DeviceUnavailable, ): - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - state = hass.states.get(state_key) - assert state is not None - assert state.state == "1" - - er = entity_registry.async_get(hass) - assert er.async_get(state_key).entity_category is EntityCategory.DIAGNOSTIC - - # Emulate device failure - with patch( - "devolo_plc_api.device_api.deviceapi.DeviceApi.async_get_wifi_neighbor_access_points", - side_effect=DeviceUnavailable, - ): - async_fire_time_changed(hass, dt.utcnow() + LONG_UPDATE_INTERVAL) - await hass.async_block_till_done() - - state = hass.states.get(state_key) - assert state is not None - assert state.state == STATE_UNAVAILABLE - - # Emulate state change async_fire_time_changed(hass, dt.utcnow() + LONG_UPDATE_INTERVAL) await hass.async_block_till_done() state = hass.states.get(state_key) assert state is not None - assert state.state == "1" + assert state.state == STATE_UNAVAILABLE - await hass.config_entries.async_unload(entry.entry_id) + # Emulate state change + async_fire_time_changed(hass, dt.utcnow() + LONG_UPDATE_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get(state_key) + assert state is not None + assert state.state == "1" + + await hass.config_entries.async_unload(entry.entry_id) -@pytest.mark.usefixtures("mock_device") -@pytest.mark.usefixtures("mock_zeroconf") +@pytest.mark.usefixtures("entity_registry_enabled_by_default", "mock_device") async def test_update_connected_plc_devices(hass: HomeAssistant): """Test state change of a connected_plc_devices sensor device.""" state_key = f"{DOMAIN}.connected_plc_devices" entry = configure_integration(hass) + er = entity_registry.async_get(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(state_key) + assert state is not None + assert state.state == "1" + assert er.async_get(state_key).entity_category is EntityCategory.DIAGNOSTIC + + # Emulate device failure with patch( - "homeassistant.helpers.entity.Entity.entity_registry_enabled_default", - return_value=True, + "devolo_plc_api.plcnet_api.plcnetapi.PlcNetApi.async_get_network_overview", + side_effect=DeviceUnavailable, ): - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - state = hass.states.get(state_key) - assert state is not None - assert state.state == "1" - - er = entity_registry.async_get(hass) - assert er.async_get(state_key).entity_category is EntityCategory.DIAGNOSTIC - - # Emulate device failure - with patch( - "devolo_plc_api.plcnet_api.plcnetapi.PlcNetApi.async_get_network_overview", - side_effect=DeviceUnavailable, - ): - async_fire_time_changed(hass, dt.utcnow() + LONG_UPDATE_INTERVAL) - await hass.async_block_till_done() - - state = hass.states.get(state_key) - assert state is not None - assert state.state == STATE_UNAVAILABLE - - # Emulate state change async_fire_time_changed(hass, dt.utcnow() + LONG_UPDATE_INTERVAL) await hass.async_block_till_done() state = hass.states.get(state_key) assert state is not None - assert state.state == "1" + assert state.state == STATE_UNAVAILABLE - await hass.config_entries.async_unload(entry.entry_id) + # Emulate state change + async_fire_time_changed(hass, dt.utcnow() + LONG_UPDATE_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get(state_key) + assert state is not None + assert state.state == "1" + + await hass.config_entries.async_unload(entry.entry_id) diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index a809d6eb5ab..c6d889eab72 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -3,7 +3,6 @@ import datetime import threading from unittest.mock import MagicMock, patch -import pytest from scapy import arch # pylint: disable=unused-import # noqa: F401 from scapy.error import Scapy_Exception from scapy.layers.dhcp import DHCP @@ -973,27 +972,3 @@ async def test_aiodiscover_finds_new_hosts_after_interval(hass): hostname="connect", macaddress="b8b7f16db533", ) - - -@pytest.mark.usefixtures("mock_integration_frame") -async def test_service_info_compatibility(hass, caplog): - """Test compatibility with old-style dict. - - To be removed in 2022.6 - """ - discovery_info = dhcp.DhcpServiceInfo( - ip="192.168.210.56", - hostname="connect", - macaddress="b8b7f16db533", - ) - - with patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()): - assert discovery_info["ip"] == "192.168.210.56" - assert "Detected integration that accessed discovery_info['ip']" in caplog.text - - with patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()): - assert discovery_info.get("ip") == "192.168.210.56" - assert "Detected integration that accessed discovery_info.get('ip')" in caplog.text - - assert discovery_info.get("ip", "fallback_host") == "192.168.210.56" - assert discovery_info.get("invalid_key", "fallback_host") == "fallback_host" diff --git a/tests/components/discord/__init__.py b/tests/components/discord/__init__.py index ebc23360555..bf7c188b7b5 100644 --- a/tests/components/discord/__init__.py +++ b/tests/components/discord/__init__.py @@ -6,7 +6,7 @@ import nextcord from homeassistant.components.discord.const import DOMAIN from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_API_TOKEN, CONF_NAME, CONF_TOKEN +from homeassistant.const import CONF_API_TOKEN, CONF_NAME from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry @@ -21,10 +21,6 @@ CONF_DATA = { CONF_NAME: NAME, } -CONF_IMPORT_DATA_NO_NAME = {CONF_TOKEN: TOKEN} - -CONF_IMPORT_DATA = CONF_IMPORT_DATA_NO_NAME | {CONF_NAME: NAME} - def create_entry(hass: HomeAssistant) -> ConfigEntry: """Add config entry in Home Assistant.""" diff --git a/tests/components/discord/test_config_flow.py b/tests/components/discord/test_config_flow.py index 64588b052fe..59030187866 100644 --- a/tests/components/discord/test_config_flow.py +++ b/tests/components/discord/test_config_flow.py @@ -1,6 +1,5 @@ """Test Discord config flow.""" import nextcord -from pytest import LogCaptureFixture from homeassistant import config_entries, data_entry_flow from homeassistant.components.discord.const import DOMAIN @@ -9,8 +8,6 @@ from homeassistant.core import HomeAssistant from . import ( CONF_DATA, - CONF_IMPORT_DATA, - CONF_IMPORT_DATA_NO_NAME, CONF_INPUT, NAME, create_entry, @@ -121,49 +118,6 @@ async def test_flow_user_unknown_error(hass: HomeAssistant) -> None: assert result["data"] == CONF_DATA -async def test_flow_import(hass: HomeAssistant, caplog: LogCaptureFixture) -> None: - """Test an import flow.""" - with mocked_discord_info(), patch_discord_login(): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=CONF_IMPORT_DATA.copy(), - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == NAME - assert result["data"] == CONF_DATA - assert "Discord integration in YAML" in caplog.text - - -async def test_flow_import_no_name(hass: HomeAssistant) -> None: - """Test import flow with no name in config.""" - with mocked_discord_info(), patch_discord_login(): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=CONF_IMPORT_DATA_NO_NAME, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == NAME - assert result["data"] == CONF_DATA - - -async def test_flow_import_already_configured(hass: HomeAssistant) -> None: - """Test an import flow already configured.""" - create_entry(hass) - with mocked_discord_info(), patch_discord_login(): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=CONF_IMPORT_DATA, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - - async def test_flow_reauth(hass: HomeAssistant) -> None: """Test a reauth flow.""" entry = create_entry(hass) diff --git a/tests/components/dlna_dmr/test_config_flow.py b/tests/components/dlna_dmr/test_config_flow.py index 6112c7c5ed5..7ec25906f99 100644 --- a/tests/components/dlna_dmr/test_config_flow.py +++ b/tests/components/dlna_dmr/test_config_flow.py @@ -11,6 +11,7 @@ import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.components import ssdp from homeassistant.components.dlna_dmr.const import ( + CONF_BROWSE_UNFILTERED, CONF_CALLBACK_URL_OVERRIDE, CONF_LISTEN_PORT, CONF_POLL_AVAILABILITY, @@ -74,7 +75,7 @@ MOCK_DISCOVERY = ssdp.SsdpServiceInfo( ] }, }, - x_homeassistant_matching_domains=(DLNA_DOMAIN,), + x_homeassistant_matching_domains={DLNA_DOMAIN}, ) @@ -390,7 +391,7 @@ async def test_ssdp_missing_services(hass: HomeAssistant) -> None: """Test SSDP ignores devices that are missing required services.""" # No service list at all discovery = dataclasses.replace(MOCK_DISCOVERY) - discovery.upnp = discovery.upnp.copy() + discovery.upnp = dict(discovery.upnp) del discovery.upnp[ssdp.ATTR_UPNP_SERVICE_LIST] result = await hass.config_entries.flow.async_init( DLNA_DOMAIN, @@ -414,7 +415,7 @@ async def test_ssdp_missing_services(hass: HomeAssistant) -> None: # AVTransport service is missing discovery = dataclasses.replace(MOCK_DISCOVERY) - discovery.upnp = discovery.upnp.copy() + discovery.upnp = dict(discovery.upnp) discovery.upnp[ssdp.ATTR_UPNP_SERVICE_LIST] = { "service": [ service @@ -465,7 +466,7 @@ async def test_ssdp_ignore_device(hass: HomeAssistant) -> None: assert result["reason"] == "alternative_integration" discovery = dataclasses.replace(MOCK_DISCOVERY) - discovery.upnp = discovery.upnp.copy() + discovery.upnp = dict(discovery.upnp) discovery.upnp[ ssdp.ATTR_UPNP_DEVICE_TYPE ] = "urn:schemas-upnp-org:device:ZonePlayer:1" @@ -484,7 +485,7 @@ async def test_ssdp_ignore_device(hass: HomeAssistant) -> None: ("Royal Philips Electronics", "Philips TV DMR"), ]: discovery = dataclasses.replace(MOCK_DISCOVERY) - discovery.upnp = discovery.upnp.copy() + discovery.upnp = dict(discovery.upnp) discovery.upnp[ssdp.ATTR_UPNP_MANUFACTURER] = manufacturer discovery.upnp[ssdp.ATTR_UPNP_MODEL_NAME] = model result = await hass.config_entries.flow.async_init( @@ -592,6 +593,7 @@ async def test_options_flow( user_input={ CONF_CALLBACK_URL_OVERRIDE: "Bad url", CONF_POLL_AVAILABILITY: False, + CONF_BROWSE_UNFILTERED: False, }, ) @@ -606,6 +608,7 @@ async def test_options_flow( CONF_LISTEN_PORT: 2222, CONF_CALLBACK_URL_OVERRIDE: "http://override/callback", CONF_POLL_AVAILABILITY: True, + CONF_BROWSE_UNFILTERED: True, }, ) @@ -614,4 +617,5 @@ async def test_options_flow( CONF_LISTEN_PORT: 2222, CONF_CALLBACK_URL_OVERRIDE: "http://override/callback", CONF_POLL_AVAILABILITY: True, + CONF_BROWSE_UNFILTERED: True, } diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index bed89f3db9d..eef5d936396 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -23,6 +23,7 @@ from homeassistant import const as ha_const from homeassistant.components import ssdp from homeassistant.components.dlna_dmr import media_player from homeassistant.components.dlna_dmr.const import ( + CONF_BROWSE_UNFILTERED, CONF_CALLBACK_URL_OVERRIDE, CONF_LISTEN_PORT, CONF_POLL_AVAILABILITY, @@ -997,6 +998,26 @@ async def test_browse_media( # Audio file should appear assert expected_child_audio in response["result"]["children"] + # Device specifies extra parameters in MIME type, uses non-standard "x-" + # prefix, and capitilizes things, all of which should be ignored + dmr_device_mock.sink_protocol_info = [ + "http-get:*:audio/X-MPEG;codecs=mp3:*", + ] + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": mock_entity_id, + } + ) + response = await client.receive_json() + assert response["success"] + # Video file should not be shown + assert expected_child_video not in response["result"]["children"] + # Audio file should appear + assert expected_child_audio in response["result"]["children"] + # Device does not specify what it can play dmr_device_mock.sink_protocol_info = [] client = await hass_ws_client() @@ -1014,6 +1035,87 @@ async def test_browse_media( assert expected_child_audio in response["result"]["children"] +async def test_browse_media_unfiltered( + hass: HomeAssistant, + hass_ws_client, + config_entry_mock: MockConfigEntry, + dmr_device_mock: Mock, + mock_entity_id: str, +) -> None: + """Test the async_browse_media method with filtering turned off and on.""" + # Based on cast's test_entity_browse_media + await async_setup_component(hass, MS_DOMAIN, {MS_DOMAIN: {}}) + await hass.async_block_till_done() + + expected_child_video = { + "title": "Epic Sax Guy 10 Hours.mp4", + "media_class": "video", + "media_content_type": "video/mp4", + "media_content_id": "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4", + "can_play": True, + "can_expand": False, + "thumbnail": None, + "children_media_class": None, + } + expected_child_audio = { + "title": "test.mp3", + "media_class": "music", + "media_content_type": "audio/mpeg", + "media_content_id": "media-source://media_source/local/test.mp3", + "can_play": True, + "can_expand": False, + "thumbnail": None, + "children_media_class": None, + } + + # Device can only play MIME type audio/mpeg and audio/vorbis + dmr_device_mock.sink_protocol_info = [ + "http-get:*:audio/mpeg:*", + "http-get:*:audio/vorbis:*", + ] + + # Filtering turned on by default + assert CONF_BROWSE_UNFILTERED not in config_entry_mock.options + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": mock_entity_id, + } + ) + response = await client.receive_json() + assert response["success"] + # Video file should not be shown + assert expected_child_video not in response["result"]["children"] + # Audio file should appear + assert expected_child_audio in response["result"]["children"] + + # Filtering turned off via config entry + hass.config_entries.async_update_entry( + config_entry_mock, + options={ + CONF_BROWSE_UNFILTERED: True, + }, + ) + await hass.async_block_till_done() + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": mock_entity_id, + } + ) + response = await client.receive_json() + assert response["success"] + # All files should be returned + assert expected_child_video in response["result"]["children"] + assert expected_child_audio in response["result"]["children"] + + async def test_playback_update_state( hass: HomeAssistant, dmr_device_mock: Mock, mock_entity_id: str ) -> None: diff --git a/tests/components/dlna_dms/test_device_availability.py b/tests/components/dlna_dms/test_device_availability.py index 67ad1024709..a3ec5326f00 100644 --- a/tests/components/dlna_dms/test_device_availability.py +++ b/tests/components/dlna_dms/test_device_availability.py @@ -152,15 +152,15 @@ async def test_unavailable_device( ) with pytest.raises(Unresolvable, match="DMS is not connected"): await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}//resolve_path" + hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}//resolve_path", None ) with pytest.raises(Unresolvable, match="DMS is not connected"): await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/:resolve_object" + hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/:resolve_object", None ) with pytest.raises(Unresolvable): await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/?resolve_search" + hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/?resolve_search", None ) @@ -651,7 +651,7 @@ async def test_become_unavailable( # Check async_resolve_object currently works assert await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/:object_id" + hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/:object_id", None ) # Now break the network connection @@ -660,7 +660,7 @@ async def test_become_unavailable( # async_resolve_object should fail with pytest.raises(Unresolvable): await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/:object_id" + hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/:object_id", None ) # The device should now be unavailable diff --git a/tests/components/dlna_dms/test_dms_device_source.py b/tests/components/dlna_dms/test_dms_device_source.py index 5e4021a5dda..622a3b8a4f9 100644 --- a/tests/components/dlna_dms/test_dms_device_source.py +++ b/tests/components/dlna_dms/test_dms_device_source.py @@ -45,7 +45,7 @@ async def async_resolve_media( ) -> DidlPlayMedia: """Call media_source.async_resolve_media with the test source's ID.""" result = await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/{media_content_id}" + hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/{media_content_id}", None ) assert isinstance(result, DidlPlayMedia) return result diff --git a/tests/components/dlna_dms/test_media_source.py b/tests/components/dlna_dms/test_media_source.py index f2c3011e274..35f34d0689b 100644 --- a/tests/components/dlna_dms/test_media_source.py +++ b/tests/components/dlna_dms/test_media_source.py @@ -49,7 +49,7 @@ async def test_get_media_source(hass: HomeAssistant) -> None: async def test_resolve_media_unconfigured(hass: HomeAssistant) -> None: """Test resolve_media without any devices being configured.""" source = DmsMediaSource(hass) - item = MediaSourceItem(hass, DOMAIN, "source_id/media_id") + item = MediaSourceItem(hass, DOMAIN, "source_id/media_id", None) with pytest.raises(Unresolvable, match="No sources have been configured"): await source.async_resolve_media(item) @@ -60,31 +60,31 @@ async def test_resolve_media_bad_identifier( """Test trying to resolve an item that has an unresolvable identifier.""" # Empty identifier with pytest.raises(Unresolvable, match="No source ID.*"): - await media_source.async_resolve_media(hass, f"media-source://{DOMAIN}") + await media_source.async_resolve_media(hass, f"media-source://{DOMAIN}", None) # Identifier has media_id but no source_id # media_source.URI_SCHEME_REGEX won't let the ID through to dlna_dms with pytest.raises(Unresolvable, match="Invalid media source URI"): await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}//media_id" + hass, f"media-source://{DOMAIN}//media_id", None ) # Identifier has source_id but no media_id with pytest.raises(Unresolvable, match="No media ID.*"): await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}/source_id/" + hass, f"media-source://{DOMAIN}/source_id/", None ) # Identifier is missing source_id/media_id separator with pytest.raises(Unresolvable, match="No media ID.*"): await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}/source_id" + hass, f"media-source://{DOMAIN}/source_id", None ) # Identifier has an unknown source_id with pytest.raises(Unresolvable, match="Unknown source ID: unknown_source"): await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}/unknown_source/media_id" + hass, f"media-source://{DOMAIN}/unknown_source/media_id", None ) @@ -105,7 +105,7 @@ async def test_resolve_media_success( dms_device_mock.async_browse_metadata.return_value = didl_item result = await media_source.async_resolve_media( - hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/:{object_id}" + hass, f"media-source://{DOMAIN}/{MOCK_SOURCE_ID}/:{object_id}", None ) assert isinstance(result, DidlPlayMedia) assert result.url == f"{MOCK_DEVICE_BASE_URL}/{res_url}" @@ -116,11 +116,11 @@ async def test_resolve_media_success( async def test_browse_media_unconfigured(hass: HomeAssistant) -> None: """Test browse_media without any devices being configured.""" source = DmsMediaSource(hass) - item = MediaSourceItem(hass, DOMAIN, "source_id/media_id") + item = MediaSourceItem(hass, DOMAIN, "source_id/media_id", None) with pytest.raises(BrowseError, match="No sources have been configured"): await source.async_browse_media(item) - item = MediaSourceItem(hass, DOMAIN, "") + item = MediaSourceItem(hass, DOMAIN, "", None) with pytest.raises(BrowseError, match="No sources have been configured"): await source.async_browse_media(item) @@ -239,7 +239,7 @@ async def test_browse_media_source_id( dms_device_mock.async_browse_metadata.side_effect = UpnpError # Browse by source_id - item = MediaSourceItem(hass, DOMAIN, f"{MOCK_SOURCE_ID}/:media-item-id") + item = MediaSourceItem(hass, DOMAIN, f"{MOCK_SOURCE_ID}/:media-item-id", None) dms_source = DmsMediaSource(hass) with pytest.raises(BrowseError): await dms_source.async_browse_media(item) diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index 93dd78034cc..4502f61586a 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -393,7 +393,7 @@ async def test_belgian_meter(hass, dsmr_connection_fixture): (connection_factory, transport, protocol) = dsmr_connection_fixture from dsmr_parser.obis_references import ( - BELGIUM_HOURLY_GAS_METER_READING, + BELGIUM_5MIN_GAS_METER_READING, ELECTRICITY_ACTIVE_TARIFF, ) from dsmr_parser.objects import CosemObject, MBusObject @@ -411,7 +411,7 @@ async def test_belgian_meter(hass, dsmr_connection_fixture): } telegram = { - BELGIUM_HOURLY_GAS_METER_READING: MBusObject( + BELGIUM_5MIN_GAS_METER_READING: MBusObject( [ {"value": datetime.datetime.fromtimestamp(1551642213)}, {"value": Decimal(745.695), "unit": "m3"}, diff --git a/tests/components/dunehd/test_config_flow.py b/tests/components/dunehd/test_config_flow.py index 73732236077..76585ac73a1 100644 --- a/tests/components/dunehd/test_config_flow.py +++ b/tests/components/dunehd/test_config_flow.py @@ -3,7 +3,7 @@ from unittest.mock import patch from homeassistant import data_entry_flow from homeassistant.components.dunehd.const import DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_HOST from tests.common import MockConfigEntry @@ -14,49 +14,6 @@ CONFIG_IP = {CONF_HOST: "10.10.10.12"} DUNEHD_STATE = {"protocol_version": "4", "player_state": "navigator"} -async def test_import(hass): - """Test that the import works.""" - with patch("homeassistant.components.dunehd.async_setup_entry"), patch( - "pdunehd.DuneHDPlayer.update_state", return_value=DUNEHD_STATE - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=CONFIG_HOSTNAME - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "dunehd-host" - assert result["data"] == {CONF_HOST: "dunehd-host"} - - -async def test_import_cannot_connect(hass): - """Test that errors are shown when cannot connect to the host during import.""" - with patch("pdunehd.DuneHDPlayer.update_state", return_value={}): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=CONFIG_HOSTNAME - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "cannot_connect" - - -async def test_import_duplicate_error(hass): - """Test that errors are shown when duplicates are added during import.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - data={CONF_HOST: "dunehd-host"}, - title="dunehd-host", - ) - config_entry.add_to_hass(hass) - - with patch("pdunehd.DuneHDPlayer.update_state", return_value=DUNEHD_STATE): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=CONFIG_HOSTNAME - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - - async def test_user_invalid_host(hass): """Test that errors are shown when the host is invalid.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/efergy/test_init.py b/tests/components/efergy/test_init.py index 07c80e7bb04..49a152516b7 100644 --- a/tests/components/efergy/test_init.py +++ b/tests/components/efergy/test_init.py @@ -49,7 +49,7 @@ async def test_async_setup_entry_auth_failed(hass: HomeAssistant): async def test_device_info(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker): """Test device info.""" entry = await setup_platform(hass, aioclient_mock, SENSOR_DOMAIN) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_device({(DOMAIN, entry.entry_id)}) diff --git a/tests/components/elkm1/test_logbook.py b/tests/components/elkm1/test_logbook.py new file mode 100644 index 00000000000..d6ff9f75d5d --- /dev/null +++ b/tests/components/elkm1/test_logbook.py @@ -0,0 +1,63 @@ +"""The tests for elkm1 logbook.""" +from homeassistant.components.elkm1.const import ( + ATTR_KEY, + ATTR_KEY_NAME, + ATTR_KEYPAD_ID, + ATTR_KEYPAD_NAME, + DOMAIN, + EVENT_ELKM1_KEYPAD_KEY_PRESSED, +) +from homeassistant.const import CONF_HOST +from homeassistant.setup import async_setup_component + +from . import _patch_discovery, _patch_elk + +from tests.common import MockConfigEntry +from tests.components.logbook.common import MockRow, mock_humanify + + +async def test_humanify_elkm1_keypad_event(hass): + """Test humanifying elkm1 keypad presses.""" + hass.config.components.add("recorder") + assert await async_setup_component(hass, "logbook", {}) + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "elks://1.2.3.4"}, + unique_id="aa:bb:cc:dd:ee:ff", + ) + config_entry.add_to_hass(hass) + + with _patch_discovery(), _patch_elk(): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + (event1, event2) = mock_humanify( + hass, + [ + MockRow( + EVENT_ELKM1_KEYPAD_KEY_PRESSED, + { + ATTR_KEYPAD_ID: 1, + ATTR_KEY_NAME: "four", + ATTR_KEY: "4", + ATTR_KEYPAD_NAME: "Main Bedroom", + }, + ), + MockRow( + EVENT_ELKM1_KEYPAD_KEY_PRESSED, + { + ATTR_KEYPAD_ID: 1, + ATTR_KEY_NAME: "five", + ATTR_KEY: "5", + }, + ), + ], + ) + + assert event1["name"] == "Elk Keypad Main Bedroom" + assert event1["domain"] == DOMAIN + assert event1["message"] == "pressed four (4)" + + assert event2["name"] == "Elk Keypad 1" + assert event2["domain"] == DOMAIN + assert event2["message"] == "pressed five (5)" diff --git a/tests/components/fan/test_device_action.py b/tests/components/fan/test_device_action.py index ee300aad39f..8800e61675e 100644 --- a/tests/components/fan/test_device_action.py +++ b/tests/components/fan/test_device_action.py @@ -50,7 +50,7 @@ async def test_get_actions(hass, device_reg, entity_reg): "entity_id": f"{DOMAIN}.test_5678", "metadata": {"secondary": False}, } - for action in ["turn_on", "turn_off"] + for action in ["turn_on", "turn_off", "toggle"] ] actions = await async_get_device_automations( hass, DeviceAutomationType.ACTION, device_entry.id @@ -98,7 +98,7 @@ async def test_get_actions_hidden_auxiliary( "entity_id": f"{DOMAIN}.test_5678", "metadata": {"secondary": True}, } - for action in ["turn_on", "turn_off"] + for action in ["turn_on", "turn_off", "toggle"] ] actions = await async_get_device_automations( hass, DeviceAutomationType.ACTION, device_entry.id @@ -137,19 +137,40 @@ async def test_action(hass): "type": "turn_on", }, }, + { + "trigger": { + "platform": "event", + "event_type": "test_event_toggle", + }, + "action": { + "domain": DOMAIN, + "device_id": "abcdefgh", + "entity_id": "fan.entity", + "type": "toggle", + }, + }, ] }, ) turn_off_calls = async_mock_service(hass, "fan", "turn_off") turn_on_calls = async_mock_service(hass, "fan", "turn_on") + toggle_calls = async_mock_service(hass, "fan", "toggle") hass.bus.async_fire("test_event_turn_off") await hass.async_block_till_done() assert len(turn_off_calls) == 1 assert len(turn_on_calls) == 0 + assert len(toggle_calls) == 0 hass.bus.async_fire("test_event_turn_on") await hass.async_block_till_done() assert len(turn_off_calls) == 1 assert len(turn_on_calls) == 1 + assert len(toggle_calls) == 0 + + hass.bus.async_fire("test_event_toggle") + await hass.async_block_till_done() + assert len(turn_off_calls) == 1 + assert len(turn_on_calls) == 1 + assert len(toggle_calls) == 1 diff --git a/tests/components/fan/test_reproduce_state.py b/tests/components/fan/test_reproduce_state.py index cca56d3e128..e33f1005d75 100644 --- a/tests/components/fan/test_reproduce_state.py +++ b/tests/components/fan/test_reproduce_state.py @@ -183,8 +183,8 @@ async def test_modern_turn_on_invalid(hass, start_state): set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode") # Turn on with an invalid config (speed, percentage, preset_modes all None) - await hass.helpers.state.async_reproduce_state( - [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_INVALID_STATE)] + await async_reproduce_state( + hass, [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_INVALID_STATE)] ) assert len(turn_on_calls) == 1 @@ -227,8 +227,8 @@ async def test_modern_turn_on_percentage_from_different_speed(hass, start_state) set_percentage_mode = async_mock_service(hass, "fan", "set_percentage") set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode") - await hass.helpers.state.async_reproduce_state( - [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PERCENTAGE15_STATE)] + await async_reproduce_state( + hass, [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PERCENTAGE15_STATE)] ) assert len(turn_on_calls) == 1 @@ -256,8 +256,8 @@ async def test_modern_turn_on_percentage_from_same_speed(hass): set_percentage_mode = async_mock_service(hass, "fan", "set_percentage") set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode") - await hass.helpers.state.async_reproduce_state( - [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PERCENTAGE15_STATE)] + await async_reproduce_state( + hass, [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PERCENTAGE15_STATE)] ) assert len(turn_on_calls) == 1 @@ -293,8 +293,8 @@ async def test_modern_turn_on_preset_mode_from_different_speed(hass, start_state set_percentage_mode = async_mock_service(hass, "fan", "set_percentage") set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode") - await hass.helpers.state.async_reproduce_state( - [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PRESET_MODE_AUTO_STATE)] + await async_reproduce_state( + hass, [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PRESET_MODE_AUTO_STATE)] ) assert len(turn_on_calls) == 1 @@ -324,8 +324,8 @@ async def test_modern_turn_on_preset_mode_from_same_speed(hass): set_percentage_mode = async_mock_service(hass, "fan", "set_percentage") set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode") - await hass.helpers.state.async_reproduce_state( - [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PRESET_MODE_AUTO_STATE)] + await async_reproduce_state( + hass, [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PRESET_MODE_AUTO_STATE)] ) assert len(turn_on_calls) == 1 @@ -361,8 +361,9 @@ async def test_modern_turn_on_preset_mode_reverse(hass, start_state): set_percentage_mode = async_mock_service(hass, "fan", "set_percentage") set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode") - await hass.helpers.state.async_reproduce_state( - [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_PRESET_MODE_AUTO_REVERSE_STATE)] + await async_reproduce_state( + hass, + [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_PRESET_MODE_AUTO_REVERSE_STATE)], ) assert len(turn_on_calls) == 1 @@ -403,8 +404,8 @@ async def test_modern_to_preset(hass, start_state): set_percentage_mode = async_mock_service(hass, "fan", "set_percentage") set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode") - await hass.helpers.state.async_reproduce_state( - [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PRESET_MODE_AUTO_STATE)] + await async_reproduce_state( + hass, [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PRESET_MODE_AUTO_STATE)] ) assert len(turn_on_calls) == 0 @@ -439,8 +440,8 @@ async def test_modern_to_percentage(hass, start_state): set_percentage_mode = async_mock_service(hass, "fan", "set_percentage") set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode") - await hass.helpers.state.async_reproduce_state( - [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PERCENTAGE15_STATE)] + await async_reproduce_state( + hass, [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_ON_PERCENTAGE15_STATE)] ) assert len(turn_on_calls) == 0 @@ -467,8 +468,9 @@ async def test_modern_direction(hass): set_percentage_mode = async_mock_service(hass, "fan", "set_percentage") set_preset_mode = async_mock_service(hass, "fan", "set_preset_mode") - await hass.helpers.state.async_reproduce_state( - [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_PRESET_MODE_AUTO_REVERSE_STATE)] + await async_reproduce_state( + hass, + [State(MODERN_FAN_ENTITY, "on", MODERN_FAN_PRESET_MODE_AUTO_REVERSE_STATE)], ) assert len(turn_on_calls) == 0 diff --git a/tests/components/filesize/__init__.py b/tests/components/filesize/__init__.py index d7aa8dd2481..3e09a745387 100644 --- a/tests/components/filesize/__init__.py +++ b/tests/components/filesize/__init__.py @@ -1,6 +1,8 @@ """Tests for the filesize component.""" import os +from homeassistant.core import HomeAssistant + TEST_DIR = os.path.join(os.path.dirname(__file__)) TEST_FILE_NAME = "mock_file_test_filesize.txt" TEST_FILE_NAME2 = "mock_file_test_filesize2.txt" @@ -8,7 +10,12 @@ TEST_FILE = os.path.join(TEST_DIR, TEST_FILE_NAME) TEST_FILE2 = os.path.join(TEST_DIR, TEST_FILE_NAME2) -def create_file(path) -> None: +async def async_create_file(hass: HomeAssistant, path: str) -> None: """Create a test file.""" + await hass.async_add_executor_job(create_file, path) + + +def create_file(path: str) -> None: + """Create the test file.""" with open(path, "w", encoding="utf-8") as test_file: test_file.write("test") diff --git a/tests/components/filesize/test_config_flow.py b/tests/components/filesize/test_config_flow.py index 0209444ec42..16f0ab38dc1 100644 --- a/tests/components/filesize/test_config_flow.py +++ b/tests/components/filesize/test_config_flow.py @@ -1,10 +1,8 @@ """Tests for the Filesize config flow.""" from unittest.mock import patch -import pytest - from homeassistant.components.filesize.const import DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_FILE_PATH from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import ( @@ -13,14 +11,14 @@ from homeassistant.data_entry_flow import ( RESULT_TYPE_FORM, ) -from . import TEST_DIR, TEST_FILE, TEST_FILE_NAME, create_file +from . import TEST_DIR, TEST_FILE, TEST_FILE_NAME, async_create_file from tests.common import MockConfigEntry async def test_full_user_flow(hass: HomeAssistant) -> None: """Test the full user configuration flow.""" - create_file(TEST_FILE) + await async_create_file(hass, TEST_FILE) hass.config.allowlist_external_dirs = {TEST_DIR} result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -40,42 +38,26 @@ async def test_full_user_flow(hass: HomeAssistant) -> None: assert result2.get("data") == {CONF_FILE_PATH: TEST_FILE} -@pytest.mark.parametrize("source", [SOURCE_USER, SOURCE_IMPORT]) async def test_unique_path( hass: HomeAssistant, mock_config_entry: MockConfigEntry, - source: str, ) -> None: """Test we abort if already setup.""" + await async_create_file(hass, TEST_FILE) hass.config.allowlist_external_dirs = {TEST_DIR} mock_config_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": source}, data={CONF_FILE_PATH: TEST_FILE} + DOMAIN, context={"source": SOURCE_USER}, data={CONF_FILE_PATH: TEST_FILE} ) assert result.get("type") == RESULT_TYPE_ABORT assert result.get("reason") == "already_configured" -async def test_import_flow(hass: HomeAssistant) -> None: - """Test the import configuration flow.""" - create_file(TEST_FILE) - hass.config.allowlist_external_dirs = {TEST_DIR} - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_FILE_PATH: TEST_FILE}, - ) - - assert result.get("type") == RESULT_TYPE_CREATE_ENTRY - assert result.get("title") == TEST_FILE_NAME - assert result.get("data") == {CONF_FILE_PATH: TEST_FILE} - - async def test_flow_fails_on_validation(hass: HomeAssistant) -> None: """Test config flow errors.""" - create_file(TEST_FILE) + hass.config.allowlist_external_dirs = {} result = await hass.config_entries.flow.async_init( @@ -85,19 +67,17 @@ async def test_flow_fails_on_validation(hass: HomeAssistant) -> None: assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == SOURCE_USER - with patch( - "homeassistant.components.filesize.config_flow.pathlib.Path", - side_effect=OSError, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - CONF_FILE_PATH: TEST_FILE, - }, - ) + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_FILE_PATH: TEST_FILE, + }, + ) assert result2["errors"] == {"base": "not_valid"} + await async_create_file(hass, TEST_FILE) + with patch("homeassistant.components.filesize.config_flow.pathlib.Path",), patch( "homeassistant.components.filesize.async_setup_entry", return_value=True, diff --git a/tests/components/filesize/test_init.py b/tests/components/filesize/test_init.py index fecd3033c91..effab8f75d8 100644 --- a/tests/components/filesize/test_init.py +++ b/tests/components/filesize/test_init.py @@ -1,21 +1,10 @@ """Tests for the Filesize integration.""" -from unittest.mock import AsyncMock - -from homeassistant.components.filesize.const import CONF_FILE_PATHS, DOMAIN -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.filesize.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_FILE_PATH from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component -from . import ( - TEST_DIR, - TEST_FILE, - TEST_FILE2, - TEST_FILE_NAME, - TEST_FILE_NAME2, - create_file, -) +from . import async_create_file from tests.common import MockConfigEntry @@ -25,7 +14,7 @@ async def test_load_unload_config_entry( ) -> None: """Test the Filesize configuration entry loading/unloading.""" testfile = f"{tmpdir}/file.txt" - create_file(testfile) + await async_create_file(hass, testfile) hass.config.allowlist_external_dirs = {tmpdir} mock_config_entry.add_to_hass(hass) hass.config_entries.async_update_entry( @@ -65,7 +54,7 @@ async def test_not_valid_path_to_file( ) -> None: """Test that an invalid path is caught.""" testfile = f"{tmpdir}/file.txt" - create_file(testfile) + await async_create_file(hass, testfile) mock_config_entry.add_to_hass(hass) hass.config_entries.async_update_entry( mock_config_entry, unique_id=testfile, data={CONF_FILE_PATH: testfile} @@ -75,36 +64,3 @@ async def test_not_valid_path_to_file( await hass.async_block_till_done() assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY - - -async def test_import_config( - hass: HomeAssistant, - mock_setup_entry: AsyncMock, -) -> None: - """Test Filesize being set up from config via import.""" - create_file(TEST_FILE) - create_file(TEST_FILE2) - hass.config.allowlist_external_dirs = {TEST_DIR} - assert await async_setup_component( - hass, - SENSOR_DOMAIN, - { - SENSOR_DOMAIN: { - "platform": DOMAIN, - CONF_FILE_PATHS: [TEST_FILE, TEST_FILE2], - } - }, - ) - await hass.async_block_till_done() - - config_entries = hass.config_entries.async_entries(DOMAIN) - assert len(config_entries) == 2 - - entry = config_entries[0] - assert entry.title == TEST_FILE_NAME - assert entry.unique_id == TEST_FILE - assert entry.data == {CONF_FILE_PATH: TEST_FILE} - entry2 = config_entries[1] - assert entry2.title == TEST_FILE_NAME2 - assert entry2.unique_id == TEST_FILE2 - assert entry2.data == {CONF_FILE_PATH: TEST_FILE2} diff --git a/tests/components/filesize/test_sensor.py b/tests/components/filesize/test_sensor.py index 6f21119f95f..5b072769f56 100644 --- a/tests/components/filesize/test_sensor.py +++ b/tests/components/filesize/test_sensor.py @@ -1,13 +1,11 @@ """The tests for the filesize sensor.""" import os -from homeassistant.components.filesize.const import DOMAIN from homeassistant.const import CONF_FILE_PATH, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_component import async_update_entity -from homeassistant.setup import async_setup_component -from . import TEST_FILE, TEST_FILE_NAME, create_file +from . import TEST_FILE, TEST_FILE_NAME, async_create_file from tests.common import MockConfigEntry @@ -30,7 +28,7 @@ async def test_valid_path( ) -> None: """Test for a valid path.""" testfile = f"{tmpdir}/file.txt" - create_file(testfile) + await async_create_file(hass, testfile) hass.config.allowlist_external_dirs = {tmpdir} mock_config_entry.add_to_hass(hass) hass.config_entries.async_update_entry( @@ -52,7 +50,7 @@ async def test_state_unavailable( ) -> None: """Verify we handle state unavailable.""" testfile = f"{tmpdir}/file.txt" - create_file(testfile) + await async_create_file(hass, testfile) hass.config.allowlist_external_dirs = {tmpdir} mock_config_entry.add_to_hass(hass) hass.config_entries.async_update_entry( @@ -71,23 +69,3 @@ async def test_state_unavailable( state = hass.states.get("sensor.file_txt_size") assert state.state == STATE_UNAVAILABLE - - -async def test_import_query(hass: HomeAssistant, tmpdir: str) -> None: - """Test import from yaml.""" - testfile = f"{tmpdir}/file.txt" - create_file(testfile) - hass.config.allowlist_external_dirs = {tmpdir} - config = { - "sensor": { - "platform": "filesize", - "file_paths": [testfile], - } - } - - assert await async_setup_component(hass, "sensor", config) - await hass.async_block_till_done() - - assert hass.config_entries.async_entries(DOMAIN) - data = hass.config_entries.async_entries(DOMAIN)[0].data - assert data[CONF_FILE_PATH] == testfile diff --git a/tests/components/flux_led/test_init.py b/tests/components/flux_led/test_init.py index b0a2c5dd33b..3504dbf3bea 100644 --- a/tests/components/flux_led/test_init.py +++ b/tests/components/flux_led/test_init.py @@ -2,10 +2,11 @@ from __future__ import annotations from datetime import timedelta -from unittest.mock import patch +from unittest.mock import AsyncMock, patch import pytest +from homeassistant import config_entries from homeassistant.components import flux_led from homeassistant.components.flux_led.const import ( CONF_REMOTE_ACCESS_ENABLED, @@ -19,6 +20,8 @@ from homeassistant.const import ( CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STARTED, + STATE_ON, + STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -27,6 +30,7 @@ from homeassistant.util.dt import utcnow from . import ( DEFAULT_ENTRY_TITLE, + DHCP_DISCOVERY, FLUX_DISCOVERY, FLUX_DISCOVERY_PARTIAL, IP_ADDRESS, @@ -113,6 +117,70 @@ async def test_config_entry_retry(hass: HomeAssistant) -> None: assert config_entry.state == ConfigEntryState.SETUP_RETRY +async def test_config_entry_retry_right_away_on_discovery(hass: HomeAssistant) -> None: + """Test discovery makes the config entry reload if its in a retry state.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=MAC_ADDRESS + ) + config_entry.add_to_hass(hass) + with _patch_discovery(no_device=True), _patch_wifibulb(no_device=True): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.SETUP_RETRY + + with _patch_discovery(), _patch_wifibulb(): + await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=DHCP_DISCOVERY, + ) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.LOADED + + +async def test_coordinator_retry_right_away_on_discovery_already_setup( + hass: HomeAssistant, +) -> None: + """Test discovery makes the coordinator force poll if its already setup.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE}, + unique_id=MAC_ADDRESS, + ) + config_entry.add_to_hass(hass) + bulb = _mocked_bulb() + with _patch_discovery(), _patch_wifibulb(device=bulb): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + + assert config_entry.state == ConfigEntryState.LOADED + + entity_id = "light.bulb_rgbcw_ddeeff" + entity_registry = er.async_get(hass) + assert entity_registry.async_get(entity_id).unique_id == MAC_ADDRESS + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + now = utcnow() + bulb.async_update = AsyncMock(side_effect=RuntimeError) + async_fire_time_changed(hass, now + timedelta(seconds=50)) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == STATE_UNAVAILABLE + bulb.async_update = AsyncMock() + + with _patch_discovery(), _patch_wifibulb(): + await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=DHCP_DISCOVERY, + ) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + @pytest.mark.parametrize( "discovery,title", [ diff --git a/tests/components/fritz/const.py b/tests/components/fritz/const.py index 39533d07a93..f8f6f8370d7 100644 --- a/tests/components/fritz/const.py +++ b/tests/components/fritz/const.py @@ -30,8 +30,11 @@ MOCK_IPS = {"fritz.box": "192.168.178.1", "printer": "192.168.178.2"} MOCK_MODELNAME = "FRITZ!Box 7530 AX" MOCK_FIRMWARE = "256.07.29" MOCK_FIRMWARE_AVAILABLE = "256.07.50" +MOCK_FIRMWARE_RELEASE_URL = ( + "http://download.avm.de/fritzbox/fritzbox-7530-ax/deutschland/fritz.os/info_de.txt" +) MOCK_SERIAL_NUMBER = "fake_serial_number" -MOCK_FIRMWARE_INFO = [True, "1.1.1"] +MOCK_FIRMWARE_INFO = [True, "1.1.1", "some-release-url"] MOCK_MESH_SSID = "TestSSID" MOCK_MESH_MASTER_MAC = "1C:ED:6F:12:34:11" MOCK_MESH_MASTER_WIFI1_MAC = "1C:ED:6F:12:34:12" diff --git a/tests/components/fritz/test_update.py b/tests/components/fritz/test_update.py index f43b35755e8..2261ef3ef9b 100644 --- a/tests/components/fritz/test_update.py +++ b/tests/components/fritz/test_update.py @@ -10,7 +10,12 @@ from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from .const import MOCK_FIRMWARE, MOCK_FIRMWARE_AVAILABLE, MOCK_USER_DATA +from .const import ( + MOCK_FIRMWARE, + MOCK_FIRMWARE_AVAILABLE, + MOCK_FIRMWARE_RELEASE_URL, + MOCK_USER_DATA, +) from tests.common import MockConfigEntry @@ -38,7 +43,7 @@ async def test_update_available( with patch( "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", - return_value=(True, MOCK_FIRMWARE_AVAILABLE), + return_value=(True, MOCK_FIRMWARE_AVAILABLE, MOCK_FIRMWARE_RELEASE_URL), ): entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) entry.add_to_hass(hass) @@ -52,6 +57,7 @@ async def test_update_available( assert update.state == "on" assert update.attributes.get("installed_version") == MOCK_FIRMWARE assert update.attributes.get("latest_version") == MOCK_FIRMWARE_AVAILABLE + assert update.attributes.get("release_url") == MOCK_FIRMWARE_RELEASE_URL async def test_no_update_available( @@ -80,7 +86,7 @@ async def test_available_update_can_be_installed( with patch( "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", - return_value=(True, MOCK_FIRMWARE_AVAILABLE), + return_value=(True, MOCK_FIRMWARE_AVAILABLE, MOCK_FIRMWARE_RELEASE_URL), ), patch( "homeassistant.components.fritz.common.FritzBoxTools.async_trigger_firmware_update", return_value=True, diff --git a/tests/components/fritzbox/test_light.py b/tests/components/fritzbox/test_light.py index b63d19da7e0..0759beb6849 100644 --- a/tests/components/fritzbox/test_light.py +++ b/tests/components/fritzbox/test_light.py @@ -113,7 +113,34 @@ async def test_turn_on_color(hass: HomeAssistant, fritz: Mock): assert await setup_config_entry( hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz ) + assert await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_BRIGHTNESS: 100, ATTR_HS_COLOR: (100, 70)}, + True, + ) + assert device.set_state_on.call_count == 1 + assert device.set_level.call_count == 1 + assert device.set_unmapped_color.call_count == 1 + +async def test_turn_on_color_unsupported_api_method(hass: HomeAssistant, fritz: Mock): + """Test turn device on in mapped color mode if unmapped is not supported.""" + device = FritzDeviceLightMock() + device.get_color_temps.return_value = [2700, 6500] + device.get_colors.return_value = { + "Red": [("100", "70", "10"), ("100", "50", "10"), ("100", "30", "10")] + } + mockresponse = Mock() + mockresponse.status_code = 400 + + error = HTTPError("Bad Request") + error.response = mockresponse + device.set_unmapped_color.side_effect = error + + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz + ) assert await hass.services.async_call( DOMAIN, SERVICE_TURN_ON, @@ -135,7 +162,6 @@ async def test_turn_off(hass: HomeAssistant, fritz: Mock): assert await setup_config_entry( hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz ) - assert await hass.services.async_call( DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID}, True ) diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index 8c8fd3bf671..84ca04df3ba 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -424,7 +424,7 @@ async def test_get_translations(hass, ws_client): """Test get_translations command.""" with patch( "homeassistant.components.frontend.async_get_translations", - side_effect=lambda hass, lang, category, integration, config_flow: { + side_effect=lambda hass, lang, category, integrations, config_flow: { "lang": lang }, ): @@ -444,6 +444,58 @@ async def test_get_translations(hass, ws_client): assert msg["result"] == {"resources": {"lang": "nl"}} +async def test_get_translations_for_integrations(hass, ws_client): + """Test get_translations for integrations command.""" + with patch( + "homeassistant.components.frontend.async_get_translations", + side_effect=lambda hass, lang, category, integration, config_flow: { + "lang": lang, + "integration": integration, + }, + ): + await ws_client.send_json( + { + "id": 5, + "type": "frontend/get_translations", + "integration": ["frontend", "http"], + "language": "nl", + "category": "lang", + } + ) + msg = await ws_client.receive_json() + + assert msg["id"] == 5 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + assert set(msg["result"]["resources"]["integration"]) == {"frontend", "http"} + + +async def test_get_translations_for_single_integration(hass, ws_client): + """Test get_translations for integration command.""" + with patch( + "homeassistant.components.frontend.async_get_translations", + side_effect=lambda hass, lang, category, integrations, config_flow: { + "lang": lang, + "integration": integrations, + }, + ): + await ws_client.send_json( + { + "id": 5, + "type": "frontend/get_translations", + "integration": "http", + "language": "nl", + "category": "lang", + } + ) + msg = await ws_client.receive_json() + + assert msg["id"] == 5 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + assert msg["result"] == {"resources": {"lang": "nl", "integration": ["http"]}} + + async def test_auth_load(hass): """Test auth component loaded by default.""" frontend = await async_get_integration(hass, "frontend") diff --git a/tests/components/generic/conftest.py b/tests/components/generic/conftest.py index 9daa3574e6e..dc5c545869b 100644 --- a/tests/components/generic/conftest.py +++ b/tests/components/generic/conftest.py @@ -10,6 +10,8 @@ import respx from homeassistant import config_entries, setup from homeassistant.components.generic.const import DOMAIN +from tests.common import MockConfigEntry + @pytest.fixture(scope="package") def fakeimgbytes_png(): @@ -79,3 +81,36 @@ async def user_flow(hass): assert result["errors"] == {} return result + + +@pytest.fixture(name="config_entry") +def config_entry_fixture(hass): + """Define a config entry fixture.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Test Camera", + unique_id="abc123", + data={}, + options={ + "still_image_url": "http://joebloggs:letmein1@example.com/secret1/file.jpg?pw=qwerty", + "stream_source": "http://janebloggs:letmein2@example.com/stream", + "username": "johnbloggs", + "password": "letmein123", + "limit_refetch_to_url_change": False, + "authentication": "basic", + "framerate": 2.0, + "verify_ssl": True, + "content_type": "image/jpeg", + }, + version=1, + ) + entry.add_to_hass(hass) + return entry + + +@pytest.fixture +async def setup_entry(hass, config_entry): + """Set up a config entry ready to be used in tests.""" + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + return config_entry diff --git a/tests/components/generic/test_config_flow.py b/tests/components/generic/test_config_flow.py index dd53cb8548e..a525619d962 100644 --- a/tests/components/generic/test_config_flow.py +++ b/tests/components/generic/test_config_flow.py @@ -15,12 +15,14 @@ from homeassistant.components.generic.const import ( CONF_CONTENT_TYPE, CONF_FRAMERATE, CONF_LIMIT_REFETCH_TO_URL_CHANGE, - CONF_RTSP_TRANSPORT, CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE, - CONF_USE_WALLCLOCK_AS_TIMESTAMPS, DOMAIN, ) +from homeassistant.components.stream import ( + CONF_RTSP_TRANSPORT, + CONF_USE_WALLCLOCK_AS_TIMESTAMPS, +) from homeassistant.const import ( CONF_AUTHENTICATION, CONF_NAME, @@ -532,6 +534,16 @@ async def test_options_template_error(hass, fakeimgbytes_png, mock_av_open): assert result4.get("type") == data_entry_flow.RESULT_TYPE_FORM assert result4["errors"] == {"still_image_url": "template_error"} + # verify that an invalid template reports the correct UI error. + data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/1" + data[CONF_STREAM_SOURCE] = "http://127.0.0.2/testurl/{{1/0}}" + result5 = await hass.config_entries.options.async_configure( + result4["flow_id"], + user_input=data, + ) + assert result5.get("type") == data_entry_flow.RESULT_TYPE_FORM + assert result5["errors"] == {"stream_source": "template_error"} + @respx.mock async def test_options_only_stream(hass, fakeimgbytes_png, mock_av_open): diff --git a/tests/components/generic/test_diagnostics.py b/tests/components/generic/test_diagnostics.py new file mode 100644 index 00000000000..2d4e4c536d8 --- /dev/null +++ b/tests/components/generic/test_diagnostics.py @@ -0,0 +1,24 @@ +"""Test generic (IP camera) diagnostics.""" +from homeassistant.components.diagnostics import REDACTED + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_entry_diagnostics(hass, hass_client, setup_entry): + """Test config entry diagnostics.""" + + assert await get_diagnostics_for_config_entry(hass, hass_client, setup_entry) == { + "title": "Test Camera", + "data": {}, + "options": { + "still_image_url": "http://****:****@example.com/****?****=****", + "stream_source": "http://****:****@example.com/****", + "username": REDACTED, + "password": REDACTED, + "limit_refetch_to_url_change": False, + "authentication": "basic", + "framerate": 2.0, + "verify_ssl": True, + "content_type": "image/jpeg", + }, + } diff --git a/tests/components/generic_hygrostat/test_humidifier.py b/tests/components/generic_hygrostat/test_humidifier.py index d0412731c78..b27b6324f86 100644 --- a/tests/components/generic_hygrostat/test_humidifier.py +++ b/tests/components/generic_hygrostat/test_humidifier.py @@ -811,7 +811,7 @@ async def test_humidity_change_dry_trigger_on_not_long_enough(hass, setup_comp_4 async def test_humidity_change_dry_trigger_on_long_enough(hass, setup_comp_4): """Test if humidity change turn dry on.""" fake_changed = datetime.datetime( - 1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc + 1970, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc ) with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed @@ -845,7 +845,7 @@ async def test_humidity_change_dry_trigger_off_not_long_enough(hass, setup_comp_ async def test_humidity_change_dry_trigger_off_long_enough(hass, setup_comp_4): """Test if humidity change turn dry on.""" fake_changed = datetime.datetime( - 1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc + 1970, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc ) with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed @@ -967,7 +967,7 @@ async def test_humidity_change_humidifier_trigger_on_not_long_enough( async def test_humidity_change_humidifier_trigger_on_long_enough(hass, setup_comp_6): """Test if humidity change turn humidifier on after min cycle.""" fake_changed = datetime.datetime( - 1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc + 1970, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc ) with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed @@ -989,7 +989,7 @@ async def test_humidity_change_humidifier_trigger_on_long_enough(hass, setup_com async def test_humidity_change_humidifier_trigger_off_long_enough(hass, setup_comp_6): """Test if humidity change turn humidifier off after min cycle.""" fake_changed = datetime.datetime( - 1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc + 1970, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc ) with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index d607c6dbb61..c4d23b41579 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -749,7 +749,7 @@ async def test_temp_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): async def test_temp_change_ac_trigger_on_long_enough(hass, setup_comp_4): """Test if temperature change turn ac on.""" - fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) + fake_changed = datetime.datetime(1970, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): @@ -775,7 +775,7 @@ async def test_temp_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): async def test_temp_change_ac_trigger_off_long_enough(hass, setup_comp_4): """Test if temperature change turn ac on.""" - fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) + fake_changed = datetime.datetime(1970, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): @@ -855,7 +855,7 @@ async def test_temp_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): async def test_temp_change_ac_trigger_on_long_enough_2(hass, setup_comp_5): """Test if temperature change turn ac on.""" - fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) + fake_changed = datetime.datetime(1970, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): @@ -881,7 +881,7 @@ async def test_temp_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5): async def test_temp_change_ac_trigger_off_long_enough_2(hass, setup_comp_5): """Test if temperature change turn ac on.""" - fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) + fake_changed = datetime.datetime(1970, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): @@ -969,7 +969,7 @@ async def test_temp_change_heater_trigger_on_not_long_enough(hass, setup_comp_6) async def test_temp_change_heater_trigger_on_long_enough(hass, setup_comp_6): """Test if temperature change turn heater on after min cycle.""" - fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) + fake_changed = datetime.datetime(1970, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): @@ -986,7 +986,7 @@ async def test_temp_change_heater_trigger_on_long_enough(hass, setup_comp_6): async def test_temp_change_heater_trigger_off_long_enough(hass, setup_comp_6): """Test if temperature change turn heater off after min cycle.""" - fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) + fake_changed = datetime.datetime(1970, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): diff --git a/tests/components/geo_location/test_trigger.py b/tests/components/geo_location/test_trigger.py index bbf5f42ed60..de6276545b7 100644 --- a/tests/components/geo_location/test_trigger.py +++ b/tests/components/geo_location/test_trigger.py @@ -4,7 +4,12 @@ import logging import pytest from homeassistant.components import automation, zone -from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_OFF +from homeassistant.const import ( + ATTR_ENTITY_ID, + ENTITY_MATCH_ALL, + SERVICE_TURN_OFF, + STATE_UNAVAILABLE, +) from homeassistant.core import Context from homeassistant.setup import async_setup_component @@ -189,6 +194,41 @@ async def test_if_fires_on_zone_leave(hass, calls): assert len(calls) == 1 +async def test_if_fires_on_zone_leave_2(hass, calls): + """Test for firing on zone leave for unavailable entity.""" + hass.states.async_set( + "geo_location.entity", + "hello", + {"latitude": 32.880586, "longitude": -117.237564, "source": "test_source"}, + ) + await hass.async_block_till_done() + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "geo_location", + "source": "test_source", + "zone": "zone.test", + "event": "enter", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + hass.states.async_set( + "geo_location.entity", + STATE_UNAVAILABLE, + {"source": "test_source"}, + ) + await hass.async_block_till_done() + + assert len(calls) == 0 + + async def test_if_not_fires_for_leave_on_zone_enter(hass, calls): """Test for not firing on zone enter.""" hass.states.async_set( diff --git a/tests/components/geocaching/__init__.py b/tests/components/geocaching/__init__.py new file mode 100644 index 00000000000..8bc72aa3799 --- /dev/null +++ b/tests/components/geocaching/__init__.py @@ -0,0 +1,5 @@ +"""Tests for the Geocaching integration.""" + +CLIENT_ID = "1234" +CLIENT_SECRET = "5678" +REDIRECT_URI = "https://example.com/auth/external/callback" diff --git a/tests/components/geocaching/conftest.py b/tests/components/geocaching/conftest.py new file mode 100644 index 00000000000..f59f428118e --- /dev/null +++ b/tests/components/geocaching/conftest.py @@ -0,0 +1,50 @@ +"""Fixtures for the Geocaching integration tests.""" +from __future__ import annotations + +from collections.abc import Generator +from unittest.mock import AsyncMock, MagicMock, patch + +from geocachingapi import GeocachingStatus +import pytest + +from homeassistant.components.geocaching.const import DOMAIN + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="1234AB 1", + domain=DOMAIN, + data={ + "id": "mock_user", + "auth_implementation": DOMAIN, + }, + unique_id="mock_user", + ) + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Mock setting up a config entry.""" + with patch( + "homeassistant.components.geocaching.async_setup_entry", return_value=True + ) as mock_setup: + yield mock_setup + + +@pytest.fixture +def mock_geocaching_config_flow() -> Generator[None, MagicMock, None]: + """Return a mocked Geocaching API client.""" + + mock_status = GeocachingStatus() + mock_status.user.username = "mock_user" + + with patch( + "homeassistant.components.geocaching.config_flow.GeocachingApi", autospec=True + ) as geocaching_mock: + geocachingapi = geocaching_mock.return_value + geocachingapi.update.return_value = mock_status + yield geocachingapi diff --git a/tests/components/geocaching/test_config_flow.py b/tests/components/geocaching/test_config_flow.py new file mode 100644 index 00000000000..56a301e2f3c --- /dev/null +++ b/tests/components/geocaching/test_config_flow.py @@ -0,0 +1,241 @@ +"""Test the Geocaching config flow.""" +from collections.abc import Awaitable, Callable +from http import HTTPStatus +from unittest.mock import MagicMock + +from aiohttp.test_utils import TestClient +import pytest + +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) +from homeassistant.components.geocaching.const import ( + DOMAIN, + ENVIRONMENT, + ENVIRONMENT_URLS, +) +from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_EXTERNAL_STEP +from homeassistant.helpers import config_entry_oauth2_flow +from homeassistant.setup import async_setup_component + +from . import CLIENT_ID, CLIENT_SECRET, REDIRECT_URI + +from tests.common import MockConfigEntry +from tests.test_util.aiohttp import AiohttpClientMocker + +CURRENT_ENVIRONMENT_URLS = ENVIRONMENT_URLS[ENVIRONMENT] + + +@pytest.fixture(autouse=True) +async def setup_credentials(hass: HomeAssistant) -> None: + """Fixture to setup credentials.""" + assert await async_setup_component(hass, "application_credentials", {}) + await async_import_client_credential( + hass, + DOMAIN, + ClientCredential(CLIENT_ID, CLIENT_SECRET), + ) + + +async def test_full_flow( + hass: HomeAssistant, + hass_client_no_auth: Callable[[], Awaitable[TestClient]], + aioclient_mock: AiohttpClientMocker, + current_request_with_host: None, + mock_geocaching_config_flow: MagicMock, + mock_setup_entry: MagicMock, +) -> None: + """Check full flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert "flow_id" in result + + # pylint: disable=protected-access + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": REDIRECT_URI, + }, + ) + + assert result.get("type") == RESULT_TYPE_EXTERNAL_STEP + assert result.get("step_id") == "auth" + assert result.get("url") == ( + f"{CURRENT_ENVIRONMENT_URLS['authorize_url']}?response_type=code&client_id={CLIENT_ID}" + f"&redirect_uri={REDIRECT_URI}" + f"&state={state}&scope=*" + ) + + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == HTTPStatus.OK + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + CURRENT_ENVIRONMENT_URLS["token_url"], + json={ + "access_token": "mock-access-token", + "token_type": "bearer", + "expires_in": 3599, + "refresh_token": "mock-refresh_token", + }, + ) + + await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_existing_entry( + hass: HomeAssistant, + hass_client_no_auth: Callable[[], Awaitable[TestClient]], + aioclient_mock: AiohttpClientMocker, + current_request_with_host: None, + mock_geocaching_config_flow: MagicMock, + mock_setup_entry: MagicMock, + mock_config_entry: MockConfigEntry, + setup_credentials: None, +) -> None: + """Check existing entry.""" + mock_config_entry.add_to_hass(hass) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert "flow_id" in result + # pylint: disable=protected-access + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": REDIRECT_URI, + }, + ) + + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == HTTPStatus.OK + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + CURRENT_ENVIRONMENT_URLS["token_url"], + json={ + "access_token": "mock-access-token", + "token_type": "bearer", + "expires_in": 3599, + "refresh_token": "mock-refresh_token", + }, + ) + + await hass.config_entries.flow.async_configure(result["flow_id"]) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +async def test_oauth_error( + hass: HomeAssistant, + hass_client_no_auth: Callable[[], Awaitable[TestClient]], + aioclient_mock: AiohttpClientMocker, + current_request_with_host: None, + mock_geocaching_config_flow: MagicMock, + mock_setup_entry: MagicMock, +) -> None: + """Check if aborted when oauth error occurs.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert "flow_id" in result + + # pylint: disable=protected-access + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": REDIRECT_URI, + }, + ) + assert result.get("type") == RESULT_TYPE_EXTERNAL_STEP + + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == HTTPStatus.OK + + # No user information is returned from API + mock_geocaching_config_flow.update.return_value.user = None + + aioclient_mock.post( + CURRENT_ENVIRONMENT_URLS["token_url"], + json={ + "access_token": "mock-access-token", + "token_type": "bearer", + "expires_in": 3599, + "refresh_token": "mock-refresh_token", + }, + ) + + result2 = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result2.get("type") == RESULT_TYPE_ABORT + assert result2.get("reason") == "oauth_error" + + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + assert len(mock_setup_entry.mock_calls) == 0 + + +async def test_reauthentication( + hass: HomeAssistant, + hass_client_no_auth: Callable[[], Awaitable[TestClient]], + aioclient_mock: AiohttpClientMocker, + current_request_with_host: None, + mock_geocaching_config_flow: MagicMock, + mock_setup_entry: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test Geocaching reauthentication.""" + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_REAUTH} + ) + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + assert "flow_id" in flows[0] + + result = await hass.config_entries.flow.async_configure(flows[0]["flow_id"], {}) + assert "flow_id" in result + + # pylint: disable=protected-access + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == HTTPStatus.OK + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + CURRENT_ENVIRONMENT_URLS["token_url"], + json={ + "access_token": "mock-access-token", + "token_type": "bearer", + "expires_in": 3599, + "refresh_token": "mock-refresh_token", + }, + ) + + await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/goalzero/test_init.py b/tests/components/goalzero/test_init.py index a436b491d48..5f84842ad27 100644 --- a/tests/components/goalzero/test_init.py +++ b/tests/components/goalzero/test_init.py @@ -68,7 +68,7 @@ async def test_update_failed( async def test_device_info(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker): """Test device info.""" entry = await async_init_integration(hass, aioclient_mock) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_device({(DOMAIN, entry.entry_id)}) diff --git a/tests/components/google/conftest.py b/tests/components/google/conftest.py index 9594963008f..b5566450913 100644 --- a/tests/components/google/conftest.py +++ b/tests/components/google/conftest.py @@ -4,8 +4,9 @@ from __future__ import annotations from collections.abc import Awaitable, Callable import datetime from typing import Any, Generator, TypeVar -from unittest.mock import mock_open, patch +from unittest.mock import Mock, mock_open, patch +from aiohttp.client_exceptions import ClientError from gcal_sync.auth import API_BASE_URL from oauth2client.client import Credentials, OAuth2Credentials import pytest @@ -103,11 +104,11 @@ def calendars_config(calendars_config_entity: dict[str, Any]) -> list[dict[str, def mock_calendars_yaml( hass: HomeAssistant, calendars_config: list[dict[str, Any]], -) -> None: +) -> Generator[Mock, None, None]: """Fixture that prepares the google_calendars.yaml mocks.""" mocked_open_function = mock_open(read_data=yaml.dump(calendars_config)) with patch("homeassistant.components.google.open", mocked_open_function): - yield + yield mocked_open_function class FakeStorage: @@ -169,9 +170,17 @@ def config_entry_token_expiry(token_expiry: datetime.datetime) -> float: return token_expiry.timestamp() +@pytest.fixture +def config_entry_options() -> dict[str, Any] | None: + """Fixture to set initial config entry options.""" + return None + + @pytest.fixture def config_entry( - token_scopes: list[str], config_entry_token_expiry: float + token_scopes: list[str], + config_entry_token_expiry: float, + config_entry_options: dict[str, Any] | None, ) -> MockConfigEntry: """Fixture to create a config entry for the integration.""" return MockConfigEntry( @@ -186,6 +195,7 @@ def config_entry( "expires_at": config_entry_token_expiry, }, }, + options=config_entry_options, ) @@ -206,7 +216,9 @@ def mock_events_list( """Fixture to construct a fake event list API response.""" def _put_result( - response: dict[str, Any], calendar_id: str = None, exc: Exception = None + response: dict[str, Any], + calendar_id: str = None, + exc: ClientError | None = None, ) -> None: if calendar_id is None: calendar_id = CALENDAR_ID @@ -239,7 +251,7 @@ def mock_calendars_list( ) -> ApiResult: """Fixture to construct a fake calendar list API response.""" - def _put_result(response: dict[str, Any], exc=None) -> None: + def _result(response: dict[str, Any], exc: ClientError | None = None) -> None: aioclient_mock.get( f"{API_BASE_URL}/users/me/calendarList", json=response, @@ -247,13 +259,32 @@ def mock_calendars_list( ) return - return _put_result + return _result + + +@pytest.fixture +def mock_calendar_get( + aioclient_mock: AiohttpClientMocker, +) -> Callable[[...], None]: + """Fixture for returning a calendar get response.""" + + def _result( + calendar_id: str, response: dict[str, Any], exc: ClientError | None = None + ) -> None: + aioclient_mock.get( + f"{API_BASE_URL}/calendars/{calendar_id}", + json=response, + exc=exc, + ) + return + + return _result @pytest.fixture def mock_insert_event( aioclient_mock: AiohttpClientMocker, -) -> Callable[[..., dict[str, Any]], None]: +) -> Callable[[...], None]: """Fixture for capturing event creation.""" def _expect_result(calendar_id: str = CALENDAR_ID) -> None: @@ -291,7 +322,7 @@ def google_config(google_config_track_new: bool | None) -> dict[str, Any]: @pytest.fixture def config(google_config: dict[str, Any]) -> dict[str, Any]: """Fixture for overriding component config.""" - return {DOMAIN: google_config} + return {DOMAIN: google_config} if google_config else {} @pytest.fixture diff --git a/tests/components/google/test_calendar.py b/tests/components/google/test_calendar.py index 6ae62f50914..794065ca09a 100644 --- a/tests/components/google/test_calendar.py +++ b/tests/components/google/test_calendar.py @@ -572,14 +572,16 @@ async def test_opaque_event( assert (len(events) > 0) == expect_visible_event +@pytest.mark.parametrize("mock_test_setup", [None]) async def test_scan_calendar_error( hass, component_setup, test_api_calendar, mock_calendars_list, + config_entry, ): """Test that the calendar update handles a server error.""" - + config_entry.add_to_hass(hass) mock_calendars_list({}, exc=ClientError()) assert await component_setup() diff --git a/tests/components/google/test_config_flow.py b/tests/components/google/test_config_flow.py index e96a4c3fd5f..8ac017fcba4 100644 --- a/tests/components/google/test_config_flow.py +++ b/tests/components/google/test_config_flow.py @@ -1,8 +1,13 @@ """Test the google config flow.""" +from __future__ import annotations + +from collections.abc import Callable import datetime +from typing import Any from unittest.mock import Mock, patch +from aiohttp.client_exceptions import ClientError from oauth2client.client import ( FlowExchangeError, OAuth2Credentials, @@ -11,8 +16,14 @@ from oauth2client.client import ( import pytest from homeassistant import config_entries +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.components.google.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.util.dt import utcnow from .conftest import ComponentSetup, YieldFixture @@ -21,6 +32,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed CODE_CHECK_INTERVAL = 1 CODE_CHECK_ALARM_TIMEDELTA = datetime.timedelta(seconds=CODE_CHECK_INTERVAL * 2) +EMAIL_ADDRESS = "user@gmail.com" @pytest.fixture(autouse=True) @@ -57,6 +69,24 @@ async def mock_exchange(creds: OAuth2Credentials) -> YieldFixture[Mock]: yield mock +@pytest.fixture +async def primary_calendar_error() -> ClientError | None: + """Fixture for tests to inject an error during calendar lookup.""" + return None + + +@pytest.fixture(autouse=True) +async def primary_calendar( + mock_calendar_get: Callable[[...], None], primary_calendar_error: ClientError | None +) -> None: + """Fixture to return the primary calendar.""" + mock_calendar_get( + "primary", + {"id": EMAIL_ADDRESS, "summary": "Personal"}, + exc=primary_calendar_error, + ) + + async def fire_alarm(hass, point_in_time): """Fire an alarm and wait for callbacks to run.""" with patch("homeassistant.util.dt.utcnow", return_value=point_in_time): @@ -64,7 +94,7 @@ async def fire_alarm(hass, point_in_time): await hass.async_block_till_done() -async def test_full_flow( +async def test_full_flow_yaml_creds( hass: HomeAssistant, mock_code_flow: Mock, mock_exchange: Mock, @@ -93,7 +123,7 @@ async def test_full_flow( ) assert result.get("type") == "create_entry" - assert result.get("title") == "Configuration.yaml" + assert result.get("title") == EMAIL_ADDRESS assert "data" in result data = result["data"] assert "token" in data @@ -114,6 +144,70 @@ async def test_full_flow( "token_type": "Bearer", }, } + assert result.get("options") == {"calendar_access": "read_write"} + + assert len(mock_setup.mock_calls) == 1 + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + + +@pytest.mark.parametrize("google_config", [None]) +async def test_full_flow_application_creds( + hass: HomeAssistant, + mock_code_flow: Mock, + mock_exchange: Mock, + config: dict[str, Any], + component_setup: ComponentSetup, +) -> None: + """Test successful creds setup.""" + assert await component_setup() + + await async_import_client_credential( + hass, DOMAIN, ClientCredential("client-id", "client-secret"), "imported-cred" + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == "progress" + assert result.get("step_id") == "auth" + assert "description_placeholders" in result + assert "url" in result["description_placeholders"] + + with patch( + "homeassistant.components.google.async_setup_entry", return_value=True + ) as mock_setup: + # Run one tick to invoke the credential exchange check + now = utcnow() + await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA) + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + flow_id=result["flow_id"] + ) + + assert result.get("type") == "create_entry" + assert result.get("title") == EMAIL_ADDRESS + assert "data" in result + data = result["data"] + assert "token" in data + assert 0 < data["token"]["expires_in"] < 8 * 86400 + assert ( + datetime.datetime.now().timestamp() + <= data["token"]["expires_at"] + < (datetime.datetime.now() + datetime.timedelta(days=8)).timestamp() + ) + data["token"].pop("expires_at") + data["token"].pop("expires_in") + assert data == { + "auth_implementation": "imported-cred", + "token": { + "access_token": "ACCESS_TOKEN", + "refresh_token": "REFRESH_TOKEN", + "scope": "https://www.googleapis.com/auth/calendar", + "token_type": "Bearer", + }, + } + assert result.get("options") == {"calendar_access": "read_write"} assert len(mock_setup.mock_calls) == 1 entries = hass.config_entries.async_entries(DOMAIN) @@ -210,7 +304,7 @@ async def test_exchange_error( ) assert result.get("type") == "create_entry" - assert result.get("title") == "Configuration.yaml" + assert result.get("title") == EMAIL_ADDRESS assert "data" in result data = result["data"] assert "token" in data @@ -254,12 +348,53 @@ async def test_existing_config_entry( async def test_missing_configuration( hass: HomeAssistant, ) -> None: - """Test can't configure when config entry already exists.""" + """Test can't configure when no authentication source is available.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result.get("type") == "abort" - assert result.get("reason") == "missing_configuration" + assert result.get("reason") == "missing_credentials" + + +@pytest.mark.parametrize("google_config", [None]) +async def test_missing_configuration_yaml_empty( + hass: HomeAssistant, + component_setup: ComponentSetup, +) -> None: + """Test setup with an empty yaml configuration and no credentials.""" + assert await component_setup() + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == "abort" + assert result.get("reason") == "missing_credentials" + + +async def test_wrong_configuration( + hass: HomeAssistant, +) -> None: + """Test can't use the wrong type of authentication.""" + + # Google calendar flow currently only supports device auth + config_entry_oauth2_flow.async_register_implementation( + hass, + DOMAIN, + config_entry_oauth2_flow.LocalOAuth2Implementation( + hass, + DOMAIN, + "client-id", + "client-secret", + "http://example/authorize", + "http://example/token", + ), + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == "abort" + assert result.get("reason") == "oauth_error" async def test_import_config_entry_from_existing_token( @@ -309,7 +444,12 @@ async def test_reauth_flow( assert await component_setup() result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=config_entry.data + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": config_entry.entry_id, + }, + data=config_entry.data, ) assert result["type"] == "form" assert result["step_id"] == "reauth_confirm" @@ -354,3 +494,104 @@ async def test_reauth_flow( } assert len(mock_setup.mock_calls) == 1 + + +@pytest.mark.parametrize("primary_calendar_error", [ClientError()]) +async def test_title_lookup_failure( + hass: HomeAssistant, + mock_code_flow: Mock, + mock_exchange: Mock, + component_setup: ComponentSetup, +) -> None: + """Test successful config flow and title fetch fails gracefully.""" + assert await component_setup() + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result.get("type") == "progress" + assert result.get("step_id") == "auth" + assert "description_placeholders" in result + assert "url" in result["description_placeholders"] + + with patch( + "homeassistant.components.google.async_setup_entry", return_value=True + ) as mock_setup: + # Run one tick to invoke the credential exchange check + now = utcnow() + await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA) + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + flow_id=result["flow_id"] + ) + + assert result.get("type") == "create_entry" + assert result.get("title") == "Import from configuration.yaml" + + assert len(mock_setup.mock_calls) == 1 + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + + +async def test_options_flow_triggers_reauth( + hass: HomeAssistant, + component_setup: ComponentSetup, + config_entry: MockConfigEntry, +) -> None: + """Test load and unload of a ConfigEntry.""" + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.google.async_setup_entry", return_value=True + ) as mock_setup: + await component_setup() + mock_setup.assert_called_once() + + assert config_entry.state is ConfigEntryState.LOADED + assert config_entry.options == {} # Default is read_write + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == "form" + assert result["step_id"] == "init" + data_schema = result["data_schema"].schema + assert set(data_schema) == {"calendar_access"} + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "calendar_access": "read_only", + }, + ) + assert result["type"] == "create_entry" + assert config_entry.options == {"calendar_access": "read_only"} + + +async def test_options_flow_no_changes( + hass: HomeAssistant, + component_setup: ComponentSetup, + config_entry: MockConfigEntry, +) -> None: + """Test load and unload of a ConfigEntry.""" + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.google.async_setup_entry", return_value=True + ) as mock_setup: + await component_setup() + mock_setup.assert_called_once() + + assert config_entry.state is ConfigEntryState.LOADED + assert config_entry.options == {} # Default is read_write + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == "form" + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "calendar_access": "read_write", + }, + ) + assert result["type"] == "create_entry" + assert config_entry.options == {"calendar_access": "read_write"} diff --git a/tests/components/google/test_init.py b/tests/components/google/test_init.py index c536ef7ea00..f2cf067f7bb 100644 --- a/tests/components/google/test_init.py +++ b/tests/components/google/test_init.py @@ -6,18 +6,24 @@ import datetime import http import time from typing import Any -from unittest.mock import patch +from unittest.mock import Mock, patch import pytest +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) from homeassistant.components.google import ( DOMAIN, SERVICE_ADD_EVENT, SERVICE_SCAN_CALENDARS, ) +from homeassistant.components.google.const import CONF_CALENDAR_ACCESS from homeassistant.config_entries import ConfigEntryState from homeassistant.const import STATE_OFF from homeassistant.core import HomeAssistant, State +from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow from .conftest import ( @@ -41,7 +47,7 @@ HassApi = Callable[[], Awaitable[dict[str, Any]]] def assert_state(actual: State | None, expected: State | None) -> None: """Assert that the two states are equal.""" - if actual is None: + if actual is None or expected is None: assert actual == expected return assert actual.entity_id == expected.entity_id @@ -96,6 +102,24 @@ async def test_existing_token_missing_scope( assert flows[0]["step_id"] == "reauth_confirm" +@pytest.mark.parametrize("config_entry_options", [{CONF_CALENDAR_ACCESS: "read_only"}]) +async def test_config_entry_scope_reauth( + hass: HomeAssistant, + token_scopes: list[str], + component_setup: ComponentSetup, + config_entry: MockConfigEntry, +) -> None: + """Test setup where the config entry options requires reauth to match the scope.""" + config_entry.add_to_hass(hass) + assert await component_setup() + + assert config_entry.state is ConfigEntryState.SETUP_ERROR + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + assert flows[0]["step_id"] == "reauth_confirm" + + @pytest.mark.parametrize("calendars_config", [[{"cal_id": "invalid-schema"}]]) async def test_calendar_yaml_missing_required_fields( hass: HomeAssistant, @@ -148,57 +172,6 @@ async def test_calendar_yaml_error( assert hass.states.get(TEST_API_ENTITY) -@pytest.mark.parametrize( - "google_config_track_new,calendars_config,expected_state", - [ - ( - None, - [], - State( - TEST_API_ENTITY, - STATE_OFF, - attributes={ - "offset_reached": False, - "friendly_name": TEST_API_ENTITY_NAME, - }, - ), - ), - ( - True, - [], - State( - TEST_API_ENTITY, - STATE_OFF, - attributes={ - "offset_reached": False, - "friendly_name": TEST_API_ENTITY_NAME, - }, - ), - ), - (False, [], None), - ], - ids=["default", "True", "False"], -) -async def test_track_new( - hass: HomeAssistant, - component_setup: ComponentSetup, - mock_calendars_list: ApiResult, - test_api_calendar: dict[str, Any], - mock_events_list: ApiResult, - mock_calendars_yaml: None, - expected_state: State, - setup_config_entry: MockConfigEntry, -) -> None: - """Test behavior of configuration.yaml settings for tracking new calendars not in the config.""" - - mock_calendars_list({"items": [test_api_calendar]}) - mock_events_list({}) - assert await component_setup() - - state = hass.states.get(TEST_API_ENTITY) - assert_state(state, expected_state) - - @pytest.mark.parametrize("calendars_config", [[]]) async def test_found_calendar_from_api( hass: HomeAssistant, @@ -225,7 +198,39 @@ async def test_found_calendar_from_api( @pytest.mark.parametrize( - "calendars_config_track,expected_state", + "calendars_config,google_config,config_entry_options", + [([], {}, {CONF_CALENDAR_ACCESS: "read_write"})], +) +async def test_load_application_credentials( + hass: HomeAssistant, + component_setup: ComponentSetup, + mock_calendars_yaml: None, + mock_calendars_list: ApiResult, + test_api_calendar: dict[str, Any], + mock_events_list: ApiResult, + setup_config_entry: MockConfigEntry, +) -> None: + """Test loading an application credentials and a config entry.""" + assert await async_setup_component(hass, "application_credentials", {}) + await async_import_client_credential( + hass, DOMAIN, ClientCredential("client-id", "client-secret"), "device_auth" + ) + + mock_calendars_list({"items": [test_api_calendar]}) + mock_events_list({}) + assert await component_setup() + + state = hass.states.get(TEST_API_ENTITY) + assert state + assert state.name == TEST_API_ENTITY_NAME + assert state.state == STATE_OFF + + # No yaml config loaded that overwrites the entity name + assert not hass.states.get(TEST_YAML_ENTITY) + + +@pytest.mark.parametrize( + "calendars_config_track,expected_state,google_config_track_new", [ ( True, @@ -237,8 +242,35 @@ async def test_found_calendar_from_api( "friendly_name": TEST_YAML_ENTITY_NAME, }, ), + None, ), - (False, None), + ( + True, + State( + TEST_YAML_ENTITY, + STATE_OFF, + attributes={ + "offset_reached": False, + "friendly_name": TEST_YAML_ENTITY_NAME, + }, + ), + True, + ), + ( + True, + State( + TEST_YAML_ENTITY, + STATE_OFF, + attributes={ + "offset_reached": False, + "friendly_name": TEST_YAML_ENTITY_NAME, + }, + ), + False, # Has no effect + ), + (False, None, None), + (False, None, True), + (False, None, False), ], ) async def test_calendar_config_track_new( @@ -570,3 +602,94 @@ async def test_expired_token_requires_reauth( flows = hass.config_entries.flow.async_progress() assert len(flows) == 1 assert flows[0]["step_id"] == "reauth_confirm" + + +@pytest.mark.parametrize( + "calendars_config,expect_write_calls", + [ + ( + [ + { + "cal_id": "ignored", + "entities": {"device_id": "existing", "name": "existing"}, + } + ], + True, + ), + ([], False), + ], + ids=["has_yaml", "no_yaml"], +) +async def test_calendar_yaml_update( + hass: HomeAssistant, + component_setup: ComponentSetup, + mock_calendars_yaml: Mock, + mock_calendars_list: ApiResult, + test_api_calendar: dict[str, Any], + mock_events_list: ApiResult, + setup_config_entry: MockConfigEntry, + calendars_config: dict[str, Any], + expect_write_calls: bool, +) -> None: + """Test updating the yaml file with a new calendar.""" + + mock_calendars_list({"items": [test_api_calendar]}) + mock_events_list({}) + assert await component_setup() + + mock_calendars_yaml().read.assert_called() + mock_calendars_yaml().write.called is expect_write_calls + + state = hass.states.get(TEST_API_ENTITY) + assert state + assert state.name == TEST_API_ENTITY_NAME + assert state.state == STATE_OFF + + # No yaml config loaded that overwrites the entity name + assert not hass.states.get(TEST_YAML_ENTITY) + + +async def test_update_will_reload( + hass: HomeAssistant, + component_setup: ComponentSetup, + setup_config_entry: Any, + mock_calendars_list: ApiResult, + test_api_calendar: dict[str, Any], + mock_events_list: ApiResult, + config_entry: MockConfigEntry, +) -> None: + """Test updating config entry options will trigger a reload.""" + mock_calendars_list({"items": [test_api_calendar]}) + mock_events_list({}) + await component_setup() + assert config_entry.state is ConfigEntryState.LOADED + assert config_entry.options == {} # read_write is default + + with patch( + "homeassistant.config_entries.ConfigEntries.async_reload", + return_value=None, + ) as mock_reload: + # No-op does not reload + hass.config_entries.async_update_entry( + config_entry, options={CONF_CALENDAR_ACCESS: "read_write"} + ) + await hass.async_block_till_done() + mock_reload.assert_not_called() + + # Data change does not trigger reload + hass.config_entries.async_update_entry( + config_entry, + data={ + **config_entry.data, + "example": "field", + }, + ) + await hass.async_block_till_done() + mock_reload.assert_not_called() + + # Reload when options changed + hass.config_entries.async_update_entry( + config_entry, options={CONF_CALENDAR_ACCESS: "read_only"} + ) + await hass.async_block_till_done() + mock_reload.assert_called_once() diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py index 8bf0e5573b2..e8a2603cae3 100644 --- a/tests/components/google_assistant/test_google_assistant.py +++ b/tests/components/google_assistant/test_google_assistant.py @@ -225,7 +225,7 @@ async def test_query_request(hass_fixture, assistant_client, auth_header): assert len(devices) == 4 assert devices["light.bed_light"]["on"] is False assert devices["light.ceiling_lights"]["on"] is True - assert devices["light.ceiling_lights"]["brightness"] == 70 + assert devices["light.ceiling_lights"]["brightness"] == 71 assert devices["light.ceiling_lights"]["color"]["temperatureK"] == 2631 assert devices["light.kitchen_lights"]["color"]["spectrumHsv"] == { "hue": 345, diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index 4490f0e3963..1ab573baf2a 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -345,3 +345,85 @@ def test_request_data(): config, "test_user", SOURCE_CLOUD, "test_request_id", None ) assert data.is_local_request is False + + +async def test_config_local_sdk_allow_min_version(hass, hass_client, caplog): + """Test the local SDK.""" + version = str(helpers.LOCAL_SDK_MIN_VERSION) + assert await async_setup_component(hass, "webhook", {}) + + config = MockConfig( + hass=hass, + agent_user_ids={ + "mock-user-id": { + STORE_GOOGLE_LOCAL_WEBHOOK_ID: "mock-webhook-id", + }, + }, + ) + + client = await hass_client() + + assert config._local_sdk_version_warn is False + config.async_enable_local_sdk() + + await client.post( + "/api/webhook/mock-webhook-id", + headers={helpers.LOCAL_SDK_VERSION_HEADER: version}, + json={ + "inputs": [ + { + "context": {"locale_country": "US", "locale_language": "en"}, + "intent": "action.devices.SYNC", + } + ], + "requestId": "mock-req-id", + }, + ) + assert config._local_sdk_version_warn is False + assert ( + f"Local SDK version is too old ({version}), check documentation on how " + "to update to the latest version" + ) not in caplog.text + + +@pytest.mark.parametrize("version", (None, "2.1.4")) +async def test_config_local_sdk_warn_version(hass, hass_client, caplog, version): + """Test the local SDK.""" + assert await async_setup_component(hass, "webhook", {}) + + config = MockConfig( + hass=hass, + agent_user_ids={ + "mock-user-id": { + STORE_GOOGLE_LOCAL_WEBHOOK_ID: "mock-webhook-id", + }, + }, + ) + + client = await hass_client() + + assert config._local_sdk_version_warn is False + config.async_enable_local_sdk() + + headers = {} + if version: + headers[helpers.LOCAL_SDK_VERSION_HEADER] = version + + await client.post( + "/api/webhook/mock-webhook-id", + headers=headers, + json={ + "inputs": [ + { + "context": {"locale_country": "US", "locale_language": "en"}, + "intent": "action.devices.SYNC", + } + ], + "requestId": "mock-req-id", + }, + ) + assert config._local_sdk_version_warn is True + assert ( + f"Local SDK version is too old ({version}), check documentation on how " + "to update to the latest version" + ) in caplog.text diff --git a/tests/components/google_assistant/test_logbook.py b/tests/components/google_assistant/test_logbook.py index 09d0b12e417..5875d44adc7 100644 --- a/tests/components/google_assistant/test_logbook.py +++ b/tests/components/google_assistant/test_logbook.py @@ -1,5 +1,4 @@ """The tests for Google Assistant logbook.""" -from homeassistant.components import logbook from homeassistant.components.google_assistant.const import ( DOMAIN, EVENT_COMMAND_RECEIVED, @@ -9,7 +8,7 @@ from homeassistant.components.google_assistant.const import ( from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME from homeassistant.setup import async_setup_component -from tests.components.logbook.test_init import MockLazyEventPartialState +from tests.components.logbook.common import MockRow, mock_humanify async def test_humanify_command_received(hass): @@ -18,48 +17,43 @@ async def test_humanify_command_received(hass): hass.config.components.add("frontend") hass.config.components.add("google_assistant") assert await async_setup_component(hass, "logbook", {}) - entity_attr_cache = logbook.EntityAttributeCache(hass) hass.states.async_set( "light.kitchen", "on", {ATTR_FRIENDLY_NAME: "The Kitchen Lights"} ) - events = list( - logbook.humanify( - hass, - [ - MockLazyEventPartialState( - EVENT_COMMAND_RECEIVED, - { - "request_id": "abcd", - ATTR_ENTITY_ID: ["light.kitchen"], - "execution": [ - { - "command": "action.devices.commands.OnOff", - "params": {"on": True}, - } - ], - "source": SOURCE_LOCAL, - }, - ), - MockLazyEventPartialState( - EVENT_COMMAND_RECEIVED, - { - "request_id": "abcd", - ATTR_ENTITY_ID: ["light.non_existing"], - "execution": [ - { - "command": "action.devices.commands.OnOff", - "params": {"on": False}, - } - ], - "source": SOURCE_CLOUD, - }, - ), - ], - entity_attr_cache, - {}, - ) + events = mock_humanify( + hass, + [ + MockRow( + EVENT_COMMAND_RECEIVED, + { + "request_id": "abcd", + ATTR_ENTITY_ID: ["light.kitchen"], + "execution": [ + { + "command": "action.devices.commands.OnOff", + "params": {"on": True}, + } + ], + "source": SOURCE_LOCAL, + }, + ), + MockRow( + EVENT_COMMAND_RECEIVED, + { + "request_id": "abcd", + ATTR_ENTITY_ID: ["light.non_existing"], + "execution": [ + { + "command": "action.devices.commands.OnOff", + "params": {"on": False}, + } + ], + "source": SOURCE_CLOUD, + }, + ), + ], ) assert len(events) == 2 diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index c3bbd9336f4..4b11910999a 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -385,7 +385,7 @@ async def test_query_message(hass): "light.another_light": { "on": True, "online": True, - "brightness": 30, + "brightness": 31, "color": { "spectrumHsv": { "hue": 180, @@ -1510,7 +1510,7 @@ async def test_query_recover(hass, caplog): "payload": { "devices": { "light.bad": {"online": False}, - "light.good": {"on": True, "online": True, "brightness": 19}, + "light.good": {"on": True, "online": True, "brightness": 20}, } }, } diff --git a/tests/components/google_translate/test_tts.py b/tests/components/google_translate/test_tts.py index c81cea57090..cc80d9c64b9 100644 --- a/tests/components/google_translate/test_tts.py +++ b/tests/components/google_translate/test_tts.py @@ -25,7 +25,7 @@ async def get_media_source_url(hass, media_content_id): if media_source.DOMAIN not in hass.config.components: assert await async_setup_component(hass, media_source.DOMAIN, {}) - resolved = await media_source.async_resolve_media(hass, media_content_id) + resolved = await media_source.async_resolve_media(hass, media_content_id, None) return resolved.url diff --git a/tests/components/group/test_recorder.py b/tests/components/group/test_recorder.py new file mode 100644 index 00000000000..fb68d9d3d43 --- /dev/null +++ b/tests/components/group/test_recorder.py @@ -0,0 +1,61 @@ +"""The tests for group recorder.""" +from __future__ import annotations + +from datetime import timedelta + +from homeassistant.components import group +from homeassistant.components.group import ATTR_AUTO, ATTR_ENTITY_ID, ATTR_ORDER +from homeassistant.components.recorder.models import StateAttributes, States +from homeassistant.components.recorder.util import session_scope +from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_ON +from homeassistant.core import State +from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util + +from tests.common import async_fire_time_changed +from tests.components.recorder.common import async_wait_recording_done + + +async def test_exclude_attributes(hass, recorder_mock): + """Test number registered attributes to be excluded.""" + hass.states.async_set("light.bowl", STATE_ON) + + assert await async_setup_component(hass, "light", {}) + assert await async_setup_component( + hass, + group.DOMAIN, + { + group.DOMAIN: { + "group_zero": {"entities": "light.Bowl", "icon": "mdi:work"}, + "group_one": {"entities": "light.Bowl", "icon": "mdi:work"}, + "group_two": {"entities": "light.Bowl", "icon": "mdi:work"}, + } + }, + ) + await hass.async_block_till_done() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + await async_wait_recording_done(hass) + + def _fetch_states() -> list[State]: + with session_scope(hass=hass) as session: + native_states = [] + attr_ids = {} + for db_state_attributes in session.query(StateAttributes): + attr_ids[ + db_state_attributes.attributes_id + ] = db_state_attributes.to_native() + for db_state, db_state_attributes in session.query(States, StateAttributes): + state = db_state.to_native() + state.attributes = attr_ids[db_state.attributes_id] + native_states.append(state) + return native_states + + states: list[State] = await hass.async_add_executor_job(_fetch_states) + assert len(states) > 1 + for state in states: + if state.domain == group.DOMAIN: + assert ATTR_AUTO not in state.attributes + assert ATTR_ENTITY_ID not in state.attributes + assert ATTR_ORDER not in state.attributes + assert ATTR_FRIENDLY_NAME in state.attributes diff --git a/tests/components/hardkernel/__init__.py b/tests/components/hardkernel/__init__.py new file mode 100644 index 00000000000..d63b70d5cc5 --- /dev/null +++ b/tests/components/hardkernel/__init__.py @@ -0,0 +1 @@ +"""Tests for the Hardkernel integration.""" diff --git a/tests/components/hardkernel/test_config_flow.py b/tests/components/hardkernel/test_config_flow.py new file mode 100644 index 00000000000..f74b4a4e658 --- /dev/null +++ b/tests/components/hardkernel/test_config_flow.py @@ -0,0 +1,58 @@ +"""Test the Hardkernel config flow.""" +from unittest.mock import patch + +from homeassistant.components.hardkernel.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_CREATE_ENTRY + +from tests.common import MockConfigEntry, MockModule, mock_integration + + +async def test_config_flow(hass: HomeAssistant) -> None: + """Test the config flow.""" + mock_integration(hass, MockModule("hassio")) + + with patch( + "homeassistant.components.hardkernel.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "system"} + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "Hardkernel" + assert result["data"] == {} + assert result["options"] == {} + assert len(mock_setup_entry.mock_calls) == 1 + + config_entry = hass.config_entries.async_entries(DOMAIN)[0] + assert config_entry.data == {} + assert config_entry.options == {} + assert config_entry.title == "Hardkernel" + + +async def test_config_flow_single_entry(hass: HomeAssistant) -> None: + """Test only a single entry is allowed.""" + mock_integration(hass, MockModule("hassio")) + + # Setup the config entry + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={}, + title="Hardkernel", + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.hardkernel.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "system"} + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "single_instance_allowed" + mock_setup_entry.assert_not_called() diff --git a/tests/components/hardkernel/test_hardware.py b/tests/components/hardkernel/test_hardware.py new file mode 100644 index 00000000000..1c71959719c --- /dev/null +++ b/tests/components/hardkernel/test_hardware.py @@ -0,0 +1,89 @@ +"""Test the Hardkernel hardware platform.""" +from unittest.mock import patch + +import pytest + +from homeassistant.components.hardkernel.const import DOMAIN +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, MockModule, mock_integration + + +async def test_hardware_info(hass: HomeAssistant, hass_ws_client) -> None: + """Test we can get the board info.""" + mock_integration(hass, MockModule("hassio")) + + # Setup the config entry + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={}, + title="Hardkernel", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.hardkernel.get_os_info", + return_value={"board": "odroid-n2"}, + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + client = await hass_ws_client(hass) + + with patch( + "homeassistant.components.hardkernel.hardware.get_os_info", + return_value={"board": "odroid-n2"}, + ): + await client.send_json({"id": 1, "type": "hardware/info"}) + msg = await client.receive_json() + + assert msg["id"] == 1 + assert msg["success"] + assert msg["result"] == { + "hardware": [ + { + "board": { + "hassio_board_id": "odroid-n2", + "manufacturer": "hardkernel", + "model": "odroid-n2", + "revision": None, + }, + "name": "Home Assistant Blue / Hardkernel Odroid-N2", + "url": None, + } + ] + } + + +@pytest.mark.parametrize("os_info", [None, {"board": None}, {"board": "other"}]) +async def test_hardware_info_fail(hass: HomeAssistant, hass_ws_client, os_info) -> None: + """Test async_info raises if os_info is not as expected.""" + mock_integration(hass, MockModule("hassio")) + + # Setup the config entry + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={}, + title="Hardkernel", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.hardkernel.get_os_info", + return_value={"board": "odroid-n2"}, + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + client = await hass_ws_client(hass) + + with patch( + "homeassistant.components.hardkernel.hardware.get_os_info", + return_value=os_info, + ): + await client.send_json({"id": 1, "type": "hardware/info"}) + msg = await client.receive_json() + + assert msg["id"] == 1 + assert msg["success"] + assert msg["result"] == {"hardware": []} diff --git a/tests/components/hardkernel/test_init.py b/tests/components/hardkernel/test_init.py new file mode 100644 index 00000000000..f202777f530 --- /dev/null +++ b/tests/components/hardkernel/test_init.py @@ -0,0 +1,72 @@ +"""Test the Hardkernel integration.""" +from unittest.mock import patch + +from homeassistant.components.hardkernel.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, MockModule, mock_integration + + +async def test_setup_entry(hass: HomeAssistant) -> None: + """Test setup of a config entry.""" + mock_integration(hass, MockModule("hassio")) + + # Setup the config entry + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={}, + title="Hardkernel", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.hardkernel.get_os_info", + return_value={"board": "odroid-n2"}, + ) as mock_get_os_info: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert len(mock_get_os_info.mock_calls) == 1 + + +async def test_setup_entry_wrong_board(hass: HomeAssistant) -> None: + """Test setup of a config entry with wrong board type.""" + mock_integration(hass, MockModule("hassio")) + + # Setup the config entry + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={}, + title="Hardkernel", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.hardkernel.get_os_info", + return_value={"board": "generic-x86-64"}, + ) as mock_get_os_info: + assert not await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert len(mock_get_os_info.mock_calls) == 1 + + +async def test_setup_entry_wait_hassio(hass: HomeAssistant) -> None: + """Test setup of a config entry when hassio has not fetched os_info.""" + mock_integration(hass, MockModule("hassio")) + + # Setup the config entry + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={}, + title="Hardkernel", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.hardkernel.get_os_info", + return_value=None, + ) as mock_get_os_info: + assert not await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert len(mock_get_os_info.mock_calls) == 1 + assert config_entry.state == ConfigEntryState.SETUP_RETRY diff --git a/tests/components/hardware/__init__.py b/tests/components/hardware/__init__.py new file mode 100644 index 00000000000..9b3acf65c59 --- /dev/null +++ b/tests/components/hardware/__init__.py @@ -0,0 +1 @@ +"""Tests for the Hardware integration.""" diff --git a/tests/components/hardware/test_websocket_api.py b/tests/components/hardware/test_websocket_api.py new file mode 100644 index 00000000000..116879aa628 --- /dev/null +++ b/tests/components/hardware/test_websocket_api.py @@ -0,0 +1,18 @@ +"""Test the hardware websocket API.""" +from homeassistant.components.hardware.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + + +async def test_board_info(hass: HomeAssistant, hass_ws_client) -> None: + """Test we can get the board info.""" + assert await async_setup_component(hass, DOMAIN, {}) + + client = await hass_ws_client(hass) + + await client.send_json({"id": 1, "type": "hardware/info"}) + msg = await client.receive_json() + + assert msg["id"] == 1 + assert msg["success"] + assert msg["result"] == {"hardware": []} diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 6f4b9a39a9f..ff595aaa602 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -20,8 +20,19 @@ from tests.common import MockConfigEntry, async_fire_time_changed MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} +@pytest.fixture() +def os_info(): + """Mock os/info.""" + return { + "json": { + "result": "ok", + "data": {"version_latest": "1.0.0", "version": "1.0.0"}, + } + } + + @pytest.fixture(autouse=True) -def mock_all(aioclient_mock, request): +def mock_all(aioclient_mock, request, os_info): """Mock all setup requests.""" aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"}) aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"}) @@ -64,7 +75,7 @@ def mock_all(aioclient_mock, request): ) aioclient_mock.get( "http://127.0.0.1/os/info", - json={"result": "ok", "data": {"version_latest": "1.0.0", "version": "1.0.0"}}, + **os_info, ) aioclient_mock.get( "http://127.0.0.1/supervisor/info", @@ -701,3 +712,29 @@ async def test_coordinator_updates(hass, caplog): ) assert refresh_updates_mock.call_count == 1 assert "Error on Supervisor API: Unknown" in caplog.text + + +@pytest.mark.parametrize( + "os_info", + [ + { + "json": { + "result": "ok", + "data": {"version_latest": "1.0.0", "version": "1.0.0", "board": "rpi"}, + } + } + ], +) +async def test_setup_hardware_integration(hass, aioclient_mock): + """Test setup initiates hardware integration.""" + + with patch.dict(os.environ, MOCK_ENVIRON), patch( + "homeassistant.components.raspberry_pi.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await async_setup_component(hass, "hassio", {"hassio": {}}) + assert result + await hass.async_block_till_done() + + assert aioclient_mock.call_count == 15 + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/hassio/test_system_health.py b/tests/components/hassio/test_system_health.py index dcf18f7bc13..344ac9d63be 100644 --- a/tests/components/hassio/test_system_health.py +++ b/tests/components/hassio/test_system_health.py @@ -35,6 +35,7 @@ async def test_hassio_system_health(hass, aioclient_mock): } hass.data["hassio_host_info"] = { "operating_system": "Home Assistant OS 5.9", + "agent_version": "1337", "disk_total": "32.0", "disk_used": "30.0", } @@ -52,6 +53,7 @@ async def test_hassio_system_health(hass, aioclient_mock): info[key] = await val assert info == { + "agent_version": "1337", "board": "odroid-n2", "disk_total": "32.0 GB", "disk_used": "30.0 GB", diff --git a/tests/components/here_travel_time/conftest.py b/tests/components/here_travel_time/conftest.py index 83f0659f516..368b070428e 100644 --- a/tests/components/here_travel_time/conftest.py +++ b/tests/components/here_travel_time/conftest.py @@ -12,6 +12,11 @@ RESPONSE = RoutingResponse.new_from_jsondict( ) RESPONSE.route_short = "US-29 - K St NW; US-29 - Whitehurst Fwy; I-495 N - Capital Beltway; MD-187 S - Old Georgetown Rd" +EMPTY_ATTRIBUTION_RESPONSE = RoutingResponse.new_from_jsondict( + json.loads(load_fixture("here_travel_time/empty_attribution_response.json")) +) +EMPTY_ATTRIBUTION_RESPONSE.route_short = "US-29 - K St NW; US-29 - Whitehurst Fwy; I-495 N - Capital Beltway; MD-187 S - Old Georgetown Rd" + @pytest.fixture(name="valid_response") def valid_response_fixture(): @@ -21,3 +26,13 @@ def valid_response_fixture(): return_value=RESPONSE, ) as mock: yield mock + + +@pytest.fixture(name="empty_attribution_response") +def empty_attribution_response_fixture(): + """Return valid api response with an empty attribution.""" + with patch( + "herepy.RoutingApi.public_transport_timetable", + return_value=EMPTY_ATTRIBUTION_RESPONSE, + ) as mock: + yield mock diff --git a/tests/components/here_travel_time/fixtures/empty_attribution_response.json b/tests/components/here_travel_time/fixtures/empty_attribution_response.json new file mode 100644 index 00000000000..cc1bb20a373 --- /dev/null +++ b/tests/components/here_travel_time/fixtures/empty_attribution_response.json @@ -0,0 +1,131 @@ +{ + "response": { + "metaInfo": { + "timestamp": "2019-07-19T07:38:39Z", + "mapVersion": "8.30.98.154", + "moduleVersion": "7.2.201928-4446", + "interfaceVersion": "2.6.64", + "availableMapVersion": ["8.30.98.154"] + }, + "route": [ + { + "waypoint": [ + { + "linkId": "+732182239", + "mappedPosition": { + "latitude": 38.9, + "longitude": -77.0488358 + }, + "originalPosition": { + "latitude": 38.9, + "longitude": -77.0483301 + }, + "type": "stopOver", + "spot": 0.4946237, + "sideOfStreet": "right", + "mappedRoadName": "22nd St NW", + "label": "22nd St NW", + "shapeIndex": 0, + "source": "user" + }, + { + "linkId": "+942865877", + "mappedPosition": { + "latitude": 38.9999735, + "longitude": -77.100141 + }, + "originalPosition": { + "latitude": 38.9999999, + "longitude": -77.1000001 + }, + "type": "stopOver", + "spot": 1, + "sideOfStreet": "left", + "mappedRoadName": "Service Rd S", + "label": "Service Rd S", + "shapeIndex": 279, + "source": "user" + } + ], + "mode": { + "type": "fastest", + "transportModes": ["car"], + "trafficMode": "enabled", + "feature": [] + }, + "leg": [ + { + "start": { + "linkId": "+732182239", + "mappedPosition": { + "latitude": 38.9, + "longitude": -77.0488358 + }, + "originalPosition": { + "latitude": 38.9, + "longitude": -77.0483301 + }, + "type": "stopOver", + "spot": 0.4946237, + "sideOfStreet": "right", + "mappedRoadName": "22nd St NW", + "label": "22nd St NW", + "shapeIndex": 0, + "source": "user" + }, + "end": { + "linkId": "+942865877", + "mappedPosition": { + "latitude": 38.9999735, + "longitude": -77.100141 + }, + "originalPosition": { + "latitude": 38.9999999, + "longitude": -77.1000001 + }, + "type": "stopOver", + "spot": 1, + "sideOfStreet": "left", + "mappedRoadName": "Service Rd S", + "label": "Service Rd S", + "shapeIndex": 279, + "source": "user" + }, + "length": 23903, + "travelTime": 1884, + "maneuver": [ + { + "position": { + "latitude": 38.9999735, + "longitude": -77.100141 + }, + "instruction": "Arrive at Service Rd S. Your destination is on the left.", + "travelTime": 0, + "length": 0, + "id": "M16", + "_type": "PrivateTransportManeuverType" + } + ] + } + ], + "summary": { + "distance": 23903, + "trafficTime": 1861, + "baseTime": 1803, + "flags": [ + "noThroughRoad", + "motorway", + "builtUpArea", + "park", + "privateRoad" + ], + "text": "The trip takes 23.9 km and 31 mins.", + "travelTime": 1861, + "_type": "RouteSummaryType" + } + } + ], + "language": "en-us", + "sourceAttribution": {} + } +} diff --git a/tests/components/here_travel_time/test_config_flow.py b/tests/components/here_travel_time/test_config_flow.py new file mode 100644 index 00000000000..c1ce6f823ae --- /dev/null +++ b/tests/components/here_travel_time/test_config_flow.py @@ -0,0 +1,603 @@ +"""Test the HERE Travel Time config flow.""" +from unittest.mock import patch + +from herepy import HEREError +from herepy.routing_api import InvalidCredentialsError +import pytest + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components.here_travel_time.const import ( + CONF_ARRIVAL, + CONF_ARRIVAL_TIME, + CONF_DEPARTURE, + CONF_DEPARTURE_TIME, + CONF_ROUTE_MODE, + CONF_TRAFFIC_MODE, + DOMAIN, + ROUTE_MODE_FASTEST, + TRAFFIC_MODE_ENABLED, + TRAVEL_MODE_CAR, + TRAVEL_MODE_PUBLIC_TIME_TABLE, +) +from homeassistant.components.here_travel_time.sensor import ( + CONF_DESTINATION_ENTITY_ID, + CONF_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE, + CONF_ORIGIN_ENTITY_ID, + CONF_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE, +) +from homeassistant.const import ( + CONF_API_KEY, + CONF_ENTITY_NAMESPACE, + CONF_MODE, + CONF_NAME, + CONF_SCAN_INTERVAL, + CONF_UNIT_SYSTEM, + CONF_UNIT_SYSTEM_IMPERIAL, + CONF_UNIT_SYSTEM_METRIC, +) +from homeassistant.core import HomeAssistant + +from .const import ( + API_KEY, + CAR_DESTINATION_LATITUDE, + CAR_DESTINATION_LONGITUDE, + CAR_ORIGIN_LATITUDE, + CAR_ORIGIN_LONGITUDE, +) + +from tests.common import MockConfigEntry + + +@pytest.fixture(autouse=True) +def bypass_setup_fixture(): + """Prevent setup.""" + with patch( + "homeassistant.components.here_travel_time.async_setup_entry", + return_value=True, + ): + yield + + +@pytest.fixture(name="user_step_result") +async def user_step_result_fixture(hass: HomeAssistant) -> data_entry_flow.FlowResult: + """Provide the result of a completed user step.""" + init_result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + user_step_result = await hass.config_entries.flow.async_configure( + init_result["flow_id"], + { + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_CAR, + CONF_NAME: "test", + }, + ) + await hass.async_block_till_done() + return user_step_result + + +@pytest.fixture(name="option_init_result") +async def option_init_result_fixture(hass: HomeAssistant) -> data_entry_flow.FlowResult: + """Provide the result of a completed options init step.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE), + CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE), + CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE), + CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE), + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_PUBLIC_TIME_TABLE, + CONF_NAME: "test", + }, + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + flow = await hass.config_entries.options.async_init(entry.entry_id) + result = await hass.config_entries.options.async_configure( + flow["flow_id"], + user_input={ + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + }, + ) + return result + + +@pytest.fixture(name="origin_step_result") +async def origin_step_result_fixture( + hass: HomeAssistant, user_step_result: data_entry_flow.FlowResult +) -> data_entry_flow.FlowResult: + """Provide the result of a completed origin by coordinates step.""" + origin_menu_result = await hass.config_entries.flow.async_configure( + user_step_result["flow_id"], {"next_step_id": "origin_coordinates"} + ) + + location_selector_result = await hass.config_entries.flow.async_configure( + origin_menu_result["flow_id"], + { + "origin": { + "latitude": float(CAR_ORIGIN_LATITUDE), + "longitude": float(CAR_ORIGIN_LONGITUDE), + "radius": 3.0, + } + }, + ) + return location_selector_result + + +@pytest.mark.parametrize( + "menu_options", + (["origin_coordinates", "origin_entity"],), +) +@pytest.mark.usefixtures("valid_response") +async def test_step_user(hass: HomeAssistant, menu_options) -> None: + """Test the user step.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_CAR, + CONF_NAME: "test", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_MENU + assert result2["menu_options"] == menu_options + + +@pytest.mark.usefixtures("valid_response") +async def test_step_origin_coordinates( + hass: HomeAssistant, user_step_result: data_entry_flow.FlowResult +) -> None: + """Test the origin coordinates step.""" + menu_result = await hass.config_entries.flow.async_configure( + user_step_result["flow_id"], {"next_step_id": "origin_coordinates"} + ) + assert menu_result["type"] == data_entry_flow.RESULT_TYPE_FORM + + location_selector_result = await hass.config_entries.flow.async_configure( + menu_result["flow_id"], + { + "origin": { + "latitude": float(CAR_ORIGIN_LATITUDE), + "longitude": float(CAR_ORIGIN_LONGITUDE), + "radius": 3.0, + } + }, + ) + assert location_selector_result["type"] == data_entry_flow.RESULT_TYPE_MENU + + +@pytest.mark.usefixtures("valid_response") +async def test_step_origin_entity( + hass: HomeAssistant, user_step_result: data_entry_flow.FlowResult +) -> None: + """Test the origin coordinates step.""" + menu_result = await hass.config_entries.flow.async_configure( + user_step_result["flow_id"], {"next_step_id": "origin_entity"} + ) + assert menu_result["type"] == data_entry_flow.RESULT_TYPE_FORM + + entity_selector_result = await hass.config_entries.flow.async_configure( + menu_result["flow_id"], + {"origin_entity_id": "zone.home"}, + ) + assert entity_selector_result["type"] == data_entry_flow.RESULT_TYPE_MENU + + +@pytest.mark.usefixtures("valid_response") +async def test_step_destination_coordinates( + hass: HomeAssistant, origin_step_result: data_entry_flow.FlowResult +) -> None: + """Test the origin coordinates step.""" + menu_result = await hass.config_entries.flow.async_configure( + origin_step_result["flow_id"], {"next_step_id": "destination_coordinates"} + ) + assert menu_result["type"] == data_entry_flow.RESULT_TYPE_FORM + + location_selector_result = await hass.config_entries.flow.async_configure( + menu_result["flow_id"], + { + "destination": { + "latitude": float(CAR_DESTINATION_LATITUDE), + "longitude": float(CAR_DESTINATION_LONGITUDE), + "radius": 3.0, + } + }, + ) + assert location_selector_result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.data == { + CONF_NAME: "test", + CONF_API_KEY: API_KEY, + CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE), + CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE), + CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE), + CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE), + CONF_MODE: TRAVEL_MODE_CAR, + } + + +@pytest.mark.usefixtures("valid_response") +async def test_step_destination_entity( + hass: HomeAssistant, origin_step_result: data_entry_flow.FlowResult +) -> None: + """Test the origin coordinates step.""" + menu_result = await hass.config_entries.flow.async_configure( + origin_step_result["flow_id"], {"next_step_id": "destination_entity"} + ) + assert menu_result["type"] == data_entry_flow.RESULT_TYPE_FORM + + entity_selector_result = await hass.config_entries.flow.async_configure( + menu_result["flow_id"], + {"destination_entity_id": "zone.home"}, + ) + assert entity_selector_result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.data == { + CONF_NAME: "test", + CONF_API_KEY: API_KEY, + CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE), + CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE), + CONF_DESTINATION_ENTITY_ID: "zone.home", + CONF_MODE: TRAVEL_MODE_CAR, + } + + +async def test_form_invalid_auth(hass: HomeAssistant) -> None: + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "herepy.RoutingApi.public_transport_timetable", + side_effect=InvalidCredentialsError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_CAR, + CONF_NAME: "test", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_unknown_error(hass: HomeAssistant) -> None: + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "herepy.RoutingApi.public_transport_timetable", + side_effect=HEREError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_CAR, + CONF_NAME: "test", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} + + +@pytest.mark.usefixtures("valid_response") +async def test_options_flow(hass: HomeAssistant) -> None: + """Test the options flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE), + CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE), + CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE), + CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE), + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_CAR, + CONF_NAME: "test", + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_MENU + + +@pytest.mark.usefixtures("valid_response") +async def test_options_flow_arrival_time_step( + hass: HomeAssistant, option_init_result: data_entry_flow.FlowResult +) -> None: + """Test the options flow arrival time type.""" + menu_result = await hass.config_entries.options.async_configure( + option_init_result["flow_id"], {"next_step_id": "arrival_time"} + ) + assert menu_result["type"] == data_entry_flow.RESULT_TYPE_FORM + time_selector_result = await hass.config_entries.options.async_configure( + option_init_result["flow_id"], + user_input={ + "arrival_time": "08:00:00", + }, + ) + + assert time_selector_result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.options == { + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + CONF_ARRIVAL_TIME: "08:00:00", + } + + +@pytest.mark.usefixtures("valid_response") +async def test_options_flow_departure_time_step( + hass: HomeAssistant, option_init_result: data_entry_flow.FlowResult +) -> None: + """Test the options flow departure time type.""" + menu_result = await hass.config_entries.options.async_configure( + option_init_result["flow_id"], {"next_step_id": "departure_time"} + ) + assert menu_result["type"] == data_entry_flow.RESULT_TYPE_FORM + time_selector_result = await hass.config_entries.options.async_configure( + option_init_result["flow_id"], + user_input={ + "departure_time": "08:00:00", + }, + ) + + assert time_selector_result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.options == { + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + CONF_DEPARTURE_TIME: "08:00:00", + } + + +@pytest.mark.usefixtures("valid_response") +async def test_options_flow_no_time_step( + hass: HomeAssistant, option_init_result: data_entry_flow.FlowResult +) -> None: + """Test the options flow arrival time type.""" + menu_result = await hass.config_entries.options.async_configure( + option_init_result["flow_id"], {"next_step_id": "no_time"} + ) + + assert menu_result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.options == { + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + } + + +@pytest.mark.usefixtures("valid_response") +async def test_import_flow_entity_id(hass: HomeAssistant) -> None: + """Test import_flow with entity ids.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_API_KEY: CONF_API_KEY, + CONF_ORIGIN_ENTITY_ID: "sensor.origin", + CONF_DESTINATION_ENTITY_ID: "sensor.destination", + CONF_NAME: "test_name", + CONF_MODE: TRAVEL_MODE_CAR, + CONF_DEPARTURE: "08:00:00", + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + CONF_ENTITY_NAMESPACE: "namespace", + CONF_SCAN_INTERVAL: 2678400, + }, + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "namespace test_name" + + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.data == { + CONF_NAME: "namespace test_name", + CONF_API_KEY: CONF_API_KEY, + CONF_ORIGIN_ENTITY_ID: "sensor.origin", + CONF_DESTINATION_ENTITY_ID: "sensor.destination", + CONF_MODE: TRAVEL_MODE_CAR, + } + assert entry.options == { + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL, + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + CONF_DEPARTURE_TIME: "08:00:00", + CONF_ARRIVAL_TIME: None, + } + + +@pytest.mark.usefixtures("valid_response") +async def test_import_flow_coordinates(hass: HomeAssistant) -> None: + """Test import_flow with coordinates.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_API_KEY: CONF_API_KEY, + CONF_ORIGIN_LATITUDE: CAR_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE: CAR_ORIGIN_LONGITUDE, + CONF_DESTINATION_LATITUDE: CAR_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE: CAR_DESTINATION_LONGITUDE, + CONF_NAME: "test_name", + CONF_MODE: TRAVEL_MODE_CAR, + CONF_ARRIVAL: "08:00:00", + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + }, + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "test_name" + + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.data == { + CONF_NAME: "test_name", + CONF_API_KEY: CONF_API_KEY, + CONF_ORIGIN_LATITUDE: CAR_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE: CAR_ORIGIN_LONGITUDE, + CONF_DESTINATION_LATITUDE: CAR_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE: CAR_DESTINATION_LONGITUDE, + CONF_MODE: TRAVEL_MODE_CAR, + } + assert entry.options == { + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + CONF_DEPARTURE_TIME: None, + CONF_ARRIVAL_TIME: "08:00:00", + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + } + + +@pytest.mark.usefixtures("valid_response") +async def test_dupe_import(hass: HomeAssistant) -> None: + """Test duplicate import.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_API_KEY: CONF_API_KEY, + CONF_ORIGIN_LATITUDE: CAR_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE: CAR_ORIGIN_LONGITUDE, + CONF_DESTINATION_LATITUDE: CAR_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE: CAR_DESTINATION_LONGITUDE, + CONF_NAME: "test_name", + CONF_MODE: TRAVEL_MODE_CAR, + CONF_ARRIVAL: "08:00:00", + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_API_KEY: CONF_API_KEY, + CONF_ORIGIN_LATITUDE: CAR_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE: CAR_ORIGIN_LONGITUDE, + CONF_DESTINATION_LATITUDE: CAR_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE: CAR_DESTINATION_LONGITUDE, + CONF_NAME: "test_name2", + CONF_MODE: TRAVEL_MODE_CAR, + CONF_ARRIVAL: "08:00:00", + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_API_KEY: CONF_API_KEY, + CONF_ORIGIN_LATITUDE: CAR_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE: CAR_ORIGIN_LONGITUDE, + CONF_DESTINATION_LATITUDE: CAR_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE: CAR_DESTINATION_LONGITUDE, + CONF_NAME: "test_name", + CONF_MODE: TRAVEL_MODE_CAR, + CONF_ARRIVAL: "08:00:01", + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_API_KEY: CONF_API_KEY, + CONF_ORIGIN_LATITUDE: CAR_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE: CAR_ORIGIN_LONGITUDE, + CONF_DESTINATION_LATITUDE: "40.0", + CONF_DESTINATION_LONGITUDE: CAR_DESTINATION_LONGITUDE, + CONF_NAME: "test_name", + CONF_MODE: TRAVEL_MODE_CAR, + CONF_ARRIVAL: "08:00:01", + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_API_KEY: CONF_API_KEY, + CONF_ORIGIN_LATITUDE: CAR_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE: CAR_ORIGIN_LONGITUDE, + CONF_DESTINATION_LATITUDE: CAR_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE: CAR_DESTINATION_LONGITUDE, + CONF_NAME: "test_name", + CONF_MODE: TRAVEL_MODE_CAR, + CONF_ARRIVAL: "08:00:00", + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED, + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" diff --git a/tests/components/here_travel_time/test_init.py b/tests/components/here_travel_time/test_init.py new file mode 100644 index 00000000000..05b7f6983db --- /dev/null +++ b/tests/components/here_travel_time/test_init.py @@ -0,0 +1,50 @@ +"""The test for the HERE Travel Time integration.""" + +import pytest + +from homeassistant.components.here_travel_time.config_flow import default_options +from homeassistant.components.here_travel_time.const import ( + CONF_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE, + CONF_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE, + DOMAIN, + TRAVEL_MODE_CAR, +) +from homeassistant.const import CONF_API_KEY, CONF_MODE, CONF_NAME +from homeassistant.core import HomeAssistant + +from .const import ( + API_KEY, + CAR_DESTINATION_LATITUDE, + CAR_DESTINATION_LONGITUDE, + CAR_ORIGIN_LATITUDE, + CAR_ORIGIN_LONGITUDE, +) + +from tests.common import MockConfigEntry + + +@pytest.mark.usefixtures("valid_response") +async def test_unload_entry(hass: HomeAssistant) -> None: + """Test that unloading an entry works.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE), + CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE), + CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE), + CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE), + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_CAR, + CONF_NAME: "test", + }, + options=default_options(hass), + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert await hass.config_entries.async_unload(entry.entry_id) + assert not hass.data[DOMAIN] diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py index 4ad2f757c77..dc9ba128c35 100644 --- a/tests/components/here_travel_time/test_sensor.py +++ b/tests/components/here_travel_time/test_sensor.py @@ -2,15 +2,11 @@ from unittest.mock import MagicMock, patch from herepy.here_enum import RouteMode -from herepy.routing_api import InvalidCredentialsError, NoRouteFoundError +from herepy.routing_api import NoRouteFoundError import pytest +from homeassistant.components.here_travel_time.config_flow import default_options from homeassistant.components.here_travel_time.const import ( - ROUTE_MODE_FASTEST, - TRAFFIC_MODE_ENABLED, -) -from homeassistant.components.here_travel_time.sensor import ( - ATTR_ATTRIBUTION, ATTR_DESTINATION, ATTR_DESTINATION_NAME, ATTR_DISTANCE, @@ -19,16 +15,27 @@ from homeassistant.components.here_travel_time.sensor import ( ATTR_ORIGIN, ATTR_ORIGIN_NAME, ATTR_ROUTE, - CONF_MODE, + CONF_ARRIVAL_TIME, + CONF_DEPARTURE_TIME, + CONF_DESTINATION_ENTITY_ID, + CONF_DESTINATION_LATITUDE, + CONF_DESTINATION_LONGITUDE, + CONF_ORIGIN_ENTITY_ID, + CONF_ORIGIN_LATITUDE, + CONF_ORIGIN_LONGITUDE, + CONF_ROUTE_MODE, CONF_TRAFFIC_MODE, CONF_UNIT_SYSTEM, + DOMAIN, ICON_BICYCLE, ICON_CAR, ICON_PEDESTRIAN, ICON_PUBLIC, ICON_TRUCK, NO_ROUTE_ERROR_MESSAGE, - TIME_MINUTES, + ROUTE_MODE_FASTEST, + TRAFFIC_MODE_DISABLED, + TRAFFIC_MODE_ENABLED, TRAVEL_MODE_BICYCLE, TRAVEL_MODE_CAR, TRAVEL_MODE_PEDESTRIAN, @@ -36,11 +43,19 @@ from homeassistant.components.here_travel_time.sensor import ( TRAVEL_MODE_TRUCK, TRAVEL_MODES_VEHICLE, ) -from homeassistant.const import ATTR_ICON, EVENT_HOMEASSISTANT_START +from homeassistant.const import ( + ATTR_ATTRIBUTION, + ATTR_ICON, + CONF_API_KEY, + CONF_MODE, + CONF_NAME, + EVENT_HOMEASSISTANT_START, + TIME_MINUTES, +) +from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -import homeassistant.util.dt as dt_util -from tests.components.here_travel_time.const import ( +from .const import ( API_KEY, CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE, @@ -48,72 +63,105 @@ from tests.components.here_travel_time.const import ( CAR_ORIGIN_LONGITUDE, ) -DOMAIN = "sensor" - -PLATFORM = "here_travel_time" +from tests.common import MockConfigEntry @pytest.mark.parametrize( - "mode,icon,traffic_mode,unit_system,expected_state,expected_distance,expected_duration_in_traffic", + "mode,icon,traffic_mode,unit_system,arrival_time,departure_time,expected_state,expected_distance,expected_duration_in_traffic", [ - (TRAVEL_MODE_CAR, ICON_CAR, True, "metric", "31", 23.903, 31.016666666666666), - (TRAVEL_MODE_BICYCLE, ICON_BICYCLE, False, "metric", "30", 23.903, 30.05), + ( + TRAVEL_MODE_CAR, + ICON_CAR, + TRAFFIC_MODE_ENABLED, + "metric", + None, + None, + "31", + 23.903, + 31.016666666666666, + ), + ( + TRAVEL_MODE_BICYCLE, + ICON_BICYCLE, + TRAFFIC_MODE_DISABLED, + "metric", + None, + None, + "30", + 23.903, + 30.05, + ), ( TRAVEL_MODE_PEDESTRIAN, ICON_PEDESTRIAN, - False, + TRAFFIC_MODE_DISABLED, "imperial", + None, + None, "30", - 14.852635608048994, + 14.852631013, 30.05, ), ( TRAVEL_MODE_PUBLIC_TIME_TABLE, ICON_PUBLIC, - False, + TRAFFIC_MODE_DISABLED, "imperial", + "08:00:00", + None, "30", - 14.852635608048994, + 14.852631013, 30.05, ), ( TRAVEL_MODE_TRUCK, ICON_TRUCK, - True, + TRAFFIC_MODE_ENABLED, "metric", + None, + "08:00:00", "31", 23.903, 31.016666666666666, ), ], ) +@pytest.mark.usefixtures("valid_response") async def test_sensor( - hass, + hass: HomeAssistant, mode, icon, traffic_mode, unit_system, + arrival_time, + departure_time, expected_state, expected_distance, expected_duration_in_traffic, - valid_response, ): """Test that sensor works.""" - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_latitude": CAR_ORIGIN_LATITUDE, - "origin_longitude": CAR_ORIGIN_LONGITUDE, - "destination_latitude": CAR_DESTINATION_LATITUDE, - "destination_longitude": CAR_DESTINATION_LONGITUDE, - "api_key": API_KEY, - "traffic_mode": traffic_mode, - "unit_system": unit_system, - "mode": mode, - } - } - assert await async_setup_component(hass, DOMAIN, config) + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE), + CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE), + CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE), + CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE), + CONF_API_KEY: API_KEY, + CONF_MODE: mode, + CONF_NAME: "test", + }, + options={ + CONF_TRAFFIC_MODE: traffic_mode, + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_ARRIVAL_TIME: arrival_time, + CONF_DEPARTURE_TIME: departure_time, + CONF_UNIT_SYSTEM: unit_system, + }, + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -145,7 +193,9 @@ async def test_sensor( assert sensor.attributes.get(ATTR_ORIGIN_NAME) == "22nd St NW" assert sensor.attributes.get(ATTR_DESTINATION_NAME) == "Service Rd S" assert sensor.attributes.get(CONF_MODE) == mode - assert sensor.attributes.get(CONF_TRAFFIC_MODE) is traffic_mode + assert sensor.attributes.get(CONF_TRAFFIC_MODE) is ( + traffic_mode == TRAFFIC_MODE_ENABLED + ) assert sensor.attributes.get(ATTR_ICON) == icon @@ -156,78 +206,140 @@ async def test_sensor( ) -async def test_entity_ids(hass, valid_response: MagicMock): +@pytest.mark.usefixtures("valid_response") +async def test_circular_ref(hass: HomeAssistant, caplog): + """Test that a circular ref is handled.""" + hass.states.async_set( + "test.first", + "test.second", + ) + hass.states.async_set("test.second", "test.first") + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_ENTITY_ID: "test.first", + CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE), + CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE), + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_TRUCK, + CONF_NAME: "test", + }, + options=default_options(hass), + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + + assert "No coordinatnes found for test.first" in caplog.text + + +@pytest.mark.usefixtures("empty_attribution_response") +async def test_no_attribution(hass: HomeAssistant): + """Test that an empty attribution is handled.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE), + CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE), + CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE), + CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE), + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_TRUCK, + CONF_NAME: "test", + }, + options=default_options(hass), + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + + assert hass.states.get("sensor.test").attributes.get(ATTR_ATTRIBUTION) is None + + +async def test_entity_ids(hass: HomeAssistant, valid_response: MagicMock): """Test that origin/destination supplied by entities works.""" - utcnow = dt_util.utcnow() - # Patching 'utcnow' to gain more control over the timed update. - with patch("homeassistant.util.dt.utcnow", return_value=utcnow): - zone_config = { - "zone": [ - { - "name": "Origin", - "latitude": CAR_ORIGIN_LATITUDE, - "longitude": CAR_ORIGIN_LONGITUDE, - "radius": 250, - "passive": False, - }, - ] - } - assert await async_setup_component(hass, "zone", zone_config) - hass.states.async_set( - "device_tracker.test", - "not_home", + zone_config = { + "zone": [ { - "latitude": float(CAR_DESTINATION_LATITUDE), - "longitude": float(CAR_DESTINATION_LONGITUDE), + "name": "Origin", + "latitude": CAR_ORIGIN_LATITUDE, + "longitude": CAR_ORIGIN_LONGITUDE, + "radius": 250, + "passive": False, }, - ) - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_entity_id": "zone.origin", - "destination_entity_id": "device_tracker.test", - "api_key": API_KEY, - "mode": TRAVEL_MODE_TRUCK, - } - } - assert await async_setup_component(hass, DOMAIN, config) - await hass.async_block_till_done() + ] + } + assert await async_setup_component(hass, "zone", zone_config) + hass.states.async_set( + "device_tracker.test", + "not_home", + { + "latitude": float(CAR_DESTINATION_LATITUDE), + "longitude": float(CAR_DESTINATION_LONGITUDE), + }, + ) + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_ENTITY_ID: "zone.origin", + CONF_DESTINATION_ENTITY_ID: "device_tracker.test", + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_TRUCK, + CONF_NAME: "test", + }, + options=default_options(hass), + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) - await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() - sensor = hass.states.get("sensor.test") - assert sensor.attributes.get(ATTR_DISTANCE) == 23.903 + sensor = hass.states.get("sensor.test") + assert sensor.attributes.get(ATTR_DISTANCE) == 23.903 - valid_response.assert_called_with( - [CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE], - [CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE], - True, - [ - RouteMode[ROUTE_MODE_FASTEST], - RouteMode[TRAVEL_MODE_TRUCK], - RouteMode[TRAFFIC_MODE_ENABLED], - ], - arrival=None, - departure="now", - ) + valid_response.assert_called_with( + [CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE], + [CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE], + True, + [ + RouteMode[ROUTE_MODE_FASTEST], + RouteMode[TRAVEL_MODE_TRUCK], + RouteMode[TRAFFIC_MODE_ENABLED], + ], + arrival=None, + departure="now", + ) -async def test_destination_entity_not_found(hass, caplog, valid_response: MagicMock): +@pytest.mark.usefixtures("valid_response") +async def test_destination_entity_not_found(hass: HomeAssistant, caplog): """Test that a not existing destination_entity_id is caught.""" - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_latitude": CAR_ORIGIN_LATITUDE, - "origin_longitude": CAR_ORIGIN_LONGITUDE, - "destination_entity_id": "device_tracker.test", - "api_key": API_KEY, - "mode": TRAVEL_MODE_TRUCK, - } - } - assert await async_setup_component(hass, DOMAIN, config) + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE), + CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE), + CONF_DESTINATION_ENTITY_ID: "device_tracker.test", + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_TRUCK, + CONF_NAME: "test", + }, + options=default_options(hass), + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -236,20 +348,24 @@ async def test_destination_entity_not_found(hass, caplog, valid_response: MagicM assert "device_tracker.test are not valid coordinates" in caplog.text -async def test_origin_entity_not_found(hass, caplog, valid_response: MagicMock): +@pytest.mark.usefixtures("valid_response") +async def test_origin_entity_not_found(hass: HomeAssistant, caplog): """Test that a not existing origin_entity_id is caught.""" - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_entity_id": "device_tracker.test", - "destination_latitude": CAR_ORIGIN_LATITUDE, - "destination_longitude": CAR_ORIGIN_LONGITUDE, - "api_key": API_KEY, - "mode": TRAVEL_MODE_TRUCK, - } - } - assert await async_setup_component(hass, DOMAIN, config) + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_ENTITY_ID: "device_tracker.test", + CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE), + CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE), + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_TRUCK, + CONF_NAME: "test", + }, + options=default_options(hass), + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -258,26 +374,28 @@ async def test_origin_entity_not_found(hass, caplog, valid_response: MagicMock): assert "device_tracker.test are not valid coordinates" in caplog.text -async def test_invalid_destination_entity_state( - hass, caplog, valid_response: MagicMock -): +@pytest.mark.usefixtures("valid_response") +async def test_invalid_destination_entity_state(hass: HomeAssistant, caplog): """Test that an invalid state of the destination_entity_id is caught.""" hass.states.async_set( "device_tracker.test", "test_state", ) - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_latitude": CAR_ORIGIN_LATITUDE, - "origin_longitude": CAR_ORIGIN_LONGITUDE, - "destination_entity_id": "device_tracker.test", - "api_key": API_KEY, - "mode": TRAVEL_MODE_TRUCK, - } - } - assert await async_setup_component(hass, DOMAIN, config) + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE), + CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE), + CONF_DESTINATION_ENTITY_ID: "device_tracker.test", + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_TRUCK, + CONF_NAME: "test", + }, + options=default_options(hass), + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -286,24 +404,28 @@ async def test_invalid_destination_entity_state( assert "test_state are not valid coordinates" in caplog.text -async def test_invalid_origin_entity_state(hass, caplog, valid_response: MagicMock): +@pytest.mark.usefixtures("valid_response") +async def test_invalid_origin_entity_state(hass: HomeAssistant, caplog): """Test that an invalid state of the origin_entity_id is caught.""" hass.states.async_set( "device_tracker.test", "test_state", ) - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_entity_id": "device_tracker.test", - "destination_latitude": CAR_ORIGIN_LATITUDE, - "destination_longitude": CAR_ORIGIN_LONGITUDE, - "api_key": API_KEY, - "mode": TRAVEL_MODE_TRUCK, - } - } - assert await async_setup_component(hass, DOMAIN, config) + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_ENTITY_ID: "device_tracker.test", + CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE), + CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE), + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_TRUCK, + CONF_NAME: "test", + }, + options=default_options(hass), + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -312,27 +434,28 @@ async def test_invalid_origin_entity_state(hass, caplog, valid_response: MagicMo assert "test_state are not valid coordinates" in caplog.text -async def test_route_not_found(hass, caplog): +async def test_route_not_found(hass: HomeAssistant, caplog): """Test that route not found error is correctly handled.""" - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_latitude": CAR_ORIGIN_LATITUDE, - "origin_longitude": CAR_ORIGIN_LONGITUDE, - "destination_latitude": CAR_DESTINATION_LATITUDE, - "destination_longitude": CAR_DESTINATION_LONGITUDE, - "api_key": API_KEY, - } - } with patch( - "homeassistant.components.here_travel_time.sensor._are_valid_client_credentials", - return_value=True, - ), patch( "herepy.RoutingApi.public_transport_timetable", side_effect=NoRouteFoundError, ): - assert await async_setup_component(hass, DOMAIN, config) + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={ + CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE), + CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE), + CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE), + CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE), + CONF_API_KEY: API_KEY, + CONF_MODE: TRAVEL_MODE_TRUCK, + CONF_NAME: "test", + }, + options=default_options(hass), + ) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -340,113 +463,26 @@ async def test_route_not_found(hass, caplog): assert NO_ROUTE_ERROR_MESSAGE in caplog.text -async def test_invalid_credentials(hass, caplog): - """Test that invalid credentials error is correctly handled.""" +@pytest.mark.usefixtures("valid_response") +async def test_setup_platform(hass: HomeAssistant, caplog): + """Test that setup platform migration works.""" + config = { + "sensor": { + "platform": DOMAIN, + "name": "test", + "origin_latitude": CAR_ORIGIN_LATITUDE, + "origin_longitude": CAR_ORIGIN_LONGITUDE, + "destination_latitude": CAR_DESTINATION_LATITUDE, + "destination_longitude": CAR_DESTINATION_LONGITUDE, + "api_key": API_KEY, + } + } with patch( - "herepy.RoutingApi.public_transport_timetable", - side_effect=InvalidCredentialsError, + "homeassistant.components.here_travel_time.async_setup_entry", return_value=True ): - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_latitude": CAR_ORIGIN_LATITUDE, - "origin_longitude": CAR_ORIGIN_LONGITUDE, - "destination_latitude": CAR_DESTINATION_LATITUDE, - "destination_longitude": CAR_DESTINATION_LONGITUDE, - "api_key": API_KEY, - } - } - assert await async_setup_component(hass, DOMAIN, config) + await async_setup_component(hass, "sensor", config) await hass.async_block_till_done() - assert "Invalid credentials" in caplog.text - - -async def test_arrival(hass, valid_response): - """Test that arrival works.""" - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_latitude": CAR_ORIGIN_LATITUDE, - "origin_longitude": CAR_ORIGIN_LONGITUDE, - "destination_latitude": CAR_DESTINATION_LATITUDE, - "destination_longitude": CAR_DESTINATION_LONGITUDE, - "api_key": API_KEY, - "mode": TRAVEL_MODE_PUBLIC_TIME_TABLE, - "arrival": "01:00:00", - } - } - assert await async_setup_component(hass, DOMAIN, config) - await hass.async_block_till_done() - - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) - await hass.async_block_till_done() - - sensor = hass.states.get("sensor.test") - assert sensor.state == "30" - - -async def test_departure(hass, valid_response): - """Test that departure works.""" - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_latitude": CAR_ORIGIN_LATITUDE, - "origin_longitude": CAR_ORIGIN_LONGITUDE, - "destination_latitude": CAR_DESTINATION_LATITUDE, - "destination_longitude": CAR_DESTINATION_LONGITUDE, - "api_key": API_KEY, - "mode": TRAVEL_MODE_PUBLIC_TIME_TABLE, - "departure": "23:00:00", - } - } - assert await async_setup_component(hass, DOMAIN, config) - await hass.async_block_till_done() - - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) - await hass.async_block_till_done() - - sensor = hass.states.get("sensor.test") - assert sensor.state == "30" - - -async def test_arrival_only_allowed_for_timetable(hass, caplog): - """Test that arrival is only allowed when mode is publicTransportTimeTable.""" - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_latitude": CAR_ORIGIN_LATITUDE, - "origin_longitude": CAR_ORIGIN_LONGITUDE, - "destination_latitude": CAR_DESTINATION_LATITUDE, - "destination_longitude": CAR_DESTINATION_LONGITUDE, - "api_key": API_KEY, - "arrival": "01:00:00", - } - } - assert await async_setup_component(hass, DOMAIN, config) - await hass.async_block_till_done() - assert "[arrival] is an invalid option" in caplog.text - - -async def test_exclusive_arrival_and_departure(hass, caplog): - """Test that arrival and departure are exclusive.""" - config = { - DOMAIN: { - "platform": PLATFORM, - "name": "test", - "origin_latitude": CAR_ORIGIN_LATITUDE, - "origin_longitude": CAR_ORIGIN_LONGITUDE, - "destination_latitude": CAR_DESTINATION_LATITUDE, - "destination_longitude": CAR_DESTINATION_LONGITUDE, - "api_key": API_KEY, - "arrival": "01:00:00", - "mode": TRAVEL_MODE_PUBLIC_TIME_TABLE, - "departure": "01:00:00", - } - } - assert await async_setup_component(hass, DOMAIN, config) - await hass.async_block_till_done() - assert "two or more values in the same group of exclusion" in caplog.text + assert ( + "Your HERE travel time configuration has been imported into the UI" + in caplog.text + ) diff --git a/tests/components/history/conftest.py b/tests/components/history/conftest.py index 5e81b444393..a2916153acc 100644 --- a/tests/components/history/conftest.py +++ b/tests/components/history/conftest.py @@ -2,6 +2,7 @@ import pytest from homeassistant.components import history +from homeassistant.const import CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE from homeassistant.setup import setup_component @@ -13,13 +14,13 @@ def hass_history(hass_recorder): config = history.CONFIG_SCHEMA( { history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["media_player"], - history.CONF_ENTITIES: ["thermostat.test"], + CONF_INCLUDE: { + CONF_DOMAINS: ["media_player"], + CONF_ENTITIES: ["thermostat.test"], }, - history.CONF_EXCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], + CONF_EXCLUDE: { + CONF_DOMAINS: ["thermostat"], + CONF_ENTITIES: ["media_player.test"], }, } } diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index 4ae1354464d..cbc5e86c37e 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -8,9 +8,10 @@ from unittest.mock import patch, sentinel import pytest from pytest import approx -from homeassistant.components import history, recorder +from homeassistant.components import history from homeassistant.components.recorder.history import get_significant_states from homeassistant.components.recorder.models import process_timestamp +from homeassistant.const import CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE import homeassistant.core as ha from homeassistant.helpers.json import JSONEncoder from homeassistant.setup import async_setup_component @@ -20,6 +21,7 @@ from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM from tests.components.recorder.common import ( async_recorder_block_till_done, async_wait_recording_done, + do_adhoc_statistics, wait_recording_done, ) @@ -59,23 +61,30 @@ def test_get_significant_states_minimal_response(hass_history): hist = get_significant_states( hass, zero, four, filters=history.Filters(), minimal_response=True ) + entites_with_reducable_states = [ + "media_player.test", + "media_player.test3", + ] - # The second media_player.test state is reduced + # All states for media_player.test state are reduced # down to last_changed and state when minimal_response + # is set except for the first state. # is set. We use JSONEncoder to make sure that are # pre-encoded last_changed is always the same as what # will happen with encoding a native state - input_state = states["media_player.test"][1] - orig_last_changed = json.dumps( - process_timestamp(input_state.last_changed), - cls=JSONEncoder, - ).replace('"', "") - orig_state = input_state.state - states["media_player.test"][1] = { - "last_changed": orig_last_changed, - "state": orig_state, - } - + for entity_id in entites_with_reducable_states: + entity_states = states[entity_id] + for state_idx in range(1, len(entity_states)): + input_state = entity_states[state_idx] + orig_last_changed = orig_last_changed = json.dumps( + process_timestamp(input_state.last_changed), + cls=JSONEncoder, + ).replace('"', "") + orig_state = input_state.state + entity_states[state_idx] = { + "last_changed": orig_last_changed, + "state": orig_state, + } assert states == hist @@ -184,9 +193,7 @@ def test_get_significant_states_exclude_domain(hass_history): config = history.CONFIG_SCHEMA( { ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["media_player"]} - }, + history.DOMAIN: {CONF_EXCLUDE: {CONF_DOMAINS: ["media_player"]}}, } ) check_significant_states(hass, zero, four, states, config) @@ -205,9 +212,7 @@ def test_get_significant_states_exclude_entity(hass_history): config = history.CONFIG_SCHEMA( { ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_EXCLUDE: {history.CONF_ENTITIES: ["media_player.test"]} - }, + history.DOMAIN: {CONF_EXCLUDE: {CONF_ENTITIES: ["media_player.test"]}}, } ) check_significant_states(hass, zero, four, states, config) @@ -228,9 +233,9 @@ def test_get_significant_states_exclude(hass_history): { ha.DOMAIN: {}, history.DOMAIN: { - history.CONF_EXCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], + CONF_EXCLUDE: { + CONF_DOMAINS: ["thermostat"], + CONF_ENTITIES: ["media_player.test"], } }, } @@ -255,10 +260,8 @@ def test_get_significant_states_exclude_include_entity(hass_history): { ha.DOMAIN: {}, history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_ENTITIES: ["media_player.test", "thermostat.test"] - }, - history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["thermostat"]}, + CONF_INCLUDE: {CONF_ENTITIES: ["media_player.test", "thermostat.test"]}, + CONF_EXCLUDE: {CONF_DOMAINS: ["thermostat"]}, }, } ) @@ -280,9 +283,7 @@ def test_get_significant_states_include_domain(hass_history): config = history.CONFIG_SCHEMA( { ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: {history.CONF_DOMAINS: ["thermostat", "script"]} - }, + history.DOMAIN: {CONF_INCLUDE: {CONF_DOMAINS: ["thermostat", "script"]}}, } ) check_significant_states(hass, zero, four, states, config) @@ -304,9 +305,7 @@ def test_get_significant_states_include_entity(hass_history): config = history.CONFIG_SCHEMA( { ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: {history.CONF_ENTITIES: ["media_player.test"]} - }, + history.DOMAIN: {CONF_INCLUDE: {CONF_ENTITIES: ["media_player.test"]}}, } ) check_significant_states(hass, zero, four, states, config) @@ -328,9 +327,9 @@ def test_get_significant_states_include(hass_history): { ha.DOMAIN: {}, history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], + CONF_INCLUDE: { + CONF_DOMAINS: ["thermostat"], + CONF_ENTITIES: ["media_player.test"], } }, } @@ -357,8 +356,8 @@ def test_get_significant_states_include_exclude_domain(hass_history): { ha.DOMAIN: {}, history.DOMAIN: { - history.CONF_INCLUDE: {history.CONF_DOMAINS: ["media_player"]}, - history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["media_player"]}, + CONF_INCLUDE: {CONF_DOMAINS: ["media_player"]}, + CONF_EXCLUDE: {CONF_DOMAINS: ["media_player"]}, }, } ) @@ -384,8 +383,8 @@ def test_get_significant_states_include_exclude_entity(hass_history): { ha.DOMAIN: {}, history.DOMAIN: { - history.CONF_INCLUDE: {history.CONF_ENTITIES: ["media_player.test"]}, - history.CONF_EXCLUDE: {history.CONF_ENTITIES: ["media_player.test"]}, + CONF_INCLUDE: {CONF_ENTITIES: ["media_player.test"]}, + CONF_EXCLUDE: {CONF_ENTITIES: ["media_player.test"]}, }, } ) @@ -408,13 +407,13 @@ def test_get_significant_states_include_exclude(hass_history): { ha.DOMAIN: {}, history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["media_player"], - history.CONF_ENTITIES: ["thermostat.test"], + CONF_INCLUDE: { + CONF_DOMAINS: ["media_player"], + CONF_ENTITIES: ["thermostat.test"], }, - history.CONF_EXCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], + CONF_EXCLUDE: { + CONF_DOMAINS: ["thermostat"], + CONF_ENTITIES: ["media_player.test"], }, }, } @@ -459,23 +458,28 @@ def test_get_significant_states_only(hass_history): points.append(start + timedelta(minutes=i)) states = [] - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=start + ): set_state("123", attributes={"attribute": 10.64}) with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[0] + "homeassistant.components.recorder.core.dt_util.utcnow", + return_value=points[0], ): # Attributes are different, state not states.append(set_state("123", attributes={"attribute": 21.42})) with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[1] + "homeassistant.components.recorder.core.dt_util.utcnow", + return_value=points[1], ): # state is different, attributes not states.append(set_state("32", attributes={"attribute": 21.42})) with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[2] + "homeassistant.components.recorder.core.dt_util.utcnow", + return_value=points[2], ): # everything is different states.append(set_state("412", attributes={"attribute": 54.23})) @@ -496,14 +500,14 @@ def test_get_significant_states_only(hass_history): def check_significant_states(hass, zero, four, states, config): """Check if significant states are retrieved.""" filters = history.Filters() - exclude = config[history.DOMAIN].get(history.CONF_EXCLUDE) + exclude = config[history.DOMAIN].get(CONF_EXCLUDE) if exclude: - filters.excluded_entities = exclude.get(history.CONF_ENTITIES, []) - filters.excluded_domains = exclude.get(history.CONF_DOMAINS, []) - include = config[history.DOMAIN].get(history.CONF_INCLUDE) + filters.excluded_entities = exclude.get(CONF_ENTITIES, []) + filters.excluded_domains = exclude.get(CONF_DOMAINS, []) + include = config[history.DOMAIN].get(CONF_INCLUDE) if include: - filters.included_entities = include.get(history.CONF_ENTITIES, []) - filters.included_domains = include.get(history.CONF_DOMAINS, []) + filters.included_entities = include.get(CONF_ENTITIES, []) + filters.included_domains = include.get(CONF_DOMAINS, []) hist = get_significant_states(hass, zero, four, filters=filters) assert states == hist @@ -536,7 +540,9 @@ def record_states(hass): four = three + timedelta(seconds=1) states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []} - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=one): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=one + ): states[mp].append( set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) ) @@ -553,7 +559,9 @@ def record_states(hass): set_state(therm, 20, attributes={"current_temperature": 19.5}) ) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=two): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=two + ): # This state will be skipped only different in time set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}) # This state will be skipped because domain is excluded @@ -568,7 +576,9 @@ def record_states(hass): set_state(therm2, 20, attributes={"current_temperature": 19}) ) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=three): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=three + ): states[mp].append( set_state(mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}) ) @@ -613,6 +623,9 @@ async def test_fetch_period_api_with_minimal_response(hass, recorder_mock, hass_ hass.states.async_set("sensor.power", 50, {"attr": "any"}) await async_wait_recording_done(hass) hass.states.async_set("sensor.power", 23, {"attr": "any"}) + last_changed = hass.states.get("sensor.power").last_changed + await async_wait_recording_done(hass) + hass.states.async_set("sensor.power", 23, {"attr": "any"}) await async_wait_recording_done(hass) client = await hass_client() response = await client.get( @@ -631,9 +644,13 @@ async def test_fetch_period_api_with_minimal_response(hass, recorder_mock, hass_ assert "entity_id" not in state_list[1] assert state_list[1]["state"] == "50" - assert state_list[2]["entity_id"] == "sensor.power" - assert state_list[2]["attributes"] == {} + assert "attributes" not in state_list[2] + assert "entity_id" not in state_list[2] assert state_list[2]["state"] == "23" + assert state_list[2]["last_changed"] == json.dumps( + process_timestamp(last_changed), + cls=JSONEncoder, + ).replace('"', "") async def test_fetch_period_api_with_no_timestamp(hass, hass_client, recorder_mock): @@ -702,7 +719,7 @@ async def test_fetch_period_api_with_entity_glob_exclude( { "history": { "exclude": { - "entity_globs": ["light.k*"], + "entity_globs": ["light.k*", "binary_sensor.*_?"], "domains": "switch", "entities": "media_player.test", }, @@ -714,6 +731,9 @@ async def test_fetch_period_api_with_entity_glob_exclude( hass.states.async_set("light.match", "on") hass.states.async_set("switch.match", "on") hass.states.async_set("media_player.test", "on") + hass.states.async_set("binary_sensor.sensor_l", "on") + hass.states.async_set("binary_sensor.sensor_r", "on") + hass.states.async_set("binary_sensor.sensor", "on") await async_wait_recording_done(hass) @@ -723,9 +743,10 @@ async def test_fetch_period_api_with_entity_glob_exclude( ) assert response.status == HTTPStatus.OK response_json = await response.json() - assert len(response_json) == 2 - assert response_json[0][0]["entity_id"] == "light.cow" - assert response_json[1][0]["entity_id"] == "light.match" + assert len(response_json) == 3 + assert response_json[0][0]["entity_id"] == "binary_sensor.sensor" + assert response_json[1][0]["entity_id"] == "light.cow" + assert response_json[2][0]["entity_id"] == "light.match" async def test_fetch_period_api_with_entity_glob_include_and_exclude( @@ -868,7 +889,7 @@ async def test_statistics_during_period( hass.states.async_set("sensor.test", state, attributes=attributes) await async_wait_recording_done(hass) - hass.data[recorder.DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_wait_recording_done(hass) client = await hass_ws_client() @@ -1010,7 +1031,7 @@ async def test_list_statistic_ids( } ] - hass.data[recorder.DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) # Remove the state, statistics will now be fetched from the database hass.states.async_remove("sensor.test") @@ -1058,3 +1079,471 @@ async def test_list_statistic_ids( response = await client.receive_json() assert response["success"] assert response["result"] == [] + + +async def test_history_during_period(hass, hass_ws_client, recorder_mock): + """Test history_during_period.""" + now = dt_util.utcnow() + + await async_setup_component(hass, "history", {}) + await async_setup_component(hass, "sensor", {}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "on", attributes={"any": "attr"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "off", attributes={"any": "attr"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "off", attributes={"any": "changed"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "off", attributes={"any": "again"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "on", attributes={"any": "attr"}) + await async_wait_recording_done(hass) + + do_adhoc_statistics(hass, start=now) + await async_wait_recording_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "history/history_during_period", + "start_time": now.isoformat(), + "end_time": now.isoformat(), + "entity_ids": ["sensor.test"], + "include_start_time_state": True, + "significant_changes_only": False, + "no_attributes": True, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + await client.send_json( + { + "id": 2, + "type": "history/history_during_period", + "start_time": now.isoformat(), + "entity_ids": ["sensor.test"], + "include_start_time_state": True, + "significant_changes_only": False, + "no_attributes": True, + "minimal_response": True, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 2 + + sensor_test_history = response["result"]["sensor.test"] + assert len(sensor_test_history) == 3 + + assert sensor_test_history[0]["s"] == "on" + assert sensor_test_history[0]["a"] == {} + assert isinstance(sensor_test_history[0]["lu"], float) + assert "lc" not in sensor_test_history[0] # skipped if the same a last_updated (lu) + + assert "a" not in sensor_test_history[1] + assert sensor_test_history[1]["s"] == "off" + assert isinstance(sensor_test_history[1]["lu"], float) + assert "lc" not in sensor_test_history[1] # skipped if the same a last_updated (lu) + + assert sensor_test_history[2]["s"] == "on" + assert "a" not in sensor_test_history[2] + + await client.send_json( + { + "id": 3, + "type": "history/history_during_period", + "start_time": now.isoformat(), + "entity_ids": ["sensor.test"], + "include_start_time_state": True, + "significant_changes_only": False, + "no_attributes": False, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 3 + sensor_test_history = response["result"]["sensor.test"] + + assert len(sensor_test_history) == 5 + + assert sensor_test_history[0]["s"] == "on" + assert sensor_test_history[0]["a"] == {"any": "attr"} + assert isinstance(sensor_test_history[0]["lu"], float) + assert "lc" not in sensor_test_history[0] # skipped if the same a last_updated (lu) + + assert sensor_test_history[1]["s"] == "off" + assert isinstance(sensor_test_history[1]["lu"], float) + assert "lc" not in sensor_test_history[1] # skipped if the same a last_updated (lu) + assert sensor_test_history[1]["a"] == {"any": "attr"} + + assert sensor_test_history[4]["s"] == "on" + assert sensor_test_history[4]["a"] == {"any": "attr"} + + await client.send_json( + { + "id": 4, + "type": "history/history_during_period", + "start_time": now.isoformat(), + "entity_ids": ["sensor.test"], + "include_start_time_state": True, + "significant_changes_only": True, + "no_attributes": False, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 4 + sensor_test_history = response["result"]["sensor.test"] + + assert len(sensor_test_history) == 3 + + assert sensor_test_history[0]["s"] == "on" + assert sensor_test_history[0]["a"] == {"any": "attr"} + assert isinstance(sensor_test_history[0]["lu"], float) + assert "lc" not in sensor_test_history[0] # skipped if the same a last_updated (lu) + + assert sensor_test_history[1]["s"] == "off" + assert isinstance(sensor_test_history[1]["lu"], float) + assert "lc" not in sensor_test_history[1] # skipped if the same a last_updated (lu) + assert sensor_test_history[1]["a"] == {"any": "attr"} + + assert sensor_test_history[2]["s"] == "on" + assert sensor_test_history[2]["a"] == {"any": "attr"} + + +async def test_history_during_period_impossible_conditions( + hass, hass_ws_client, recorder_mock +): + """Test history_during_period returns when condition cannot be true.""" + now = dt_util.utcnow() + + await async_setup_component(hass, "history", {}) + await async_setup_component(hass, "sensor", {}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "on", attributes={"any": "attr"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "off", attributes={"any": "attr"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "off", attributes={"any": "changed"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "off", attributes={"any": "again"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "on", attributes={"any": "attr"}) + await async_wait_recording_done(hass) + + do_adhoc_statistics(hass, start=now) + await async_wait_recording_done(hass) + + after = dt_util.utcnow() + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "history/history_during_period", + "start_time": after.isoformat(), + "end_time": after.isoformat(), + "entity_ids": ["sensor.test"], + "include_start_time_state": False, + "significant_changes_only": False, + "no_attributes": True, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 1 + assert response["result"] == {} + + future = dt_util.utcnow() + timedelta(hours=10) + + await client.send_json( + { + "id": 2, + "type": "history/history_during_period", + "start_time": future.isoformat(), + "entity_ids": ["sensor.test"], + "include_start_time_state": True, + "significant_changes_only": True, + "no_attributes": True, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 2 + assert response["result"] == {} + + +@pytest.mark.parametrize( + "time_zone", ["UTC", "Europe/Berlin", "America/Chicago", "US/Hawaii"] +) +async def test_history_during_period_significant_domain( + time_zone, hass, hass_ws_client, recorder_mock +): + """Test history_during_period with climate domain.""" + hass.config.set_time_zone(time_zone) + now = dt_util.utcnow() + + await async_setup_component(hass, "history", {}) + await async_setup_component(hass, "sensor", {}) + await async_recorder_block_till_done(hass) + hass.states.async_set("climate.test", "on", attributes={"temperature": "1"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("climate.test", "off", attributes={"temperature": "2"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("climate.test", "off", attributes={"temperature": "3"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("climate.test", "off", attributes={"temperature": "4"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("climate.test", "on", attributes={"temperature": "5"}) + await async_wait_recording_done(hass) + + do_adhoc_statistics(hass, start=now) + await async_wait_recording_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "history/history_during_period", + "start_time": now.isoformat(), + "end_time": now.isoformat(), + "entity_ids": ["climate.test"], + "include_start_time_state": True, + "significant_changes_only": False, + "no_attributes": True, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + await client.send_json( + { + "id": 2, + "type": "history/history_during_period", + "start_time": now.isoformat(), + "entity_ids": ["climate.test"], + "include_start_time_state": True, + "significant_changes_only": False, + "no_attributes": True, + "minimal_response": True, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 2 + + sensor_test_history = response["result"]["climate.test"] + assert len(sensor_test_history) == 5 + + assert sensor_test_history[0]["s"] == "on" + assert sensor_test_history[0]["a"] == {} + assert isinstance(sensor_test_history[0]["lu"], float) + assert "lc" not in sensor_test_history[0] # skipped if the same a last_updated (lu) + + assert "a" in sensor_test_history[1] + assert sensor_test_history[1]["s"] == "off" + assert "lc" not in sensor_test_history[1] # skipped if the same a last_updated (lu) + + assert sensor_test_history[4]["s"] == "on" + assert sensor_test_history[4]["a"] == {} + + await client.send_json( + { + "id": 3, + "type": "history/history_during_period", + "start_time": now.isoformat(), + "entity_ids": ["climate.test"], + "include_start_time_state": True, + "significant_changes_only": False, + "no_attributes": False, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 3 + sensor_test_history = response["result"]["climate.test"] + + assert len(sensor_test_history) == 5 + + assert sensor_test_history[0]["s"] == "on" + assert sensor_test_history[0]["a"] == {"temperature": "1"} + assert isinstance(sensor_test_history[0]["lu"], float) + assert "lc" not in sensor_test_history[0] # skipped if the same a last_updated (lu) + + assert sensor_test_history[1]["s"] == "off" + assert isinstance(sensor_test_history[1]["lu"], float) + assert "lc" not in sensor_test_history[1] # skipped if the same a last_updated (lu) + assert sensor_test_history[1]["a"] == {"temperature": "2"} + + assert sensor_test_history[4]["s"] == "on" + assert sensor_test_history[4]["a"] == {"temperature": "5"} + + await client.send_json( + { + "id": 4, + "type": "history/history_during_period", + "start_time": now.isoformat(), + "entity_ids": ["climate.test"], + "include_start_time_state": True, + "significant_changes_only": True, + "no_attributes": False, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 4 + sensor_test_history = response["result"]["climate.test"] + + assert len(sensor_test_history) == 5 + + assert sensor_test_history[0]["s"] == "on" + assert sensor_test_history[0]["a"] == {"temperature": "1"} + assert isinstance(sensor_test_history[0]["lu"], float) + assert "lc" not in sensor_test_history[0] # skipped if the same a last_updated (lu) + + assert sensor_test_history[1]["s"] == "off" + assert isinstance(sensor_test_history[1]["lu"], float) + assert "lc" not in sensor_test_history[1] # skipped if the same a last_updated (lu) + assert sensor_test_history[1]["a"] == {"temperature": "2"} + + assert sensor_test_history[2]["s"] == "off" + assert sensor_test_history[2]["a"] == {"temperature": "3"} + + assert sensor_test_history[3]["s"] == "off" + assert sensor_test_history[3]["a"] == {"temperature": "4"} + + assert sensor_test_history[4]["s"] == "on" + assert sensor_test_history[4]["a"] == {"temperature": "5"} + + # Test we impute the state time state + later = dt_util.utcnow() + await client.send_json( + { + "id": 5, + "type": "history/history_during_period", + "start_time": later.isoformat(), + "entity_ids": ["climate.test"], + "include_start_time_state": True, + "significant_changes_only": True, + "no_attributes": False, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 5 + sensor_test_history = response["result"]["climate.test"] + + assert len(sensor_test_history) == 1 + + assert sensor_test_history[0]["s"] == "on" + assert sensor_test_history[0]["a"] == {"temperature": "5"} + assert sensor_test_history[0]["lu"] == later.timestamp() + assert "lc" not in sensor_test_history[0] # skipped if the same a last_updated (lu) + + +async def test_history_during_period_bad_start_time( + hass, hass_ws_client, recorder_mock +): + """Test history_during_period bad state time.""" + await async_setup_component( + hass, + "history", + {"history": {}}, + ) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "history/history_during_period", + "start_time": "cats", + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_start_time" + + +async def test_history_during_period_bad_end_time(hass, hass_ws_client, recorder_mock): + """Test history_during_period bad end time.""" + now = dt_util.utcnow() + + await async_setup_component( + hass, + "history", + {"history": {}}, + ) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "history/history_during_period", + "start_time": now.isoformat(), + "end_time": "dogs", + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_end_time" + + +async def test_history_during_period_with_use_include_order( + hass, hass_ws_client, recorder_mock +): + """Test history_during_period.""" + now = dt_util.utcnow() + sort_order = ["sensor.two", "sensor.four", "sensor.one"] + await async_setup_component( + hass, + "history", + { + history.DOMAIN: { + history.CONF_ORDER: True, + CONF_INCLUDE: { + CONF_ENTITIES: sort_order, + CONF_DOMAINS: ["sensor"], + }, + } + }, + ) + await async_setup_component(hass, "sensor", {}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.one", "on", attributes={"any": "attr"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.two", "off", attributes={"any": "attr"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.three", "off", attributes={"any": "changed"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.four", "off", attributes={"any": "again"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("switch.excluded", "off", attributes={"any": "again"}) + await async_wait_recording_done(hass) + + do_adhoc_statistics(hass, start=now) + await async_wait_recording_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "history/history_during_period", + "start_time": now.isoformat(), + "include_start_time_state": True, + "significant_changes_only": False, + "no_attributes": True, + "minimal_response": True, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 1 + + assert list(response["result"]) == [ + *sort_order, + "sensor.three", + ] diff --git a/tests/components/hive/test_config_flow.py b/tests/components/hive/test_config_flow.py index ce13e52fe96..bb567b0bdfc 100644 --- a/tests/components/hive/test_config_flow.py +++ b/tests/components/hive/test_config_flow.py @@ -13,7 +13,7 @@ USERNAME = "username@home-assistant.com" UPDATED_USERNAME = "updated_username@home-assistant.com" PASSWORD = "test-password" UPDATED_PASSWORD = "updated-password" -INCORRECT_PASSWORD = "incoreect-password" +INCORRECT_PASSWORD = "incorrect-password" SCAN_INTERVAL = 120 UPDATED_SCAN_INTERVAL = 60 MFA_CODE = "1234" @@ -33,6 +33,13 @@ async def test_import_flow(hass): "AccessToken": "mock-access-token", }, }, + ), patch( + "homeassistant.components.hive.config_flow.Auth.getDeviceData", + return_value=[ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], ), patch( "homeassistant.components.hive.async_setup", return_value=True ) as mock_setup, patch( @@ -57,6 +64,11 @@ async def test_import_flow(hass): }, "ChallengeName": "SUCCESS", }, + "device_data": [ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], } assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert len(mock_setup.mock_calls) == 1 @@ -81,6 +93,13 @@ async def test_user_flow(hass): "AccessToken": "mock-access-token", }, }, + ), patch( + "homeassistant.components.hive.config_flow.Auth.getDeviceData", + return_value=[ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], ), patch( "homeassistant.components.hive.async_setup", return_value=True ) as mock_setup, patch( @@ -105,6 +124,11 @@ async def test_user_flow(hass): }, "ChallengeName": "SUCCESS", }, + "device_data": [ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], } assert len(mock_setup.mock_calls) == 1 @@ -148,6 +172,13 @@ async def test_user_flow_2fa(hass): "AccessToken": "mock-access-token", }, }, + ), patch( + "homeassistant.components.hive.config_flow.Auth.getDeviceData", + return_value=[ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], ), patch( "homeassistant.components.hive.async_setup", return_value=True ) as mock_setup, patch( @@ -171,6 +202,11 @@ async def test_user_flow_2fa(hass): }, "ChallengeName": "SUCCESS", }, + "device_data": [ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], } assert len(mock_setup.mock_calls) == 1 @@ -243,7 +279,15 @@ async def test_option_flow(hass): entry = MockConfigEntry( domain=DOMAIN, title=USERNAME, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + data={ + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + "device_data": [ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], + }, ) entry.add_to_hass(hass) @@ -317,6 +361,13 @@ async def test_user_flow_2fa_send_new_code(hass): "AccessToken": "mock-access-token", }, }, + ), patch( + "homeassistant.components.hive.config_flow.Auth.getDeviceData", + return_value=[ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], ), patch( "homeassistant.components.hive.async_setup", return_value=True ) as mock_setup, patch( @@ -340,6 +391,11 @@ async def test_user_flow_2fa_send_new_code(hass): }, "ChallengeName": "SUCCESS", }, + "device_data": [ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/home_plus_control/test_switch.py b/tests/components/home_plus_control/test_switch.py index 75d416ba2b1..44a969392bf 100644 --- a/tests/components/home_plus_control/test_switch.py +++ b/tests/components/home_plus_control/test_switch.py @@ -16,6 +16,7 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import async_fire_time_changed from tests.components.home_plus_control.conftest import ( @@ -33,8 +34,8 @@ def entity_assertions( expected_devices=None, ): """Assert number of entities and devices.""" - entity_reg = hass.helpers.entity_registry.async_get(hass) - device_reg = hass.helpers.device_registry.async_get(hass) + entity_reg = er.async_get(hass) + device_reg = dr.async_get(hass) if num_exp_devices is None: num_exp_devices = num_exp_entities @@ -53,13 +54,11 @@ def entity_assertions( def one_entity_state(hass, device_uid): """Assert the presence of an entity and return its state.""" - entity_reg = hass.helpers.entity_registry.async_get(hass) - device_reg = hass.helpers.device_registry.async_get(hass) + entity_reg = er.async_get(hass) + device_reg = dr.async_get(hass) device_id = device_reg.async_get_device({(DOMAIN, device_uid)}).id - entity_entries = hass.helpers.entity_registry.async_entries_for_device( - entity_reg, device_id - ) + entity_entries = er.async_entries_for_device(entity_reg, device_id) assert len(entity_entries) == 1 entity_entry = entity_entries[0] diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index ff0f3f4d72b..be514ce2b6a 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -49,7 +49,7 @@ from homeassistant.const import ( STATE_ON, ) from homeassistant.core import HomeAssistantError, State -from homeassistant.helpers import device_registry, entity_registry as er +from homeassistant.helpers import device_registry, entity_registry as er, instance_id from homeassistant.helpers.entityfilter import ( CONF_EXCLUDE_DOMAINS, CONF_EXCLUDE_ENTITIES, @@ -201,7 +201,7 @@ async def test_homekit_setup(hass, hk_driver, mock_async_zeroconf): hass.states.async_set("light.demo", "on") hass.states.async_set("light.demo2", "on") zeroconf_mock = MagicMock() - uuid = await hass.helpers.instance_id.async_get() + uuid = await instance_id.async_get(hass) with patch(f"{PATH_HOMEKIT}.HomeDriver", return_value=hk_driver) as mock_driver: await hass.async_add_executor_job(homekit.setup, zeroconf_mock, uuid) @@ -245,7 +245,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver, mock_async_zeroconf): ) path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) - uuid = await hass.helpers.instance_id.async_get() + uuid = await instance_id.async_get(hass) with patch(f"{PATH_HOMEKIT}.HomeDriver", return_value=hk_driver) as mock_driver: await hass.async_add_executor_job(homekit.setup, mock_async_zeroconf, uuid) mock_driver.assert_called_with( @@ -287,7 +287,7 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_async_zeroconf): async_zeroconf_instance = MagicMock() path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) - uuid = await hass.helpers.instance_id.async_get() + uuid = await instance_id.async_get(hass) with patch(f"{PATH_HOMEKIT}.HomeDriver", return_value=hk_driver) as mock_driver: await hass.async_add_executor_job(homekit.setup, async_zeroconf_instance, uuid) mock_driver.assert_called_with( diff --git a/tests/components/homekit/test_init.py b/tests/components/homekit/test_init.py index 8652f8b032a..17933616fc4 100644 --- a/tests/components/homekit/test_init.py +++ b/tests/components/homekit/test_init.py @@ -1,7 +1,6 @@ """Test HomeKit initialization.""" from unittest.mock import patch -from homeassistant.components import logbook from homeassistant.components.homekit.const import ( ATTR_DISPLAY_NAME, ATTR_VALUE, @@ -11,7 +10,7 @@ from homeassistant.components.homekit.const import ( from homeassistant.const import ATTR_ENTITY_ID, ATTR_SERVICE from homeassistant.setup import async_setup_component -from tests.components.logbook.test_init import MockLazyEventPartialState +from tests.components.logbook.common import MockRow, mock_humanify async def test_humanify_homekit_changed_event(hass, hk_driver, mock_get_source_ip): @@ -20,33 +19,28 @@ async def test_humanify_homekit_changed_event(hass, hk_driver, mock_get_source_i with patch("homeassistant.components.homekit.HomeKit"): assert await async_setup_component(hass, "homekit", {"homekit": {}}) assert await async_setup_component(hass, "logbook", {}) - entity_attr_cache = logbook.EntityAttributeCache(hass) - event1, event2 = list( - logbook.humanify( - hass, - [ - MockLazyEventPartialState( - EVENT_HOMEKIT_CHANGED, - { - ATTR_ENTITY_ID: "lock.front_door", - ATTR_DISPLAY_NAME: "Front Door", - ATTR_SERVICE: "lock", - }, - ), - MockLazyEventPartialState( - EVENT_HOMEKIT_CHANGED, - { - ATTR_ENTITY_ID: "cover.window", - ATTR_DISPLAY_NAME: "Window", - ATTR_SERVICE: "set_cover_position", - ATTR_VALUE: 75, - }, - ), - ], - entity_attr_cache, - {}, - ) + event1, event2 = mock_humanify( + hass, + [ + MockRow( + EVENT_HOMEKIT_CHANGED, + { + ATTR_ENTITY_ID: "lock.front_door", + ATTR_DISPLAY_NAME: "Front Door", + ATTR_SERVICE: "lock", + }, + ), + MockRow( + EVENT_HOMEKIT_CHANGED, + { + ATTR_ENTITY_ID: "cover.window", + ATTR_DISPLAY_NAME: "Window", + ATTR_SERVICE: "set_cover_position", + ATTR_VALUE: 75, + }, + ), + ], ) assert event1["name"] == "HomeKit" diff --git a/tests/components/homewizard/conftest.py b/tests/components/homewizard/conftest.py index 15993aa35ed..1617db35458 100644 --- a/tests/components/homewizard/conftest.py +++ b/tests/components/homewizard/conftest.py @@ -1,10 +1,15 @@ """Fixtures for HomeWizard integration tests.""" +import json +from unittest.mock import AsyncMock, patch + +from homewizard_energy.models import Data, Device, State import pytest from homeassistant.components.homewizard.const import DOMAIN from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.core import HomeAssistant -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, load_fixture @pytest.fixture @@ -25,6 +30,46 @@ def mock_config_entry() -> MockConfigEntry: return MockConfigEntry( title="Product Name (aabbccddeeff)", domain=DOMAIN, - data={}, + data={CONF_IP_ADDRESS: "1.2.3.4"}, unique_id="aabbccddeeff", ) + + +@pytest.fixture +def mock_homewizardenergy(): + """Return a mocked P1 meter.""" + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + ) as device: + client = device.return_value + client.device = AsyncMock( + return_value=Device.from_dict( + json.loads(load_fixture("homewizard/device.json")) + ) + ) + client.data = AsyncMock( + return_value=Data.from_dict( + json.loads(load_fixture("homewizard/data.json")) + ) + ) + client.state = AsyncMock( + return_value=State.from_dict( + json.loads(load_fixture("homewizard/state.json")) + ) + ) + yield device + + +@pytest.fixture +async def init_integration( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homewizardenergy: AsyncMock, +) -> MockConfigEntry: + """Set up the HomeWizard integration for testing.""" + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + return mock_config_entry diff --git a/tests/components/homewizard/fixtures/data.json b/tests/components/homewizard/fixtures/data.json new file mode 100644 index 00000000000..b6eada38038 --- /dev/null +++ b/tests/components/homewizard/fixtures/data.json @@ -0,0 +1,16 @@ +{ + "smr_version": 50, + "meter_model": "ISKRA 2M550T-101", + "wifi_ssid": "My Wi-Fi", + "wifi_strength": 100, + "total_power_import_t1_kwh": 1234.111, + "total_power_import_t2_kwh": 5678.222, + "total_power_export_t1_kwh": 4321.333, + "total_power_export_t2_kwh": 8765.444, + "active_power_w": -123, + "active_power_l1_w": -123, + "active_power_l2_w": 456, + "active_power_l3_w": 123.456, + "total_gas_m3": 1122.333, + "gas_timestamp": 210314112233 +} diff --git a/tests/components/homewizard/fixtures/device.json b/tests/components/homewizard/fixtures/device.json new file mode 100644 index 00000000000..493daa12b94 --- /dev/null +++ b/tests/components/homewizard/fixtures/device.json @@ -0,0 +1,7 @@ +{ + "product_type": "HWE-P1", + "product_name": "P1 Meter", + "serial": "3c39e7aabbcc", + "firmware_version": "2.11", + "api_version": "v1" +} diff --git a/tests/components/homewizard/fixtures/state.json b/tests/components/homewizard/fixtures/state.json new file mode 100644 index 00000000000..bbc0242ed58 --- /dev/null +++ b/tests/components/homewizard/fixtures/state.json @@ -0,0 +1,5 @@ +{ + "power_on": true, + "switch_lock": false, + "brightness": 255 +} diff --git a/tests/components/homewizard/generator.py b/tests/components/homewizard/generator.py index 74d33c9e609..0f94580ad84 100644 --- a/tests/components/homewizard/generator.py +++ b/tests/components/homewizard/generator.py @@ -2,6 +2,8 @@ from unittest.mock import AsyncMock +from homewizard_energy.models import Device + def get_mock_device( serial="aabbccddeeff", @@ -13,15 +15,18 @@ def get_mock_device( mock_device = AsyncMock() mock_device.host = host - mock_device.device.product_name = product_name - mock_device.device.product_type = product_type - mock_device.device.serial = serial - mock_device.device.api_version = "v1" - mock_device.device.firmware_version = "1.00" + mock_device.device = AsyncMock( + return_value=Device( + product_name=product_name, + product_type=product_type, + serial=serial, + api_version="V1", + firmware_version="1.00", + ) + ) + mock_device.data = AsyncMock(return_value=None) + mock_device.state = AsyncMock(return_value=None) - mock_device.state = None - - mock_device.initialize = AsyncMock() mock_device.close = AsyncMock() return mock_device diff --git a/tests/components/homewizard/test_config_flow.py b/tests/components/homewizard/test_config_flow.py index d0dc7d04509..d2e7d4c58ae 100644 --- a/tests/components/homewizard/test_config_flow.py +++ b/tests/components/homewizard/test_config_flow.py @@ -2,7 +2,7 @@ import logging from unittest.mock import patch -from aiohwenergy import DisabledError +from homewizard_energy.errors import DisabledError, UnsupportedError from homeassistant import config_entries from homeassistant.components import zeroconf @@ -33,7 +33,10 @@ async def test_manual_flow_works(hass, aioclient_mock): assert result["type"] == "form" assert result["step_id"] == "user" - with patch("aiohwenergy.HomeWizardEnergy", return_value=device,), patch( + with patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=device, + ), patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, ) as mock_setup_entry: @@ -42,12 +45,12 @@ async def test_manual_flow_works(hass, aioclient_mock): ) assert result["type"] == "create_entry" - assert result["title"] == f"{device.device.product_name} (aabbccddeeff)" + assert result["title"] == "P1 meter (aabbccddeeff)" assert result["data"][CONF_IP_ADDRESS] == "2.2.2.2" assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - assert len(device.initialize.mock_calls) == 1 + assert len(device.device.mock_calls) == 1 assert len(device.close.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -72,7 +75,10 @@ async def test_discovery_flow_works(hass, aioclient_mock): }, ) - with patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): + with patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=get_mock_device(), + ): flow = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, @@ -82,7 +88,10 @@ async def test_discovery_flow_works(hass, aioclient_mock): with patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, - ), patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): + ), patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=get_mock_device(), + ): result = await hass.config_entries.flow.async_configure( flow["flow_id"], user_input=None ) @@ -92,7 +101,10 @@ async def test_discovery_flow_works(hass, aioclient_mock): with patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, - ), patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): + ), patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=get_mock_device(), + ): result = await hass.config_entries.flow.async_configure( flow["flow_id"], user_input={"ip_address": "192.168.43.183"} ) @@ -113,7 +125,10 @@ async def test_config_flow_imports_entry(aioclient_mock, hass): mock_entry = MockConfigEntry(domain="homewizard_energy", data={"host": "1.2.3.4"}) mock_entry.add_to_hass(hass) - with patch("aiohwenergy.HomeWizardEnergy", return_value=device,), patch( + with patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=device, + ), patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, ) as mock_setup_entry: @@ -127,11 +142,11 @@ async def test_config_flow_imports_entry(aioclient_mock, hass): ) assert result["type"] == "create_entry" - assert result["title"] == f"{device.device.product_name} (aabbccddeeff)" + assert result["title"] == "P1 meter (aabbccddeeff)" assert result["data"][CONF_IP_ADDRESS] == "1.2.3.4" assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - assert len(device.initialize.mock_calls) == 1 + assert len(device.device.mock_calls) == 1 assert len(device.close.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -166,7 +181,10 @@ async def test_discovery_disabled_api(hass, aioclient_mock): with patch( "homeassistant.components.homewizard.async_setup_entry", return_value=True, - ), patch("aiohwenergy.HomeWizardEnergy", return_value=get_mock_device()): + ), patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + return_value=get_mock_device(), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"ip_address": "192.168.43.183"} ) @@ -240,7 +258,7 @@ async def test_check_disabled_api(hass, aioclient_mock): raise DisabledError device = get_mock_device() - device.initialize.side_effect = mock_initialize + device.device.side_effect = mock_initialize result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -250,7 +268,7 @@ async def test_check_disabled_api(hass, aioclient_mock): assert result["step_id"] == "user" with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", return_value=device, ): result = await hass.config_entries.flow.async_configure( @@ -268,7 +286,7 @@ async def test_check_error_handling_api(hass, aioclient_mock): raise Exception() device = get_mock_device() - device.initialize.side_effect = mock_initialize + device.device.side_effect = mock_initialize result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -278,32 +296,7 @@ async def test_check_error_handling_api(hass, aioclient_mock): assert result["step_id"] == "user" with patch( - "aiohwenergy.HomeWizardEnergy", - return_value=device, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_IP_ADDRESS: "2.2.2.2"} - ) - - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "unknown_error" - - -async def test_check_detects_unexpected_api_response(hass, aioclient_mock): - """Test check detecting device endpoint failed fetching data.""" - - device = get_mock_device() - device.device = None - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - - with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", return_value=device, ): result = await hass.config_entries.flow.async_configure( @@ -317,8 +310,11 @@ async def test_check_detects_unexpected_api_response(hass, aioclient_mock): async def test_check_detects_invalid_api(hass, aioclient_mock): """Test check detecting device endpoint failed fetching data.""" + def mock_initialize(): + raise UnsupportedError + device = get_mock_device() - device.device.api_version = "not_v1" + device.device.side_effect = mock_initialize result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -328,7 +324,7 @@ async def test_check_detects_invalid_api(hass, aioclient_mock): assert result["step_id"] == "user" with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", return_value=device, ): result = await hass.config_entries.flow.async_configure( @@ -337,27 +333,3 @@ async def test_check_detects_invalid_api(hass, aioclient_mock): assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "unsupported_api_version" - - -async def test_check_detects_unsuported_device(hass, aioclient_mock): - """Test check detecting device endpoint failed fetching data.""" - - device = get_mock_device(product_type="not_an_energy_device") - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - - with patch( - "aiohwenergy.HomeWizardEnergy", - return_value=device, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_IP_ADDRESS: "2.2.2.2"} - ) - - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "device_not_supported" diff --git a/tests/components/homewizard/test_diagnostics.py b/tests/components/homewizard/test_diagnostics.py new file mode 100644 index 00000000000..e477c94d914 --- /dev/null +++ b/tests/components/homewizard/test_diagnostics.py @@ -0,0 +1,47 @@ +"""Tests for diagnostics data.""" +from aiohttp import ClientSession + +from homeassistant.components.diagnostics import REDACTED +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSession, + init_integration: MockConfigEntry, +): + """Test diagnostics.""" + assert await get_diagnostics_for_config_entry( + hass, hass_client, init_integration + ) == { + "entry": {"ip_address": REDACTED}, + "data": { + "device": { + "product_name": "P1 Meter", + "product_type": "HWE-P1", + "serial": REDACTED, + "api_version": "v1", + "firmware_version": "2.11", + }, + "data": { + "smr_version": 50, + "meter_model": "ISKRA 2M550T-101", + "wifi_ssid": REDACTED, + "wifi_strength": 100, + "total_power_import_t1_kwh": 1234.111, + "total_power_import_t2_kwh": 5678.222, + "total_power_export_t1_kwh": 4321.333, + "total_power_export_t2_kwh": 8765.444, + "active_power_w": -123, + "active_power_l1_w": -123, + "active_power_l2_w": 456, + "active_power_l3_w": 123.456, + "total_gas_m3": 1122.333, + "gas_timestamp": "2021-03-14T11:22:33", + }, + "state": {"power_on": True, "switch_lock": False, "brightness": 255}, + }, + } diff --git a/tests/components/homewizard/test_init.py b/tests/components/homewizard/test_init.py index 984a5431004..c7cc5bd7bdb 100644 --- a/tests/components/homewizard/test_init.py +++ b/tests/components/homewizard/test_init.py @@ -2,7 +2,7 @@ from asyncio import TimeoutError from unittest.mock import patch -from aiohwenergy import AiohwenergyException, DisabledError +from homewizard_energy.errors import DisabledError, HomeWizardEnergyException from homeassistant import config_entries from homeassistant.components.homewizard.const import DOMAIN @@ -28,7 +28,7 @@ async def test_load_unload(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) @@ -50,7 +50,7 @@ async def test_load_failed_host_unavailable(aioclient_mock, hass): raise TimeoutError() device = get_mock_device() - device.initialize.side_effect = MockInitialize + device.device.side_effect = MockInitialize entry = MockConfigEntry( domain=DOMAIN, @@ -60,7 +60,7 @@ async def test_load_failed_host_unavailable(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) @@ -127,7 +127,7 @@ async def test_init_accepts_and_migrates_old_entry(aioclient_mock, hass): # Add the entry_id to trigger migration with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(imported_entry.entry_id) @@ -168,7 +168,7 @@ async def test_load_detect_api_disabled(aioclient_mock, hass): raise DisabledError() device = get_mock_device() - device.initialize.side_effect = MockInitialize + device.device.side_effect = MockInitialize entry = MockConfigEntry( domain=DOMAIN, @@ -178,24 +178,24 @@ async def test_load_detect_api_disabled(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert entry.state is ConfigEntryState.SETUP_ERROR + assert entry.state is ConfigEntryState.SETUP_RETRY -async def test_load_handles_aiohwenergy_exception(aioclient_mock, hass): +async def test_load_handles_homewizardenergy_exception(aioclient_mock, hass): """Test setup handles exception from API.""" def MockInitialize(): - raise AiohwenergyException() + raise HomeWizardEnergyException() device = get_mock_device() - device.initialize.side_effect = MockInitialize + device.device.side_effect = MockInitialize entry = MockConfigEntry( domain=DOMAIN, @@ -205,7 +205,7 @@ async def test_load_handles_aiohwenergy_exception(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) @@ -222,7 +222,7 @@ async def test_load_handles_generic_exception(aioclient_mock, hass): raise Exception() device = get_mock_device() - device.initialize.side_effect = MockInitialize + device.device.side_effect = MockInitialize entry = MockConfigEntry( domain=DOMAIN, @@ -232,7 +232,7 @@ async def test_load_handles_generic_exception(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) @@ -256,7 +256,7 @@ async def test_load_handles_initialization_error(aioclient_mock, hass): entry.add_to_hass(hass) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=device, ): await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/homewizard/test_sensor.py b/tests/components/homewizard/test_sensor.py index c1a98c07108..8195aa11708 100644 --- a/tests/components/homewizard/test_sensor.py +++ b/tests/components/homewizard/test_sensor.py @@ -3,7 +3,8 @@ from datetime import timedelta from unittest.mock import AsyncMock, patch -from aiohwenergy.errors import DisabledError +from homewizard_energy.errors import DisabledError, RequestError +from homewizard_energy.models import Data from homeassistant.components.sensor import ( ATTR_STATE_CLASS, @@ -36,13 +37,10 @@ async def test_sensor_entity_smr_version( """Test entity loads smr version.""" api = get_mock_device() - api.data.available_datapoints = [ - "smr_version", - ] - api.data.smr_version = 50 + api.data = AsyncMock(return_value=Data.from_dict({"smr_version": 50})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -77,13 +75,10 @@ async def test_sensor_entity_meter_model( """Test entity loads meter model.""" api = get_mock_device() - api.data.available_datapoints = [ - "meter_model", - ] - api.data.meter_model = "Model X" + api.data = AsyncMock(return_value=Data.from_dict({"meter_model": "Model X"})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -118,13 +113,10 @@ async def test_sensor_entity_wifi_ssid(hass, mock_config_entry_data, mock_config """Test entity loads wifi ssid.""" api = get_mock_device() - api.data.available_datapoints = [ - "wifi_ssid", - ] - api.data.wifi_ssid = "My Wifi" + api.data = AsyncMock(return_value=Data.from_dict({"wifi_ssid": "My Wifi"})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -159,13 +151,10 @@ async def test_sensor_entity_wifi_strength( """Test entity loads wifi strength.""" api = get_mock_device() - api.data.available_datapoints = [ - "wifi_strength", - ] - api.data.wifi_strength = 42 + api.data = AsyncMock(return_value=Data.from_dict({"wifi_strength": 42})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -189,13 +178,12 @@ async def test_sensor_entity_total_power_import_t1_kwh( """Test entity loads total power import t1.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_import_t1_kwh", - ] - api.data.total_power_import_t1_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_import_t1_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -232,13 +220,12 @@ async def test_sensor_entity_total_power_import_t2_kwh( """Test entity loads total power import t2.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_import_t2_kwh", - ] - api.data.total_power_import_t2_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_import_t2_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -275,13 +262,12 @@ async def test_sensor_entity_total_power_export_t1_kwh( """Test entity loads total power export t1.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_export_t1_kwh", - ] - api.data.total_power_export_t1_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_export_t1_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -318,13 +304,12 @@ async def test_sensor_entity_total_power_export_t2_kwh( """Test entity loads total power export t2.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_export_t2_kwh", - ] - api.data.total_power_export_t2_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_export_t2_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -361,13 +346,10 @@ async def test_sensor_entity_active_power( """Test entity loads active power.""" api = get_mock_device() - api.data.available_datapoints = [ - "active_power_w", - ] - api.data.active_power_w = 123.123 + api.data = AsyncMock(return_value=Data.from_dict({"active_power_w": 123.123})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -402,13 +384,10 @@ async def test_sensor_entity_active_power_l1( """Test entity loads active power l1.""" api = get_mock_device() - api.data.available_datapoints = [ - "active_power_l1_w", - ] - api.data.active_power_l1_w = 123.123 + api.data = AsyncMock(return_value=Data.from_dict({"active_power_l1_w": 123.123})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -445,13 +424,10 @@ async def test_sensor_entity_active_power_l2( """Test entity loads active power l2.""" api = get_mock_device() - api.data.available_datapoints = [ - "active_power_l2_w", - ] - api.data.active_power_l2_w = 456.456 + api.data = AsyncMock(return_value=Data.from_dict({"active_power_l2_w": 456.456})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -488,13 +464,10 @@ async def test_sensor_entity_active_power_l3( """Test entity loads active power l3.""" api = get_mock_device() - api.data.available_datapoints = [ - "active_power_l3_w", - ] - api.data.active_power_l3_w = 789.789 + api.data = AsyncMock(return_value=Data.from_dict({"active_power_l3_w": 789.789})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -529,13 +502,10 @@ async def test_sensor_entity_total_gas(hass, mock_config_entry_data, mock_config """Test entity loads total gas.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_gas_m3", - ] - api.data.total_gas_m3 = 50 + api.data = AsyncMock(return_value=Data.from_dict({"total_gas_m3": 50})) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -570,17 +540,14 @@ async def test_sensor_entity_disabled_when_null( """Test sensor disables data with null by default.""" api = get_mock_device() - api.data.available_datapoints = [ - "active_power_l2_w", - "active_power_l3_w", - "total_gas_m3", - ] - api.data.active_power_l2_w = None - api.data.active_power_l3_w = None - api.data.total_gas_m3 = None + api.data = AsyncMock( + return_value=Data.from_dict( + {"active_power_l2_w": None, "active_power_l3_w": None, "total_gas_m3": None} + ) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -612,15 +579,14 @@ async def test_sensor_entity_export_disabled_when_unused( """Test sensor disables export if value is 0.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_export_t1_kwh", - "total_power_export_t2_kwh", - ] - api.data.total_power_export_t1_kwh = 0 - api.data.total_power_export_t2_kwh = 0 + api.data = AsyncMock( + return_value=Data.from_dict( + {"total_power_export_t1_kwh": 0, "total_power_export_t2_kwh": 0} + ) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -649,17 +615,14 @@ async def test_sensors_unreachable(hass, mock_config_entry_data, mock_config_ent """Test sensor handles api unreachable.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_import_t1_kwh", - ] - api.data.total_power_import_t1_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_import_t1_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): - api.update = AsyncMock(return_value=True) - entry = mock_config_entry entry.data = mock_config_entry_data entry.add_to_hass(hass) @@ -675,7 +638,7 @@ async def test_sensors_unreachable(hass, mock_config_entry_data, mock_config_ent == "1234.123" ) - api.update = AsyncMock(return_value=False) + api.data.side_effect = RequestError async_fire_time_changed(hass, utcnow + timedelta(seconds=5)) await hass.async_block_till_done() assert ( @@ -685,7 +648,7 @@ async def test_sensors_unreachable(hass, mock_config_entry_data, mock_config_ent == "unavailable" ) - api.update = AsyncMock(return_value=True) + api.data.side_effect = None async_fire_time_changed(hass, utcnow + timedelta(seconds=10)) await hass.async_block_till_done() assert ( @@ -700,17 +663,14 @@ async def test_api_disabled(hass, mock_config_entry_data, mock_config_entry): """Test sensor handles api unreachable.""" api = get_mock_device() - api.data.available_datapoints = [ - "total_power_import_t1_kwh", - ] - api.data.total_power_import_t1_kwh = 1234.123 + api.data = AsyncMock( + return_value=Data.from_dict({"total_power_import_t1_kwh": 1234.123}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): - api.update = AsyncMock(return_value=True) - entry = mock_config_entry entry.data = mock_config_entry_data entry.add_to_hass(hass) @@ -726,7 +686,7 @@ async def test_api_disabled(hass, mock_config_entry_data, mock_config_entry): == "1234.123" ) - api.update = AsyncMock(side_effect=DisabledError) + api.data.side_effect = DisabledError async_fire_time_changed(hass, utcnow + timedelta(seconds=5)) await hass.async_block_till_done() assert ( @@ -736,7 +696,7 @@ async def test_api_disabled(hass, mock_config_entry_data, mock_config_entry): == "unavailable" ) - api.update = AsyncMock(return_value=True) + api.data.side_effect = None async_fire_time_changed(hass, utcnow + timedelta(seconds=10)) await hass.async_block_till_done() assert ( diff --git a/tests/components/homewizard/test_switch.py b/tests/components/homewizard/test_switch.py index f3792a9d75b..118f0774a47 100644 --- a/tests/components/homewizard/test_switch.py +++ b/tests/components/homewizard/test_switch.py @@ -2,6 +2,8 @@ from unittest.mock import AsyncMock, patch +from homewizard_energy.models import State + from homeassistant.components import switch from homeassistant.components.switch import DEVICE_CLASS_OUTLET, DEVICE_CLASS_SWITCH from homeassistant.const import ( @@ -27,7 +29,7 @@ async def test_switch_entity_not_loaded_when_not_available( api = get_mock_device() with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -48,13 +50,12 @@ async def test_switch_loads_entities(hass, mock_config_entry_data, mock_config_e """Test entity loads smr version.""" api = get_mock_device() - api.state = AsyncMock() - - api.state.power_on = False - api.state.switch_lock = False + api.state = AsyncMock( + return_value=State.from_dict({"power_on": False, "switch_lock": False}) + ) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -104,17 +105,19 @@ async def test_switch_power_on_off(hass, mock_config_entry_data, mock_config_ent """Test entity turns switch on and off.""" api = get_mock_device() - api.state = AsyncMock() - api.state.power_on = False - api.state.switch_lock = False + api.state = AsyncMock( + return_value=State.from_dict({"power_on": False, "switch_lock": False}) + ) - def set_power_on(power_on): - api.state.power_on = power_on + def state_set(power_on): + api.state = AsyncMock( + return_value=State.from_dict({"power_on": power_on, "switch_lock": False}) + ) - api.state.set = AsyncMock(side_effect=set_power_on) + api.state_set = AsyncMock(side_effect=state_set) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -138,7 +141,7 @@ async def test_switch_power_on_off(hass, mock_config_entry_data, mock_config_ent ) await hass.async_block_till_done() - assert len(api.state.set.mock_calls) == 1 + assert len(api.state_set.mock_calls) == 1 assert ( hass.states.get("switch.product_name_aabbccddeeff_switch").state == STATE_ON ) @@ -156,7 +159,7 @@ async def test_switch_power_on_off(hass, mock_config_entry_data, mock_config_ent hass.states.get("switch.product_name_aabbccddeeff_switch").state == STATE_OFF ) - assert len(api.state.set.mock_calls) == 2 + assert len(api.state_set.mock_calls) == 2 async def test_switch_lock_power_on_off( @@ -165,17 +168,19 @@ async def test_switch_lock_power_on_off( """Test entity turns switch on and off.""" api = get_mock_device() - api.state = AsyncMock() - api.state.power_on = False - api.state.switch_lock = False + api.state = AsyncMock( + return_value=State.from_dict({"power_on": False, "switch_lock": False}) + ) - def set_switch_lock(switch_lock): - api.state.switch_lock = switch_lock + def state_set(switch_lock): + api.state = AsyncMock( + return_value=State.from_dict({"power_on": True, "switch_lock": switch_lock}) + ) - api.state.set = AsyncMock(side_effect=set_switch_lock) + api.state_set = AsyncMock(side_effect=state_set) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -199,7 +204,7 @@ async def test_switch_lock_power_on_off( ) await hass.async_block_till_done() - assert len(api.state.set.mock_calls) == 1 + assert len(api.state_set.mock_calls) == 1 assert ( hass.states.get("switch.product_name_aabbccddeeff_switch_lock").state == STATE_ON @@ -218,7 +223,7 @@ async def test_switch_lock_power_on_off( hass.states.get("switch.product_name_aabbccddeeff_switch_lock").state == STATE_OFF ) - assert len(api.state.set.mock_calls) == 2 + assert len(api.state_set.mock_calls) == 2 async def test_switch_lock_sets_power_on_unavailable( @@ -227,17 +232,19 @@ async def test_switch_lock_sets_power_on_unavailable( """Test entity turns switch on and off.""" api = get_mock_device() - api.state = AsyncMock() - api.state.power_on = True - api.state.switch_lock = False + api.state = AsyncMock( + return_value=State.from_dict({"power_on": True, "switch_lock": False}) + ) - def set_switch_lock(switch_lock): - api.state.switch_lock = switch_lock + def state_set(switch_lock): + api.state = AsyncMock( + return_value=State.from_dict({"power_on": True, "switch_lock": switch_lock}) + ) - api.state.set = AsyncMock(side_effect=set_switch_lock) + api.state_set = AsyncMock(side_effect=state_set) with patch( - "aiohwenergy.HomeWizardEnergy", + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", return_value=api, ): entry = mock_config_entry @@ -264,7 +271,7 @@ async def test_switch_lock_sets_power_on_unavailable( ) await hass.async_block_till_done() - assert len(api.state.set.mock_calls) == 1 + assert len(api.state_set.mock_calls) == 1 assert ( hass.states.get("switch.product_name_aabbccddeeff_switch").state == STATE_UNAVAILABLE @@ -290,4 +297,4 @@ async def test_switch_lock_sets_power_on_unavailable( hass.states.get("switch.product_name_aabbccddeeff_switch_lock").state == STATE_OFF ) - assert len(api.state.set.mock_calls) == 2 + assert len(api.state_set.mock_calls) == 2 diff --git a/tests/components/huawei_lte/test_config_flow.py b/tests/components/huawei_lte/test_config_flow.py index 52b46c57b98..b363836cc4f 100644 --- a/tests/components/huawei_lte/test_config_flow.py +++ b/tests/components/huawei_lte/test_config_flow.py @@ -127,7 +127,7 @@ def login_requests_mock(requests_mock): LoginErrorEnum.USERNAME_PWD_WRONG, {CONF_USERNAME: "invalid_auth"}, ), - (LoginErrorEnum.USERNAME_PWD_ORERRUN, {"base": "login_attempts_exceeded"}), + (LoginErrorEnum.USERNAME_PWD_OVERRUN, {"base": "login_attempts_exceeded"}), (ResponseCodeEnum.ERROR_SYSTEM_UNKNOWN, {"base": "response_error"}), ), ) diff --git a/tests/components/hue/test_light_v1.py b/tests/components/hue/test_light_v1.py index 8c82a544ede..5423e6bd799 100644 --- a/tests/components/hue/test_light_v1.py +++ b/tests/components/hue/test_light_v1.py @@ -7,6 +7,7 @@ import aiohue from homeassistant.components import hue from homeassistant.components.hue.const import CONF_ALLOW_HUE_GROUPS from homeassistant.components.hue.v1 import light as hue_light +from homeassistant.components.light import COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util import color @@ -236,6 +237,11 @@ async def test_lights_color_mode(hass, mock_bridge_v1): assert lamp_1.attributes["brightness"] == 145 assert lamp_1.attributes["hs_color"] == (36.067, 69.804) assert "color_temp" not in lamp_1.attributes + assert lamp_1.attributes["color_mode"] == COLOR_MODE_HS + assert lamp_1.attributes["supported_color_modes"] == [ + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, + ] new_light1_on = LIGHT_1_ON.copy() new_light1_on["state"] = new_light1_on["state"].copy() @@ -256,6 +262,11 @@ async def test_lights_color_mode(hass, mock_bridge_v1): assert lamp_1.attributes["brightness"] == 145 assert lamp_1.attributes["color_temp"] == 467 assert "hs_color" in lamp_1.attributes + assert lamp_1.attributes["color_mode"] == COLOR_MODE_COLOR_TEMP + assert lamp_1.attributes["supported_color_modes"] == [ + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, + ] async def test_groups(hass, mock_bridge_v1): @@ -651,6 +662,7 @@ def test_available(): bridge=Mock(config_entry=Mock(options={"allow_unreachable": False})), coordinator=Mock(last_update_success=True), is_group=False, + supported_color_modes=hue_light.COLOR_MODES_HUE_EXTENDED, supported_features=hue_light.SUPPORT_HUE_EXTENDED, rooms={}, ) @@ -666,6 +678,7 @@ def test_available(): ), coordinator=Mock(last_update_success=True), is_group=False, + supported_color_modes=hue_light.COLOR_MODES_HUE_EXTENDED, supported_features=hue_light.SUPPORT_HUE_EXTENDED, rooms={}, bridge=Mock(config_entry=Mock(options={"allow_unreachable": True})), @@ -682,6 +695,7 @@ def test_available(): ), coordinator=Mock(last_update_success=True), is_group=True, + supported_color_modes=hue_light.COLOR_MODES_HUE_EXTENDED, supported_features=hue_light.SUPPORT_HUE_EXTENDED, rooms={}, bridge=Mock(config_entry=Mock(options={"allow_unreachable": False})), @@ -702,6 +716,7 @@ def test_hs_color(): coordinator=Mock(last_update_success=True), bridge=Mock(), is_group=False, + supported_color_modes=hue_light.COLOR_MODES_HUE_EXTENDED, supported_features=hue_light.SUPPORT_HUE_EXTENDED, rooms={}, ) @@ -718,6 +733,7 @@ def test_hs_color(): coordinator=Mock(last_update_success=True), bridge=Mock(), is_group=False, + supported_color_modes=hue_light.COLOR_MODES_HUE_EXTENDED, supported_features=hue_light.SUPPORT_HUE_EXTENDED, rooms={}, ) @@ -734,6 +750,7 @@ def test_hs_color(): coordinator=Mock(last_update_success=True), bridge=Mock(), is_group=False, + supported_color_modes=hue_light.COLOR_MODES_HUE_EXTENDED, supported_features=hue_light.SUPPORT_HUE_EXTENDED, rooms={}, ) @@ -910,15 +927,20 @@ async def test_group_features(hass, mock_bridge_v1): assert len(mock_bridge_v1.mock_requests) == 2 color_temp_feature = hue_light.SUPPORT_HUE["Color temperature light"] + color_temp_mode = sorted(hue_light.COLOR_MODES_HUE["Color temperature light"]) extended_color_feature = hue_light.SUPPORT_HUE["Extended color light"] + extended_color_mode = sorted(hue_light.COLOR_MODES_HUE["Extended color light"]) group_1 = hass.states.get("light.group_1") + assert group_1.attributes["supported_color_modes"] == color_temp_mode assert group_1.attributes["supported_features"] == color_temp_feature group_2 = hass.states.get("light.living_room") + assert group_2.attributes["supported_color_modes"] == extended_color_mode assert group_2.attributes["supported_features"] == extended_color_feature group_3 = hass.states.get("light.dining_room") + assert group_3.attributes["supported_color_modes"] == extended_color_mode assert group_3.attributes["supported_features"] == extended_color_feature entity_registry = er.async_get(hass) diff --git a/tests/components/hue/test_logbook.py b/tests/components/hue/test_logbook.py new file mode 100644 index 00000000000..fb7934da126 --- /dev/null +++ b/tests/components/hue/test_logbook.py @@ -0,0 +1,104 @@ +"""The tests for hue logbook.""" + +from homeassistant.components.hue.const import ATTR_HUE_EVENT, CONF_SUBTYPE, DOMAIN +from homeassistant.components.hue.v1.hue_event import CONF_LAST_UPDATED +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_DEVICE_ID, + CONF_EVENT, + CONF_ID, + CONF_TYPE, + CONF_UNIQUE_ID, +) +from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component + +from .conftest import setup_platform + +from tests.components.logbook.common import MockRow, mock_humanify + +# v1 event +SAMPLE_V1_EVENT = { + CONF_DEVICE_ID: "fe346f17a9f8c15be633f9cc3f3d6631", + CONF_EVENT: 18, + CONF_ID: "hue_tap", + CONF_LAST_UPDATED: "2019-12-28T22:58:03", + CONF_UNIQUE_ID: "00:00:00:00:00:44:23:08-f2", +} +# v2 event +SAMPLE_V2_EVENT = { + CONF_DEVICE_ID: "f974028e7933aea703a2199a855bc4a3", + CONF_ID: "wall_switch_with_2_controls_button", + CONF_SUBTYPE: 1, + CONF_TYPE: "initial_press", + CONF_UNIQUE_ID: "c658d3d8-a013-4b81-8ac6-78b248537e70", +} + + +async def test_humanify_hue_events(hass, mock_bridge_v2): + """Test hue events when the devices are present in the registry.""" + await setup_platform(hass, mock_bridge_v2, "sensor") + hass.config.components.add("recorder") + assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() + entry: ConfigEntry = hass.config_entries.async_entries(DOMAIN)[0] + + dev_reg = device_registry.async_get(hass) + v1_device = dev_reg.async_get_or_create( + identifiers={(DOMAIN, "v1")}, name="Remote 1", config_entry_id=entry.entry_id + ) + v2_device = dev_reg.async_get_or_create( + identifiers={(DOMAIN, "v2")}, name="Remote 2", config_entry_id=entry.entry_id + ) + + (v1_event, v2_event) = mock_humanify( + hass, + [ + MockRow( + ATTR_HUE_EVENT, + {**SAMPLE_V1_EVENT, CONF_DEVICE_ID: v1_device.id}, + ), + MockRow( + ATTR_HUE_EVENT, + {**SAMPLE_V2_EVENT, CONF_DEVICE_ID: v2_device.id}, + ), + ], + ) + + assert v1_event["name"] == "Remote 1" + assert v1_event["domain"] == DOMAIN + assert v1_event["message"] == "Event 18" + + assert v2_event["name"] == "Remote 2" + assert v2_event["domain"] == DOMAIN + assert v2_event["message"] == "first button pressed initially" + + +async def test_humanify_hue_events_devices_removed(hass, mock_bridge_v2): + """Test hue events when the devices have been removed from the registry.""" + await setup_platform(hass, mock_bridge_v2, "sensor") + hass.config.components.add("recorder") + assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() + + (v1_event, v2_event) = mock_humanify( + hass, + [ + MockRow( + ATTR_HUE_EVENT, + SAMPLE_V1_EVENT, + ), + MockRow( + ATTR_HUE_EVENT, + SAMPLE_V2_EVENT, + ), + ], + ) + + assert v1_event["name"] == "hue_tap" + assert v1_event["domain"] == DOMAIN + assert v1_event["message"] == "Event 18" + + assert v2_event["name"] == "wall_switch_with_2_controls_button" + assert v2_event["domain"] == DOMAIN + assert v2_event["message"] == "first button pressed initially" diff --git a/tests/components/humidifier/test_intent.py b/tests/components/humidifier/test_intent.py index 66ff62872f3..ed207231bc6 100644 --- a/tests/components/humidifier/test_intent.py +++ b/tests/components/humidifier/test_intent.py @@ -15,7 +15,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) -from homeassistant.helpers.intent import IntentHandleError +from homeassistant.helpers.intent import IntentHandleError, async_handle from tests.common import async_mock_service @@ -29,7 +29,8 @@ async def test_intent_set_humidity(hass): turn_on_calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) await intent.async_setup_intents(hass) - result = await hass.helpers.intent.async_handle( + result = await async_handle( + hass, "test", intent.INTENT_HUMIDITY, {"name": {"value": "Bedroom humidifier"}, "humidity": {"value": "50"}}, @@ -56,7 +57,8 @@ async def test_intent_set_humidity_and_turn_on(hass): turn_on_calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) await intent.async_setup_intents(hass) - result = await hass.helpers.intent.async_handle( + result = await async_handle( + hass, "test", intent.INTENT_HUMIDITY, {"name": {"value": "Bedroom humidifier"}, "humidity": {"value": "50"}}, @@ -97,7 +99,8 @@ async def test_intent_set_mode(hass): turn_on_calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) await intent.async_setup_intents(hass) - result = await hass.helpers.intent.async_handle( + result = await async_handle( + hass, "test", intent.INTENT_MODE, {"name": {"value": "Bedroom humidifier"}, "mode": {"value": "away"}}, @@ -134,7 +137,8 @@ async def test_intent_set_mode_and_turn_on(hass): turn_on_calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) await intent.async_setup_intents(hass) - result = await hass.helpers.intent.async_handle( + result = await async_handle( + hass, "test", intent.INTENT_MODE, {"name": {"value": "Bedroom humidifier"}, "mode": {"value": "away"}}, @@ -168,7 +172,8 @@ async def test_intent_set_mode_tests_feature(hass): await intent.async_setup_intents(hass) try: - await hass.helpers.intent.async_handle( + await async_handle( + hass, "test", intent.INTENT_MODE, {"name": {"value": "Bedroom humidifier"}, "mode": {"value": "away"}}, @@ -196,7 +201,8 @@ async def test_intent_set_unknown_mode(hass): await intent.async_setup_intents(hass) try: - await hass.helpers.intent.async_handle( + await async_handle( + hass, "test", intent.INTENT_MODE, {"name": {"value": "Bedroom humidifier"}, "mode": {"value": "eco"}}, diff --git a/tests/components/hyperion/test_camera.py b/tests/components/hyperion/test_camera.py index 71e1e42cb1a..f83ed9c7e78 100644 --- a/tests/components/hyperion/test_camera.py +++ b/tests/components/hyperion/test_camera.py @@ -200,7 +200,7 @@ async def test_device_info(hass: HomeAssistant) -> None: assert device.model == HYPERION_MODEL_NAME assert device.name == TEST_INSTANCE_1["friendly_name"] - entity_registry = await er.async_get_registry(hass) + entity_registry = er.async_get(hass) entities_from_device = [ entry.entity_id for entry in er.async_entries_for_device(entity_registry, device.id) diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index 2514f6b6cba..136a67f0dba 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -1331,7 +1331,7 @@ async def test_device_info(hass: HomeAssistant) -> None: assert device.model == HYPERION_MODEL_NAME assert device.name == TEST_INSTANCE_1["friendly_name"] - entity_registry = await er.async_get_registry(hass) + entity_registry = er.async_get(hass) entities_from_device = [ entry.entity_id for entry in er.async_entries_for_device(entity_registry, device.id) diff --git a/tests/components/hyperion/test_switch.py b/tests/components/hyperion/test_switch.py index 7ec441716ce..cd1dbdcda5b 100644 --- a/tests/components/hyperion/test_switch.py +++ b/tests/components/hyperion/test_switch.py @@ -172,7 +172,7 @@ async def test_device_info(hass: HomeAssistant) -> None: assert device.model == HYPERION_MODEL_NAME assert device.name == TEST_INSTANCE_1["friendly_name"] - entity_registry = await er.async_get_registry(hass) + entity_registry = er.async_get(hass) entities_from_device = [ entry.entity_id for entry in er.async_entries_for_device(entity_registry, device.id) diff --git a/tests/components/ialarm_xr/__init__.py b/tests/components/ialarm_xr/__init__.py new file mode 100644 index 00000000000..4097867f70b --- /dev/null +++ b/tests/components/ialarm_xr/__init__.py @@ -0,0 +1 @@ +"""Tests for the Antifurto365 iAlarmXR integration.""" diff --git a/tests/components/ialarm_xr/test_config_flow.py b/tests/components/ialarm_xr/test_config_flow.py new file mode 100644 index 00000000000..804249dd5cb --- /dev/null +++ b/tests/components/ialarm_xr/test_config_flow.py @@ -0,0 +1,167 @@ +"""Test the Antifurto365 iAlarmXR config flow.""" + +from unittest.mock import patch + +from pyialarmxr import IAlarmXRGenericException, IAlarmXRSocketTimeoutException + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components.ialarm_xr.const import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME + +from tests.common import MockConfigEntry + +TEST_DATA = { + CONF_HOST: "1.1.1.1", + CONF_PORT: 18034, + CONF_USERNAME: "000ZZZ0Z00", + CONF_PASSWORD: "00000000", +} + +TEST_MAC = "00:00:54:12:34:56" + + +async def test_form(hass): + """Test we get the form.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["handler"] == "ialarm_xr" + assert result["data_schema"].schema.get("host") == str + assert result["data_schema"].schema.get("port") == int + assert result["data_schema"].schema.get("password") == str + assert result["data_schema"].schema.get("username") == str + assert result["errors"] == {} + + with patch( + "homeassistant.components.ialarm_xr.config_flow.IAlarmXR.get_status", + return_value=1, + ), patch( + "homeassistant.components.ialarm_xr.config_flow.IAlarmXR.get_mac", + return_value=TEST_MAC, + ), patch( + "homeassistant.components.ialarm_xr.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], TEST_DATA + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == TEST_DATA["host"] + assert result2["data"] == TEST_DATA + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_exception(hass): + """Test we handle unknown exception.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.ialarm_xr.config_flow.IAlarmXR.get_mac", + side_effect=Exception, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], TEST_DATA + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} + + +async def test_form_cannot_connect_throwing_connection_error(hass): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.ialarm_xr.config_flow.IAlarmXR.get_mac", + side_effect=ConnectionError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], TEST_DATA + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_cannot_connect_throwing_socket_timeout_exception(hass): + """Test we handle cannot connect error because of socket timeout.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.ialarm_xr.config_flow.IAlarmXR.get_mac", + side_effect=IAlarmXRSocketTimeoutException, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], TEST_DATA + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] == {"base": "timeout"} + + +async def test_form_cannot_connect_throwing_generic_exception(hass): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.ialarm_xr.config_flow.IAlarmXR.get_mac", + side_effect=IAlarmXRGenericException, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], TEST_DATA + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_already_exists(hass): + """Test that a flow with an existing host aborts.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=TEST_MAC, + data=TEST_DATA, + ) + + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.ialarm_xr.config_flow.IAlarmXR.get_mac", + return_value=TEST_MAC, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], TEST_DATA + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result2["reason"] == "already_configured" + + +async def test_flow_user_step_no_input(hass): + """Test appropriate error when no input is provided.""" + _result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + result = await hass.config_entries.flow.async_configure( + _result["flow_id"], user_input=None + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == config_entries.SOURCE_USER + assert result["errors"] == {} diff --git a/tests/components/ialarm_xr/test_init.py b/tests/components/ialarm_xr/test_init.py new file mode 100644 index 00000000000..0898b6bebf8 --- /dev/null +++ b/tests/components/ialarm_xr/test_init.py @@ -0,0 +1,110 @@ +"""Test the Antifurto365 iAlarmXR init.""" +import asyncio +from datetime import timedelta +from unittest.mock import Mock, patch +from uuid import uuid4 + +import pytest + +from homeassistant.components.ialarm_xr.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.util.dt import utcnow + +from tests.common import MockConfigEntry, async_fire_time_changed + + +@pytest.fixture(name="ialarmxr_api") +def ialarmxr_api_fixture(): + """Set up IAlarmXR API fixture.""" + with patch("homeassistant.components.ialarm_xr.IAlarmXR") as mock_ialarm_api: + yield mock_ialarm_api + + +@pytest.fixture(name="mock_config_entry") +def mock_config_fixture(): + """Return a fake config entry.""" + return MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "192.168.10.20", + CONF_PORT: 18034, + CONF_USERNAME: "000ZZZ0Z00", + CONF_PASSWORD: "00000000", + }, + entry_id=str(uuid4()), + ) + + +async def test_setup_entry(hass, ialarmxr_api, mock_config_entry): + """Test setup entry.""" + ialarmxr_api.return_value.get_mac = Mock(return_value="00:00:54:12:34:56") + + mock_config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + ialarmxr_api.return_value.get_mac.assert_called_once() + assert mock_config_entry.state is ConfigEntryState.LOADED + + +async def test_unload_entry(hass, ialarmxr_api, mock_config_entry): + """Test being able to unload an entry.""" + ialarmxr_api.return_value.get_mac = Mock(return_value="00:00:54:12:34:56") + + mock_config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.LOADED + assert await hass.config_entries.async_unload(mock_config_entry.entry_id) + assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + + +async def test_setup_not_ready_connection_error(hass, ialarmxr_api, mock_config_entry): + """Test setup failed because we can't connect to the alarm system.""" + ialarmxr_api.return_value.get_status = Mock(side_effect=ConnectionError) + + mock_config_entry.add_to_hass(hass) + assert not await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + future = utcnow() + timedelta(seconds=30) + async_fire_time_changed(hass, future) + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_setup_not_ready_timeout(hass, ialarmxr_api, mock_config_entry): + """Test setup failed because we can't connect to the alarm system.""" + ialarmxr_api.return_value.get_status = Mock(side_effect=asyncio.TimeoutError) + + mock_config_entry.add_to_hass(hass) + assert not await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + future = utcnow() + timedelta(seconds=30) + async_fire_time_changed(hass, future) + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_setup_entry_and_then_fail_on_update( + hass, ialarmxr_api, mock_config_entry +): + """Test setup entry.""" + ialarmxr_api.return_value.get_mac = Mock(return_value="00:00:54:12:34:56") + ialarmxr_api.return_value.get_status = Mock(value=ialarmxr_api.DISARMED) + + mock_config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + ialarmxr_api.return_value.get_mac.assert_called_once() + ialarmxr_api.return_value.get_status.assert_called_once() + assert mock_config_entry.state is ConfigEntryState.LOADED + + ialarmxr_api.return_value.get_status = Mock(side_effect=asyncio.TimeoutError) + future = utcnow() + timedelta(seconds=60) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + ialarmxr_api.return_value.get_status.assert_called_once() + assert hass.states.get("alarm_control_panel.ialarm_xr").state == "unavailable" diff --git a/tests/components/iaqualink/test_config_flow.py b/tests/components/iaqualink/test_config_flow.py index 2d00284775d..df2add36b9c 100644 --- a/tests/components/iaqualink/test_config_flow.py +++ b/tests/components/iaqualink/test_config_flow.py @@ -5,13 +5,11 @@ from iaqualink.exception import ( AqualinkServiceException, AqualinkServiceUnauthorizedException, ) -import pytest from homeassistant.components.iaqualink import config_flow -@pytest.mark.parametrize("step", ["import", "user"]) -async def test_already_configured(hass, config_entry, config_data, step): +async def test_already_configured(hass, config_entry, config_data): """Test config flow when iaqualink component is already setup.""" config_entry.add_to_hass(hass) @@ -19,81 +17,67 @@ async def test_already_configured(hass, config_entry, config_data, step): flow.hass = hass flow.context = {} - fname = f"async_step_{step}" - func = getattr(flow, fname) - result = await func(config_data) + result = await flow.async_step_user(config_data) assert result["type"] == "abort" -@pytest.mark.parametrize("step", ["import", "user"]) -async def test_without_config(hass, step): +async def test_without_config(hass): """Test config flow with no configuration.""" flow = config_flow.AqualinkFlowHandler() flow.hass = hass flow.context = {} - fname = f"async_step_{step}" - func = getattr(flow, fname) - result = await func() + result = await flow.async_step_user() assert result["type"] == "form" assert result["step_id"] == "user" assert result["errors"] == {} -@pytest.mark.parametrize("step", ["import", "user"]) -async def test_with_invalid_credentials(hass, config_data, step): +async def test_with_invalid_credentials(hass, config_data): """Test config flow with invalid username and/or password.""" flow = config_flow.AqualinkFlowHandler() flow.hass = hass - fname = f"async_step_{step}" - func = getattr(flow, fname) with patch( "homeassistant.components.iaqualink.config_flow.AqualinkClient.login", side_effect=AqualinkServiceUnauthorizedException, ): - result = await func(config_data) + result = await flow.async_step_user(config_data) assert result["type"] == "form" assert result["step_id"] == "user" assert result["errors"] == {"base": "invalid_auth"} -@pytest.mark.parametrize("step", ["import", "user"]) -async def test_service_exception(hass, config_data, step): +async def test_service_exception(hass, config_data): """Test config flow encountering service exception.""" flow = config_flow.AqualinkFlowHandler() flow.hass = hass - fname = f"async_step_{step}" - func = getattr(flow, fname) with patch( "homeassistant.components.iaqualink.config_flow.AqualinkClient.login", side_effect=AqualinkServiceException, ): - result = await func(config_data) + result = await flow.async_step_user(config_data) assert result["type"] == "form" assert result["step_id"] == "user" assert result["errors"] == {"base": "cannot_connect"} -@pytest.mark.parametrize("step", ["import", "user"]) -async def test_with_existing_config(hass, config_data, step): +async def test_with_existing_config(hass, config_data): """Test config flow with existing configuration.""" flow = config_flow.AqualinkFlowHandler() flow.hass = hass flow.context = {} - fname = f"async_step_{step}" - func = getattr(flow, fname) with patch( "homeassistant.components.iaqualink.config_flow.AqualinkClient.login", return_value=None, ): - result = await func(config_data) + result = await flow.async_step_user(config_data) assert result["type"] == "create_entry" assert result["title"] == config_data["username"] diff --git a/tests/components/insteon/test_api_device.py b/tests/components/insteon/test_api_device.py index 49588c6ea8f..206565cb7c7 100644 --- a/tests/components/insteon/test_api_device.py +++ b/tests/components/insteon/test_api_device.py @@ -17,7 +17,7 @@ from homeassistant.components.insteon.api.device import ( async_device_name, ) from homeassistant.components.insteon.const import DOMAIN, MULTIPLE -from homeassistant.helpers.device_registry import async_get_registry +from homeassistant.helpers import device_registry as dr from .const import MOCK_USER_INPUT_PLM from .mock_devices import MockDevices @@ -40,7 +40,7 @@ async def _async_setup(hass, hass_ws_client): devices = MockDevices() await devices.async_load() - dev_reg = await async_get_registry(hass) + dev_reg = dr.async_get(hass) # Create device registry entry for mock node ha_device = dev_reg.async_get_or_create( config_entry_id=config_entry.entry_id, @@ -94,7 +94,7 @@ async def test_no_insteon_device(hass, hass_ws_client): devices = MockDevices() await devices.async_load() - dev_reg = await async_get_registry(hass) + dev_reg = dr.async_get(hass) # Create device registry entry for a Insteon device not in the Insteon devices list ha_device_1 = dev_reg.async_get_or_create( config_entry_id=config_entry.entry_id, diff --git a/tests/components/integration/test_sensor.py b/tests/components/integration/test_sensor.py index 90c9e6f0fbc..8999c1f8d04 100644 --- a/tests/components/integration/test_sensor.py +++ b/tests/components/integration/test_sensor.py @@ -5,11 +5,15 @@ from unittest.mock import patch from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, + DATA_KILOBYTES, + DATA_RATE_BYTES_PER_SECOND, ENERGY_KILO_WATT_HOUR, ENERGY_WATT_HOUR, POWER_KILO_WATT, POWER_WATT, + STATE_UNAVAILABLE, STATE_UNKNOWN, + TIME_HOURS, TIME_SECONDS, ) from homeassistant.core import HomeAssistant, State @@ -299,7 +303,9 @@ async def test_suffix(hass): assert await async_setup_component(hass, "sensor", config) entity_id = config["sensor"]["source"] - hass.states.async_set(entity_id, 1000, {ATTR_UNIT_OF_MEASUREMENT: POWER_KILO_WATT}) + hass.states.async_set( + entity_id, 1000, {ATTR_UNIT_OF_MEASUREMENT: DATA_RATE_BYTES_PER_SECOND} + ) await hass.async_block_till_done() now = dt_util.utcnow() + timedelta(seconds=10) @@ -307,7 +313,7 @@ async def test_suffix(hass): hass.states.async_set( entity_id, 1000, - {ATTR_UNIT_OF_MEASUREMENT: POWER_KILO_WATT}, + {ATTR_UNIT_OF_MEASUREMENT: DATA_RATE_BYTES_PER_SECOND}, force_update=True, ) await hass.async_block_till_done() @@ -317,6 +323,43 @@ async def test_suffix(hass): # Testing a network speed sensor at 1000 bytes/s over 10s = 10kbytes assert round(float(state.state)) == 10 + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == DATA_KILOBYTES + + +async def test_suffix_2(hass): + """Test integration sensor state.""" + config = { + "sensor": { + "platform": "integration", + "name": "integration", + "source": "sensor.cubic_meters_per_hour", + "round": 2, + "unit_time": TIME_HOURS, + } + } + + assert await async_setup_component(hass, "sensor", config) + + entity_id = config["sensor"]["source"] + hass.states.async_set(entity_id, 1000, {ATTR_UNIT_OF_MEASUREMENT: "m³/h"}) + await hass.async_block_till_done() + + now = dt_util.utcnow() + timedelta(hours=1) + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.states.async_set( + entity_id, + 1000, + {ATTR_UNIT_OF_MEASUREMENT: "m³/h"}, + force_update=True, + ) + await hass.async_block_till_done() + + state = hass.states.get("sensor.integration") + assert state is not None + + # Testing a flow sensor at 1000 m³/h over 1h = 1000 m³ + assert round(float(state.state)) == 1000 + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == "m³" async def test_units(hass): @@ -350,6 +393,15 @@ async def test_units(hass): # they became valid assert state.attributes.get("unit_of_measurement") == ENERGY_WATT_HOUR + # When source state goes to None / Unknown, expect an early exit without + # changes to the state or unit_of_measurement + hass.states.async_set(entity_id, STATE_UNAVAILABLE, None) + await hass.async_block_till_done() + + new_state = hass.states.get("sensor.integration") + assert state == new_state + assert state.attributes.get("unit_of_measurement") == ENERGY_WATT_HOUR + async def test_device_class(hass): """Test integration sensor units using a power source.""" diff --git a/tests/components/iss/test_config_flow.py b/tests/components/iss/test_config_flow.py index 09f7f391b89..3260b76432f 100644 --- a/tests/components/iss/test_config_flow.py +++ b/tests/components/iss/test_config_flow.py @@ -2,31 +2,15 @@ from unittest.mock import patch from homeassistant import data_entry_flow -from homeassistant.components.iss.binary_sensor import DEFAULT_NAME from homeassistant.components.iss.const import DOMAIN from homeassistant.config import async_process_ha_core_config -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER -from homeassistant.const import CONF_NAME, CONF_SHOW_ON_MAP +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import CONF_SHOW_ON_MAP from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry -async def test_import(hass: HomeAssistant): - """Test entry will be imported.""" - - imported_config = {CONF_NAME: DEFAULT_NAME, CONF_SHOW_ON_MAP: False} - - with patch("homeassistant.components.iss.async_setup_entry", return_value=True): - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=imported_config - ) - assert result.get("type") == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result.get("result").title == DEFAULT_NAME - assert result.get("result").options == {CONF_SHOW_ON_MAP: False} - - async def test_create_entry(hass: HomeAssistant): """Test we can finish a config flow.""" diff --git a/tests/components/isy994/test_config_flow.py b/tests/components/isy994/test_config_flow.py index 60e9fd964b6..8458dc0dc67 100644 --- a/tests/components/isy994/test_config_flow.py +++ b/tests/components/isy994/test_config_flow.py @@ -173,7 +173,7 @@ async def test_form_invalid_auth(hass: HomeAssistant): ) assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["errors"] == {"base": "invalid_auth"} + assert result2["errors"] == {CONF_PASSWORD: "invalid_auth"} async def test_form_unknown_exeption(hass: HomeAssistant): @@ -679,3 +679,70 @@ async def test_form_dhcp_existing_ignored_entry(hass: HomeAssistant): assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + + +async def test_reauth(hass): + """Test we can reauth.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_USERNAME: "bob", + CONF_HOST: f"http://{MOCK_HOSTNAME}:1443{ISY_URL_POSTFIX}", + }, + unique_id=MOCK_UUID, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_REAUTH, "unique_id": MOCK_UUID}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "reauth_confirm" + + with patch( + PATCH_CONNECTION, + side_effect=ISYInvalidAuthError(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"password": "invalid_auth"} + + with patch( + PATCH_CONNECTION, + side_effect=ISYConnectionError(), + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + + assert result3["type"] == "form" + assert result3["errors"] == {"base": "cannot_connect"} + + with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch( + "homeassistant.components.isy994.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result4 = await hass.config_entries.flow.async_configure( + result3["flow_id"], + { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + + assert mock_setup_entry.called + assert result4["type"] == "abort" + assert result4["reason"] == "reauth_successful" diff --git a/tests/components/jewish_calendar/test_binary_sensor.py b/tests/components/jewish_calendar/test_binary_sensor.py index 31cc0095af1..3ecac44b59c 100644 --- a/tests/components/jewish_calendar/test_binary_sensor.py +++ b/tests/components/jewish_calendar/test_binary_sensor.py @@ -4,6 +4,7 @@ from datetime import datetime as dt, timedelta import pytest from homeassistant.components import jewish_calendar +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -302,3 +303,15 @@ async def test_issur_melacha_sensor_update( hass.states.get("binary_sensor.test_issur_melacha_in_effect").state == result[1] ) + + +async def test_no_discovery_info(hass, caplog): + """Test setup without discovery info.""" + assert BINARY_SENSOR_DOMAIN not in hass.config.components + assert await async_setup_component( + hass, + BINARY_SENSOR_DOMAIN, + {BINARY_SENSOR_DOMAIN: {"platform": jewish_calendar.DOMAIN}}, + ) + await hass.async_block_till_done() + assert BINARY_SENSOR_DOMAIN in hass.config.components diff --git a/tests/components/jewish_calendar/test_sensor.py b/tests/components/jewish_calendar/test_sensor.py index d7d62a7ac97..a5dae8b4ce7 100644 --- a/tests/components/jewish_calendar/test_sensor.py +++ b/tests/components/jewish_calendar/test_sensor.py @@ -4,6 +4,7 @@ from datetime import datetime as dt, timedelta import pytest from homeassistant.components import jewish_calendar +from homeassistant.components.binary_sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -639,3 +640,15 @@ async def test_dafyomi_sensor(hass, test_time, result): await hass.async_block_till_done() assert hass.states.get("sensor.test_daf_yomi").state == result + + +async def test_no_discovery_info(hass, caplog): + """Test setup without discovery info.""" + assert SENSOR_DOMAIN not in hass.config.components + assert await async_setup_component( + hass, + SENSOR_DOMAIN, + {SENSOR_DOMAIN: {"platform": jewish_calendar.DOMAIN}}, + ) + await hass.async_block_till_done() + assert SENSOR_DOMAIN in hass.config.components diff --git a/tests/components/kaleidescape/test_init.py b/tests/components/kaleidescape/test_init.py index 876c02ba5a6..d0826f4714a 100644 --- a/tests/components/kaleidescape/test_init.py +++ b/tests/components/kaleidescape/test_init.py @@ -5,6 +5,7 @@ from unittest.mock import AsyncMock from homeassistant.components.kaleidescape.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr from . import MOCK_SERIAL @@ -50,7 +51,7 @@ async def test_device( mock_integration: MockConfigEntry, ) -> None: """Test device.""" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device( identifiers={("kaleidescape", MOCK_SERIAL)} ) diff --git a/tests/components/kaleidescape/test_media_player.py b/tests/components/kaleidescape/test_media_player.py index 94ba7f82fe8..11f5d5f5f2b 100644 --- a/tests/components/kaleidescape/test_media_player.py +++ b/tests/components/kaleidescape/test_media_player.py @@ -21,6 +21,7 @@ from homeassistant.const import ( STATE_PLAYING, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr from . import MOCK_SERIAL @@ -173,7 +174,7 @@ async def test_device( mock_integration: MockConfigEntry, ) -> None: """Test device attributes.""" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device( identifiers={("kaleidescape", MOCK_SERIAL)} ) diff --git a/tests/components/knx/test_binary_sensor.py b/tests/components/knx/test_binary_sensor.py index 25a223e76c8..ff58a36391c 100644 --- a/tests/components/knx/test_binary_sensor.py +++ b/tests/components/knx/test_binary_sensor.py @@ -6,10 +6,8 @@ from homeassistant.components.knx.const import CONF_STATE_ADDRESS, CONF_SYNC_STA from homeassistant.components.knx.schema import BinarySensorSchema from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, State +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import EntityCategory -from homeassistant.helpers.entity_registry import ( - async_get_registry as async_get_entity_registry, -) from homeassistant.util import dt from .conftest import KNXTestKit @@ -35,7 +33,7 @@ async def test_binary_sensor_entity_category(hass: HomeAssistant, knx: KNXTestKi await knx.assert_read("1/1/1") await knx.receive_response("1/1/1", True) - registry = await async_get_entity_registry(hass) + registry = er.async_get(hass) entity = registry.async_get("binary_sensor.test_normal") assert entity.entity_category is EntityCategory.DIAGNOSTIC diff --git a/tests/components/knx/test_scene.py b/tests/components/knx/test_scene.py index 37e4ac12728..a7de98abb20 100644 --- a/tests/components/knx/test_scene.py +++ b/tests/components/knx/test_scene.py @@ -4,10 +4,8 @@ from homeassistant.components.knx.const import KNX_ADDRESS from homeassistant.components.knx.schema import SceneSchema from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import EntityCategory -from homeassistant.helpers.entity_registry import ( - async_get_registry as async_get_entity_registry, -) from .conftest import KNXTestKit @@ -28,7 +26,7 @@ async def test_activate_knx_scene(hass: HomeAssistant, knx: KNXTestKit): ) assert len(hass.states.async_all()) == 1 - registry = await async_get_entity_registry(hass) + registry = er.async_get(hass) entity = registry.async_get("scene.test") assert entity.entity_category is EntityCategory.DIAGNOSTIC assert entity.unique_id == "1/1/1_24" diff --git a/tests/components/kraken/test_sensor.py b/tests/components/kraken/test_sensor.py index bc37b67e676..1d6b9e6518d 100644 --- a/tests/components/kraken/test_sensor.py +++ b/tests/components/kraken/test_sensor.py @@ -11,7 +11,7 @@ from homeassistant.components.kraken.const import ( DOMAIN, ) from homeassistant.const import CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_START -from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers import device_registry as dr, entity_registry as er import homeassistant.util.dt as dt_util from .const import ( @@ -52,7 +52,7 @@ async def test_sensor(hass): ) entry.add_to_hass(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) # Pre-create registry entries for disabled by default sensors registry.async_get_or_create( @@ -248,13 +248,13 @@ async def test_sensors_available_after_restart(hass): }, ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, "XBT_USD")}, name="XBT USD", manufacturer="Kraken.com", - entry_type=DeviceEntryType.SERVICE, + entry_type=dr.DeviceEntryType.SERVICE, ) entry.add_to_hass(hass) diff --git a/tests/components/laundrify/__init__.py b/tests/components/laundrify/__init__.py new file mode 100644 index 00000000000..c09c6290adf --- /dev/null +++ b/tests/components/laundrify/__init__.py @@ -0,0 +1,22 @@ +"""Tests for the laundrify integration.""" + +from homeassistant.components.laundrify import DOMAIN +from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.core import HomeAssistant + +from .const import VALID_ACCESS_TOKEN, VALID_ACCOUNT_ID + +from tests.common import MockConfigEntry + + +def create_entry( + hass: HomeAssistant, access_token: str = VALID_ACCESS_TOKEN +) -> MockConfigEntry: + """Create laundrify entry in Home Assistant.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=VALID_ACCOUNT_ID, + data={CONF_ACCESS_TOKEN: access_token}, + ) + entry.add_to_hass(hass) + return entry diff --git a/tests/components/laundrify/conftest.py b/tests/components/laundrify/conftest.py new file mode 100644 index 00000000000..13e408b61a9 --- /dev/null +++ b/tests/components/laundrify/conftest.py @@ -0,0 +1,51 @@ +"""Configure py.test.""" +import json +from unittest.mock import patch + +import pytest + +from .const import VALID_ACCESS_TOKEN, VALID_ACCOUNT_ID + +from tests.common import load_fixture + + +@pytest.fixture(name="laundrify_setup_entry") +def laundrify_setup_entry_fixture(): + """Mock laundrify setup entry function.""" + with patch( + "homeassistant.components.laundrify.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture(name="laundrify_exchange_code") +def laundrify_exchange_code_fixture(): + """Mock laundrify exchange_auth_code function.""" + with patch( + "laundrify_aio.LaundrifyAPI.exchange_auth_code", + return_value=VALID_ACCESS_TOKEN, + ) as exchange_code_mock: + yield exchange_code_mock + + +@pytest.fixture(name="laundrify_validate_token") +def laundrify_validate_token_fixture(): + """Mock laundrify validate_token function.""" + with patch( + "laundrify_aio.LaundrifyAPI.validate_token", + return_value=True, + ) as validate_token_mock: + yield validate_token_mock + + +@pytest.fixture(name="laundrify_api_mock", autouse=True) +def laundrify_api_fixture(laundrify_exchange_code, laundrify_validate_token): + """Mock valid laundrify API responses.""" + with patch( + "laundrify_aio.LaundrifyAPI.get_account_id", + return_value=VALID_ACCOUNT_ID, + ), patch( + "laundrify_aio.LaundrifyAPI.get_machines", + return_value=json.loads(load_fixture("laundrify/machines.json")), + ) as get_machines_mock: + yield get_machines_mock diff --git a/tests/components/laundrify/const.py b/tests/components/laundrify/const.py new file mode 100644 index 00000000000..644631917c6 --- /dev/null +++ b/tests/components/laundrify/const.py @@ -0,0 +1,11 @@ +"""Constants for the laundrify tests.""" + +from homeassistant.const import CONF_CODE + +VALID_AUTH_CODE = "999-001" +VALID_ACCESS_TOKEN = "validAccessToken1234" +VALID_ACCOUNT_ID = "1234" + +VALID_USER_INPUT = { + CONF_CODE: VALID_AUTH_CODE, +} diff --git a/tests/components/laundrify/fixtures/machines.json b/tests/components/laundrify/fixtures/machines.json new file mode 100644 index 00000000000..ab1a737cb45 --- /dev/null +++ b/tests/components/laundrify/fixtures/machines.json @@ -0,0 +1,8 @@ +[ + { + "_id": "14", + "name": "Demo Waschmaschine", + "status": "OFF", + "firmwareVersion": "2.1.0" + } +] diff --git a/tests/components/laundrify/test_config_flow.py b/tests/components/laundrify/test_config_flow.py new file mode 100644 index 00000000000..5ee3efe1e45 --- /dev/null +++ b/tests/components/laundrify/test_config_flow.py @@ -0,0 +1,129 @@ +"""Test the laundrify config flow.""" + +from laundrify_aio import exceptions + +from homeassistant.components.laundrify.const import DOMAIN +from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CODE, CONF_SOURCE +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) + +from . import create_entry +from .const import VALID_ACCESS_TOKEN, VALID_AUTH_CODE, VALID_USER_INPUT + + +async def test_form(hass: HomeAssistant, laundrify_setup_entry) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=VALID_USER_INPUT, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == DOMAIN + assert result["data"] == { + CONF_ACCESS_TOKEN: VALID_ACCESS_TOKEN, + } + assert len(laundrify_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_format( + hass: HomeAssistant, laundrify_exchange_code +) -> None: + """Test we handle invalid format.""" + laundrify_exchange_code.side_effect = exceptions.InvalidFormat + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, + data={CONF_CODE: "invalidFormat"}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {CONF_CODE: "invalid_format"} + + +async def test_form_invalid_auth(hass: HomeAssistant, laundrify_exchange_code) -> None: + """Test we handle invalid auth.""" + laundrify_exchange_code.side_effect = exceptions.UnknownAuthCode + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, + data=VALID_USER_INPUT, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {CONF_CODE: "invalid_auth"} + + +async def test_form_cannot_connect(hass: HomeAssistant, laundrify_exchange_code): + """Test we handle cannot connect error.""" + laundrify_exchange_code.side_effect = exceptions.ApiConnectionException + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, + data=VALID_USER_INPUT, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_form_unkown_exception(hass: HomeAssistant, laundrify_exchange_code): + """Test we handle all other errors.""" + laundrify_exchange_code.side_effect = Exception + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, + data=VALID_USER_INPUT, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "unknown"} + + +async def test_step_reauth(hass: HomeAssistant) -> None: + """Test the reauth form is shown.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_REAUTH} + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_FORM + + +async def test_integration_already_exists(hass: HomeAssistant): + """Test we only allow a single config flow.""" + create_entry(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={CONF_SOURCE: SOURCE_USER} + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_CODE: VALID_AUTH_CODE, + }, + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" diff --git a/tests/components/laundrify/test_coordinator.py b/tests/components/laundrify/test_coordinator.py new file mode 100644 index 00000000000..58bc297cc42 --- /dev/null +++ b/tests/components/laundrify/test_coordinator.py @@ -0,0 +1,50 @@ +"""Test the laundrify coordinator.""" + +from laundrify_aio import exceptions + +from homeassistant.components.laundrify.const import DOMAIN +from homeassistant.core import HomeAssistant + +from . import create_entry + + +async def test_coordinator_update_success(hass: HomeAssistant): + """Test the coordinator update is performed successfully.""" + config_entry = create_entry(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"] + await coordinator.async_refresh() + await hass.async_block_till_done() + + assert coordinator.last_update_success + + +async def test_coordinator_update_unauthorized(hass: HomeAssistant, laundrify_api_mock): + """Test the coordinator update fails if an UnauthorizedException is thrown.""" + config_entry = create_entry(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"] + laundrify_api_mock.side_effect = exceptions.UnauthorizedException + await coordinator.async_refresh() + await hass.async_block_till_done() + + assert not coordinator.last_update_success + + +async def test_coordinator_update_connection_failed( + hass: HomeAssistant, laundrify_api_mock +): + """Test the coordinator update fails if an ApiConnectionException is thrown.""" + config_entry = create_entry(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"] + laundrify_api_mock.side_effect = exceptions.ApiConnectionException + await coordinator.async_refresh() + await hass.async_block_till_done() + + assert not coordinator.last_update_success diff --git a/tests/components/laundrify/test_init.py b/tests/components/laundrify/test_init.py new file mode 100644 index 00000000000..129848e8808 --- /dev/null +++ b/tests/components/laundrify/test_init.py @@ -0,0 +1,59 @@ +"""Test the laundrify init file.""" + +from laundrify_aio import exceptions + +from homeassistant.components.laundrify.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from . import create_entry + + +async def test_setup_entry_api_unauthorized( + hass: HomeAssistant, laundrify_validate_token +): + """Test that ConfigEntryAuthFailed is thrown when authentication fails.""" + laundrify_validate_token.side_effect = exceptions.UnauthorizedException + config_entry = create_entry(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert config_entry.state == ConfigEntryState.SETUP_ERROR + assert not hass.data.get(DOMAIN) + + +async def test_setup_entry_api_cannot_connect( + hass: HomeAssistant, laundrify_validate_token +): + """Test that ApiConnectionException is thrown when connection fails.""" + laundrify_validate_token.side_effect = exceptions.ApiConnectionException + config_entry = create_entry(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert config_entry.state == ConfigEntryState.SETUP_RETRY + assert not hass.data.get(DOMAIN) + + +async def test_setup_entry_successful(hass: HomeAssistant): + """Test entry can be setup successfully.""" + config_entry = create_entry(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert config_entry.state == ConfigEntryState.LOADED + + +async def test_setup_entry_unload(hass: HomeAssistant): + """Test unloading the laundrify entry.""" + config_entry = create_entry(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.config_entries.async_unload(config_entry.entry_id) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert config_entry.state == ConfigEntryState.NOT_LOADED diff --git a/tests/components/lcn/test_config_flow.py b/tests/components/lcn/test_config_flow.py index 49351b023b6..36b5d23739a 100644 --- a/tests/components/lcn/test_config_flow.py +++ b/tests/components/lcn/test_config_flow.py @@ -7,6 +7,8 @@ import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.components.lcn.const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN from homeassistant.const import ( + CONF_DEVICES, + CONF_ENTITIES, CONF_HOST, CONF_IP_ADDRESS, CONF_PASSWORD, @@ -24,6 +26,8 @@ IMPORT_DATA = { CONF_PASSWORD: "lcn", CONF_SK_NUM_TRIES: 0, CONF_DIM_MODE: "STEPS200", + CONF_DEVICES: [], + CONF_ENTITIES: [], } diff --git a/tests/components/lcn/test_cover.py b/tests/components/lcn/test_cover.py index f89cfa41071..8c6b814e525 100644 --- a/tests/components/lcn/test_cover.py +++ b/tests/components/lcn/test_cover.py @@ -18,6 +18,7 @@ from homeassistant.const import ( STATE_OPENING, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from .conftest import MockModuleConnection @@ -35,7 +36,7 @@ async def test_setup_lcn_cover(hass, entry, lcn_connection): async def test_entity_attributes(hass, entry, lcn_connection): """Test the attributes of an entity.""" - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entity_outputs = entity_registry.async_get("cover.cover_outputs") diff --git a/tests/components/lcn/test_light.py b/tests/components/lcn/test_light.py index c74ecd2beb9..efde0daa68f 100644 --- a/tests/components/lcn/test_light.py +++ b/tests/components/lcn/test_light.py @@ -23,6 +23,7 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from .conftest import MockModuleConnection @@ -54,7 +55,7 @@ async def test_entity_state(hass, lcn_connection): async def test_entity_attributes(hass, entry, lcn_connection): """Test the attributes of an entity.""" - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entity_output = entity_registry.async_get("light.light_output1") diff --git a/tests/components/lcn/test_switch.py b/tests/components/lcn/test_switch.py index aee063f310d..8c4fb1ff0a8 100644 --- a/tests/components/lcn/test_switch.py +++ b/tests/components/lcn/test_switch.py @@ -15,6 +15,7 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from .conftest import MockModuleConnection @@ -34,7 +35,7 @@ async def test_setup_lcn_switch(hass, lcn_connection): async def test_entity_attributes(hass, entry, lcn_connection): """Test the attributes of an entity.""" - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entity_output = entity_registry.async_get("switch.switch_output1") diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 3a8efa5fd97..ae9b00baeaa 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -16,7 +16,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) -from homeassistant.exceptions import Unauthorized +from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.setup import async_setup_component import homeassistant.util.color as color_util @@ -2417,3 +2417,44 @@ def test_valid_supported_color_modes(): supported = {light.ColorMode.BRIGHTNESS, light.ColorMode.COLOR_TEMP} with pytest.raises(vol.Error): light.valid_supported_color_modes(supported) + + +def test_filter_supported_color_modes(): + """Test filter_supported_color_modes.""" + supported = {light.ColorMode.HS} + assert light.filter_supported_color_modes(supported) == supported + + # Supported color modes must not be empty + supported = set() + with pytest.raises(HomeAssistantError): + light.filter_supported_color_modes(supported) + + # ColorMode.WHITE must be combined with a color mode supporting color + supported = {light.ColorMode.WHITE} + with pytest.raises(HomeAssistantError): + light.filter_supported_color_modes(supported) + + supported = {light.ColorMode.WHITE, light.ColorMode.COLOR_TEMP} + with pytest.raises(HomeAssistantError): + light.filter_supported_color_modes(supported) + + supported = {light.ColorMode.WHITE, light.ColorMode.HS} + assert light.filter_supported_color_modes(supported) == supported + + # ColorMode.ONOFF will be removed if combined with other modes + supported = {light.ColorMode.ONOFF} + assert light.filter_supported_color_modes(supported) == supported + + supported = {light.ColorMode.ONOFF, light.ColorMode.COLOR_TEMP} + assert light.filter_supported_color_modes(supported) == {light.ColorMode.COLOR_TEMP} + + # ColorMode.BRIGHTNESS will be removed if combined with other modes + supported = {light.ColorMode.BRIGHTNESS} + assert light.filter_supported_color_modes(supported) == supported + + supported = {light.ColorMode.BRIGHTNESS, light.ColorMode.COLOR_TEMP} + assert light.filter_supported_color_modes(supported) == {light.ColorMode.COLOR_TEMP} + + # ColorMode.BRIGHTNESS has priority over ColorMode.ONOFF + supported = {light.ColorMode.ONOFF, light.ColorMode.BRIGHTNESS} + assert light.filter_supported_color_modes(supported) == {light.ColorMode.BRIGHTNESS} diff --git a/tests/components/light/test_intent.py b/tests/components/light/test_intent.py index 19b25efa772..0c837a49c42 100644 --- a/tests/components/light/test_intent.py +++ b/tests/components/light/test_intent.py @@ -2,7 +2,7 @@ from homeassistant.components import light from homeassistant.components.light import ATTR_SUPPORTED_COLOR_MODES, ColorMode, intent from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON -from homeassistant.helpers.intent import IntentHandleError +from homeassistant.helpers.intent import IntentHandleError, async_handle from tests.common import async_mock_service @@ -16,7 +16,8 @@ async def test_intent_set_color(hass): calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON) await intent.async_setup_intents(hass) - result = await hass.helpers.intent.async_handle( + result = await async_handle( + hass, "test", intent.INTENT_SET, {"name": {"value": "Hello"}, "color": {"value": "blue"}}, @@ -40,7 +41,8 @@ async def test_intent_set_color_tests_feature(hass): await intent.async_setup_intents(hass) try: - await hass.helpers.intent.async_handle( + await async_handle( + hass, "test", intent.INTENT_SET, {"name": {"value": "Hello"}, "color": {"value": "blue"}}, @@ -61,7 +63,8 @@ async def test_intent_set_color_and_brightness(hass): calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON) await intent.async_setup_intents(hass) - result = await hass.helpers.intent.async_handle( + result = await async_handle( + hass, "test", intent.INTENT_SET, { diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py index c5355a218af..e8ec5324ae6 100644 --- a/tests/components/litterrobot/conftest.py +++ b/tests/components/litterrobot/conftest.py @@ -1,6 +1,7 @@ """Configure pytest for Litter-Robot tests.""" from __future__ import annotations +from datetime import datetime from typing import Any from unittest.mock import AsyncMock, MagicMock, patch @@ -9,6 +10,7 @@ from pylitterbot.exceptions import InvalidCommandException import pytest from homeassistant.components import litterrobot +from homeassistant.components.litterrobot.vacuum import UNAVAILABLE_AFTER from homeassistant.core import HomeAssistant from .common import CONFIG, ROBOT_DATA @@ -65,6 +67,20 @@ def mock_account_with_sleeping_robot() -> MagicMock: return create_mock_account({"sleepModeActive": "102:00:00"}) +@pytest.fixture +def mock_account_with_sleep_disabled_robot() -> MagicMock: + """Mock a Litter-Robot account with a robot that has sleep mode disabled.""" + return create_mock_account({"sleepModeActive": "0"}) + + +@pytest.fixture +def mock_account_with_robot_not_recently_seen() -> MagicMock: + """Mock a Litter-Robot account with a sleeping robot.""" + return create_mock_account( + {"lastSeen": (datetime.now() - UNAVAILABLE_AFTER).isoformat()} + ) + + @pytest.fixture def mock_account_with_error() -> MagicMock: """Mock a Litter-Robot account with error.""" diff --git a/tests/components/litterrobot/test_sensor.py b/tests/components/litterrobot/test_sensor.py index e3e62d5f5e4..ce91541f0d7 100644 --- a/tests/components/litterrobot/test_sensor.py +++ b/tests/components/litterrobot/test_sensor.py @@ -1,16 +1,19 @@ """Test the Litter-Robot sensor entity.""" -from unittest.mock import Mock +from unittest.mock import MagicMock -from homeassistant.components.litterrobot.sensor import LitterRobotSleepTimeSensor from homeassistant.components.sensor import DOMAIN as PLATFORM_DOMAIN, SensorDeviceClass -from homeassistant.const import PERCENTAGE +from homeassistant.const import PERCENTAGE, STATE_UNKNOWN +from homeassistant.core import HomeAssistant -from .conftest import create_mock_robot, setup_integration +from .conftest import setup_integration WASTE_DRAWER_ENTITY_ID = "sensor.test_waste_drawer" +SLEEP_START_TIME_ENTITY_ID = "sensor.test_sleep_mode_start_time" -async def test_waste_drawer_sensor(hass, mock_account): +async def test_waste_drawer_sensor( + hass: HomeAssistant, mock_account: MagicMock +) -> None: """Tests the waste drawer sensor entity was set up.""" await setup_integration(hass, mock_account, PLATFORM_DOMAIN) @@ -20,20 +23,21 @@ async def test_waste_drawer_sensor(hass, mock_account): assert sensor.attributes["unit_of_measurement"] == PERCENTAGE -async def test_sleep_time_sensor_with_none_state(hass): - """Tests the sleep mode start time sensor where sleep mode is inactive.""" - robot = create_mock_robot({"sleepModeActive": "0"}) - sensor = LitterRobotSleepTimeSensor( - robot, "Sleep Mode Start Time", Mock(), "sleep_mode_start_time" +async def test_sleep_time_sensor_with_sleep_disabled( + hass: HomeAssistant, mock_account_with_sleep_disabled_robot: MagicMock +) -> None: + """Tests the sleep mode start time sensor where sleep mode is disabled.""" + await setup_integration( + hass, mock_account_with_sleep_disabled_robot, PLATFORM_DOMAIN ) - sensor.hass = hass + sensor = hass.states.get(SLEEP_START_TIME_ENTITY_ID) assert sensor - assert sensor.state is None - assert sensor.device_class is SensorDeviceClass.TIMESTAMP + assert sensor.state == STATE_UNKNOWN + assert sensor.attributes["device_class"] == SensorDeviceClass.TIMESTAMP -async def test_gauge_icon(): +async def test_gauge_icon() -> None: """Test icon generator for gauge sensor.""" from homeassistant.components.litterrobot.sensor import icon_for_gauge_level diff --git a/tests/components/litterrobot/test_vacuum.py b/tests/components/litterrobot/test_vacuum.py index 89f8f077b55..3adf820d6aa 100644 --- a/tests/components/litterrobot/test_vacuum.py +++ b/tests/components/litterrobot/test_vacuum.py @@ -24,7 +24,7 @@ from homeassistant.components.vacuum import ( STATE_DOCKED, STATE_ERROR, ) -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.util.dt import utcnow @@ -62,6 +62,19 @@ async def test_vacuum_status_when_sleeping( assert vacuum.attributes.get(ATTR_STATUS) == "Ready (Sleeping)" +async def test_vacuum_state_when_not_recently_seen( + hass: HomeAssistant, mock_account_with_robot_not_recently_seen: MagicMock +) -> None: + """Tests the vacuum state when not seen recently.""" + await setup_integration( + hass, mock_account_with_robot_not_recently_seen, PLATFORM_DOMAIN + ) + + vacuum = hass.states.get(VACUUM_ENTITY_ID) + assert vacuum + assert vacuum.state == STATE_UNAVAILABLE + + async def test_no_robots( hass: HomeAssistant, mock_account_with_no_robots: MagicMock ) -> None: diff --git a/tests/components/logbook/common.py b/tests/components/logbook/common.py new file mode 100644 index 00000000000..b88c3854967 --- /dev/null +++ b/tests/components/logbook/common.py @@ -0,0 +1,76 @@ +"""Tests for the logbook component.""" +from __future__ import annotations + +import json +from typing import Any + +from homeassistant.components import logbook +from homeassistant.components.logbook import processor +from homeassistant.components.recorder.models import process_timestamp_to_utc_isoformat +from homeassistant.core import Context +from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.json import JSONEncoder +import homeassistant.util.dt as dt_util + + +class MockRow: + """Minimal row mock.""" + + def __init__( + self, + event_type: str, + data: dict[str, Any] | None = None, + context: Context | None = None, + ): + """Init the fake row.""" + self.event_type = event_type + self.shared_data = json.dumps(data, cls=JSONEncoder) + self.data = data + self.time_fired = dt_util.utcnow() + self.context_parent_id = context.parent_id if context else None + self.context_user_id = context.user_id if context else None + self.context_id = context.id if context else None + self.state = None + self.entity_id = None + self.state_id = None + self.event_id = None + self.shared_attrs = None + self.attributes = None + self.context_only = False + + @property + def time_fired_minute(self): + """Minute the event was fired.""" + return self.time_fired.minute + + @property + def time_fired_isoformat(self): + """Time event was fired in utc isoformat.""" + return process_timestamp_to_utc_isoformat(self.time_fired) + + +def mock_humanify(hass_, rows): + """Wrap humanify with mocked logbook objects.""" + entity_name_cache = processor.EntityNameCache(hass_) + ent_reg = er.async_get(hass_) + event_cache = processor.EventCache({}) + context_lookup = processor.ContextLookup(hass_) + external_events = hass_.data.get(logbook.DOMAIN, {}) + logbook_run = processor.LogbookRun( + context_lookup, + external_events, + event_cache, + entity_name_cache, + include_entity_name=True, + format_time=processor._row_time_fired_isoformat, + ) + context_augmenter = processor.ContextAugmenter(logbook_run) + return list( + processor._humanify( + rows, + None, + ent_reg, + logbook_run, + context_augmenter, + ), + ) diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index e1975b52e3c..2903f29f5dc 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -1,9 +1,11 @@ """The tests for the logbook component.""" # pylint: disable=protected-access,invalid-name +import asyncio import collections from datetime import datetime, timedelta from http import HTTPStatus import json +from typing import Callable from unittest.mock import Mock, patch import pytest @@ -12,14 +14,18 @@ import voluptuous as vol from homeassistant.components import logbook from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME from homeassistant.components.automation import EVENT_AUTOMATION_TRIGGERED -from homeassistant.components.recorder.models import process_timestamp_to_utc_isoformat +from homeassistant.components.logbook.models import LazyEventPartialState +from homeassistant.components.logbook.processor import EventProcessor +from homeassistant.components.logbook.queries.common import PSUEDO_EVENT_STATE_CHANGED from homeassistant.components.script import EVENT_SCRIPT_STARTED +from homeassistant.components.sensor import SensorStateClass from homeassistant.const import ( ATTR_DOMAIN, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_NAME, ATTR_SERVICE, + ATTR_UNIT_OF_MEASUREMENT, CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, @@ -28,17 +34,21 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, - EVENT_STATE_CHANGED, + EVENT_LOGBOOK_ENTRY, STATE_OFF, STATE_ON, ) import homeassistant.core as ha +from homeassistant.core import Event, HomeAssistant +from homeassistant.helpers import device_registry, entity_registry as er from homeassistant.helpers.entityfilter import CONF_ENTITY_GLOBS from homeassistant.helpers.json import JSONEncoder from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import async_capture_events, mock_platform +from .common import MockRow, mock_humanify + +from tests.common import MockConfigEntry, async_capture_events, mock_platform from tests.components.recorder.common import ( async_recorder_block_till_done, async_wait_recording_done, @@ -88,10 +98,10 @@ async def test_service_call_create_logbook_entry(hass_): # Our service call will unblock when the event listeners have been # scheduled. This means that they may not have been processed yet. await async_wait_recording_done(hass_) + event_processor = EventProcessor(hass_, (EVENT_LOGBOOK_ENTRY,)) events = list( - logbook._get_events( - hass_, + event_processor.get_events( dt_util.utcnow() - timedelta(hours=1), dt_util.utcnow() + timedelta(hours=1), ) @@ -113,6 +123,34 @@ async def test_service_call_create_logbook_entry(hass_): assert last_call.data.get(logbook.ATTR_DOMAIN) == "logbook" +async def test_service_call_create_logbook_entry_invalid_entity_id(hass, recorder_mock): + """Test if service call create log book entry with an invalid entity id.""" + await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() + hass.bus.async_fire( + logbook.EVENT_LOGBOOK_ENTRY, + { + logbook.ATTR_NAME: "Alarm", + logbook.ATTR_MESSAGE: "is triggered", + logbook.ATTR_DOMAIN: "switch", + logbook.ATTR_ENTITY_ID: 1234, + }, + ) + await async_wait_recording_done(hass) + event_processor = EventProcessor(hass, (EVENT_LOGBOOK_ENTRY,)) + events = list( + event_processor.get_events( + dt_util.utcnow() - timedelta(hours=1), + dt_util.utcnow() + timedelta(hours=1), + ) + ) + assert len(events) == 1 + assert events[0][logbook.ATTR_DOMAIN] == "switch" + assert events[0][logbook.ATTR_NAME] == "Alarm" + assert events[0][logbook.ATTR_ENTITY_ID] == 1234 + assert events[0][logbook.ATTR_MESSAGE] == "is triggered" + + async def test_service_call_create_log_book_entry_no_message(hass_): """Test if service call create log book entry without message.""" calls = async_capture_events(hass_, logbook.EVENT_LOGBOOK_ENTRY) @@ -128,69 +166,83 @@ async def test_service_call_create_log_book_entry_no_message(hass_): assert len(calls) == 0 -def test_humanify_filter_sensor(hass_): - """Test humanify filter too frequent sensor values.""" - entity_id = "sensor.bla" +async def test_filter_sensor(hass_: ha.HomeAssistant, hass_client): + """Test numeric sensors are filtered.""" - pointA = dt_util.utcnow().replace(minute=2) - pointB = pointA.replace(minute=5) - pointC = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) - entity_attr_cache = logbook.EntityAttributeCache(hass_) + registry = er.async_get(hass_) - eventA = create_state_changed_event(pointA, entity_id, 10) - eventB = create_state_changed_event(pointB, entity_id, 20) - eventC = create_state_changed_event(pointC, entity_id, 30) + # Unregistered sensor without a unit of measurement - should be in logbook + entity_id1 = "sensor.bla" + attributes_1 = None + # Unregistered sensor with a unit of measurement - should be excluded from logbook + entity_id2 = "sensor.blu" + attributes_2 = {ATTR_UNIT_OF_MEASUREMENT: "cats"} + # Registered sensor with state class - should be excluded from logbook + entity_id3 = registry.async_get_or_create( + "sensor", + "test", + "unique_3", + suggested_object_id="bli", + capabilities={"state_class": SensorStateClass.MEASUREMENT}, + ).entity_id + attributes_3 = None + # Registered sensor without state class or unit - should be in logbook + entity_id4 = registry.async_get_or_create( + "sensor", "test", "unique_4", suggested_object_id="ble" + ).entity_id + attributes_4 = None - entries = list( - logbook.humanify(hass_, (eventA, eventB, eventC), entity_attr_cache, {}) - ) + hass_.states.async_set(entity_id1, None, attributes_1) # Excluded + hass_.states.async_set(entity_id1, 10, attributes_1) # Included + hass_.states.async_set(entity_id2, None, attributes_2) # Excluded + hass_.states.async_set(entity_id2, 10, attributes_2) # Excluded + hass_.states.async_set(entity_id3, None, attributes_3) # Excluded + hass_.states.async_set(entity_id3, 10, attributes_3) # Excluded + hass_.states.async_set(entity_id1, 20, attributes_1) # Included + hass_.states.async_set(entity_id2, 20, attributes_2) # Excluded + hass_.states.async_set(entity_id4, None, attributes_4) # Excluded + hass_.states.async_set(entity_id4, 10, attributes_4) # Included - assert len(entries) == 2 - assert_entry(entries[0], pointB, "bla", entity_id=entity_id) + await async_wait_recording_done(hass_) + client = await hass_client() + entries = await _async_fetch_logbook(client) - assert_entry(entries[1], pointC, "bla", entity_id=entity_id) + assert len(entries) == 3 + _assert_entry(entries[0], name="bla", entity_id=entity_id1, state="10") + _assert_entry(entries[1], name="bla", entity_id=entity_id1, state="20") + _assert_entry(entries[2], name="ble", entity_id=entity_id4, state="10") -def test_home_assistant_start_stop_grouped(hass_): - """Test if HA start and stop events are grouped. - - Events that are occurring in the same minute. - """ - entity_attr_cache = logbook.EntityAttributeCache(hass_) - entries = list( - logbook.humanify( - hass_, - ( - MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP), - MockLazyEventPartialState(EVENT_HOMEASSISTANT_START), - ), - entity_attr_cache, - {}, +async def test_home_assistant_start_stop_not_grouped(hass_): + """Test if HA start and stop events are no longer grouped.""" + await async_setup_component(hass_, "homeassistant", {}) + await hass_.async_block_till_done() + entries = mock_humanify( + hass_, + ( + MockRow(EVENT_HOMEASSISTANT_STOP), + MockRow(EVENT_HOMEASSISTANT_START), ), ) - assert len(entries) == 1 - assert_entry( - entries[0], name="Home Assistant", message="restarted", domain=ha.DOMAIN - ) + assert len(entries) == 2 + assert_entry(entries[0], name="Home Assistant", message="stopped", domain=ha.DOMAIN) + assert_entry(entries[1], name="Home Assistant", message="started", domain=ha.DOMAIN) -def test_home_assistant_start(hass_): +async def test_home_assistant_start(hass_): """Test if HA start is not filtered or converted into a restart.""" + await async_setup_component(hass_, "homeassistant", {}) + await hass_.async_block_till_done() entity_id = "switch.bla" pointA = dt_util.utcnow() - entity_attr_cache = logbook.EntityAttributeCache(hass_) - entries = list( - logbook.humanify( - hass_, - ( - MockLazyEventPartialState(EVENT_HOMEASSISTANT_START), - create_state_changed_event(pointA, entity_id, 10), - ), - entity_attr_cache, - {}, - ) + entries = mock_humanify( + hass_, + ( + MockRow(EVENT_HOMEASSISTANT_START), + create_state_changed_event(pointA, entity_id, 10).row, + ), ) assert len(entries) == 2 @@ -203,24 +255,19 @@ def test_process_custom_logbook_entries(hass_): name = "Nice name" message = "has a custom entry" entity_id = "sun.sun" - entity_attr_cache = logbook.EntityAttributeCache(hass_) - entries = list( - logbook.humanify( - hass_, - ( - MockLazyEventPartialState( - logbook.EVENT_LOGBOOK_ENTRY, - { - logbook.ATTR_NAME: name, - logbook.ATTR_MESSAGE: message, - logbook.ATTR_ENTITY_ID: entity_id, - }, - ), + entries = mock_humanify( + hass_, + ( + MockRow( + logbook.EVENT_LOGBOOK_ENTRY, + { + logbook.ATTR_NAME: name, + logbook.ATTR_MESSAGE: message, + logbook.ATTR_ENTITY_ID: entity_id, + }, ), - entity_attr_cache, - {}, - ) + ), ) assert len(entries) == 1 @@ -279,23 +326,30 @@ def create_state_changed_event_from_old_new( "state_id", "old_state_id", "shared_attrs", + "shared_data", + "context_only", ], ) - row.event_type = EVENT_STATE_CHANGED + row.event_type = PSUEDO_EVENT_STATE_CHANGED row.event_data = "{}" + row.shared_data = "{}" row.attributes = attributes_json row.shared_attrs = attributes_json row.time_fired = event_time_fired row.state = new_state and new_state.get("state") row.entity_id = entity_id row.domain = entity_id and ha.split_entity_id(entity_id)[0] + row.context_only = False row.context_id = None + row.friendly_name = None + row.icon = None + row.old_format_icon = None row.context_user_id = None row.context_parent_id = None row.old_state_id = old_state and 1 row.state_id = new_state and 1 - return logbook.LazyEventPartialState(row) + return LazyEventPartialState(row, {}) async def test_logbook_view(hass, hass_client, recorder_mock): @@ -307,6 +361,26 @@ async def test_logbook_view(hass, hass_client, recorder_mock): assert response.status == HTTPStatus.OK +async def test_logbook_view_invalid_start_date_time(hass, hass_client, recorder_mock): + """Test the logbook view with an invalid date time.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + client = await hass_client() + response = await client.get("/api/logbook/INVALID") + assert response.status == HTTPStatus.BAD_REQUEST + + +async def test_logbook_view_invalid_end_date_time(hass, hass_client, recorder_mock): + """Test the logbook view.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + client = await hass_client() + response = await client.get( + f"/api/logbook/{dt_util.utcnow().isoformat()}?end_time=INVALID" + ) + assert response.status == HTTPStatus.BAD_REQUEST + + async def test_logbook_view_period_entity(hass, hass_client, recorder_mock, set_utc): """Test the logbook view with period and entity.""" await async_setup_component(hass, "logbook", {}) @@ -436,7 +510,7 @@ async def test_exclude_described_event(hass, hass_client, recorder_mock): return { "name": "Test Name", "message": "tested a message", - "entity_id": event.data.get(ATTR_ENTITY_ID), + "entity_id": event.data[ATTR_ENTITY_ID], } def async_describe_events(hass, async_describe_event): @@ -544,9 +618,12 @@ async def test_logbook_view_end_time_entity(hass, hass_client, recorder_mock): async def test_logbook_entity_filter_with_automations(hass, hass_client, recorder_mock): """Test the logbook view with end_time and entity with automations and scripts.""" - await async_setup_component(hass, "logbook", {}) - await async_setup_component(hass, "automation", {}) - await async_setup_component(hass, "script", {}) + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) await async_recorder_block_till_done(hass) @@ -649,7 +726,7 @@ async def test_logbook_entity_no_longer_in_state_machine( ) assert response.status == HTTPStatus.OK json_dict = await response.json() - assert json_dict[0]["name"] == "Alarm Control Panel" + assert json_dict[0]["name"] == "area 001" async def test_filter_continuous_sensor_values( @@ -689,7 +766,12 @@ async def test_filter_continuous_sensor_values( async def test_exclude_new_entities(hass, hass_client, recorder_mock, set_utc): """Test if events are excluded on first update.""" - await async_setup_component(hass, "logbook", {}) + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) await async_recorder_block_till_done(hass) entity_id = "climate.bla" @@ -721,7 +803,12 @@ async def test_exclude_new_entities(hass, hass_client, recorder_mock, set_utc): async def test_exclude_removed_entities(hass, hass_client, recorder_mock, set_utc): """Test if events are excluded on last update.""" - await async_setup_component(hass, "logbook", {}) + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) await async_recorder_block_till_done(hass) entity_id = "climate.bla" @@ -760,7 +847,12 @@ async def test_exclude_removed_entities(hass, hass_client, recorder_mock, set_ut async def test_exclude_attribute_changes(hass, hass_client, recorder_mock, set_utc): """Test if events of attribute changes are filtered.""" - await async_setup_component(hass, "logbook", {}) + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) await async_recorder_block_till_done(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -795,9 +887,12 @@ async def test_exclude_attribute_changes(hass, hass_client, recorder_mock, set_u async def test_logbook_entity_context_id(hass, recorder_mock, hass_client): """Test the logbook view with end_time and entity with automations and scripts.""" - await async_setup_component(hass, "logbook", {}) - await async_setup_component(hass, "automation", {}) - await async_setup_component(hass, "script", {}) + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) await async_recorder_block_till_done(hass) @@ -940,11 +1035,105 @@ async def test_logbook_entity_context_id(hass, recorder_mock, hass_client): assert json_dict[7]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474" +async def test_logbook_context_id_automation_script_started_manually( + hass, recorder_mock, hass_client +): + """Test the logbook populates context_ids for scripts and automations started manually.""" + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + + await async_recorder_block_till_done(hass) + + # An Automation + automation_entity_id_test = "automation.alarm" + automation_context = ha.Context( + id="fc5bd62de45711eaaeb351041eec8dd9", + user_id="f400facee45711eaa9308bfd3d19e474", + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation", ATTR_ENTITY_ID: automation_entity_id_test}, + context=automation_context, + ) + script_context = ha.Context( + id="ac5bd62de45711eaaeb351041eec8dd9", + user_id="b400facee45711eaa9308bfd3d19e474", + ) + hass.bus.async_fire( + EVENT_SCRIPT_STARTED, + {ATTR_NAME: "Mock script", ATTR_ENTITY_ID: "script.mock_script"}, + context=script_context, + ) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + + script_2_context = ha.Context( + id="1234", + user_id="b400facee45711eaa9308bfd3d19e474", + ) + hass.bus.async_fire( + EVENT_SCRIPT_STARTED, + {ATTR_NAME: "Mock script"}, + context=script_2_context, + ) + hass.states.async_set("switch.new", STATE_ON, context=script_2_context) + hass.states.async_set("switch.new", STATE_OFF, context=script_2_context) + + await hass.async_block_till_done() + await async_wait_recording_done(hass) + + client = await hass_client() + + # Today time 00:00:00 + start = dt_util.utcnow().date() + start_date = datetime(start.year, start.month, start.day) + + # Test today entries with filter by end_time + end_time = start + timedelta(hours=24) + response = await client.get( + f"/api/logbook/{start_date.isoformat()}?end_time={end_time}" + ) + assert response.status == HTTPStatus.OK + json_dict = await response.json() + + assert json_dict[0]["entity_id"] == "automation.alarm" + assert "context_entity_id" not in json_dict[0] + assert json_dict[0]["context_user_id"] == "f400facee45711eaa9308bfd3d19e474" + assert json_dict[0]["context_id"] == "fc5bd62de45711eaaeb351041eec8dd9" + + assert json_dict[1]["entity_id"] == "script.mock_script" + assert "context_entity_id" not in json_dict[1] + assert json_dict[1]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474" + assert json_dict[1]["context_id"] == "ac5bd62de45711eaaeb351041eec8dd9" + + assert json_dict[2]["domain"] == "homeassistant" + + assert json_dict[3]["entity_id"] is None + assert json_dict[3]["name"] == "Mock script" + assert "context_entity_id" not in json_dict[1] + assert json_dict[3]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474" + assert json_dict[3]["context_id"] == "1234" + + assert json_dict[4]["entity_id"] == "switch.new" + assert json_dict[4]["state"] == "off" + assert "context_entity_id" not in json_dict[1] + assert json_dict[4]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474" + assert json_dict[4]["context_event_type"] == "script_started" + assert json_dict[4]["context_domain"] == "script" + + async def test_logbook_entity_context_parent_id(hass, hass_client, recorder_mock): """Test the logbook view links events via context parent_id.""" - await async_setup_component(hass, "logbook", {}) - await async_setup_component(hass, "automation", {}) - await async_setup_component(hass, "script", {}) + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) await async_recorder_block_till_done(hass) @@ -1118,7 +1307,13 @@ async def test_logbook_entity_context_parent_id(hass, hass_client, recorder_mock async def test_logbook_context_from_template(hass, hass_client, recorder_mock): """Test the logbook view with end_time and entity with automations and scripts.""" - await async_setup_component(hass, "logbook", {}) + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) + assert await async_setup_component( hass, "switch", @@ -1197,8 +1392,8 @@ async def test_logbook_context_from_template(hass, hass_client, recorder_mock): assert json_dict[5]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474" -async def test_logbook_entity_matches_only(hass, hass_client, recorder_mock): - """Test the logbook view with a single entity and entity_matches_only.""" +async def test_logbook_(hass, hass_client, recorder_mock): + """Test the logbook view with a single entity and .""" await async_setup_component(hass, "logbook", {}) assert await async_setup_component( hass, @@ -1253,7 +1448,7 @@ async def test_logbook_entity_matches_only(hass, hass_client, recorder_mock): # Test today entries with filter by end_time end_time = start + timedelta(hours=24) response = await client.get( - f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=switch.test_state&entity_matches_only" + f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=switch.test_state" ) assert response.status == HTTPStatus.OK json_dict = await response.json() @@ -1266,10 +1461,78 @@ async def test_logbook_entity_matches_only(hass, hass_client, recorder_mock): assert json_dict[1]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474" -async def test_custom_log_entry_discoverable_via_entity_matches_only( - hass, hass_client, recorder_mock -): - """Test if a custom log entry is later discoverable via entity_matches_only.""" +async def test_logbook_many_entities_multiple_calls(hass, hass_client, recorder_mock): + """Test the logbook view with a many entities called multiple times.""" + await async_setup_component(hass, "logbook", {}) + await async_setup_component(hass, "automation", {}) + + await async_recorder_block_till_done(hass) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + for automation_id in range(5): + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + { + ATTR_NAME: f"Mock automation {automation_id}", + ATTR_ENTITY_ID: f"automation.mock_{automation_id}_automation", + }, + ) + await async_wait_recording_done(hass) + client = await hass_client() + + # Today time 00:00:00 + start = dt_util.utcnow().date() + start_date = datetime(start.year, start.month, start.day) + end_time = start + timedelta(hours=24) + + for automation_id in range(5): + # Test today entries with filter by end_time + response = await client.get( + f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=automation.mock_{automation_id}_automation" + ) + assert response.status == HTTPStatus.OK + json_dict = await response.json() + + assert len(json_dict) == 1 + assert ( + json_dict[0]["entity_id"] == f"automation.mock_{automation_id}_automation" + ) + + response = await client.get( + f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=automation.mock_0_automation,automation.mock_1_automation,automation.mock_2_automation" + ) + assert response.status == HTTPStatus.OK + json_dict = await response.json() + + assert len(json_dict) == 3 + assert json_dict[0]["entity_id"] == "automation.mock_0_automation" + assert json_dict[1]["entity_id"] == "automation.mock_1_automation" + assert json_dict[2]["entity_id"] == "automation.mock_2_automation" + + response = await client.get( + f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=automation.mock_4_automation,automation.mock_2_automation,automation.mock_0_automation,automation.mock_1_automation" + ) + assert response.status == HTTPStatus.OK + json_dict = await response.json() + + assert len(json_dict) == 4 + assert json_dict[0]["entity_id"] == "automation.mock_0_automation" + assert json_dict[1]["entity_id"] == "automation.mock_1_automation" + assert json_dict[2]["entity_id"] == "automation.mock_2_automation" + assert json_dict[3]["entity_id"] == "automation.mock_4_automation" + + response = await client.get( + f"/api/logbook/{end_time.isoformat()}?end_time={end_time}&entity=automation.mock_4_automation,automation.mock_2_automation,automation.mock_0_automation,automation.mock_1_automation" + ) + assert response.status == HTTPStatus.OK + json_dict = await response.json() + assert len(json_dict) == 0 + + +async def test_custom_log_entry_discoverable_via_(hass, hass_client, recorder_mock): + """Test if a custom log entry is later discoverable via .""" await async_setup_component(hass, "logbook", {}) await async_recorder_block_till_done(hass) @@ -1291,7 +1554,7 @@ async def test_custom_log_entry_discoverable_via_entity_matches_only( # Test today entries with filter by end_time end_time = start + timedelta(hours=24) response = await client.get( - f"/api/logbook/{start_date.isoformat()}?end_time={end_time.isoformat()}&entity=switch.test_switch&entity_matches_only" + f"/api/logbook/{start_date.isoformat()}?end_time={end_time.isoformat()}&entity=switch.test_switch" ) assert response.status == HTTPStatus.OK json_dict = await response.json() @@ -1303,8 +1566,8 @@ async def test_custom_log_entry_discoverable_via_entity_matches_only( assert json_dict[0]["entity_id"] == "switch.test_switch" -async def test_logbook_entity_matches_only_multiple(hass, hass_client, recorder_mock): - """Test the logbook view with a multiple entities and entity_matches_only.""" +async def test_logbook_multiple_entities(hass, hass_client, recorder_mock): + """Test the logbook view with a multiple entities.""" await async_setup_component(hass, "logbook", {}) assert await async_setup_component( hass, @@ -1336,12 +1599,14 @@ async def test_logbook_entity_matches_only_multiple(hass, hass_client, recorder_ # Entity added (should not be logged) hass.states.async_set("switch.test_state", STATE_ON) hass.states.async_set("light.test_state", STATE_ON) + hass.states.async_set("binary_sensor.test_state", STATE_ON) await hass.async_block_till_done() # First state change (should be logged) hass.states.async_set("switch.test_state", STATE_OFF) hass.states.async_set("light.test_state", STATE_OFF) + hass.states.async_set("binary_sensor.test_state", STATE_OFF) await hass.async_block_till_done() @@ -1353,6 +1618,9 @@ async def test_logbook_entity_matches_only_multiple(hass, hass_client, recorder_ "switch.test_state", STATE_ON, context=switch_turn_off_context ) hass.states.async_set("light.test_state", STATE_ON, context=switch_turn_off_context) + hass.states.async_set( + "binary_sensor.test_state", STATE_ON, context=switch_turn_off_context + ) await async_wait_recording_done(hass) client = await hass_client() @@ -1364,7 +1632,7 @@ async def test_logbook_entity_matches_only_multiple(hass, hass_client, recorder_ # Test today entries with filter by end_time end_time = start + timedelta(hours=24) response = await client.get( - f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=switch.test_state,light.test_state&entity_matches_only" + f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=switch.test_state,light.test_state" ) assert response.status == HTTPStatus.OK json_dict = await response.json() @@ -1381,6 +1649,46 @@ async def test_logbook_entity_matches_only_multiple(hass, hass_client, recorder_ assert json_dict[3]["entity_id"] == "light.test_state" assert json_dict[3]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474" + # Test today entries with filter by end_time + end_time = start + timedelta(hours=24) + response = await client.get( + f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=binary_sensor.test_state,light.test_state" + ) + assert response.status == HTTPStatus.OK + json_dict = await response.json() + + assert len(json_dict) == 4 + + assert json_dict[0]["entity_id"] == "light.test_state" + + assert json_dict[1]["entity_id"] == "binary_sensor.test_state" + + assert json_dict[2]["entity_id"] == "light.test_state" + assert json_dict[2]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474" + + assert json_dict[3]["entity_id"] == "binary_sensor.test_state" + assert json_dict[3]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474" + + # Test today entries with filter by end_time + end_time = start + timedelta(hours=24) + response = await client.get( + f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=light.test_state,binary_sensor.test_state" + ) + assert response.status == HTTPStatus.OK + json_dict = await response.json() + + assert len(json_dict) == 4 + + assert json_dict[0]["entity_id"] == "light.test_state" + + assert json_dict[1]["entity_id"] == "binary_sensor.test_state" + + assert json_dict[2]["entity_id"] == "light.test_state" + assert json_dict[2]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474" + + assert json_dict[3]["entity_id"] == "binary_sensor.test_state" + assert json_dict[3]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474" + async def test_logbook_invalid_entity(hass, hass_client, recorder_mock): """Test the logbook view with requesting an invalid entity.""" @@ -1395,14 +1703,20 @@ async def test_logbook_invalid_entity(hass, hass_client, recorder_mock): # Test today entries with filter by end_time end_time = start + timedelta(hours=24) response = await client.get( - f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=invalid&entity_matches_only" + f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity=invalid" ) assert response.status == HTTPStatus.INTERNAL_SERVER_ERROR async def test_icon_and_state(hass, hass_client, recorder_mock): """Test to ensure state and custom icons are returned.""" - await async_setup_component(hass, "logbook", {}) + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) + await async_recorder_block_till_done(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -1437,11 +1751,48 @@ async def test_icon_and_state(hass, hass_client, recorder_mock): assert response_json[2]["state"] == STATE_OFF +async def test_fire_logbook_entries(hass, hass_client, recorder_mock): + """Test many logbook entry calls.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + + for _ in range(10): + hass.bus.async_fire( + logbook.EVENT_LOGBOOK_ENTRY, + { + logbook.ATTR_NAME: "Alarm", + logbook.ATTR_MESSAGE: "is triggered", + logbook.ATTR_DOMAIN: "switch", + logbook.ATTR_ENTITY_ID: "sensor.xyz", + }, + ) + hass.bus.async_fire( + logbook.EVENT_LOGBOOK_ENTRY, + {}, + ) + hass.bus.async_fire( + logbook.EVENT_LOGBOOK_ENTRY, + { + logbook.ATTR_NAME: "Alarm", + logbook.ATTR_MESSAGE: "is triggered", + logbook.ATTR_DOMAIN: "switch", + }, + ) + await async_wait_recording_done(hass) + + client = await hass_client() + response_json = await _async_fetch_logbook(client) + + # The empty events should be skipped + assert len(response_json) == 11 + + async def test_exclude_events_domain(hass, hass_client, recorder_mock): """Test if events are filtered if domain is excluded in config.""" entity_id = "switch.bla" entity_id2 = "sensor.blu" + await async_setup_component(hass, "homeassistant", {}) config = logbook.CONFIG_SCHEMA( { ha.DOMAIN: {}, @@ -1487,7 +1838,10 @@ async def test_exclude_events_domain_glob(hass, hass_client, recorder_mock): }, } ) - await async_setup_component(hass, "logbook", config) + await asyncio.gather( + async_setup_component(hass, "homeassistant", {}), + async_setup_component(hass, "logbook", config), + ) await async_recorder_block_till_done(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -1526,7 +1880,10 @@ async def test_include_events_entity(hass, hass_client, recorder_mock): }, } ) - await async_setup_component(hass, "logbook", config) + await asyncio.gather( + async_setup_component(hass, "homeassistant", {}), + async_setup_component(hass, "logbook", config), + ) await async_recorder_block_till_done(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -1558,7 +1915,10 @@ async def test_exclude_events_entity(hass, hass_client, recorder_mock): logbook.DOMAIN: {CONF_EXCLUDE: {CONF_ENTITIES: [entity_id]}}, } ) - await async_setup_component(hass, "logbook", config) + await asyncio.gather( + async_setup_component(hass, "homeassistant", {}), + async_setup_component(hass, "logbook", config), + ) await async_recorder_block_till_done(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -1591,7 +1951,10 @@ async def test_include_events_domain(hass, hass_client, recorder_mock): }, } ) - await async_setup_component(hass, "logbook", config) + await asyncio.gather( + async_setup_component(hass, "homeassistant", {}), + async_setup_component(hass, "logbook", config), + ) await async_recorder_block_till_done(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -1634,9 +1997,20 @@ async def test_include_events_domain_glob(hass, hass_client, recorder_mock): }, } ) - await async_setup_component(hass, "logbook", config) + await asyncio.gather( + async_setup_component(hass, "homeassistant", {}), + async_setup_component(hass, "logbook", config), + ) await async_recorder_block_till_done(hass) + hass.bus.async_fire( + logbook.EVENT_LOGBOOK_ENTRY, + { + logbook.ATTR_NAME: "Alarm", + logbook.ATTR_MESSAGE: "is triggered", + logbook.ATTR_ENTITY_ID: "switch.any", + }, + ) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire( @@ -1685,7 +2059,10 @@ async def test_include_exclude_events(hass, hass_client, recorder_mock): }, } ) - await async_setup_component(hass, "logbook", config) + await asyncio.gather( + async_setup_component(hass, "homeassistant", {}), + async_setup_component(hass, "logbook", config), + ) await async_recorder_block_till_done(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -1705,12 +2082,13 @@ async def test_include_exclude_events(hass, hass_client, recorder_mock): client = await hass_client() entries = await _async_fetch_logbook(client) - assert len(entries) == 3 + assert len(entries) == 4 _assert_entry( entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN ) - _assert_entry(entries[1], name="blu", entity_id=entity_id2) - _assert_entry(entries[2], name="keep", entity_id=entity_id4) + _assert_entry(entries[1], name="blu", entity_id=entity_id2, state="10") + _assert_entry(entries[2], name="blu", entity_id=entity_id2, state="20") + _assert_entry(entries[3], name="keep", entity_id=entity_id4, state="10") async def test_include_exclude_events_with_glob_filters( @@ -1740,7 +2118,10 @@ async def test_include_exclude_events_with_glob_filters( }, } ) - await async_setup_component(hass, "logbook", config) + await asyncio.gather( + async_setup_component(hass, "homeassistant", {}), + async_setup_component(hass, "logbook", config), + ) await async_recorder_block_till_done(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -1764,12 +2145,13 @@ async def test_include_exclude_events_with_glob_filters( client = await hass_client() entries = await _async_fetch_logbook(client) - assert len(entries) == 3 + assert len(entries) == 4 _assert_entry( entries[0], name="Home Assistant", message="started", domain=ha.DOMAIN ) - _assert_entry(entries[1], name="blu", entity_id=entity_id2) - _assert_entry(entries[2], name="included", entity_id=entity_id4) + _assert_entry(entries[1], name="blu", entity_id=entity_id2, state="10") + _assert_entry(entries[2], name="blu", entity_id=entity_id2, state="20") + _assert_entry(entries[3], name="included", entity_id=entity_id4, state="30") async def test_empty_config(hass, hass_client, recorder_mock): @@ -1782,7 +2164,10 @@ async def test_empty_config(hass, hass_client, recorder_mock): logbook.DOMAIN: {}, } ) - await async_setup_component(hass, "logbook", config) + await asyncio.gather( + async_setup_component(hass, "homeassistant", {}), + async_setup_component(hass, "logbook", config), + ) await async_recorder_block_till_done(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -1854,54 +2239,560 @@ def _assert_entry( entry, when=None, name=None, message=None, domain=None, entity_id=None, state=None ): """Assert an entry is what is expected.""" - if when: + if when is not None: assert when.isoformat() == entry["when"] - if name: + if name is not None: assert name == entry["name"] - if message: + if message is not None: assert message == entry["message"] - if domain: + if domain is not None: assert domain == entry["domain"] - if entity_id: + if entity_id is not None: assert entity_id == entry["entity_id"] - if state: + if state is not None: assert state == entry["state"] -class MockLazyEventPartialState(ha.Event): - """Minimal mock of a Lazy event.""" +async def test_get_events(hass, hass_ws_client, recorder_mock): + """Test logbook get_events.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) + await async_recorder_block_till_done(hass) - @property - def data_entity_id(self): - """Lookup entity id.""" - return self.data.get(ATTR_ENTITY_ID) + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) - @property - def data_domain(self): - """Lookup domain.""" - return self.data.get(ATTR_DOMAIN) + hass.states.async_set("light.kitchen", STATE_OFF) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 100}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 200}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 300}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 400}) + await hass.async_block_till_done() + context = ha.Context( + id="ac5bd62de45711eaaeb351041eec8dd9", + user_id="b400facee45711eaa9308bfd3d19e474", + ) - @property - def time_fired_minute(self): - """Minute the event was fired.""" - return self.time_fired.minute + hass.states.async_set("light.kitchen", STATE_OFF, context=context) + await hass.async_block_till_done() - @property - def context_user_id(self): - """Context user id of event.""" - return self.context.user_id + await async_wait_recording_done(hass) - @property - def context_id(self): - """Context id of event.""" - return self.context.id + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "end_time": now.isoformat(), + "entity_ids": ["light.kitchen"], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] - @property - def time_fired_isoformat(self): - """Time event was fired in utc isoformat.""" - return process_timestamp_to_utc_isoformat(self.time_fired) + await client.send_json( + { + "id": 2, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "entity_ids": ["sensor.test"], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 2 + assert response["result"] == [] + + await client.send_json( + { + "id": 3, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "entity_ids": ["light.kitchen"], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 3 + + results = response["result"] + assert results[0]["entity_id"] == "light.kitchen" + assert results[0]["state"] == "on" + assert results[1]["entity_id"] == "light.kitchen" + assert results[1]["state"] == "off" + + await client.send_json( + { + "id": 4, + "type": "logbook/get_events", + "start_time": now.isoformat(), + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 4 + + results = response["result"] + assert len(results) == 3 + assert results[0]["message"] == "started" + assert results[1]["entity_id"] == "light.kitchen" + assert results[1]["state"] == "on" + assert isinstance(results[1]["when"], float) + assert results[2]["entity_id"] == "light.kitchen" + assert results[2]["state"] == "off" + assert isinstance(results[2]["when"], float) + + await client.send_json( + { + "id": 5, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "context_id": "ac5bd62de45711eaaeb351041eec8dd9", + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 5 + + results = response["result"] + assert len(results) == 1 + assert results[0]["entity_id"] == "light.kitchen" + assert results[0]["state"] == "off" + assert isinstance(results[0]["when"], float) + + +async def test_get_events_future_start_time(hass, hass_ws_client, recorder_mock): + """Test get_events with a future start time.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + future = dt_util.utcnow() + timedelta(hours=10) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": future.isoformat(), + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 1 + + results = response["result"] + assert isinstance(results, list) + assert len(results) == 0 + + +async def test_get_events_bad_start_time(hass, hass_ws_client, recorder_mock): + """Test get_events bad start time.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": "cats", + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_start_time" + + +async def test_get_events_bad_end_time(hass, hass_ws_client, recorder_mock): + """Test get_events bad end time.""" + now = dt_util.utcnow() + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "end_time": "dogs", + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_end_time" + + +async def test_get_events_invalid_filters(hass, hass_ws_client, recorder_mock): + """Test get_events invalid filters.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "entity_ids": [], + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_format" + await client.send_json( + { + "id": 2, + "type": "logbook/get_events", + "device_ids": [], + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_format" + + +async def test_get_events_with_device_ids(hass, hass_ws_client, recorder_mock): + """Test logbook get_events for device ids.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) + + entry = MockConfigEntry(domain="test", data={"first": True}, options=None) + entry.add_to_hass(hass) + dev_reg = device_registry.async_get(hass) + device = dev_reg.async_get_or_create( + config_entry_id=entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + sw_version="sw-version", + name="device name", + manufacturer="manufacturer", + model="model", + suggested_area="Game Room", + ) + + class MockLogbookPlatform: + """Mock a logbook platform.""" + + @ha.callback + def async_describe_events( + hass: HomeAssistant, + async_describe_event: Callable[ + [str, str, Callable[[Event], dict[str, str]]], None + ], + ) -> None: + """Describe logbook events.""" + + @ha.callback + def async_describe_test_event(event: Event) -> dict[str, str]: + """Describe mock logbook event.""" + return { + "name": "device name", + "message": "is on fire", + } + + async_describe_event("test", "mock_event", async_describe_test_event) + + await logbook._process_logbook_platform(hass, "test", MockLogbookPlatform) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire("mock_event", {"device_id": device.id}) + + hass.states.async_set("light.kitchen", STATE_OFF) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 100}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 200}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 300}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 400}) + await hass.async_block_till_done() + context = ha.Context( + id="ac5bd62de45711eaaeb351041eec8dd9", + user_id="b400facee45711eaa9308bfd3d19e474", + ) + + hass.states.async_set("light.kitchen", STATE_OFF, context=context) + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + client = await hass_ws_client() + + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "device_ids": [device.id], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 1 + + results = response["result"] + assert len(results) == 1 + assert results[0]["name"] == "device name" + assert results[0]["message"] == "is on fire" + assert isinstance(results[0]["when"], float) + + await client.send_json( + { + "id": 2, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "entity_ids": ["light.kitchen"], + "device_ids": [device.id], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 2 + + results = response["result"] + assert results[0]["domain"] == "test" + assert results[0]["message"] == "is on fire" + assert results[0]["name"] == "device name" + assert results[1]["entity_id"] == "light.kitchen" + assert results[1]["state"] == "on" + assert results[2]["entity_id"] == "light.kitchen" + assert results[2]["state"] == "off" + + await client.send_json( + { + "id": 3, + "type": "logbook/get_events", + "start_time": now.isoformat(), + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 3 + + results = response["result"] + assert len(results) == 4 + assert results[0]["message"] == "started" + assert results[1]["name"] == "device name" + assert results[1]["message"] == "is on fire" + assert isinstance(results[1]["when"], float) + assert results[2]["entity_id"] == "light.kitchen" + assert results[2]["state"] == "on" + assert isinstance(results[2]["when"], float) + assert results[3]["entity_id"] == "light.kitchen" + assert results[3]["state"] == "off" + assert isinstance(results[3]["when"], float) + + +async def test_logbook_select_entities_context_id(hass, recorder_mock, hass_client): + """Test the logbook view with end_time and entity with automations and scripts.""" + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + + await async_recorder_block_till_done(hass) + + context = ha.Context( + id="ac5bd62de45711eaaeb351041eec8dd9", + user_id="b400facee45711eaa9308bfd3d19e474", + ) + + # An Automation + automation_entity_id_test = "automation.alarm" + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation", ATTR_ENTITY_ID: automation_entity_id_test}, + context=context, + ) + hass.bus.async_fire( + EVENT_SCRIPT_STARTED, + {ATTR_NAME: "Mock script", ATTR_ENTITY_ID: "script.mock_script"}, + context=context, + ) + hass.states.async_set( + automation_entity_id_test, + STATE_ON, + {ATTR_FRIENDLY_NAME: "Alarm Automation"}, + context=context, + ) + + entity_id_test = "alarm_control_panel.area_001" + hass.states.async_set(entity_id_test, STATE_OFF, context=context) + await hass.async_block_till_done() + hass.states.async_set(entity_id_test, STATE_ON, context=context) + await hass.async_block_till_done() + entity_id_second = "alarm_control_panel.area_002" + hass.states.async_set(entity_id_second, STATE_OFF, context=context) + await hass.async_block_till_done() + hass.states.async_set(entity_id_second, STATE_ON, context=context) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + entity_id_third = "alarm_control_panel.area_003" + + logbook.async_log_entry( + hass, + "mock_name", + "mock_message", + "alarm_control_panel", + entity_id_third, + context, + ) + await hass.async_block_till_done() + + logbook.async_log_entry( + hass, + "mock_name", + "mock_message", + "homeassistant", + None, + context, + ) + await hass.async_block_till_done() + + # A service call + light_turn_off_service_context = ha.Context( + id="9c5bd62de45711eaaeb351041eec8dd9", + user_id="9400facee45711eaa9308bfd3d19e474", + ) + hass.states.async_set("light.switch", STATE_ON) + await hass.async_block_till_done() + + hass.bus.async_fire( + EVENT_CALL_SERVICE, + { + ATTR_DOMAIN: "light", + ATTR_SERVICE: "turn_off", + ATTR_ENTITY_ID: "light.switch", + }, + context=light_turn_off_service_context, + ) + await hass.async_block_till_done() + + hass.states.async_set( + "light.switch", STATE_OFF, context=light_turn_off_service_context + ) + await async_wait_recording_done(hass) + + client = await hass_client() + + # Today time 00:00:00 + start = dt_util.utcnow().date() + start_date = datetime(start.year, start.month, start.day) + + # Test today entries with filter by end_time + end_time = start + timedelta(hours=24) + response = await client.get( + f"/api/logbook/{start_date.isoformat()}?end_time={end_time}&entity={entity_id_test},{entity_id_second},{entity_id_third},light.switch" + ) + assert response.status == HTTPStatus.OK + json_dict = await response.json() + + assert json_dict[0]["entity_id"] == entity_id_test + assert json_dict[0]["context_event_type"] == "automation_triggered" + assert json_dict[0]["context_entity_id"] == "automation.alarm" + assert json_dict[0]["context_entity_id_name"] == "Alarm Automation" + assert json_dict[0]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474" + + assert json_dict[1]["entity_id"] == entity_id_second + assert json_dict[1]["context_event_type"] == "automation_triggered" + assert json_dict[1]["context_entity_id"] == "automation.alarm" + assert json_dict[1]["context_entity_id_name"] == "Alarm Automation" + assert json_dict[1]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474" + + assert json_dict[2]["entity_id"] == "alarm_control_panel.area_003" + assert json_dict[2]["context_event_type"] == "automation_triggered" + assert json_dict[2]["context_entity_id"] == "automation.alarm" + assert json_dict[2]["domain"] == "alarm_control_panel" + assert json_dict[2]["context_entity_id_name"] == "Alarm Automation" + assert json_dict[2]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474" + + assert json_dict[3]["entity_id"] == "light.switch" + assert json_dict[3]["context_event_type"] == "call_service" + assert json_dict[3]["context_domain"] == "light" + assert json_dict[3]["context_service"] == "turn_off" + assert json_dict[3]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474" + + +async def test_get_events_with_context_state(hass, hass_ws_client, recorder_mock): + """Test logbook get_events with a context state.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) + await async_recorder_block_till_done(hass) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.states.async_set("binary_sensor.is_light", STATE_ON) + hass.states.async_set("light.kitchen1", STATE_OFF) + hass.states.async_set("light.kitchen2", STATE_OFF) + + context = ha.Context( + id="ac5bd62de45711eaaeb351041eec8dd9", + user_id="b400facee45711eaa9308bfd3d19e474", + ) + hass.states.async_set("binary_sensor.is_light", STATE_OFF, context=context) + await hass.async_block_till_done() + hass.states.async_set( + "light.kitchen1", STATE_ON, {"brightness": 100}, context=context + ) + await hass.async_block_till_done() + hass.states.async_set( + "light.kitchen2", STATE_ON, {"brightness": 200}, context=context + ) + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + + client = await hass_ws_client() + + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": now.isoformat(), + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 1 + results = response["result"] + assert results[1]["entity_id"] == "binary_sensor.is_light" + assert results[1]["state"] == "off" + assert "context_state" not in results[1] + assert results[2]["entity_id"] == "light.kitchen1" + assert results[2]["state"] == "on" + assert results[2]["context_entity_id"] == "binary_sensor.is_light" + assert results[2]["context_state"] == "off" + assert results[2]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474" + assert "context_event_type" not in results[2] + assert results[3]["entity_id"] == "light.kitchen2" + assert results[3]["state"] == "on" + assert results[3]["context_entity_id"] == "binary_sensor.is_light" + assert results[3]["context_state"] == "off" + assert results[3]["context_user_id"] == "b400facee45711eaa9308bfd3d19e474" + assert "context_event_type" not in results[3] diff --git a/tests/components/logbook/test_websocket_api.py b/tests/components/logbook/test_websocket_api.py new file mode 100644 index 00000000000..2dd08ec44ce --- /dev/null +++ b/tests/components/logbook/test_websocket_api.py @@ -0,0 +1,2140 @@ +"""The tests for the logbook component.""" +import asyncio +from collections.abc import Callable +from datetime import timedelta +from unittest.mock import ANY, patch + +from freezegun import freeze_time +import pytest + +from homeassistant import core +from homeassistant.components import logbook, recorder +from homeassistant.components.automation import ATTR_SOURCE, EVENT_AUTOMATION_TRIGGERED +from homeassistant.components.logbook import websocket_api +from homeassistant.components.script import EVENT_SCRIPT_STARTED +from homeassistant.components.websocket_api.const import TYPE_RESULT +from homeassistant.const import ( + ATTR_DOMAIN, + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + ATTR_NAME, + ATTR_UNIT_OF_MEASUREMENT, + CONF_DOMAINS, + CONF_ENTITIES, + CONF_EXCLUDE, + CONF_INCLUDE, + EVENT_HOMEASSISTANT_START, + STATE_OFF, + STATE_ON, +) +from homeassistant.core import Event, HomeAssistant, State +from homeassistant.helpers import device_registry +from homeassistant.helpers.entityfilter import CONF_ENTITY_GLOBS +from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util + +from tests.common import ( + MockConfigEntry, + SetupRecorderInstanceT, + async_fire_time_changed, +) +from tests.components.recorder.common import ( + async_block_recorder, + async_recorder_block_till_done, + async_wait_recording_done, +) + + +@pytest.fixture() +def set_utc(hass): + """Set timezone to UTC.""" + hass.config.set_time_zone("UTC") + + +async def _async_mock_device_with_logbook_platform(hass): + """Mock an integration that provides a device that are described by the logbook.""" + entry = MockConfigEntry(domain="test", data={"first": True}, options=None) + entry.add_to_hass(hass) + dev_reg = device_registry.async_get(hass) + device = dev_reg.async_get_or_create( + config_entry_id=entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + sw_version="sw-version", + name="device name", + manufacturer="manufacturer", + model="model", + suggested_area="Game Room", + ) + + class MockLogbookPlatform: + """Mock a logbook platform.""" + + @core.callback + def async_describe_events( + hass: HomeAssistant, + async_describe_event: Callable[ + [str, str, Callable[[Event], dict[str, str]]], None + ], + ) -> None: + """Describe logbook events.""" + + @core.callback + def async_describe_test_event(event: Event) -> dict[str, str]: + """Describe mock logbook event.""" + return { + "name": "device name", + "message": event.data.get("message", "is on fire"), + } + + async_describe_event("test", "mock_event", async_describe_test_event) + + await logbook._process_logbook_platform(hass, "test", MockLogbookPlatform) + return device + + +async def test_get_events(hass, hass_ws_client, recorder_mock): + """Test logbook get_events.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) + await async_recorder_block_till_done(hass) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + + hass.states.async_set("light.kitchen", STATE_OFF) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 100}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 200}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 300}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 400}) + await hass.async_block_till_done() + context = core.Context( + id="ac5bd62de45711eaaeb351041eec8dd9", + user_id="b400facee45711eaa9308bfd3d19e474", + ) + + hass.states.async_set("light.kitchen", STATE_OFF, context=context) + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "end_time": now.isoformat(), + "entity_ids": ["light.kitchen"], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] + + await client.send_json( + { + "id": 2, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "entity_ids": ["sensor.test"], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 2 + assert response["result"] == [] + + await client.send_json( + { + "id": 3, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "entity_ids": ["light.kitchen"], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 3 + + results = response["result"] + assert results[0]["entity_id"] == "light.kitchen" + assert results[0]["state"] == "on" + assert results[1]["entity_id"] == "light.kitchen" + assert results[1]["state"] == "off" + + await client.send_json( + { + "id": 4, + "type": "logbook/get_events", + "start_time": now.isoformat(), + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 4 + + results = response["result"] + assert len(results) == 3 + assert results[0]["message"] == "started" + assert results[1]["entity_id"] == "light.kitchen" + assert results[1]["state"] == "on" + assert isinstance(results[1]["when"], float) + assert results[2]["entity_id"] == "light.kitchen" + assert results[2]["state"] == "off" + assert isinstance(results[2]["when"], float) + + await client.send_json( + { + "id": 5, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "context_id": "ac5bd62de45711eaaeb351041eec8dd9", + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 5 + + results = response["result"] + assert len(results) == 1 + assert results[0]["entity_id"] == "light.kitchen" + assert results[0]["state"] == "off" + assert isinstance(results[0]["when"], float) + + +async def test_get_events_entities_filtered_away(hass, hass_ws_client, recorder_mock): + """Test logbook get_events all entities filtered away.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) + await async_recorder_block_till_done(hass) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + + hass.states.async_set("light.kitchen", STATE_ON) + await hass.async_block_till_done() + hass.states.async_set( + "light.filtered", STATE_ON, {"brightness": 100, ATTR_UNIT_OF_MEASUREMENT: "any"} + ) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_OFF, {"brightness": 200}) + await hass.async_block_till_done() + hass.states.async_set( + "light.filtered", + STATE_OFF, + {"brightness": 300, ATTR_UNIT_OF_MEASUREMENT: "any"}, + ) + + await async_wait_recording_done(hass) + client = await hass_ws_client() + + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "entity_ids": ["light.kitchen"], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 1 + + results = response["result"] + assert results[0]["entity_id"] == "light.kitchen" + assert results[0]["state"] == "off" + + await client.send_json( + { + "id": 2, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "entity_ids": ["light.filtered"], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 2 + + results = response["result"] + assert len(results) == 0 + + +async def test_get_events_future_start_time(hass, hass_ws_client, recorder_mock): + """Test get_events with a future start time.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + future = dt_util.utcnow() + timedelta(hours=10) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": future.isoformat(), + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 1 + + results = response["result"] + assert isinstance(results, list) + assert len(results) == 0 + + +async def test_get_events_bad_start_time(hass, hass_ws_client, recorder_mock): + """Test get_events bad start time.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": "cats", + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_start_time" + + +async def test_get_events_bad_end_time(hass, hass_ws_client, recorder_mock): + """Test get_events bad end time.""" + now = dt_util.utcnow() + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "end_time": "dogs", + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_end_time" + + +async def test_get_events_invalid_filters(hass, hass_ws_client, recorder_mock): + """Test get_events invalid filters.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "entity_ids": [], + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_format" + await client.send_json( + { + "id": 2, + "type": "logbook/get_events", + "device_ids": [], + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_format" + + +async def test_get_events_with_device_ids(hass, hass_ws_client, recorder_mock): + """Test logbook get_events for device ids.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook") + ] + ) + + device = await _async_mock_device_with_logbook_platform(hass) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire("mock_event", {"device_id": device.id}) + + hass.states.async_set("light.kitchen", STATE_OFF) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 100}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 200}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 300}) + await hass.async_block_till_done() + hass.states.async_set("light.kitchen", STATE_ON, {"brightness": 400}) + await hass.async_block_till_done() + context = core.Context( + id="ac5bd62de45711eaaeb351041eec8dd9", + user_id="b400facee45711eaa9308bfd3d19e474", + ) + + hass.states.async_set("light.kitchen", STATE_OFF, context=context) + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + client = await hass_ws_client() + + await client.send_json( + { + "id": 1, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "device_ids": [device.id], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 1 + + results = response["result"] + assert len(results) == 1 + assert results[0]["name"] == "device name" + assert results[0]["message"] == "is on fire" + assert isinstance(results[0]["when"], float) + + await client.send_json( + { + "id": 2, + "type": "logbook/get_events", + "start_time": now.isoformat(), + "entity_ids": ["light.kitchen"], + "device_ids": [device.id], + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 2 + + results = response["result"] + assert results[0]["domain"] == "test" + assert results[0]["message"] == "is on fire" + assert results[0]["name"] == "device name" + assert results[1]["entity_id"] == "light.kitchen" + assert results[1]["state"] == "on" + assert results[2]["entity_id"] == "light.kitchen" + assert results[2]["state"] == "off" + + await client.send_json( + { + "id": 3, + "type": "logbook/get_events", + "start_time": now.isoformat(), + } + ) + response = await client.receive_json() + assert response["success"] + assert response["id"] == 3 + + results = response["result"] + assert len(results) == 4 + assert results[0]["message"] == "started" + assert results[1]["name"] == "device name" + assert results[1]["message"] == "is on fire" + assert isinstance(results[1]["when"], float) + assert results[2]["entity_id"] == "light.kitchen" + assert results[2]["state"] == "on" + assert isinstance(results[2]["when"], float) + assert results[3]["entity_id"] == "light.kitchen" + assert results[3]["state"] == "off" + assert isinstance(results[3]["when"], float) + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_subscribe_unsubscribe_logbook_stream_excluded_entities( + hass, recorder_mock, hass_ws_client +): + """Test subscribe/unsubscribe logbook stream with excluded entities.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "automation", "script") + ] + ) + await async_setup_component( + hass, + logbook.DOMAIN, + { + logbook.DOMAIN: { + CONF_EXCLUDE: { + CONF_ENTITIES: ["light.exc"], + CONF_DOMAINS: ["switch"], + CONF_ENTITY_GLOBS: ["*.excluded"], + } + }, + }, + ) + await hass.async_block_till_done() + init_count = sum(hass.bus.async_listeners().values()) + + hass.states.async_set("light.exc", STATE_ON) + hass.states.async_set("light.exc", STATE_OFF) + hass.states.async_set("switch.any", STATE_ON) + hass.states.async_set("switch.any", STATE_OFF) + hass.states.async_set("cover.excluded", STATE_ON) + hass.states.async_set("cover.excluded", STATE_OFF) + + hass.states.async_set("binary_sensor.is_light", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_OFF) + state: State = hass.states.get("binary_sensor.is_light") + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + await websocket_client.send_json( + {"id": 7, "type": "logbook/event_stream", "start_time": now.isoformat()} + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "entity_id": "binary_sensor.is_light", + "state": "off", + "when": state.last_updated.timestamp(), + } + ] + assert msg["event"]["start_time"] == now.timestamp() + assert msg["event"]["end_time"] > msg["event"]["start_time"] + assert msg["event"]["partial"] is True + + hass.states.async_set("light.exc", STATE_ON) + hass.states.async_set("light.exc", STATE_OFF) + hass.states.async_set("switch.any", STATE_ON) + hass.states.async_set("switch.any", STATE_OFF) + hass.states.async_set("cover.excluded", STATE_ON) + hass.states.async_set("cover.excluded", STATE_OFF) + hass.states.async_set("light.alpha", "on") + hass.states.async_set("light.alpha", "off") + alpha_off_state: State = hass.states.get("light.alpha") + hass.states.async_set("light.zulu", "on", {"color": "blue"}) + hass.states.async_set("light.zulu", "off", {"effect": "help"}) + zulu_off_state: State = hass.states.get("light.zulu") + hass.states.async_set( + "light.zulu", "on", {"effect": "help", "color": ["blue", "green"]} + ) + zulu_on_state: State = hass.states.get("light.zulu") + await hass.async_block_till_done() + + hass.states.async_remove("light.zulu") + await hass.async_block_till_done() + + hass.states.async_set("light.zulu", "on", {"effect": "help", "color": "blue"}) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"]["events"] + assert msg["event"]["events"] == [] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"]["events"] + assert msg["event"]["events"] == [ + { + "entity_id": "light.alpha", + "state": "off", + "when": alpha_off_state.last_updated.timestamp(), + }, + { + "entity_id": "light.zulu", + "state": "off", + "when": zulu_off_state.last_updated.timestamp(), + }, + { + "entity_id": "light.zulu", + "state": "on", + "when": zulu_on_state.last_updated.timestamp(), + }, + ] + + await async_wait_recording_done(hass) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation 3", ATTR_ENTITY_ID: "cover.excluded"}, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + { + ATTR_NAME: "Mock automation switch matching entity", + ATTR_ENTITY_ID: "switch.match_domain", + }, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation switch matching domain", ATTR_DOMAIN: "switch"}, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation matches nothing"}, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation 3", ATTR_ENTITY_ID: "light.keep"}, + ) + hass.states.async_set("cover.excluded", STATE_ON) + hass.states.async_set("cover.excluded", STATE_OFF) + await hass.async_block_till_done() + msg = await websocket_client.receive_json() + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "context_id": ANY, + "domain": "automation", + "entity_id": None, + "message": "triggered", + "name": "Mock automation matches nothing", + "source": None, + "when": ANY, + }, + { + "context_id": ANY, + "domain": "automation", + "entity_id": "light.keep", + "message": "triggered", + "name": "Mock automation 3", + "source": None, + "when": ANY, + }, + ] + + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_subscribe_unsubscribe_logbook_stream_included_entities( + hass, recorder_mock, hass_ws_client +): + """Test subscribe/unsubscribe logbook stream with included entities.""" + test_entities = ( + "light.inc", + "switch.any", + "cover.included", + "cover.not_included", + "automation.not_included", + "binary_sensor.is_light", + ) + + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "automation", "script") + ] + ) + await async_setup_component( + hass, + logbook.DOMAIN, + { + logbook.DOMAIN: { + CONF_INCLUDE: { + CONF_ENTITIES: ["light.inc"], + CONF_DOMAINS: ["switch"], + CONF_ENTITY_GLOBS: ["*.included"], + } + }, + }, + ) + await hass.async_block_till_done() + init_count = sum(hass.bus.async_listeners().values()) + + for entity_id in test_entities: + hass.states.async_set(entity_id, STATE_ON) + hass.states.async_set(entity_id, STATE_OFF) + + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + await websocket_client.send_json( + {"id": 7, "type": "logbook/event_stream", "start_time": now.isoformat()} + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + {"entity_id": "light.inc", "state": "off", "when": ANY}, + {"entity_id": "switch.any", "state": "off", "when": ANY}, + {"entity_id": "cover.included", "state": "off", "when": ANY}, + ] + assert msg["event"]["start_time"] == now.timestamp() + assert msg["event"]["end_time"] > msg["event"]["start_time"] + assert msg["event"]["partial"] is True + + for entity_id in test_entities: + hass.states.async_set(entity_id, STATE_ON) + hass.states.async_set(entity_id, STATE_OFF) + await hass.async_block_till_done() + + hass.states.async_remove("light.zulu") + await hass.async_block_till_done() + + hass.states.async_set("light.zulu", "on", {"effect": "help", "color": "blue"}) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"]["events"] + assert msg["event"]["events"] == [] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"]["events"] + assert msg["event"]["events"] == [ + {"entity_id": "light.inc", "state": "on", "when": ANY}, + {"entity_id": "light.inc", "state": "off", "when": ANY}, + {"entity_id": "switch.any", "state": "on", "when": ANY}, + {"entity_id": "switch.any", "state": "off", "when": ANY}, + {"entity_id": "cover.included", "state": "on", "when": ANY}, + {"entity_id": "cover.included", "state": "off", "when": ANY}, + ] + + for _ in range(3): + for entity_id in test_entities: + hass.states.async_set(entity_id, STATE_ON) + hass.states.async_set(entity_id, STATE_OFF) + await async_wait_recording_done(hass) + + msg = await websocket_client.receive_json() + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + {"entity_id": "light.inc", "state": "on", "when": ANY}, + {"entity_id": "light.inc", "state": "off", "when": ANY}, + {"entity_id": "switch.any", "state": "on", "when": ANY}, + {"entity_id": "switch.any", "state": "off", "when": ANY}, + {"entity_id": "cover.included", "state": "on", "when": ANY}, + {"entity_id": "cover.included", "state": "off", "when": ANY}, + ] + + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation 3", ATTR_ENTITY_ID: "cover.included"}, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation 3", ATTR_ENTITY_ID: "cover.excluded"}, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + { + ATTR_NAME: "Mock automation switch matching entity", + ATTR_ENTITY_ID: "switch.match_domain", + }, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation switch matching domain", ATTR_DOMAIN: "switch"}, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation matches nothing"}, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation 3", ATTR_ENTITY_ID: "light.inc"}, + ) + + await hass.async_block_till_done() + + msg = await websocket_client.receive_json() + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "context_id": ANY, + "domain": "automation", + "entity_id": "cover.included", + "message": "triggered", + "name": "Mock automation 3", + "source": None, + "when": ANY, + }, + { + "context_id": ANY, + "domain": "automation", + "entity_id": "switch.match_domain", + "message": "triggered", + "name": "Mock automation switch matching entity", + "source": None, + "when": ANY, + }, + { + "context_id": ANY, + "domain": "automation", + "entity_id": None, + "message": "triggered", + "name": "Mock automation switch matching domain", + "source": None, + "when": ANY, + }, + { + "context_id": ANY, + "domain": "automation", + "entity_id": None, + "message": "triggered", + "name": "Mock automation matches nothing", + "source": None, + "when": ANY, + }, + { + "context_id": ANY, + "domain": "automation", + "entity_id": "light.inc", + "message": "triggered", + "name": "Mock automation 3", + "source": None, + "when": ANY, + }, + ] + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_logbook_stream_excluded_entities_inherits_filters_from_recorder( + hass, recorder_mock, hass_ws_client +): + """Test subscribe/unsubscribe logbook stream inherts filters from recorder.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "automation", "script") + ] + ) + await async_setup_component( + hass, + logbook.DOMAIN, + { + logbook.DOMAIN: { + CONF_EXCLUDE: { + CONF_ENTITIES: ["light.additional_excluded"], + } + }, + recorder.DOMAIN: { + CONF_EXCLUDE: { + CONF_ENTITIES: ["light.exc"], + CONF_DOMAINS: ["switch"], + CONF_ENTITY_GLOBS: ["*.excluded", "*.no_matches"], + } + }, + }, + ) + await hass.async_block_till_done() + init_count = sum(hass.bus.async_listeners().values()) + + hass.states.async_set("light.exc", STATE_ON) + hass.states.async_set("light.exc", STATE_OFF) + hass.states.async_set("switch.any", STATE_ON) + hass.states.async_set("switch.any", STATE_OFF) + hass.states.async_set("cover.excluded", STATE_ON) + hass.states.async_set("cover.excluded", STATE_OFF) + hass.states.async_set("light.additional_excluded", STATE_ON) + hass.states.async_set("light.additional_excluded", STATE_OFF) + hass.states.async_set("binary_sensor.is_light", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_OFF) + state: State = hass.states.get("binary_sensor.is_light") + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + await websocket_client.send_json( + {"id": 7, "type": "logbook/event_stream", "start_time": now.isoformat()} + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "entity_id": "binary_sensor.is_light", + "state": "off", + "when": state.last_updated.timestamp(), + } + ] + assert msg["event"]["start_time"] == now.timestamp() + assert msg["event"]["end_time"] > msg["event"]["start_time"] + assert msg["event"]["partial"] is True + + hass.states.async_set("light.exc", STATE_ON) + hass.states.async_set("light.exc", STATE_OFF) + hass.states.async_set("switch.any", STATE_ON) + hass.states.async_set("switch.any", STATE_OFF) + hass.states.async_set("cover.excluded", STATE_ON) + hass.states.async_set("cover.excluded", STATE_OFF) + hass.states.async_set("light.additional_excluded", STATE_ON) + hass.states.async_set("light.additional_excluded", STATE_OFF) + hass.states.async_set("light.alpha", "on") + hass.states.async_set("light.alpha", "off") + alpha_off_state: State = hass.states.get("light.alpha") + hass.states.async_set("light.zulu", "on", {"color": "blue"}) + hass.states.async_set("light.zulu", "off", {"effect": "help"}) + zulu_off_state: State = hass.states.get("light.zulu") + hass.states.async_set( + "light.zulu", "on", {"effect": "help", "color": ["blue", "green"]} + ) + zulu_on_state: State = hass.states.get("light.zulu") + await hass.async_block_till_done() + + hass.states.async_remove("light.zulu") + await hass.async_block_till_done() + + hass.states.async_set("light.zulu", "on", {"effect": "help", "color": "blue"}) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"]["events"] + assert msg["event"]["events"] == [] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"]["events"] + assert msg["event"]["events"] == [ + { + "entity_id": "light.alpha", + "state": "off", + "when": alpha_off_state.last_updated.timestamp(), + }, + { + "entity_id": "light.zulu", + "state": "off", + "when": zulu_off_state.last_updated.timestamp(), + }, + { + "entity_id": "light.zulu", + "state": "on", + "when": zulu_on_state.last_updated.timestamp(), + }, + ] + + await async_wait_recording_done(hass) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation 3", ATTR_ENTITY_ID: "cover.excluded"}, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + { + ATTR_NAME: "Mock automation switch matching entity", + ATTR_ENTITY_ID: "switch.match_domain", + }, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation switch matching domain", ATTR_DOMAIN: "switch"}, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation matches nothing"}, + ) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation 3", ATTR_ENTITY_ID: "light.keep"}, + ) + hass.states.async_set("cover.excluded", STATE_ON) + hass.states.async_set("cover.excluded", STATE_OFF) + await hass.async_block_till_done() + msg = await websocket_client.receive_json() + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "context_id": ANY, + "domain": "automation", + "entity_id": None, + "message": "triggered", + "name": "Mock automation matches nothing", + "source": None, + "when": ANY, + }, + { + "context_id": ANY, + "domain": "automation", + "entity_id": "light.keep", + "message": "triggered", + "name": "Mock automation 3", + "source": None, + "when": ANY, + }, + ] + + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_subscribe_unsubscribe_logbook_stream( + hass, recorder_mock, hass_ws_client +): + """Test subscribe/unsubscribe logbook stream.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + + await hass.async_block_till_done() + init_count = sum(hass.bus.async_listeners().values()) + + hass.states.async_set("binary_sensor.is_light", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_OFF) + state: State = hass.states.get("binary_sensor.is_light") + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + await websocket_client.send_json( + {"id": 7, "type": "logbook/event_stream", "start_time": now.isoformat()} + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "entity_id": "binary_sensor.is_light", + "state": "off", + "when": state.last_updated.timestamp(), + } + ] + assert msg["event"]["start_time"] == now.timestamp() + assert msg["event"]["end_time"] > msg["event"]["start_time"] + assert msg["event"]["partial"] is True + + hass.states.async_set("light.alpha", "on") + hass.states.async_set("light.alpha", "off") + alpha_off_state: State = hass.states.get("light.alpha") + hass.states.async_set("light.zulu", "on", {"color": "blue"}) + hass.states.async_set("light.zulu", "off", {"effect": "help"}) + zulu_off_state: State = hass.states.get("light.zulu") + hass.states.async_set( + "light.zulu", "on", {"effect": "help", "color": ["blue", "green"]} + ) + zulu_on_state: State = hass.states.get("light.zulu") + await hass.async_block_till_done() + + hass.states.async_remove("light.zulu") + await hass.async_block_till_done() + + hass.states.async_set("light.zulu", "on", {"effect": "help", "color": "blue"}) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"]["events"] + assert msg["event"]["events"] == [] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"]["events"] + assert msg["event"]["events"] == [ + { + "entity_id": "light.alpha", + "state": "off", + "when": alpha_off_state.last_updated.timestamp(), + }, + { + "entity_id": "light.zulu", + "state": "off", + "when": zulu_off_state.last_updated.timestamp(), + }, + { + "entity_id": "light.zulu", + "state": "on", + "when": zulu_on_state.last_updated.timestamp(), + }, + ] + + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + { + ATTR_NAME: "Mock automation", + ATTR_ENTITY_ID: "automation.mock_automation", + ATTR_SOURCE: "numeric state of sensor.hungry_dogs", + }, + ) + hass.bus.async_fire( + EVENT_SCRIPT_STARTED, + { + ATTR_NAME: "Mock script", + ATTR_ENTITY_ID: "script.mock_script", + ATTR_SOURCE: "numeric state of sensor.hungry_dogs", + }, + ) + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "context_id": ANY, + "domain": "automation", + "entity_id": "automation.mock_automation", + "message": "triggered by numeric state of sensor.hungry_dogs", + "name": "Mock automation", + "source": "numeric state of sensor.hungry_dogs", + "when": ANY, + }, + { + "context_id": ANY, + "domain": "script", + "entity_id": "script.mock_script", + "message": "started", + "name": "Mock script", + "when": ANY, + }, + { + "domain": "homeassistant", + "icon": "mdi:home-assistant", + "message": "started", + "name": "Home Assistant", + "when": ANY, + }, + ] + + context = core.Context( + id="ac5bd62de45711eaaeb351041eec8dd9", + user_id="b400facee45711eaa9308bfd3d19e474", + ) + automation_entity_id_test = "automation.alarm" + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + { + ATTR_NAME: "Mock automation", + ATTR_ENTITY_ID: automation_entity_id_test, + ATTR_SOURCE: "state of binary_sensor.dog_food_ready", + }, + context=context, + ) + hass.bus.async_fire( + EVENT_SCRIPT_STARTED, + {ATTR_NAME: "Mock script", ATTR_ENTITY_ID: "script.mock_script"}, + context=context, + ) + hass.states.async_set( + automation_entity_id_test, + STATE_ON, + {ATTR_FRIENDLY_NAME: "Alarm Automation"}, + context=context, + ) + entity_id_test = "alarm_control_panel.area_001" + hass.states.async_set(entity_id_test, STATE_OFF, context=context) + hass.states.async_set(entity_id_test, STATE_ON, context=context) + entity_id_second = "alarm_control_panel.area_002" + hass.states.async_set(entity_id_second, STATE_OFF, context=context) + hass.states.async_set(entity_id_second, STATE_ON, context=context) + + await hass.async_block_till_done() + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "context_id": "ac5bd62de45711eaaeb351041eec8dd9", + "context_user_id": "b400facee45711eaa9308bfd3d19e474", + "domain": "automation", + "entity_id": "automation.alarm", + "message": "triggered by state of binary_sensor.dog_food_ready", + "name": "Mock automation", + "source": "state of binary_sensor.dog_food_ready", + "when": ANY, + }, + { + "context_domain": "automation", + "context_entity_id": "automation.alarm", + "context_event_type": "automation_triggered", + "context_id": "ac5bd62de45711eaaeb351041eec8dd9", + "context_message": "triggered by state of " "binary_sensor.dog_food_ready", + "context_name": "Mock automation", + "context_source": "state of binary_sensor.dog_food_ready", + "context_user_id": "b400facee45711eaa9308bfd3d19e474", + "domain": "script", + "entity_id": "script.mock_script", + "message": "started", + "name": "Mock script", + "when": ANY, + }, + { + "context_domain": "automation", + "context_entity_id": "automation.alarm", + "context_event_type": "automation_triggered", + "context_message": "triggered by state of " "binary_sensor.dog_food_ready", + "context_name": "Mock automation", + "context_source": "state of binary_sensor.dog_food_ready", + "context_user_id": "b400facee45711eaa9308bfd3d19e474", + "entity_id": "alarm_control_panel.area_001", + "state": "on", + "when": ANY, + }, + { + "context_domain": "automation", + "context_entity_id": "automation.alarm", + "context_event_type": "automation_triggered", + "context_message": "triggered by state of " "binary_sensor.dog_food_ready", + "context_name": "Mock automation", + "context_source": "state of binary_sensor.dog_food_ready", + "context_user_id": "b400facee45711eaa9308bfd3d19e474", + "entity_id": "alarm_control_panel.area_002", + "state": "on", + "when": ANY, + }, + ] + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation 2", ATTR_ENTITY_ID: automation_entity_id_test}, + context=context, + ) + + await hass.async_block_till_done() + + msg = await websocket_client.receive_json() + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "context_domain": "automation", + "context_entity_id": "automation.alarm", + "context_event_type": "automation_triggered", + "context_id": "ac5bd62de45711eaaeb351041eec8dd9", + "context_message": "triggered by state of binary_sensor.dog_food_ready", + "context_name": "Mock automation", + "context_source": "state of binary_sensor.dog_food_ready", + "context_user_id": "b400facee45711eaa9308bfd3d19e474", + "domain": "automation", + "entity_id": "automation.alarm", + "message": "triggered", + "name": "Mock automation 2", + "source": None, + "when": ANY, + } + ] + + await async_wait_recording_done(hass) + hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: "Mock automation 3", ATTR_ENTITY_ID: automation_entity_id_test}, + context=context, + ) + + await hass.async_block_till_done() + msg = await websocket_client.receive_json() + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "context_domain": "automation", + "context_entity_id": "automation.alarm", + "context_event_type": "automation_triggered", + "context_id": "ac5bd62de45711eaaeb351041eec8dd9", + "context_message": "triggered by state of binary_sensor.dog_food_ready", + "context_name": "Mock automation", + "context_source": "state of binary_sensor.dog_food_ready", + "context_user_id": "b400facee45711eaa9308bfd3d19e474", + "domain": "automation", + "entity_id": "automation.alarm", + "message": "triggered", + "name": "Mock automation 3", + "source": None, + "when": ANY, + } + ] + + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_subscribe_unsubscribe_logbook_stream_entities( + hass, recorder_mock, hass_ws_client +): + """Test subscribe/unsubscribe logbook stream with specific entities.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + + await hass.async_block_till_done() + init_count = sum(hass.bus.async_listeners().values()) + hass.states.async_set("light.small", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_OFF) + state: State = hass.states.get("binary_sensor.is_light") + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + await websocket_client.send_json( + { + "id": 7, + "type": "logbook/event_stream", + "start_time": now.isoformat(), + "entity_ids": ["light.small", "binary_sensor.is_light"], + } + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "start_time" in msg["event"] + assert "end_time" in msg["event"] + assert msg["event"]["partial"] is True + assert msg["event"]["events"] == [ + { + "entity_id": "binary_sensor.is_light", + "state": "off", + "when": state.last_updated.timestamp(), + } + ] + + hass.states.async_set("light.alpha", STATE_ON) + hass.states.async_set("light.alpha", STATE_OFF) + hass.states.async_set("light.small", STATE_OFF, {"effect": "help", "color": "blue"}) + + await hass.async_block_till_done() + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "start_time" in msg["event"] + assert "end_time" in msg["event"] + assert "partial" not in msg["event"] + assert msg["event"]["events"] == [] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"] + assert msg["event"]["events"] == [ + { + "entity_id": "light.small", + "state": "off", + "when": ANY, + }, + ] + + hass.states.async_remove("light.alpha") + hass.states.async_remove("light.small") + await hass.async_block_till_done() + + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_subscribe_unsubscribe_logbook_stream_entities_with_end_time( + hass, recorder_mock, hass_ws_client +): + """Test subscribe/unsubscribe logbook stream with specific entities and an end_time.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + + await hass.async_block_till_done() + init_count = sum(hass.bus.async_listeners().values()) + hass.states.async_set("light.small", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_OFF) + state: State = hass.states.get("binary_sensor.is_light") + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + await websocket_client.send_json( + { + "id": 7, + "type": "logbook/event_stream", + "start_time": now.isoformat(), + "end_time": (now + timedelta(minutes=10)).isoformat(), + "entity_ids": ["light.small", "binary_sensor.is_light"], + } + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["partial"] is True + assert msg["event"]["events"] == [ + { + "entity_id": "binary_sensor.is_light", + "state": "off", + "when": state.last_updated.timestamp(), + } + ] + + hass.states.async_set("light.alpha", STATE_ON) + hass.states.async_set("light.alpha", STATE_OFF) + hass.states.async_set("light.small", STATE_OFF, {"effect": "help", "color": "blue"}) + + await hass.async_block_till_done() + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"] + assert msg["event"]["events"] == [] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert "partial" not in msg["event"] + assert msg["event"]["events"] == [ + { + "entity_id": "light.small", + "state": "off", + "when": ANY, + }, + ] + + hass.states.async_remove("light.alpha") + hass.states.async_remove("light.small") + await hass.async_block_till_done() + + async_fire_time_changed(hass, now + timedelta(minutes=11)) + await hass.async_block_till_done() + + # These states should not be sent since we should be unsubscribed + hass.states.async_set("light.small", STATE_ON) + hass.states.async_set("light.small", STATE_OFF) + await hass.async_block_till_done() + + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) <= init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_subscribe_unsubscribe_logbook_stream_entities_past_only( + hass, recorder_mock, hass_ws_client +): + """Test subscribe/unsubscribe logbook stream with specific entities in the past.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + + await hass.async_block_till_done() + init_count = sum(hass.bus.async_listeners().values()) + hass.states.async_set("light.small", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_OFF) + state: State = hass.states.get("binary_sensor.is_light") + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + await websocket_client.send_json( + { + "id": 7, + "type": "logbook/event_stream", + "start_time": now.isoformat(), + "end_time": (dt_util.utcnow() - timedelta(microseconds=1)).isoformat(), + "entity_ids": ["light.small", "binary_sensor.is_light"], + } + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "entity_id": "binary_sensor.is_light", + "state": "off", + "when": state.last_updated.timestamp(), + } + ] + + # These states should not be sent since we should be unsubscribed + # since we only asked for the past + hass.states.async_set("light.small", STATE_ON) + hass.states.async_set("light.small", STATE_OFF) + await hass.async_block_till_done() + + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_subscribe_unsubscribe_logbook_stream_big_query( + hass, recorder_mock, hass_ws_client +): + """Test subscribe/unsubscribe logbook stream and ask for a large time frame. + + We should get the data for the first 24 hours in the first message, and + anything older will come in a followup message. + """ + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + + await hass.async_block_till_done() + init_count = sum(hass.bus.async_listeners().values()) + four_days_ago = now - timedelta(days=4) + five_days_ago = now - timedelta(days=5) + + with freeze_time(four_days_ago): + hass.states.async_set("binary_sensor.four_days_ago", STATE_ON) + hass.states.async_set("binary_sensor.four_days_ago", STATE_OFF) + four_day_old_state: State = hass.states.get("binary_sensor.four_days_ago") + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + # Verify our state was recorded in the past + assert (now - four_day_old_state.last_updated).total_seconds() > 86400 * 3 + + hass.states.async_set("binary_sensor.is_light", STATE_OFF) + hass.states.async_set("binary_sensor.is_light", STATE_ON) + current_state: State = hass.states.get("binary_sensor.is_light") + + # Verify our new state was recorded in the recent timeframe + assert (now - current_state.last_updated).total_seconds() < 2 + + await async_wait_recording_done(hass) + + websocket_client = await hass_ws_client() + await websocket_client.send_json( + { + "id": 7, + "type": "logbook/event_stream", + "start_time": five_days_ago.isoformat(), + } + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # With a big query we get the current state first + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "entity_id": "binary_sensor.is_light", + "state": "on", + "when": current_state.last_updated.timestamp(), + } + ] + + # With a big query we get the old states second + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["partial"] is True + assert msg["event"]["events"] == [ + { + "entity_id": "binary_sensor.four_days_ago", + "state": "off", + "when": four_day_old_state.last_updated.timestamp(), + } + ] + + # And finally a response without partial set to indicate no more + # historical data is coming + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [] + + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_subscribe_unsubscribe_logbook_stream_device( + hass, recorder_mock, hass_ws_client +): + """Test subscribe/unsubscribe logbook stream with a device.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + device = await _async_mock_device_with_logbook_platform(hass) + + await hass.async_block_till_done() + init_count = sum(hass.bus.async_listeners().values()) + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + await websocket_client.send_json( + { + "id": 7, + "type": "logbook/event_stream", + "start_time": now.isoformat(), + "device_ids": [device.id], + } + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # There are no answers to our initial query + # so we get an empty reply. This is to ensure + # consumers of the api know there are no results + # and its not a failure case. This is useful + # in the frontend so we can tell the user there + # are no results vs waiting for them to appear + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [] + + hass.states.async_set("binary_sensor.should_not_appear", STATE_ON) + hass.states.async_set("binary_sensor.should_not_appear", STATE_OFF) + hass.bus.async_fire("mock_event", {"device_id": device.id}) + await hass.async_block_till_done() + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + {"domain": "test", "message": "is on fire", "name": "device name", "when": ANY} + ] + + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count + + +async def test_event_stream_bad_start_time(hass, hass_ws_client, recorder_mock): + """Test event_stream bad start time.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/event_stream", + "start_time": "cats", + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_start_time" + + +async def test_event_stream_bad_end_time(hass, hass_ws_client, recorder_mock): + """Test event_stream bad end time.""" + await async_setup_component(hass, "logbook", {}) + await async_recorder_block_till_done(hass) + utc_now = dt_util.utcnow() + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "logbook/event_stream", + "start_time": utc_now.isoformat(), + "end_time": "cats", + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_end_time" + + await client.send_json( + { + "id": 2, + "type": "logbook/event_stream", + "start_time": utc_now.isoformat(), + "end_time": (utc_now - timedelta(hours=5)).isoformat(), + } + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "invalid_end_time" + + +async def test_live_stream_with_one_second_commit_interval( + hass: HomeAssistant, + async_setup_recorder_instance: SetupRecorderInstanceT, + hass_ws_client, +): + """Test the recorder with a 1s commit interval.""" + config = {recorder.CONF_COMMIT_INTERVAL: 0.5} + await async_setup_recorder_instance(hass, config) + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + device = await _async_mock_device_with_logbook_platform(hass) + + await hass.async_block_till_done() + init_count = sum(hass.bus.async_listeners().values()) + + hass.bus.async_fire("mock_event", {"device_id": device.id, "message": "1"}) + + await async_wait_recording_done(hass) + + hass.bus.async_fire("mock_event", {"device_id": device.id, "message": "2"}) + + await hass.async_block_till_done() + + hass.bus.async_fire("mock_event", {"device_id": device.id, "message": "3"}) + + websocket_client = await hass_ws_client() + await websocket_client.send_json( + { + "id": 7, + "type": "logbook/event_stream", + "start_time": now.isoformat(), + "device_ids": [device.id], + } + ) + hass.bus.async_fire("mock_event", {"device_id": device.id, "message": "4"}) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + hass.bus.async_fire("mock_event", {"device_id": device.id, "message": "5"}) + + recieved_rows = [] + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + recieved_rows.extend(msg["event"]["events"]) + + hass.bus.async_fire("mock_event", {"device_id": device.id, "message": "6"}) + + await hass.async_block_till_done() + + hass.bus.async_fire("mock_event", {"device_id": device.id, "message": "7"}) + + while not len(recieved_rows) == 7: + msg = await asyncio.wait_for(websocket_client.receive_json(), 2.5) + assert msg["id"] == 7 + assert msg["type"] == "event" + recieved_rows.extend(msg["event"]["events"]) + + # Make sure we get rows back in order + assert recieved_rows == [ + {"domain": "test", "message": "1", "name": "device name", "when": ANY}, + {"domain": "test", "message": "2", "name": "device name", "when": ANY}, + {"domain": "test", "message": "3", "name": "device name", "when": ANY}, + {"domain": "test", "message": "4", "name": "device name", "when": ANY}, + {"domain": "test", "message": "5", "name": "device name", "when": ANY}, + {"domain": "test", "message": "6", "name": "device name", "when": ANY}, + {"domain": "test", "message": "7", "name": "device name", "when": ANY}, + ] + + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_subscribe_disconnected(hass, recorder_mock, hass_ws_client): + """Test subscribe/unsubscribe logbook stream gets disconnected.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + await async_wait_recording_done(hass) + + init_count = sum(hass.bus.async_listeners().values()) + hass.states.async_set("light.small", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_OFF) + state: State = hass.states.get("binary_sensor.is_light") + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + await websocket_client.send_json( + { + "id": 7, + "type": "logbook/event_stream", + "start_time": now.isoformat(), + "entity_ids": ["light.small", "binary_sensor.is_light"], + } + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + { + "entity_id": "binary_sensor.is_light", + "state": "off", + "when": state.last_updated.timestamp(), + } + ] + + await websocket_client.close() + await hass.async_block_till_done() + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_stream_consumer_stop_processing(hass, recorder_mock, hass_ws_client): + """Test we unsubscribe if the stream consumer fails or is canceled.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + await async_wait_recording_done(hass) + init_count = sum(hass.bus.async_listeners().values()) + hass.states.async_set("light.small", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_OFF) + await hass.async_block_till_done() + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + + after_ws_created_count = sum(hass.bus.async_listeners().values()) + + with patch.object(websocket_api, "MAX_PENDING_LOGBOOK_EVENTS", 5), patch.object( + websocket_api, "_async_events_consumer" + ): + await websocket_client.send_json( + { + "id": 7, + "type": "logbook/event_stream", + "start_time": now.isoformat(), + "entity_ids": ["light.small", "binary_sensor.is_light"], + } + ) + await async_wait_recording_done(hass) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + assert sum(hass.bus.async_listeners().values()) != init_count + for _ in range(5): + hass.states.async_set("binary_sensor.is_light", STATE_ON) + hass.states.async_set("binary_sensor.is_light", STATE_OFF) + await async_wait_recording_done(hass) + + # Check our listener got unsubscribed because + # the queue got full and the overload safety tripped + assert sum(hass.bus.async_listeners().values()) == after_ws_created_count + await websocket_client.close() + assert sum(hass.bus.async_listeners().values()) == init_count + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +@patch("homeassistant.components.logbook.websocket_api.MAX_RECORDER_WAIT", 0.15) +async def test_recorder_is_far_behind(hass, recorder_mock, hass_ws_client, caplog): + """Test we still start live streaming if the recorder is far behind.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + await async_wait_recording_done(hass) + device = await _async_mock_device_with_logbook_platform(hass) + await async_wait_recording_done(hass) + + # Block the recorder queue + await async_block_recorder(hass, 0.3) + await hass.async_block_till_done() + + websocket_client = await hass_ws_client() + await websocket_client.send_json( + { + "id": 7, + "type": "logbook/event_stream", + "start_time": now.isoformat(), + "device_ids": [device.id], + } + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + # There are no answers to our initial query + # so we get an empty reply. This is to ensure + # consumers of the api know there are no results + # and its not a failure case. This is useful + # in the frontend so we can tell the user there + # are no results vs waiting for them to appear + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [] + + hass.bus.async_fire("mock_event", {"device_id": device.id, "message": "1"}) + await hass.async_block_till_done() + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + {"domain": "test", "message": "1", "name": "device name", "when": ANY} + ] + + hass.bus.async_fire("mock_event", {"device_id": device.id, "message": "2"}) + await hass.async_block_till_done() + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [ + {"domain": "test", "message": "2", "name": "device name", "when": ANY} + ] + + await websocket_client.send_json( + {"id": 8, "type": "unsubscribe_events", "subscription": 7} + ) + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + + assert msg["id"] == 8 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + assert "Recorder is behind" in caplog.text + + +@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0) +async def test_subscribe_all_entities_have_uom(hass, recorder_mock, hass_ws_client): + """Test subscribe/unsubscribe logbook stream with entities that are always filtered.""" + now = dt_util.utcnow() + await asyncio.gather( + *[ + async_setup_component(hass, comp, {}) + for comp in ("homeassistant", "logbook", "automation", "script") + ] + ) + await async_wait_recording_done(hass) + + init_count = sum(hass.bus.async_listeners().values()) + hass.states.async_set("sensor.uom", "1", {ATTR_UNIT_OF_MEASUREMENT: "any"}) + hass.states.async_set("sensor.uom", "2", {ATTR_UNIT_OF_MEASUREMENT: "any"}) + hass.states.async_set("sensor.uom", "3", {ATTR_UNIT_OF_MEASUREMENT: "any"}) + + await async_wait_recording_done(hass) + websocket_client = await hass_ws_client() + await websocket_client.send_json( + { + "id": 7, + "type": "logbook/event_stream", + "start_time": now.isoformat(), + "entity_ids": ["sensor.uom"], + } + ) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + hass.states.async_set("sensor.uom", "1", {ATTR_UNIT_OF_MEASUREMENT: "any"}) + hass.states.async_set("sensor.uom", "2", {ATTR_UNIT_OF_MEASUREMENT: "any"}) + hass.states.async_set("sensor.uom", "3", {ATTR_UNIT_OF_MEASUREMENT: "any"}) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [] + + await websocket_client.close() + await hass.async_block_till_done() + + # Check our listener got unsubscribed + assert sum(hass.bus.async_listeners().values()) == init_count diff --git a/tests/components/lutron_caseta/test_logbook.py b/tests/components/lutron_caseta/test_logbook.py new file mode 100644 index 00000000000..3a202eadf58 --- /dev/null +++ b/tests/components/lutron_caseta/test_logbook.py @@ -0,0 +1,73 @@ +"""The tests for lutron caseta logbook.""" +from unittest.mock import patch + +from homeassistant.components.lutron_caseta.const import ( + ATTR_ACTION, + ATTR_AREA_NAME, + ATTR_BUTTON_NUMBER, + ATTR_DEVICE_NAME, + ATTR_LEAP_BUTTON_NUMBER, + ATTR_SERIAL, + ATTR_TYPE, + CONF_CA_CERTS, + CONF_CERTFILE, + CONF_KEYFILE, + DOMAIN, + LUTRON_CASETA_BUTTON_EVENT, +) +from homeassistant.const import ATTR_DEVICE_ID, CONF_HOST +from homeassistant.setup import async_setup_component + +from . import MockBridge + +from tests.common import MockConfigEntry +from tests.components.logbook.common import MockRow, mock_humanify + + +async def test_humanify_lutron_caseta_button_event(hass): + """Test humanifying lutron_caseta_button_events.""" + hass.config.components.add("recorder") + assert await async_setup_component(hass, "logbook", {}) + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "1.1.1.1", + CONF_KEYFILE: "", + CONF_CERTFILE: "", + CONF_CA_CERTS: "", + }, + unique_id="abc", + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.lutron_caseta.Smartbridge.create_tls", + return_value=MockBridge(can_connect=True), + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + await hass.async_block_till_done() + + (event1,) = mock_humanify( + hass, + [ + MockRow( + LUTRON_CASETA_BUTTON_EVENT, + { + ATTR_SERIAL: "123", + ATTR_DEVICE_ID: "1234", + ATTR_TYPE: "Pico3ButtonRaiseLower", + ATTR_LEAP_BUTTON_NUMBER: 3, + ATTR_BUTTON_NUMBER: 3, + ATTR_DEVICE_NAME: "Pico", + ATTR_AREA_NAME: "Living Room", + ATTR_ACTION: "press", + }, + ), + ], + ) + + assert event1["name"] == "Living Room Pico" + assert event1["domain"] == DOMAIN + assert event1["message"] == "press raise" diff --git a/tests/components/lyric/test_config_flow.py b/tests/components/lyric/test_config_flow.py index 25cc49c6c09..d9262e215fb 100644 --- a/tests/components/lyric/test_config_flow.py +++ b/tests/components/lyric/test_config_flow.py @@ -41,7 +41,7 @@ async def test_abort_if_no_configuration(hass): DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "missing_configuration" + assert result["reason"] == "missing_credentials" async def test_full_flow( diff --git a/tests/components/marytts/test_tts.py b/tests/components/marytts/test_tts.py index 843b6578746..60211f7dc0c 100644 --- a/tests/components/marytts/test_tts.py +++ b/tests/components/marytts/test_tts.py @@ -21,7 +21,7 @@ async def get_media_source_url(hass, media_content_id): if media_source.DOMAIN not in hass.config.components: assert await async_setup_component(hass, media_source.DOMAIN, {}) - resolved = await media_source.async_resolve_media(hass, media_content_id) + resolved = await media_source.async_resolve_media(hass, media_content_id, None) return resolved.url diff --git a/tests/components/mazda/test_sensor.py b/tests/components/mazda/test_sensor.py index f7fb379da51..f2e9039397b 100644 --- a/tests/components/mazda/test_sensor.py +++ b/tests/components/mazda/test_sensor.py @@ -75,6 +75,7 @@ async def test_sensors(hass): state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Front Left Tire Pressure" ) assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.PRESSURE assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT assert state.state == "35" @@ -90,6 +91,7 @@ async def test_sensors(hass): == "My Mazda3 Front Right Tire Pressure" ) assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.PRESSURE assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT assert state.state == "35" @@ -104,6 +106,7 @@ async def test_sensors(hass): state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Rear Left Tire Pressure" ) assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.PRESSURE assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT assert state.state == "33" @@ -118,6 +121,7 @@ async def test_sensors(hass): state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Rear Right Tire Pressure" ) assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.PRESSURE assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT assert state.state == "33" diff --git a/tests/components/meater/test_config_flow.py b/tests/components/meater/test_config_flow.py index 597b72c354a..11312111311 100644 --- a/tests/components/meater/test_config_flow.py +++ b/tests/components/meater/test_config_flow.py @@ -4,9 +4,8 @@ from unittest.mock import AsyncMock, patch from meater import AuthenticationError, ServiceUnavailableError import pytest -from homeassistant import data_entry_flow +from homeassistant import config_entries, data_entry_flow from homeassistant.components.meater import DOMAIN -from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from tests.common import MockConfigEntry @@ -35,7 +34,7 @@ async def test_duplicate_error(hass): ) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=conf + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT @@ -48,7 +47,7 @@ async def test_unknown_auth_error(hass, mock_meater): conf = {CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"} result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=conf + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf ) assert result["errors"] == {"base": "unknown_auth_error"} @@ -59,7 +58,7 @@ async def test_invalid_credentials(hass, mock_meater): conf = {CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"} result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=conf + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf ) assert result["errors"] == {"base": "invalid_auth"} @@ -72,7 +71,7 @@ async def test_service_unavailable(hass, mock_meater): conf = {CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"} result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=conf + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf ) assert result["errors"] == {"base": "service_unavailable_error"} @@ -82,7 +81,7 @@ async def test_user_flow(hass, mock_meater): conf = {CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"} result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=None + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=None ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" @@ -106,3 +105,42 @@ async def test_user_flow(hass, mock_meater): CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123", } + + +async def test_reauth_flow(hass, mock_meater): + """Test that the reauth flow works.""" + data = { + CONF_USERNAME: "user@host.com", + CONF_PASSWORD: "password123", + } + mock_config = MockConfigEntry( + domain=DOMAIN, + unique_id="user@host.com", + data=data, + ) + mock_config.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_REAUTH}, + data=data, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "reauth_confirm" + assert result["errors"] is None + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"password": "passwordabc"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result2["reason"] == "reauth_successful" + + config_entry = hass.config_entries.async_entries(DOMAIN)[0] + assert config_entry.data == { + CONF_USERNAME: "user@host.com", + CONF_PASSWORD: "passwordabc", + } diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index aa5e1b164f4..eceb7e9ec4f 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -4,6 +4,9 @@ import base64 from http import HTTPStatus from unittest.mock import patch +import pytest +import voluptuous as vol + from homeassistant.components import media_player from homeassistant.components.media_player.browse_media import BrowseMedia from homeassistant.components.websocket_api.const import TYPE_RESULT @@ -251,3 +254,63 @@ async def test_group_members_available_when_off(hass): state = hass.states.get("media_player.bedroom") assert state.state == STATE_OFF assert "group_members" in state.attributes + + +@pytest.mark.parametrize( + "input,expected", + ( + (True, media_player.MediaPlayerEnqueue.ADD), + (False, media_player.MediaPlayerEnqueue.PLAY), + ("play", media_player.MediaPlayerEnqueue.PLAY), + ("next", media_player.MediaPlayerEnqueue.NEXT), + ("add", media_player.MediaPlayerEnqueue.ADD), + ("replace", media_player.MediaPlayerEnqueue.REPLACE), + ), +) +async def test_enqueue_rewrite(hass, input, expected): + """Test that group_members are still available when media_player is off.""" + await async_setup_component( + hass, "media_player", {"media_player": {"platform": "demo"}} + ) + await hass.async_block_till_done() + + # Fake group support for DemoYoutubePlayer + with patch( + "homeassistant.components.demo.media_player.DemoYoutubePlayer.play_media", + ) as mock_play_media: + await hass.services.async_call( + "media_player", + "play_media", + { + "entity_id": "media_player.bedroom", + "media_content_type": "music", + "media_content_id": "1234", + "enqueue": input, + }, + blocking=True, + ) + + assert len(mock_play_media.mock_calls) == 1 + assert mock_play_media.mock_calls[0][2]["enqueue"] == expected + + +async def test_enqueue_alert_exclusive(hass): + """Test that alert and enqueue cannot be used together.""" + await async_setup_component( + hass, "media_player", {"media_player": {"platform": "demo"}} + ) + await hass.async_block_till_done() + + with pytest.raises(vol.Invalid): + await hass.services.async_call( + "media_player", + "play_media", + { + "entity_id": "media_player.bedroom", + "media_content_type": "music", + "media_content_id": "1234", + "enqueue": "play", + "announce": True, + }, + blocking=True, + ) diff --git a/tests/components/media_player/test_reproduce_state.py b/tests/components/media_player/test_reproduce_state.py index f1a243337e1..f880130d4bd 100644 --- a/tests/components/media_player/test_reproduce_state.py +++ b/tests/components/media_player/test_reproduce_state.py @@ -6,7 +6,6 @@ from homeassistant.components.media_player.const import ( ATTR_INPUT_SOURCE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, - ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_SOUND_MODE, @@ -253,7 +252,6 @@ async def test_play_media(hass): value_1 = "dummy_1" value_2 = "dummy_2" - value_3 = "dummy_3" await async_reproduce_states( hass, @@ -275,7 +273,6 @@ async def test_play_media(hass): { ATTR_MEDIA_CONTENT_TYPE: value_1, ATTR_MEDIA_CONTENT_ID: value_2, - ATTR_MEDIA_ENQUEUE: value_3, }, ) ], @@ -294,5 +291,4 @@ async def test_play_media(hass): "entity_id": ENTITY_1, ATTR_MEDIA_CONTENT_TYPE: value_1, ATTR_MEDIA_CONTENT_ID: value_2, - ATTR_MEDIA_ENQUEUE: value_3, } diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index 491b1972cb6..33dd263c46c 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -103,12 +103,32 @@ async def test_async_resolve_media(hass): media = await media_source.async_resolve_media( hass, media_source.generate_media_source_id(media_source.DOMAIN, "local/test.mp3"), + None, ) assert isinstance(media, media_source.models.PlayMedia) assert media.url == "/media/local/test.mp3" assert media.mime_type == "audio/mpeg" +@patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()) +async def test_async_resolve_media_no_entity(hass, caplog): + """Test browse media.""" + assert await async_setup_component(hass, media_source.DOMAIN, {}) + await hass.async_block_till_done() + + media = await media_source.async_resolve_media( + hass, + media_source.generate_media_source_id(media_source.DOMAIN, "local/test.mp3"), + ) + assert isinstance(media, media_source.models.PlayMedia) + assert media.url == "/media/local/test.mp3" + assert media.mime_type == "audio/mpeg" + assert ( + "calls media_source.async_resolve_media without passing an entity_id" + in caplog.text + ) + + async def test_async_unresolve_media(hass): """Test browse media.""" assert await async_setup_component(hass, media_source.DOMAIN, {}) @@ -116,15 +136,17 @@ async def test_async_unresolve_media(hass): # Test no media content with pytest.raises(media_source.Unresolvable): - await media_source.async_resolve_media(hass, "") + await media_source.async_resolve_media(hass, "", None) # Test invalid media content with pytest.raises(media_source.Unresolvable): - await media_source.async_resolve_media(hass, "invalid") + await media_source.async_resolve_media(hass, "invalid", None) # Test invalid media source with pytest.raises(media_source.Unresolvable): - await media_source.async_resolve_media(hass, "media-source://media_source2") + await media_source.async_resolve_media( + hass, "media-source://media_source2", None + ) async def test_websocket_browse_media(hass, hass_ws_client): @@ -242,4 +264,4 @@ async def test_browse_resolve_without_setup(): await media_source.async_browse_media(Mock(data={}), None) with pytest.raises(media_source.Unresolvable): - await media_source.async_resolve_media(Mock(data={}), None) + await media_source.async_resolve_media(Mock(data={}), None, None) diff --git a/tests/components/mikrotik/test_init.py b/tests/components/mikrotik/test_init.py index 281b70e36be..bc00602789c 100644 --- a/tests/components/mikrotik/test_init.py +++ b/tests/components/mikrotik/test_init.py @@ -25,7 +25,7 @@ async def test_successful_config_entry(hass): mock_registry = Mock() with patch.object(mikrotik, "MikrotikHub") as mock_hub, patch( - "homeassistant.helpers.device_registry.async_get_registry", + "homeassistant.components.mikrotik.dr.async_get", return_value=mock_registry, ): mock_hub.return_value.async_setup = AsyncMock(return_value=True) @@ -76,7 +76,7 @@ async def test_unload_entry(hass): entry.add_to_hass(hass) with patch.object(mikrotik, "MikrotikHub") as mock_hub, patch( - "homeassistant.helpers.device_registry.async_get_registry", + "homeassistant.helpers.device_registry.async_get", return_value=Mock(), ): mock_hub.return_value.async_setup = AsyncMock(return_value=True) diff --git a/tests/components/mobile_app/test_logbook.py b/tests/components/mobile_app/test_logbook.py new file mode 100644 index 00000000000..b151bd11a26 --- /dev/null +++ b/tests/components/mobile_app/test_logbook.py @@ -0,0 +1,50 @@ +"""The tests for mobile_app logbook.""" + +from homeassistant.components.mobile_app.logbook import ( + DOMAIN, + IOS_EVENT_ZONE_ENTERED, + IOS_EVENT_ZONE_EXITED, +) +from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.setup import async_setup_component + +from tests.components.logbook.common import MockRow, mock_humanify + + +async def test_humanify_ios_events(hass): + """Test humanifying ios events.""" + hass.config.components.add("recorder") + assert await async_setup_component(hass, "logbook", {}) + await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + hass.states.async_set( + "zone.bad_place", + "0", + {ATTR_FRIENDLY_NAME: "passport control", ATTR_ICON: "mdi:airplane-marker"}, + ) + await hass.async_block_till_done() + + (event1, event2) = mock_humanify( + hass, + [ + MockRow( + IOS_EVENT_ZONE_ENTERED, + {"sourceDeviceName": "test_phone", "zone": "zone.happy_place"}, + ), + MockRow( + IOS_EVENT_ZONE_EXITED, + {"sourceDeviceName": "test_phone", "zone": "zone.bad_place"}, + ), + ], + ) + + assert event1["name"] == "test_phone" + assert event1["domain"] == DOMAIN + assert event1["message"] == "entered zone zone.happy_place" + assert event1["icon"] == "mdi:crosshairs-gps" + assert event1["entity_id"] == "zone.happy_place" + + assert event2["name"] == "test_phone" + assert event2["domain"] == DOMAIN + assert event2["message"] == "exited zone passport control" + assert event2["icon"] == "mdi:airplane-marker" + assert event2["entity_id"] == "zone.bad_place" diff --git a/tests/components/mobile_app/test_sensor.py b/tests/components/mobile_app/test_sensor.py index 7eb99df8d8f..c0f7f126a49 100644 --- a/tests/components/mobile_app/test_sensor.py +++ b/tests/components/mobile_app/test_sensor.py @@ -340,3 +340,103 @@ async def test_sensor_datetime( assert entity.attributes["device_class"] == device_class assert entity.domain == "sensor" assert entity.state == state_value + + +async def test_default_disabling_entity(hass, create_registrations, webhook_client): + """Test that sensors can be disabled by default upon registration.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + reg_resp = await webhook_client.post( + webhook_url, + json={ + "type": "register_sensor", + "data": { + "name": "Battery State", + "type": "sensor", + "unique_id": "battery_state", + "disabled": True, + }, + }, + ) + + assert reg_resp.status == HTTPStatus.CREATED + + json = await reg_resp.json() + assert json == {"success": True} + await hass.async_block_till_done() + + entity = hass.states.get("sensor.test_1_battery_state") + assert entity is None + + assert ( + er.async_get(hass).async_get("sensor.test_1_battery_state").disabled_by + == er.RegistryEntryDisabler.INTEGRATION + ) + + +async def test_updating_disabled_sensor(hass, create_registrations, webhook_client): + """Test that sensors return error if disabled in instance.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + reg_resp = await webhook_client.post( + webhook_url, + json={ + "type": "register_sensor", + "data": { + "name": "Battery State", + "state": None, + "type": "sensor", + "unique_id": "battery_state", + }, + }, + ) + + assert reg_resp.status == HTTPStatus.CREATED + + update_resp = await webhook_client.post( + webhook_url, + json={ + "type": "update_sensor_states", + "data": [ + { + "icon": "mdi:battery-unknown", + "state": 123, + "type": "sensor", + "unique_id": "battery_state", + }, + ], + }, + ) + + assert update_resp.status == HTTPStatus.OK + + json = await update_resp.json() + assert json["battery_state"]["success"] is True + assert "is_disabled" not in json["battery_state"] + + er.async_get(hass).async_update_entity( + "sensor.test_1_battery_state", disabled_by=er.RegistryEntryDisabler.USER + ) + + update_resp = await webhook_client.post( + webhook_url, + json={ + "type": "update_sensor_states", + "data": [ + { + "icon": "mdi:battery-unknown", + "state": 123, + "type": "sensor", + "unique_id": "battery_state", + }, + ], + }, + ) + + assert update_resp.status == HTTPStatus.OK + + json = await update_resp.json() + assert json["battery_state"]["success"] is True + assert json["battery_state"]["is_disabled"] is True diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index 3eac2d97b19..0bc237b1c11 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -254,10 +254,30 @@ async def test_webhook_handle_get_zones(hass, create_registrations, webhook_clie async def test_webhook_handle_get_config(hass, create_registrations, webhook_client): """Test that we can get config properly.""" - resp = await webhook_client.post( - "/api/webhook/{}".format(create_registrations[1]["webhook_id"]), - json={"type": "get_config"}, - ) + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + # Create two entities + for sensor in ( + { + "name": "Battery State", + "type": "sensor", + "unique_id": "battery-state-id", + }, + { + "name": "Battery Charging", + "type": "sensor", + "unique_id": "battery-charging-id", + "disabled": True, + }, + ): + reg_resp = await webhook_client.post( + webhook_url, + json={"type": "register_sensor", "data": sensor}, + ) + assert reg_resp.status == HTTPStatus.CREATED + + resp = await webhook_client.post(webhook_url, json={"type": "get_config"}) assert resp.status == HTTPStatus.OK @@ -279,6 +299,11 @@ async def test_webhook_handle_get_config(hass, create_registrations, webhook_cli "components": hass_config["components"], "version": hass_config["version"], "theme_color": "#03A9F4", # Default frontend theme color + "entities": { + "mock-device-id": {"disabled": False}, + "battery-state-id": {"disabled": False}, + "battery-charging-id": {"disabled": True}, + }, } assert expected_dict == json @@ -902,6 +927,7 @@ async def test_reregister_sensor(hass, create_registrations, webhook_client): assert entry.unit_of_measurement is None assert entry.entity_category is None assert entry.original_icon == "mdi:cellphone" + assert entry.disabled_by is None reg_resp = await webhook_client.post( webhook_url, @@ -917,6 +943,7 @@ async def test_reregister_sensor(hass, create_registrations, webhook_client): "entity_category": "diagnostic", "icon": "mdi:new-icon", "unit_of_measurement": "%", + "disabled": True, }, }, ) @@ -928,3 +955,21 @@ async def test_reregister_sensor(hass, create_registrations, webhook_client): assert entry.unit_of_measurement == "%" assert entry.entity_category == "diagnostic" assert entry.original_icon == "mdi:new-icon" + assert entry.disabled_by == er.RegistryEntryDisabler.INTEGRATION + + reg_resp = await webhook_client.post( + webhook_url, + json={ + "type": "register_sensor", + "data": { + "name": "New Name", + "type": "sensor", + "unique_id": "abcd", + "disabled": False, + }, + }, + ) + + assert reg_resp.status == HTTPStatus.CREATED + entry = ent_reg.async_get("sensor.test_1_battery_state") + assert entry.disabled_by is None diff --git a/tests/components/motion_blinds/test_config_flow.py b/tests/components/motion_blinds/test_config_flow.py index 57ad3c20779..57ab45d9dbb 100644 --- a/tests/components/motion_blinds/test_config_flow.py +++ b/tests/components/motion_blinds/test_config_flow.py @@ -336,10 +336,14 @@ async def test_dhcp_flow(hass): assert result["step_id"] == "connect" assert result["errors"] == {} - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_API_KEY: TEST_API_KEY}, - ) + with patch( + "homeassistant.components.motion_blinds.gateway.AsyncMotionMulticast.Start_listen", + side_effect=OSError, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_API_KEY: TEST_API_KEY}, + ) assert result["type"] == "create_entry" assert result["title"] == DEFAULT_GATEWAY_NAME diff --git a/tests/components/motioneye/test_camera.py b/tests/components/motioneye/test_camera.py index 15462f6c592..8ba9fb07715 100644 --- a/tests/components/motioneye/test_camera.py +++ b/tests/components/motioneye/test_camera.py @@ -42,7 +42,6 @@ from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID, CONF_URL from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er -from homeassistant.helpers.device_registry import async_get_registry import homeassistant.util.dt as dt_util from . import ( @@ -138,8 +137,8 @@ async def test_setup_camera_new_data_same(hass: HomeAssistant) -> None: async def test_setup_camera_new_data_camera_removed(hass: HomeAssistant) -> None: """Test a data refresh with a removed camera.""" - device_registry = await async_get_registry(hass) - entity_registry = await er.async_get_registry(hass) + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) client = create_mock_motioneye_client() config_entry = await setup_mock_motioneye_config_entry(hass, client=client) @@ -328,7 +327,7 @@ async def test_device_info(hass: HomeAssistant) -> None: assert device.model == MOTIONEYE_MANUFACTURER assert device.name == TEST_CAMERA_NAME - entity_registry = await er.async_get_registry(hass) + entity_registry = er.async_get(hass) entities_from_device = [ entry.entity_id for entry in er.async_entries_for_device(entity_registry, device.id) diff --git a/tests/components/motioneye/test_media_source.py b/tests/components/motioneye/test_media_source.py index 6979d5c645d..2cf31c21da7 100644 --- a/tests/components/motioneye/test_media_source.py +++ b/tests/components/motioneye/test_media_source.py @@ -80,7 +80,7 @@ async def test_async_browse_media_success(hass: HomeAssistant) -> None: client = create_mock_motioneye_client() config = await setup_mock_motioneye_config_entry(hass, client=client) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( config_entry_id=config.entry_id, identifiers={TEST_CAMERA_DEVICE_IDENTIFIER}, @@ -301,7 +301,7 @@ async def test_async_browse_media_images_success(hass: HomeAssistant) -> None: client = create_mock_motioneye_client() config = await setup_mock_motioneye_config_entry(hass, client=client) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( config_entry_id=config.entry_id, identifiers={TEST_CAMERA_DEVICE_IDENTIFIER}, @@ -353,7 +353,7 @@ async def test_async_resolve_media_success(hass: HomeAssistant) -> None: config = await setup_mock_motioneye_config_entry(hass, client=client) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( config_entry_id=config.entry_id, identifiers={TEST_CAMERA_DEVICE_IDENTIFIER}, @@ -367,6 +367,7 @@ async def test_async_resolve_media_success(hass: HomeAssistant) -> None: f"{const.URI_SCHEME}{DOMAIN}" f"/{TEST_CONFIG_ENTRY_ID}#{device.id}#movies#/foo.mp4" ), + None, ) assert media == PlayMedia(url="http://movie-url", mime_type="video/mp4") assert client.get_movie_url.call_args == call(TEST_CAMERA_ID, "/foo.mp4") @@ -379,6 +380,7 @@ async def test_async_resolve_media_success(hass: HomeAssistant) -> None: f"{const.URI_SCHEME}{DOMAIN}" f"/{TEST_CONFIG_ENTRY_ID}#{device.id}#images#/foo.jpg" ), + None, ) assert media == PlayMedia(url="http://image-url", mime_type="image/jpeg") assert client.get_image_url.call_args == call(TEST_CAMERA_ID, "/foo.jpg") @@ -391,7 +393,7 @@ async def test_async_resolve_media_failure(hass: HomeAssistant) -> None: config = await setup_mock_motioneye_config_entry(hass, client=client) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( config_entry_id=config.entry_id, identifiers={TEST_CAMERA_DEVICE_IDENTIFIER}, @@ -409,18 +411,20 @@ async def test_async_resolve_media_failure(hass: HomeAssistant) -> None: # URI doesn't contain necessary components. with pytest.raises(Unresolvable): - await media_source.async_resolve_media(hass, f"{const.URI_SCHEME}{DOMAIN}/foo") + await media_source.async_resolve_media( + hass, f"{const.URI_SCHEME}{DOMAIN}/foo", None + ) # Config entry doesn't exist. with pytest.raises(MediaSourceError): await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/1#2#3#4" + hass, f"{const.URI_SCHEME}{DOMAIN}/1#2#3#4", None ) # Device doesn't exist. with pytest.raises(MediaSourceError): await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{TEST_CONFIG_ENTRY_ID}#2#3#4" + hass, f"{const.URI_SCHEME}{DOMAIN}/{TEST_CONFIG_ENTRY_ID}#2#3#4", None ) # Device identifiers are incorrect (no camera id) @@ -431,6 +435,7 @@ async def test_async_resolve_media_failure(hass: HomeAssistant) -> None: f"{const.URI_SCHEME}{DOMAIN}" f"/{TEST_CONFIG_ENTRY_ID}#{broken_device_1.id}#images#4" ), + None, ) # Device identifiers are incorrect (non integer camera id) @@ -441,6 +446,7 @@ async def test_async_resolve_media_failure(hass: HomeAssistant) -> None: f"{const.URI_SCHEME}{DOMAIN}" f"/{TEST_CONFIG_ENTRY_ID}#{broken_device_2.id}#images#4" ), + None, ) # Kind is incorrect. @@ -448,6 +454,7 @@ async def test_async_resolve_media_failure(hass: HomeAssistant) -> None: await media_source.async_resolve_media( hass, f"{const.URI_SCHEME}{DOMAIN}/{TEST_CONFIG_ENTRY_ID}#{device.id}#games#moo", + None, ) # Playback URL raises exception. @@ -459,6 +466,7 @@ async def test_async_resolve_media_failure(hass: HomeAssistant) -> None: f"{const.URI_SCHEME}{DOMAIN}" f"/{TEST_CONFIG_ENTRY_ID}#{device.id}#movies#/foo.mp4" ), + None, ) # Media path does not start with '/' @@ -470,6 +478,7 @@ async def test_async_resolve_media_failure(hass: HomeAssistant) -> None: f"{const.URI_SCHEME}{DOMAIN}" f"/{TEST_CONFIG_ENTRY_ID}#{device.id}#movies#foo.mp4" ), + None, ) # Media missing path. diff --git a/tests/components/motioneye/test_sensor.py b/tests/components/motioneye/test_sensor.py index 5ab6fc46f49..ea07834976b 100644 --- a/tests/components/motioneye/test_sensor.py +++ b/tests/components/motioneye/test_sensor.py @@ -91,7 +91,7 @@ async def test_sensor_device_info(hass: HomeAssistant) -> None: device = device_registry.async_get_device({device_identifer}) assert device - entity_registry = await er.async_get_registry(hass) + entity_registry = er.async_get(hass) entities_from_device = [ entry.entity_id for entry in er.async_entries_for_device(entity_registry, device.id) diff --git a/tests/components/motioneye/test_switch.py b/tests/components/motioneye/test_switch.py index 09db967e5e3..03c39a4b542 100644 --- a/tests/components/motioneye/test_switch.py +++ b/tests/components/motioneye/test_switch.py @@ -196,7 +196,7 @@ async def test_switch_device_info(hass: HomeAssistant) -> None: device = device_registry.async_get_device({device_identifer}) assert device - entity_registry = await er.async_get_registry(hass) + entity_registry = er.async_get(hass) entities_from_device = [ entry.entity_id for entry in er.async_entries_for_device(entity_registry, device.id) diff --git a/tests/components/motioneye/test_web_hooks.py b/tests/components/motioneye/test_web_hooks.py index c7aaa4a8638..442ea9eb782 100644 --- a/tests/components/motioneye/test_web_hooks.py +++ b/tests/components/motioneye/test_web_hooks.py @@ -67,7 +67,7 @@ async def test_setup_camera_without_webhook(hass: HomeAssistant) -> None: client = create_mock_motioneye_client() config_entry = await setup_mock_motioneye_config_entry(hass, client=client) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_device( identifiers={TEST_CAMERA_DEVICE_IDENTIFIER} ) @@ -122,7 +122,7 @@ async def test_setup_camera_with_wrong_webhook( ) await hass.async_block_till_done() - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_device( identifiers={TEST_CAMERA_DEVICE_IDENTIFIER} ) @@ -175,7 +175,7 @@ async def test_setup_camera_with_old_webhook( ) assert client.async_set_camera.called - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_device( identifiers={TEST_CAMERA_DEVICE_IDENTIFIER} ) @@ -211,7 +211,7 @@ async def test_setup_camera_with_correct_webhook( hass, data={CONF_URL: TEST_URL, CONF_WEBHOOK_ID: "webhook_secret_id"} ) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={TEST_CAMERA_DEVICE_IDENTIFIER}, @@ -281,7 +281,7 @@ async def test_good_query(hass: HomeAssistant, hass_client_no_auth: Any) -> None """Test good callbacks.""" await async_setup_component(hass, "http", {"http": {}}) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) client = create_mock_motioneye_client() config_entry = await setup_mock_motioneye_config_entry(hass, client=client) @@ -378,7 +378,7 @@ async def test_event_media_data(hass: HomeAssistant, hass_client_no_auth: Any) - """Test an event with a file path generates media data.""" await async_setup_component(hass, "http", {"http": {}}) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) client = create_mock_motioneye_client() config_entry = await setup_mock_motioneye_config_entry(hass, client=client) diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index 5c73463924e..f4d76d5474c 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -57,6 +57,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -848,3 +849,15 @@ async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): domain = alarm_control_panel.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = alarm_control_panel.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index 5055550be7c..e4a48b07940 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -41,6 +41,7 @@ from .test_common import ( help_test_reloadable_late, help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -973,3 +974,15 @@ async def test_skip_restoring_state_with_over_due_expire_trigger( assert await async_setup_component(hass, domain, {domain: config3}) await hass.async_block_till_done() assert "Skip state recovery after reload for binary_sensor.test3" in caplog.text + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = binary_sensor.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_button.py b/tests/components/mqtt/test_button.py index 83ef7a42705..941f08e541c 100644 --- a/tests/components/mqtt/test_button.py +++ b/tests/components/mqtt/test_button.py @@ -30,6 +30,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -413,3 +414,15 @@ async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): domain = button.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = button.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index 07fd7dc2c14..204103152a7 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -1,4 +1,6 @@ """The tests for mqtt camera component.""" +from base64 import b64encode +import copy from http import HTTPStatus import json from unittest.mock import patch @@ -31,6 +33,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -64,6 +67,34 @@ async def test_run_camera_setup(hass, hass_client_no_auth, mqtt_mock): assert body == "beer" +async def test_run_camera_b64_encoded(hass, hass_client_no_auth, mqtt_mock): + """Test that it fetches the given encoded payload.""" + topic = "test/camera" + await async_setup_component( + hass, + "camera", + { + "camera": { + "platform": "mqtt", + "topic": topic, + "name": "Test Camera", + "encoding": "b64", + } + }, + ) + await hass.async_block_till_done() + + url = hass.states.get("camera.test_camera").attributes["entity_picture"] + + async_fire_mqtt_message(hass, topic, b64encode(b"grass")) + + client = await hass_client_no_auth() + resp = await client.get(url) + assert resp.status == HTTPStatus.OK + body = await resp.text() + assert body == "grass" + + async def test_availability_when_connection_lost(hass, mqtt_mock): """Test availability after MQTT disconnection.""" await help_test_availability_when_connection_lost( @@ -260,3 +291,15 @@ async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): domain = camera.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = camera.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 0c1db16d6fe..98af86248e4 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -19,23 +19,14 @@ from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_ACTIONS, DOMAIN as CLIMATE_DOMAIN, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, PRESET_AWAY, PRESET_ECO, PRESET_NONE, - SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.components.mqtt.climate import MQTT_CLIMATE_ATTRIBUTES_BLOCKED -from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF +from homeassistant.const import ATTR_TEMPERATURE from homeassistant.setup import async_setup_component from .test_common import ( @@ -62,6 +53,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -171,12 +163,12 @@ async def test_supported_features(hass, mqtt_mock): state = hass.states.get(ENTITY_CLIMATE) support = ( - SUPPORT_TARGET_TEMPERATURE - | SUPPORT_SWING_MODE - | SUPPORT_FAN_MODE - | SUPPORT_PRESET_MODE - | SUPPORT_AUX_HEAT - | SUPPORT_TARGET_TEMPERATURE_RANGE + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.SWING_MODE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.AUX_HEAT + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE ) assert state.attributes.get("supported_features") == support @@ -190,12 +182,12 @@ async def test_get_hvac_modes(hass, mqtt_mock): state = hass.states.get(ENTITY_CLIMATE) modes = state.attributes.get("hvac_modes") assert [ - HVAC_MODE_AUTO, - STATE_OFF, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, + HVACMode.AUTO, + HVACMode.OFF, + HVACMode.COOL, + HVACMode.HEAT, + HVACMode.DRY, + HVACMode.FAN_ONLY, ] == modes @@ -1770,3 +1762,15 @@ async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): domain = CLIMATE_DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = CLIMATE_DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 8cf7353d196..b5bb5732617 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -1690,3 +1690,24 @@ async def help_test_reloadable_late(hass, caplog, tmp_path, domain, config): assert hass.states.get(f"{domain}.test_new_1") assert hass.states.get(f"{domain}.test_new_2") assert hass.states.get(f"{domain}.test_new_3") + + +async def help_test_setup_manual_entity_from_yaml( + hass, + caplog, + tmp_path, + platform, + config, +): + """Help to test setup from yaml through configuration entry.""" + config_structure = {mqtt.DOMAIN: {platform: config}} + + await async_setup_component(hass, mqtt.DOMAIN, config_structure) + # Mock config entry + entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) + entry.add_to_hass(hass) + + with patch("paho.mqtt.client.Client") as mock_client: + mock_client().connect = lambda *args: 0 + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index aad6fa5d9ca..285af765ab4 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -1,4 +1,6 @@ """The tests for the MQTT cover platform.""" + +import copy from unittest.mock import patch import pytest @@ -68,6 +70,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -3206,3 +3209,15 @@ async def test_encoding_subscribable_topics( attribute_value, skip_raw_test=True, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = cover.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_device_tracker.py b/tests/components/mqtt/test_device_tracker.py index c85fcef7dc4..020fbad6166 100644 --- a/tests/components/mqtt/test_device_tracker.py +++ b/tests/components/mqtt/test_device_tracker.py @@ -1,22 +1,17 @@ -"""The tests for the MQTT device tracker platform.""" +"""The tests for the MQTT device tracker platform using configuration.yaml.""" from unittest.mock import patch -import pytest - from homeassistant.components.device_tracker.const import DOMAIN, SOURCE_TYPE_BLUETOOTH from homeassistant.const import CONF_PLATFORM, STATE_HOME, STATE_NOT_HOME from homeassistant.setup import async_setup_component +from .test_common import help_test_setup_manual_entity_from_yaml + from tests.common import async_fire_mqtt_message -@pytest.fixture(autouse=True) -def setup_comp(hass, mqtt_mock): - """Set up mqtt component.""" - pass - - -async def test_ensure_device_tracker_platform_validation(hass): +# Deprecated in HA Core 2022.6 +async def test_legacy_ensure_device_tracker_platform_validation(hass, mqtt_mock): """Test if platform validation was done.""" async def mock_setup_scanner(hass, config, see, discovery_info=None): @@ -37,7 +32,8 @@ async def test_ensure_device_tracker_platform_validation(hass): assert mock_sp.call_count == 1 -async def test_new_message(hass, mock_device_tracker_conf): +# Deprecated in HA Core 2022.6 +async def test_legacy_new_message(hass, mock_device_tracker_conf, mqtt_mock): """Test new message.""" dev_id = "paulus" entity_id = f"{DOMAIN}.{dev_id}" @@ -53,7 +49,10 @@ async def test_new_message(hass, mock_device_tracker_conf): assert hass.states.get(entity_id).state == location -async def test_single_level_wildcard_topic(hass, mock_device_tracker_conf): +# Deprecated in HA Core 2022.6 +async def test_legacy_single_level_wildcard_topic( + hass, mock_device_tracker_conf, mqtt_mock +): """Test single level wildcard topic.""" dev_id = "paulus" entity_id = f"{DOMAIN}.{dev_id}" @@ -72,7 +71,10 @@ async def test_single_level_wildcard_topic(hass, mock_device_tracker_conf): assert hass.states.get(entity_id).state == location -async def test_multi_level_wildcard_topic(hass, mock_device_tracker_conf): +# Deprecated in HA Core 2022.6 +async def test_legacy_multi_level_wildcard_topic( + hass, mock_device_tracker_conf, mqtt_mock +): """Test multi level wildcard topic.""" dev_id = "paulus" entity_id = f"{DOMAIN}.{dev_id}" @@ -91,7 +93,10 @@ async def test_multi_level_wildcard_topic(hass, mock_device_tracker_conf): assert hass.states.get(entity_id).state == location -async def test_single_level_wildcard_topic_not_matching(hass, mock_device_tracker_conf): +# Deprecated in HA Core 2022.6 +async def test_legacy_single_level_wildcard_topic_not_matching( + hass, mock_device_tracker_conf, mqtt_mock +): """Test not matching single level wildcard topic.""" dev_id = "paulus" entity_id = f"{DOMAIN}.{dev_id}" @@ -110,7 +115,10 @@ async def test_single_level_wildcard_topic_not_matching(hass, mock_device_tracke assert hass.states.get(entity_id) is None -async def test_multi_level_wildcard_topic_not_matching(hass, mock_device_tracker_conf): +# Deprecated in HA Core 2022.6 +async def test_legacy_multi_level_wildcard_topic_not_matching( + hass, mock_device_tracker_conf, mqtt_mock +): """Test not matching multi level wildcard topic.""" dev_id = "paulus" entity_id = f"{DOMAIN}.{dev_id}" @@ -129,8 +137,9 @@ async def test_multi_level_wildcard_topic_not_matching(hass, mock_device_tracker assert hass.states.get(entity_id) is None -async def test_matching_custom_payload_for_home_and_not_home( - hass, mock_device_tracker_conf +# Deprecated in HA Core 2022.6 +async def test_legacy_matching_custom_payload_for_home_and_not_home( + hass, mock_device_tracker_conf, mqtt_mock ): """Test custom payload_home sets state to home and custom payload_not_home sets state to not_home.""" dev_id = "paulus" @@ -161,8 +170,9 @@ async def test_matching_custom_payload_for_home_and_not_home( assert hass.states.get(entity_id).state == STATE_NOT_HOME -async def test_not_matching_custom_payload_for_home_and_not_home( - hass, mock_device_tracker_conf +# Deprecated in HA Core 2022.6 +async def test_legacy_not_matching_custom_payload_for_home_and_not_home( + hass, mock_device_tracker_conf, mqtt_mock ): """Test not matching payload does not set state to home or not_home.""" dev_id = "paulus" @@ -191,7 +201,8 @@ async def test_not_matching_custom_payload_for_home_and_not_home( assert hass.states.get(entity_id).state != STATE_NOT_HOME -async def test_matching_source_type(hass, mock_device_tracker_conf): +# Deprecated in HA Core 2022.6 +async def test_legacy_matching_source_type(hass, mock_device_tracker_conf, mqtt_mock): """Test setting source type.""" dev_id = "paulus" entity_id = f"{DOMAIN}.{dev_id}" @@ -215,3 +226,21 @@ async def test_matching_source_type(hass, mock_device_tracker_conf): async_fire_mqtt_message(hass, topic, location) await hass.async_block_till_done() assert hass.states.get(entity_id).attributes["source_type"] == SOURCE_TYPE_BLUETOOTH + + +async def test_setup_with_modern_schema( + hass, caplog, tmp_path, mock_device_tracker_conf +): + """Test setup using the modern schema.""" + dev_id = "jan" + entity_id = f"{DOMAIN}.{dev_id}" + topic = "/location/jan" + + hass.config.components = {"zone"} + config = {"name": dev_id, "state_topic": topic} + + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, DOMAIN, config + ) + + assert hass.states.get(entity_id) is not None diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index a1ef6ea477a..9215ab651b2 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -24,6 +24,7 @@ from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, + async_capture_events, async_fire_mqtt_message, mock_device_registry, mock_entity_platform, @@ -438,14 +439,7 @@ async def test_rediscover(hass, mqtt_mock, caplog): async def test_rapid_rediscover(hass, mqtt_mock, caplog): """Test immediate rediscover of removed component.""" - events = [] - - @ha.callback - def callback(event): - """Verify event got called.""" - events.append(event) - - hass.bus.async_listen(EVENT_STATE_CHANGED, callback) + events = async_capture_events(hass, EVENT_STATE_CHANGED) async_fire_mqtt_message( hass, diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index 64b5d272af8..1a533db63c0 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -55,6 +55,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -1805,3 +1806,15 @@ async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): domain = fan.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = fan.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_humidifier.py b/tests/components/mqtt/test_humidifier.py index 4aa5ff2350b..bc00d3afffb 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -56,6 +56,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -1182,3 +1183,15 @@ async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): domain = humidifier.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = humidifier.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 763cbfc1b71..07c39d70df0 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1,5 +1,6 @@ """The tests for the MQTT component.""" import asyncio +import copy from datetime import datetime, timedelta from functools import partial import json @@ -30,6 +31,8 @@ from homeassistant.helpers.entity import Entity from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow +from .test_common import help_test_setup_manual_entity_from_yaml + from tests.common import ( MockConfigEntry, async_fire_mqtt_message, @@ -1279,6 +1282,51 @@ async def test_setup_override_configuration(hass, caplog, tmp_path): assert calls_username_password_set[0][1] == "somepassword" +async def test_setup_manual_mqtt_with_platform_key(hass, caplog, tmp_path): + """Test set up a manual MQTT item with a platform key.""" + config = {"platform": "mqtt", "name": "test", "command_topic": "test-topic"} + await help_test_setup_manual_entity_from_yaml( + hass, + caplog, + tmp_path, + "light", + config, + ) + assert ( + "Invalid config for [light]: [platform] is an invalid option for [light]. " + "Check: light->platform. (See ?, line ?)" in caplog.text + ) + + +async def test_setup_manual_mqtt_with_invalid_config(hass, caplog, tmp_path): + """Test set up a manual MQTT item with an invalid config.""" + config = {"name": "test"} + await help_test_setup_manual_entity_from_yaml( + hass, + caplog, + tmp_path, + "light", + config, + ) + assert ( + "Invalid config for [light]: required key not provided @ data['command_topic']." + " Got None. (See ?, line ?)" in caplog.text + ) + + +async def test_setup_manual_mqtt_empty_platform(hass, caplog, tmp_path): + """Test set up a manual MQTT platform without items.""" + config = None + await help_test_setup_manual_entity_from_yaml( + hass, + caplog, + tmp_path, + "light", + config, + ) + assert "voluptuous.error.MultipleInvalid" not in caplog.text + + async def test_setup_mqtt_client_protocol(hass): """Test MQTT client protocol setup.""" entry = MockConfigEntry( @@ -1628,7 +1676,8 @@ async def test_setup_entry_with_config_override(hass, device_reg, mqtt_client_mo # User sets up a config entry entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) + with patch("homeassistant.components.mqtt.PLATFORMS", []): + assert await hass.config_entries.async_setup(entry.entry_id) # Discover a device to verify the entry was setup correctly async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) @@ -1666,11 +1715,6 @@ async def test_update_incomplete_entry( "The 'broker' option is deprecated, please remove it from your configuration" in caplog.text ) - assert ( - "Deprecated configuration settings found in configuration.yaml. These settings " - "from your configuration entry will override: {'broker': 'yaml_broker'}" - in caplog.text - ) # Discover a device to verify the entry was setup correctly async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) @@ -2380,26 +2424,6 @@ async def test_publish_json_from_template(hass, mqtt_mock): assert mqtt_mock.async_publish.call_args[0][1] == test_str -@pytest.mark.usefixtures("mock_integration_frame") -async def test_service_info_compatibility(hass, caplog): - """Test compatibility with old-style dict. - - To be removed in 2022.6 - """ - discovery_info = mqtt.MqttServiceInfo( - topic="tasmota/discovery/DC4F220848A2/config", - payload="", - qos=0, - retain=False, - subscribed_topic="tasmota/discovery/#", - timestamp=None, - ) - - with patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()): - assert discovery_info["topic"] == "tasmota/discovery/DC4F220848A2/config" - assert "Detected integration that accessed discovery_info['topic']" in caplog.text - - async def test_subscribe_connection_status(hass, mqtt_mock, mqtt_client_mock): """Test connextion status subscription.""" mqtt_connected_calls = [] @@ -2433,3 +2457,23 @@ async def test_subscribe_connection_status(hass, mqtt_mock, mqtt_client_mock): assert len(mqtt_connected_calls) == 2 assert mqtt_connected_calls[0] is True assert mqtt_connected_calls[1] is False + + +async def test_one_deprecation_warning_per_platform(hass, mqtt_mock, caplog): + """Test a deprecation warning is is logged once per platform.""" + platform = "light" + config = {"platform": "mqtt", "command_topic": "test-topic"} + config1 = copy.deepcopy(config) + config1["name"] = "test1" + config2 = copy.deepcopy(config) + config2["name"] = "test2" + await async_setup_component(hass, platform, {platform: [config1, config2]}) + await hass.async_block_till_done() + count = 0 + for record in caplog.records: + if record.levelname == "WARNING" and ( + f"Manually configured MQTT {platform}(s) found under platform key '{platform}'" + in record.message + ): + count += 1 + assert count == 1 diff --git a/tests/components/mqtt/test_legacy_vacuum.py b/tests/components/mqtt/test_legacy_vacuum.py index 992275105c5..f451079e0f0 100644 --- a/tests/components/mqtt/test_legacy_vacuum.py +++ b/tests/components/mqtt/test_legacy_vacuum.py @@ -56,6 +56,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -901,3 +902,15 @@ async def test_encoding_subscribable_topics( attribute_value, skip_raw_test=True, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = vacuum.DOMAIN + config = deepcopy(DEFAULT_CONFIG) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index ee59928c0c8..957178da14f 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -237,6 +237,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -3674,3 +3675,15 @@ async def test_sending_mqtt_effect_command_with_template(hass, mqtt_mock): state = hass.states.get("light.test") assert state.state == STATE_ON assert state.attributes.get("effect") == "colorloop" + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = light.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 93bb9b0f573..962bf534370 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -131,6 +131,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -2038,3 +2039,15 @@ async def test_encoding_subscribable_topics( init_payload, skip_raw_test=True, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = light.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index 4461cf14ef4..a88fc094f6d 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -69,6 +69,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -1213,3 +1214,15 @@ async def test_encoding_subscribable_topics( attribute_value, init_payload, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = light.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_lock.py b/tests/components/mqtt/test_lock.py index 86e21a261a3..ef752ef8749 100644 --- a/tests/components/mqtt/test_lock.py +++ b/tests/components/mqtt/test_lock.py @@ -1,4 +1,5 @@ """The tests for the MQTT lock platform.""" +import copy from unittest.mock import patch import pytest @@ -44,6 +45,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -675,3 +677,15 @@ async def test_encoding_subscribable_topics( attribute, attribute_value, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = LOCK_DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_number.py b/tests/components/mqtt/test_number.py index 73c1da357fa..4eb8fdec351 100644 --- a/tests/components/mqtt/test_number.py +++ b/tests/components/mqtt/test_number.py @@ -1,4 +1,5 @@ """The tests for mqtt number component.""" +import copy import json from unittest.mock import patch @@ -50,6 +51,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -729,3 +731,15 @@ async def test_encoding_subscribable_topics( attribute, attribute_value, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = number.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_scene.py b/tests/components/mqtt/test_scene.py index 1ccacc1c5ee..15bbd3964e6 100644 --- a/tests/components/mqtt/test_scene.py +++ b/tests/components/mqtt/test_scene.py @@ -20,6 +20,7 @@ from .test_common import ( help_test_discovery_update_unchanged, help_test_reloadable, help_test_reloadable_late, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, ) @@ -191,3 +192,15 @@ async def test_reloadable_late(hass, mqtt_client_mock, caplog, tmp_path): domain = scene.DOMAIN config = DEFAULT_CONFIG[domain] await help_test_reloadable_late(hass, caplog, tmp_path, domain, config) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = scene.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_select.py b/tests/components/mqtt/test_select.py index 069c0dfe4c9..cf5abf55854 100644 --- a/tests/components/mqtt/test_select.py +++ b/tests/components/mqtt/test_select.py @@ -41,6 +41,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -610,3 +611,15 @@ async def test_encoding_subscribable_topics( attribute, attribute_value, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = select.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index b653e04c82e..befb5785cdd 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -55,6 +55,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -1106,3 +1107,15 @@ async def test_encoding_subscribable_topics( attribute_value, skip_raw_test=True, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = sensor.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_siren.py b/tests/components/mqtt/test_siren.py index f39154badc9..197ed34b7e4 100644 --- a/tests/components/mqtt/test_siren.py +++ b/tests/components/mqtt/test_siren.py @@ -42,6 +42,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -895,3 +896,15 @@ async def test_encoding_subscribable_topics( attribute, attribute_value, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = siren.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_state_vacuum.py b/tests/components/mqtt/test_state_vacuum.py index 8691aa73323..3f752f1b528 100644 --- a/tests/components/mqtt/test_state_vacuum.py +++ b/tests/components/mqtt/test_state_vacuum.py @@ -58,6 +58,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -648,3 +649,15 @@ async def test_encoding_subscribable_topics( attribute_value, skip_raw_test=True, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = vacuum.DOMAIN + config = deepcopy(DEFAULT_CONFIG) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index a7dd0d8c31e..699f0de87f0 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -39,6 +39,7 @@ from .test_common import ( help_test_setting_attribute_via_mqtt_json_message, help_test_setting_attribute_with_template, help_test_setting_blocked_attribute_via_mqtt_json_message, + help_test_setup_manual_entity_from_yaml, help_test_unique_id, help_test_update_with_json_attrs_bad_JSON, help_test_update_with_json_attrs_not_dict, @@ -585,3 +586,15 @@ async def test_encoding_subscribable_topics( attribute, attribute_value, ) + + +async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): + """Test setup manual configured MQTT entity.""" + platform = switch.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + await help_test_setup_manual_entity_from_yaml( + hass, caplog, tmp_path, platform, config + ) + assert hass.states.get(f"{platform}.test") is not None diff --git a/tests/components/neato/test_config_flow.py b/tests/components/neato/test_config_flow.py index 48bdf247f51..7e187f1e2fd 100644 --- a/tests/components/neato/test_config_flow.py +++ b/tests/components/neato/test_config_flow.py @@ -27,7 +27,6 @@ async def test_full_flow( "neato", { "neato": {"client_id": CLIENT_ID, "client_secret": CLIENT_SECRET}, - "http": {"base_url": "https://example.com"}, }, ) @@ -99,7 +98,6 @@ async def test_reauth( "neato", { "neato": {"client_id": CLIENT_ID, "client_secret": CLIENT_SECRET}, - "http": {"base_url": "https://example.com"}, }, ) diff --git a/tests/components/nest/test_climate_sdm.py b/tests/components/nest/test_climate_sdm.py index 5f3efa362b3..123742607ad 100644 --- a/tests/components/nest/test_climate_sdm.py +++ b/tests/components/nest/test_climate_sdm.py @@ -6,8 +6,10 @@ pubsub subscriber. """ from collections.abc import Awaitable, Callable +from http import HTTPStatus from typing import Any +import aiohttp from google_nest_sdm.auth import AbstractAuth from google_nest_sdm.event import EventMessage import pytest @@ -41,6 +43,7 @@ from homeassistant.components.climate.const import ( ) from homeassistant.const import ATTR_TEMPERATURE from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .common import ( DEVICE_COMMAND, @@ -1380,3 +1383,60 @@ async def test_thermostat_invalid_set_preset_mode( # Preset is unchanged assert thermostat.attributes[ATTR_PRESET_MODE] == PRESET_NONE assert thermostat.attributes[ATTR_PRESET_MODES] == [PRESET_ECO, PRESET_NONE] + + +async def test_thermostat_hvac_mode_failure( + hass: HomeAssistant, + setup_platform: PlatformSetup, + auth: FakeAuth, + create_device: CreateDevice, +) -> None: + """Test setting an hvac_mode that is not supported.""" + create_device.create( + { + "sdm.devices.traits.ThermostatHvac": {"status": "OFF"}, + "sdm.devices.traits.ThermostatMode": { + "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], + "mode": "OFF", + }, + "sdm.devices.traits.Fan": { + "timerMode": "OFF", + "timerTimeout": "2019-05-10T03:22:54Z", + }, + "sdm.devices.traits.ThermostatEco": { + "availableModes": ["MANUAL_ECO", "OFF"], + "mode": "OFF", + "heatCelsius": 15.0, + "coolCelsius": 28.0, + }, + } + ) + await setup_platform() + + assert len(hass.states.async_all()) == 1 + thermostat = hass.states.get("climate.my_thermostat") + assert thermostat is not None + assert thermostat.state == HVAC_MODE_OFF + assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF + + auth.responses = [aiohttp.web.Response(status=HTTPStatus.BAD_REQUEST)] + with pytest.raises(HomeAssistantError): + await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT) + await hass.async_block_till_done() + + auth.responses = [aiohttp.web.Response(status=HTTPStatus.BAD_REQUEST)] + with pytest.raises(HomeAssistantError): + await common.async_set_temperature( + hass, hvac_mode=HVAC_MODE_HEAT, temperature=25.0 + ) + await hass.async_block_till_done() + + auth.responses = [aiohttp.web.Response(status=HTTPStatus.BAD_REQUEST)] + with pytest.raises(HomeAssistantError): + await common.async_set_fan_mode(hass, FAN_ON) + await hass.async_block_till_done() + + auth.responses = [aiohttp.web.Response(status=HTTPStatus.BAD_REQUEST)] + with pytest.raises(HomeAssistantError): + await common.async_set_preset_mode(hass, PRESET_ECO) + await hass.async_block_till_done() diff --git a/tests/components/nest/test_diagnostics.py b/tests/components/nest/test_diagnostics.py index b69f5970c2d..8e28222e356 100644 --- a/tests/components/nest/test_diagnostics.py +++ b/tests/components/nest/test_diagnostics.py @@ -2,7 +2,7 @@ from unittest.mock import patch -from google_nest_sdm.exceptions import ApiException, SubscriberException +from google_nest_sdm.exceptions import SubscriberException import pytest from homeassistant.components.nest.const import DOMAIN @@ -139,34 +139,6 @@ async def test_setup_susbcriber_failure( assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == {} -async def test_device_manager_failure( - hass, - hass_client, - config_entry, - setup_platform, - create_device, -): - """Test configuration error.""" - create_device.create(raw_data=DEVICE_API_DATA) - await setup_platform() - assert config_entry.state is ConfigEntryState.LOADED - - device_registry = dr.async_get(hass) - device = device_registry.async_get_device({(DOMAIN, NEST_DEVICE_ID)}) - assert device is not None - - with patch( - "homeassistant.components.nest.diagnostics._get_nest_devices", - side_effect=ApiException("Device manager failure"), - ): - assert await get_diagnostics_for_config_entry( - hass, hass_client, config_entry - ) == {"error": "Device manager failure"} - assert await get_diagnostics_for_device( - hass, hass_client, config_entry, device - ) == {"error": "Device manager failure"} - - @pytest.mark.parametrize("nest_test_config", [TEST_CONFIG_LEGACY]) async def test_legacy_config_entry_diagnostics( hass, hass_client, config_entry, setup_base_platform diff --git a/tests/components/nest/test_media_source.py b/tests/components/nest/test_media_source.py index 1536d0bee1e..09a3f9f625c 100644 --- a/tests/components/nest/test_media_source.py +++ b/tests/components/nest/test_media_source.py @@ -361,7 +361,7 @@ async def test_camera_event(hass, auth, hass_client): # Resolving the event links to the media media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier}", None ) assert media.url == f"/api/nest/event_media/{device.id}/{event_identifier}" assert media.mime_type == "image/jpeg" @@ -374,7 +374,7 @@ async def test_camera_event(hass, auth, hass_client): # Resolving the device id points to the most recent event media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}", None ) assert media.url == f"/api/nest/event_media/{device.id}/{event_identifier}" assert media.mime_type == "image/jpeg" @@ -535,7 +535,7 @@ async def test_multiple_image_events_in_session(hass, auth, hass_client): # Resolve the most recent event media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier2}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier2}", None ) assert media.url == f"/api/nest/event_media/{device.id}/{event_identifier2}" assert media.mime_type == "image/jpeg" @@ -548,7 +548,7 @@ async def test_multiple_image_events_in_session(hass, auth, hass_client): # Resolving the event links to the media media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier1}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier1}", None ) assert media.url == f"/api/nest/event_media/{device.id}/{event_identifier1}" assert media.mime_type == "image/jpeg" @@ -632,7 +632,7 @@ async def test_multiple_clip_preview_events_in_session(hass, auth, hass_client): # to the same clip preview media clip object. # Resolve media for the first event media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier1}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier1}", None ) assert media.url == f"/api/nest/event_media/{device.id}/{event_identifier1}" assert media.mime_type == "video/mp4" @@ -645,7 +645,7 @@ async def test_multiple_clip_preview_events_in_session(hass, auth, hass_client): # Resolve media for the second event media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier1}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier1}", None ) assert media.url == f"/api/nest/event_media/{device.id}/{event_identifier1}" assert media.mime_type == "video/mp4" @@ -712,6 +712,7 @@ async def test_resolve_missing_event_id(hass, auth): await media_source.async_resolve_media( hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}", + None, ) @@ -723,6 +724,7 @@ async def test_resolve_invalid_device_id(hass, auth): await media_source.async_resolve_media( hass, f"{const.URI_SCHEME}{DOMAIN}/invalid-device-id/GXXWRWVeHNUlUU3V3MGV3bUOYW...", + None, ) @@ -740,6 +742,7 @@ async def test_resolve_invalid_event_id(hass, auth): media = await media_source.async_resolve_media( hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/GXXWRWVeHNUlUU3V3MGV3bUOYW...", + None, ) assert ( media.url == f"/api/nest/event_media/{device.id}/GXXWRWVeHNUlUU3V3MGV3bUOYW..." @@ -835,7 +838,7 @@ async def test_camera_event_clip_preview(hass, auth, hass_client, mp4): # Resolving the event links to the media media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier}", None ) assert media.url == f"/api/nest/event_media/{device.id}/{event_identifier}" assert media.mime_type == "video/mp4" @@ -921,7 +924,7 @@ async def test_event_media_failure(hass, auth, hass_client): # Resolving the event links to the media media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{event_identifier}", None ) assert media.url == f"/api/nest/event_media/{device.id}/{event_identifier}" assert media.mime_type == "image/jpeg" @@ -1128,7 +1131,7 @@ async def test_media_store_persistence(hass, auth, hass_client, event_store): event_identifier = browse.children[0].identifier media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{event_identifier}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{event_identifier}", None ) assert media.url == f"/api/nest/event_media/{event_identifier}" assert media.mime_type == "video/mp4" @@ -1182,7 +1185,7 @@ async def test_media_store_persistence(hass, auth, hass_client, event_store): event_identifier = browse.children[0].identifier media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{event_identifier}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{event_identifier}", None ) assert media.url == f"/api/nest/event_media/{event_identifier}" assert media.mime_type == "video/mp4" @@ -1234,7 +1237,7 @@ async def test_media_store_save_filesystem_error(hass, auth, hass_client): event = browse.children[0] media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/{event.identifier}" + hass, f"{const.URI_SCHEME}{DOMAIN}/{event.identifier}", None ) assert media.url == f"/api/nest/event_media/{event.identifier}" assert media.mime_type == "video/mp4" diff --git a/tests/components/netatmo/test_media_source.py b/tests/components/netatmo/test_media_source.py index db1a79145b4..390da95496a 100644 --- a/tests/components/netatmo/test_media_source.py +++ b/tests/components/netatmo/test_media_source.py @@ -79,7 +79,7 @@ async def test_async_browse_media(hass): # Test successful event resolve media = await media_source.async_resolve_media( - hass, f"{const.URI_SCHEME}{DOMAIN}/events/12:34:56:78:90:ab/1599152672" + hass, f"{const.URI_SCHEME}{DOMAIN}/events/12:34:56:78:90:ab/1599152672", None ) assert media == PlayMedia( url="http:///files/high/index.m3u8", mime_type="application/x-mpegURL" diff --git a/tests/components/netgear/test_config_flow.py b/tests/components/netgear/test_config_flow.py index 33c634e250a..d46284f5049 100644 --- a/tests/components/netgear/test_config_flow.py +++ b/tests/components/netgear/test_config_flow.py @@ -59,6 +59,7 @@ SSL = False USERNAME = "Home_Assistant" PASSWORD = "password" SSDP_URL = f"http://{HOST}:{PORT}/rootDesc.xml" +SSDP_URLipv6 = f"http://[::ffff:a00:1]:{PORT}/rootDesc.xml" SSDP_URL_SLL = f"https://{HOST}:{PORT}/rootDesc.xml" @@ -234,6 +235,32 @@ async def test_ssdp_already_configured(hass): assert result["reason"] == "already_configured" +async def test_ssdp_ipv6(hass): + """Test ssdp abort when using a ipv6 address.""" + MockConfigEntry( + domain=DOMAIN, + data={CONF_PASSWORD: PASSWORD}, + unique_id=SERIAL, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_SSDP}, + data=ssdp.SsdpServiceInfo( + ssdp_usn="mock_usn", + ssdp_st="mock_st", + ssdp_location=SSDP_URLipv6, + upnp={ + ssdp.ATTR_UPNP_MODEL_NUMBER: "RBR20", + ssdp.ATTR_UPNP_PRESENTATION_URL: URL, + ssdp.ATTR_UPNP_SERIAL: SERIAL, + }, + ), + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "not_ipv4_address" + + async def test_ssdp(hass, service): """Test ssdp step.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/nexia/test_binary_sensor.py b/tests/components/nexia/test_binary_sensor.py index 64b2946ee2f..966f49bbb15 100644 --- a/tests/components/nexia/test_binary_sensor.py +++ b/tests/components/nexia/test_binary_sensor.py @@ -13,7 +13,7 @@ async def test_create_binary_sensors(hass): state = hass.states.get("binary_sensor.master_suite_blower_active") assert state.state == STATE_ON expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "friendly_name": "Master Suite Blower Active", } # Only test for a subset of attributes in case @@ -25,7 +25,7 @@ async def test_create_binary_sensors(hass): state = hass.states.get("binary_sensor.downstairs_east_wing_blower_active") assert state.state == STATE_OFF expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "friendly_name": "Downstairs East Wing Blower Active", } # Only test for a subset of attributes in case diff --git a/tests/components/nexia/test_climate.py b/tests/components/nexia/test_climate.py index edd5d56e79a..797fbb4014c 100644 --- a/tests/components/nexia/test_climate.py +++ b/tests/components/nexia/test_climate.py @@ -12,15 +12,13 @@ async def test_climate_zones(hass): state = hass.states.get("climate.nick_office") assert state.state == HVACMode.HEAT_COOL expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "current_humidity": 52.0, "current_temperature": 22.8, "dehumidify_setpoint": 45.0, - "dehumidify_supported": True, "fan_mode": "Auto", "fan_modes": ["Auto", "On", "Circulate"], "friendly_name": "Nick Office", - "humidify_supported": False, "humidity": 45.0, "hvac_action": "cooling", "hvac_modes": ["off", "auto", "heat_cool", "heat", "cool"], @@ -35,7 +33,6 @@ async def test_climate_zones(hass): "target_temp_low": 17.2, "target_temp_step": 1.0, "temperature": None, - "zone_status": "Relieving Air", } # Only test for a subset of attributes in case # HA changes the implementation and a new one appears @@ -47,15 +44,13 @@ async def test_climate_zones(hass): assert state.state == HVACMode.HEAT_COOL expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "current_humidity": 36.0, "current_temperature": 25.0, "dehumidify_setpoint": 50.0, - "dehumidify_supported": True, "fan_mode": "Auto", "fan_modes": ["Auto", "On", "Circulate"], "friendly_name": "Kitchen", - "humidify_supported": False, "humidity": 50.0, "hvac_action": "idle", "hvac_modes": ["off", "auto", "heat_cool", "heat", "cool"], @@ -70,7 +65,6 @@ async def test_climate_zones(hass): "target_temp_low": 17.2, "target_temp_step": 1.0, "temperature": None, - "zone_status": "Idle", } # Only test for a subset of attributes in case diff --git a/tests/components/nexia/test_config_flow.py b/tests/components/nexia/test_config_flow.py index bd3eb9180e7..5bfd7fb582c 100644 --- a/tests/components/nexia/test_config_flow.py +++ b/tests/components/nexia/test_config_flow.py @@ -1,9 +1,10 @@ """Test the nexia config flow.""" +import asyncio from unittest.mock import MagicMock, patch +import aiohttp from nexia.const import BRAND_ASAIR, BRAND_NEXIA import pytest -from requests.exceptions import ConnectTimeout, HTTPError from homeassistant import config_entries from homeassistant.components.nexia.const import CONF_BRAND, DOMAIN @@ -52,7 +53,10 @@ async def test_form_invalid_auth(hass): DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("homeassistant.components.nexia.config_flow.NexiaHome.login"): + with patch("homeassistant.components.nexia.config_flow.NexiaHome.login",), patch( + "homeassistant.components.nexia.config_flow.NexiaHome.get_name", + return_value=None, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -74,7 +78,7 @@ async def test_form_cannot_connect(hass): with patch( "homeassistant.components.nexia.config_flow.NexiaHome.login", - side_effect=ConnectTimeout, + side_effect=asyncio.TimeoutError, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -95,11 +99,11 @@ async def test_form_invalid_auth_http_401(hass): DOMAIN, context={"source": config_entries.SOURCE_USER} ) - response_mock = MagicMock() - type(response_mock).status_code = 401 with patch( "homeassistant.components.nexia.config_flow.NexiaHome.login", - side_effect=HTTPError(response=response_mock), + side_effect=aiohttp.ClientResponseError( + status=401, request_info=MagicMock(), history=MagicMock() + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -120,11 +124,11 @@ async def test_form_cannot_connect_not_found(hass): DOMAIN, context={"source": config_entries.SOURCE_USER} ) - response_mock = MagicMock() - type(response_mock).status_code = 404 with patch( "homeassistant.components.nexia.config_flow.NexiaHome.login", - side_effect=HTTPError(response=response_mock), + side_effect=aiohttp.ClientResponseError( + status=404, request_info=MagicMock(), history=MagicMock() + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/nexia/test_scene.py b/tests/components/nexia/test_scene.py index 4a325552e80..309af6b523d 100644 --- a/tests/components/nexia/test_scene.py +++ b/tests/components/nexia/test_scene.py @@ -10,7 +10,7 @@ async def test_automation_scenes(hass): state = hass.states.get("scene.away_short") expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "description": "When IFTTT activates the automation Upstairs " "West Wing will permanently hold the heat to 63.0 " "and cool to 80.0 AND Downstairs East Wing will " @@ -37,7 +37,7 @@ async def test_automation_scenes(hass): state = hass.states.get("scene.power_outage") expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "description": "When IFTTT activates the automation Upstairs " "West Wing will permanently hold the heat to 55.0 " "and cool to 90.0 AND Downstairs East Wing will " @@ -56,7 +56,7 @@ async def test_automation_scenes(hass): state = hass.states.get("scene.power_restored") expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "description": "When IFTTT activates the automation Upstairs " "West Wing will Run Schedule AND Downstairs East " "Wing will Run Schedule AND Downstairs West Wing " diff --git a/tests/components/nexia/test_sensor.py b/tests/components/nexia/test_sensor.py index 289947fbdf6..ad15fc308f4 100644 --- a/tests/components/nexia/test_sensor.py +++ b/tests/components/nexia/test_sensor.py @@ -14,7 +14,7 @@ async def test_create_sensors(hass): assert state.state == "23" expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "device_class": "temperature", "friendly_name": "Nick Office Temperature", "unit_of_measurement": TEMP_CELSIUS, @@ -28,7 +28,7 @@ async def test_create_sensors(hass): state = hass.states.get("sensor.nick_office_zone_setpoint_status") assert state.state == "Permanent Hold" expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "friendly_name": "Nick Office Zone Setpoint Status", } # Only test for a subset of attributes in case @@ -41,7 +41,7 @@ async def test_create_sensors(hass): assert state.state == "Relieving Air" expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "friendly_name": "Nick Office Zone Status", } # Only test for a subset of attributes in case @@ -54,7 +54,7 @@ async def test_create_sensors(hass): assert state.state == "auto" expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "friendly_name": "Master Suite Air Cleaner Mode", } # Only test for a subset of attributes in case @@ -67,7 +67,7 @@ async def test_create_sensors(hass): assert state.state == "69.0" expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "friendly_name": "Master Suite Current Compressor Speed", "unit_of_measurement": PERCENTAGE, } @@ -81,7 +81,7 @@ async def test_create_sensors(hass): assert state.state == "30.6" expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "device_class": "temperature", "friendly_name": "Master Suite Outdoor Temperature", "unit_of_measurement": TEMP_CELSIUS, @@ -96,7 +96,7 @@ async def test_create_sensors(hass): assert state.state == "52.0" expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "device_class": "humidity", "friendly_name": "Master Suite Relative Humidity", "unit_of_measurement": PERCENTAGE, @@ -111,7 +111,7 @@ async def test_create_sensors(hass): assert state.state == "69.0" expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "friendly_name": "Master Suite Requested Compressor Speed", "unit_of_measurement": PERCENTAGE, } @@ -125,7 +125,7 @@ async def test_create_sensors(hass): assert state.state == "Cooling" expected_attributes = { - "attribution": "Data provided by mynexia.com", + "attribution": "Data provided by Trane Technologies", "friendly_name": "Master Suite System Status", } # Only test for a subset of attributes in case diff --git a/tests/components/nexia/util.py b/tests/components/nexia/util.py index b6d5c697a18..d564ccc351c 100644 --- a/tests/components/nexia/util.py +++ b/tests/components/nexia/util.py @@ -3,13 +3,13 @@ from unittest.mock import patch import uuid from nexia.home import NexiaHome -import requests_mock from homeassistant.components.nexia.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture +from tests.test_util.aiohttp import mock_aiohttp_client async def async_init_integration( @@ -21,17 +21,18 @@ async def async_init_integration( house_fixture = "nexia/mobile_houses_123456.json" session_fixture = "nexia/session_123456.json" sign_in_fixture = "nexia/sign_in.json" - nexia = NexiaHome(auto_login=False) - - with requests_mock.mock() as m, patch( + with mock_aiohttp_client() as mock_session, patch( "nexia.home.load_or_create_uuid", return_value=uuid.uuid4() ): - m.post(nexia.API_MOBILE_SESSION_URL, text=load_fixture(session_fixture)) - m.get( + nexia = NexiaHome(mock_session) + mock_session.post( + nexia.API_MOBILE_SESSION_URL, text=load_fixture(session_fixture) + ) + mock_session.get( nexia.API_MOBILE_HOUSES_URL.format(house_id=123456), text=load_fixture(house_fixture), ) - m.post( + mock_session.post( nexia.API_MOBILE_ACCOUNTS_SIGN_IN_URL, text=load_fixture(sign_in_fixture), ) diff --git a/tests/components/nws/test_sensor.py b/tests/components/nws/test_sensor.py index aa5ca3bf66c..5a55907e53b 100644 --- a/tests/components/nws/test_sensor.py +++ b/tests/components/nws/test_sensor.py @@ -4,6 +4,7 @@ import pytest from homeassistant.components.nws.const import ATTRIBUTION, DOMAIN, SENSOR_TYPES from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNKNOWN +from homeassistant.helpers import entity_registry as er from homeassistant.util import slugify from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM @@ -33,7 +34,7 @@ async def test_imperial_metric( hass, units, result_observation, result_forecast, mock_simple_nws, no_weather ): """Test with imperial and metric units.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) for description in SENSOR_TYPES: registry.async_get_or_create( @@ -66,7 +67,7 @@ async def test_none_values(hass, mock_simple_nws, no_weather): instance = mock_simple_nws.return_value instance.observation = NONE_OBSERVATION - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) for description in SENSOR_TYPES: registry.async_get_or_create( diff --git a/tests/components/octoprint/test_binary_sensor.py b/tests/components/octoprint/test_binary_sensor.py index 55e240eb282..e4a028f346a 100644 --- a/tests/components/octoprint/test_binary_sensor.py +++ b/tests/components/octoprint/test_binary_sensor.py @@ -1,6 +1,7 @@ """The tests for Octoptint binary sensor module.""" from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE +from homeassistant.helpers import entity_registry as er from . import init_integration @@ -16,7 +17,7 @@ async def test_sensors(hass): } await init_integration(hass, "binary_sensor", printer=printer) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) state = hass.states.get("binary_sensor.octoprint_printing") assert state is not None @@ -37,7 +38,7 @@ async def test_sensors_printer_offline(hass): """Test the underlying sensors when the printer is offline.""" await init_integration(hass, "binary_sensor", printer=None) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) state = hass.states.get("binary_sensor.octoprint_printing") assert state is not None diff --git a/tests/components/onewire/__init__.py b/tests/components/onewire/__init__.py index 0ab8b8f6917..d189db8af1a 100644 --- a/tests/components/onewire/__init__.py +++ b/tests/components/onewire/__init__.py @@ -7,7 +7,6 @@ from unittest.mock import MagicMock from pyownet.protocol import ProtocolError -from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_IDENTIFIERS, @@ -29,7 +28,6 @@ from .const import ( ATTR_UNIQUE_ID, FIXED_ATTRIBUTES, MOCK_OWPROXY_DEVICES, - MOCK_SYSBUS_DEVICES, ) @@ -181,30 +179,3 @@ def _setup_owproxy_mock_device_reads( device_sensors = mock_device.get(platform, []) for expected_sensor in device_sensors: sub_read_side_effect.append(expected_sensor[ATTR_INJECT_READS]) - - -def setup_sysbus_mock_devices( - platform: str, device_ids: list[str] -) -> tuple[list[str], list[Any]]: - """Set up mock for sysbus.""" - glob_result = [] - read_side_effect = [] - - for device_id in device_ids: - mock_device = MOCK_SYSBUS_DEVICES[device_id] - - # Setup directory listing - glob_result += [f"/{DEFAULT_SYSBUS_MOUNT_DIR}/{device_id}"] - - # Setup sub-device reads - device_sensors = mock_device.get(platform, []) - for expected_sensor in device_sensors: - if isinstance(expected_sensor[ATTR_INJECT_READS], list): - read_side_effect += expected_sensor[ATTR_INJECT_READS] - else: - read_side_effect.append(expected_sensor[ATTR_INJECT_READS]) - - # Ensure enough read side effect - read_side_effect.extend([FileNotFoundError("Missing injected value")] * 20) - - return (glob_result, read_side_effect) diff --git a/tests/components/onewire/conftest.py b/tests/components/onewire/conftest.py index ab456c7d7df..9b8b53859ea 100644 --- a/tests/components/onewire/conftest.py +++ b/tests/components/onewire/conftest.py @@ -4,15 +4,9 @@ from unittest.mock import MagicMock, patch from pyownet.protocol import ConnError import pytest -from homeassistant.components.onewire.const import ( - CONF_MOUNT_DIR, - CONF_TYPE_OWSERVER, - CONF_TYPE_SYSBUS, - DEFAULT_SYSBUS_MOUNT_DIR, - DOMAIN, -) +from homeassistant.components.onewire.const import DOMAIN from homeassistant.config_entries import SOURCE_USER, ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant from .const import MOCK_OWPROXY_DEVICES @@ -33,7 +27,6 @@ def get_config_entry(hass: HomeAssistant) -> ConfigEntry: domain=DOMAIN, source=SOURCE_USER, data={ - CONF_TYPE: CONF_TYPE_OWSERVER, CONF_HOST: "1.2.3.4", CONF_PORT: 1234, }, @@ -49,24 +42,6 @@ def get_config_entry(hass: HomeAssistant) -> ConfigEntry: return config_entry -@pytest.fixture(name="sysbus_config_entry") -def get_sysbus_config_entry(hass: HomeAssistant) -> ConfigEntry: - """Create and register mock config entry.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - source=SOURCE_USER, - data={ - CONF_TYPE: CONF_TYPE_SYSBUS, - CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR, - }, - unique_id=f"{CONF_TYPE_SYSBUS}:{DEFAULT_SYSBUS_MOUNT_DIR}", - options={}, - entry_id="3", - ) - config_entry.add_to_hass(hass) - return config_entry - - @pytest.fixture(name="owproxy") def get_owproxy() -> MagicMock: """Mock owproxy.""" @@ -82,12 +57,3 @@ def get_owproxy_with_connerror() -> MagicMock: side_effect=ConnError, ) as owproxy: yield owproxy - - -@pytest.fixture(name="sysbus") -def get_sysbus() -> MagicMock: - """Mock sysbus.""" - with patch( - "homeassistant.components.onewire.onewirehub.os.path.isdir", return_value=True - ): - yield diff --git a/tests/components/onewire/const.py b/tests/components/onewire/const.py index d77b374c7c4..c28ec017a9b 100644 --- a/tests/components/onewire/const.py +++ b/tests/components/onewire/const.py @@ -1,5 +1,4 @@ """Constants for 1-Wire integration.""" -from pi1wire import InvalidCRCException, UnsupportResponseException from pyownet.protocol import Error as ProtocolError from homeassistant.components.binary_sensor import BinarySensorDeviceClass @@ -1131,142 +1130,3 @@ MOCK_OWPROXY_DEVICES = { ], }, } - -MOCK_SYSBUS_DEVICES = { - "00-111111111111": { - ATTR_UNKNOWN_DEVICE: True, - }, - "10-111111111111": { - ATTR_DEVICE_INFO: { - ATTR_IDENTIFIERS: {(DOMAIN, "10-111111111111")}, - ATTR_MANUFACTURER: MANUFACTURER_MAXIM, - ATTR_MODEL: "10", - ATTR_NAME: "10-111111111111", - }, - Platform.SENSOR: [ - { - ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, - ATTR_ENTITY_ID: "sensor.10_111111111111_temperature", - ATTR_INJECT_READS: 25.123, - ATTR_STATE: "25.1", - ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, - ATTR_UNIQUE_ID: "/sys/bus/w1/devices/10-111111111111/w1_slave", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - }, - ], - }, - "22-111111111111": { - ATTR_DEVICE_INFO: { - ATTR_IDENTIFIERS: {(DOMAIN, "22-111111111111")}, - ATTR_MANUFACTURER: MANUFACTURER_MAXIM, - ATTR_MODEL: "22", - ATTR_NAME: "22-111111111111", - }, - Platform.SENSOR: [ - { - ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, - ATTR_ENTITY_ID: "sensor.22_111111111111_temperature", - ATTR_INJECT_READS: FileNotFoundError, - ATTR_STATE: STATE_UNKNOWN, - ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, - ATTR_UNIQUE_ID: "/sys/bus/w1/devices/22-111111111111/w1_slave", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - }, - ], - }, - "28-111111111111": { - ATTR_DEVICE_INFO: { - ATTR_IDENTIFIERS: {(DOMAIN, "28-111111111111")}, - ATTR_MANUFACTURER: MANUFACTURER_MAXIM, - ATTR_MODEL: "28", - ATTR_NAME: "28-111111111111", - }, - Platform.SENSOR: [ - { - ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, - ATTR_ENTITY_ID: "sensor.28_111111111111_temperature", - ATTR_INJECT_READS: InvalidCRCException, - ATTR_STATE: STATE_UNKNOWN, - ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, - ATTR_UNIQUE_ID: "/sys/bus/w1/devices/28-111111111111/w1_slave", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - }, - ], - }, - "3B-111111111111": { - ATTR_DEVICE_INFO: { - ATTR_IDENTIFIERS: {(DOMAIN, "3B-111111111111")}, - ATTR_MANUFACTURER: MANUFACTURER_MAXIM, - ATTR_MODEL: "3B", - ATTR_NAME: "3B-111111111111", - }, - Platform.SENSOR: [ - { - ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, - ATTR_ENTITY_ID: "sensor.3b_111111111111_temperature", - ATTR_INJECT_READS: 29.993, - ATTR_STATE: "30.0", - ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, - ATTR_UNIQUE_ID: "/sys/bus/w1/devices/3B-111111111111/w1_slave", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - }, - ], - }, - "42-111111111111": { - ATTR_DEVICE_INFO: { - ATTR_IDENTIFIERS: {(DOMAIN, "42-111111111111")}, - ATTR_MANUFACTURER: MANUFACTURER_MAXIM, - ATTR_MODEL: "42", - ATTR_NAME: "42-111111111111", - }, - Platform.SENSOR: [ - { - ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, - ATTR_ENTITY_ID: "sensor.42_111111111111_temperature", - ATTR_INJECT_READS: UnsupportResponseException, - ATTR_STATE: STATE_UNKNOWN, - ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, - ATTR_UNIQUE_ID: "/sys/bus/w1/devices/42-111111111111/w1_slave", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - }, - ], - }, - "42-111111111112": { - ATTR_DEVICE_INFO: { - ATTR_IDENTIFIERS: {(DOMAIN, "42-111111111112")}, - ATTR_MANUFACTURER: MANUFACTURER_MAXIM, - ATTR_MODEL: "42", - ATTR_NAME: "42-111111111112", - }, - Platform.SENSOR: [ - { - ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, - ATTR_ENTITY_ID: "sensor.42_111111111112_temperature", - ATTR_INJECT_READS: [UnsupportResponseException] * 9 + [27.993], - ATTR_STATE: "28.0", - ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, - ATTR_UNIQUE_ID: "/sys/bus/w1/devices/42-111111111112/w1_slave", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - }, - ], - }, - "42-111111111113": { - ATTR_DEVICE_INFO: { - ATTR_IDENTIFIERS: {(DOMAIN, "42-111111111113")}, - ATTR_MANUFACTURER: MANUFACTURER_MAXIM, - ATTR_MODEL: "42", - ATTR_NAME: "42-111111111113", - }, - Platform.SENSOR: [ - { - ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, - ATTR_ENTITY_ID: "sensor.42_111111111113_temperature", - ATTR_INJECT_READS: [UnsupportResponseException] * 10 + [27.993], - ATTR_STATE: STATE_UNKNOWN, - ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, - ATTR_UNIQUE_ID: "/sys/bus/w1/devices/42-111111111113/w1_slave", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - }, - ], - }, -} diff --git a/tests/components/onewire/test_binary_sensor.py b/tests/components/onewire/test_binary_sensor.py index ee55a550cea..636228af143 100644 --- a/tests/components/onewire/test_binary_sensor.py +++ b/tests/components/onewire/test_binary_sensor.py @@ -1,4 +1,4 @@ -"""Tests for 1-Wire devices connected on OWServer.""" +"""Tests for 1-Wire binary sensors.""" import logging from unittest.mock import MagicMock, patch @@ -27,7 +27,7 @@ def override_platforms(): yield -async def test_owserver_binary_sensor( +async def test_binary_sensors( hass: HomeAssistant, config_entry: ConfigEntry, owproxy: MagicMock, diff --git a/tests/components/onewire/test_config_flow.py b/tests/components/onewire/test_config_flow.py index 7bf7b58ebfa..d599c10ea90 100644 --- a/tests/components/onewire/test_config_flow.py +++ b/tests/components/onewire/test_config_flow.py @@ -4,15 +4,9 @@ from unittest.mock import AsyncMock, patch from pyownet import protocol import pytest -from homeassistant.components.onewire.const import ( - CONF_MOUNT_DIR, - CONF_TYPE_OWSERVER, - CONF_TYPE_SYSBUS, - DEFAULT_SYSBUS_MOUNT_DIR, - DOMAIN, -) +from homeassistant.components.onewire.const import DOMAIN from homeassistant.config_entries import SOURCE_USER, ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, @@ -30,23 +24,14 @@ def override_async_setup_entry() -> AsyncMock: yield mock_setup_entry -async def test_user_owserver(hass: HomeAssistant, mock_setup_entry: AsyncMock): - """Test OWServer user flow.""" +async def test_user_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock): + """Test user flow.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == RESULT_TYPE_FORM assert not result["errors"] - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_TYPE: CONF_TYPE_OWSERVER}, - ) - - assert result["type"] == RESULT_TYPE_FORM - assert result["step_id"] == "owserver" - assert not result["errors"] - # Invalid server with patch( "homeassistant.components.onewire.onewirehub.protocol.proxy", @@ -58,7 +43,7 @@ async def test_user_owserver(hass: HomeAssistant, mock_setup_entry: AsyncMock): ) assert result["type"] == RESULT_TYPE_FORM - assert result["step_id"] == "owserver" + assert result["step_id"] == "user" assert result["errors"] == {"base": "cannot_connect"} # Valid server @@ -73,7 +58,6 @@ async def test_user_owserver(hass: HomeAssistant, mock_setup_entry: AsyncMock): assert result["type"] == RESULT_TYPE_CREATE_ENTRY assert result["title"] == "1.2.3.4" assert result["data"] == { - CONF_TYPE: CONF_TYPE_OWSERVER, CONF_HOST: "1.2.3.4", CONF_PORT: 1234, } @@ -81,10 +65,10 @@ async def test_user_owserver(hass: HomeAssistant, mock_setup_entry: AsyncMock): assert len(mock_setup_entry.mock_calls) == 1 -async def test_user_owserver_duplicate( +async def test_user_duplicate( hass: HomeAssistant, config_entry: ConfigEntry, mock_setup_entry: AsyncMock ): - """Test OWServer flow.""" + """Test user duplicate flow.""" await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert len(hass.config_entries.async_entries(DOMAIN)) == 1 @@ -93,15 +77,7 @@ async def test_user_owserver_duplicate( DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == RESULT_TYPE_FORM - assert not result["errors"] - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_TYPE: CONF_TYPE_OWSERVER}, - ) - - assert result["type"] == RESULT_TYPE_FORM - assert result["step_id"] == "owserver" + assert result["step_id"] == "user" assert not result["errors"] # Duplicate server @@ -113,93 +89,3 @@ async def test_user_owserver_duplicate( assert result["reason"] == "already_configured" await hass.async_block_till_done() assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_user_sysbus(hass: HomeAssistant, mock_setup_entry: AsyncMock): - """Test SysBus flow.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - assert result["type"] == RESULT_TYPE_FORM - assert not result["errors"] - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_TYPE: CONF_TYPE_SYSBUS}, - ) - - assert result["type"] == RESULT_TYPE_FORM - assert result["step_id"] == "mount_dir" - assert not result["errors"] - - # Invalid path - with patch( - "homeassistant.components.onewire.onewirehub.os.path.isdir", - return_value=False, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_MOUNT_DIR: "/sys/bus/invalid_directory"}, - ) - - assert result["type"] == RESULT_TYPE_FORM - assert result["step_id"] == "mount_dir" - assert result["errors"] == {"base": "invalid_path"} - - # Valid path - with patch( - "homeassistant.components.onewire.onewirehub.os.path.isdir", - return_value=True, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_MOUNT_DIR: "/sys/bus/directory"}, - ) - - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "/sys/bus/directory" - assert result["data"] == { - CONF_TYPE: CONF_TYPE_SYSBUS, - CONF_MOUNT_DIR: "/sys/bus/directory", - } - await hass.async_block_till_done() - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_user_sysbus_duplicate( - hass: HomeAssistant, sysbus_config_entry: ConfigEntry, mock_setup_entry: AsyncMock -): - """Test SysBus duplicate flow.""" - await hass.config_entries.async_setup(sysbus_config_entry.entry_id) - await hass.async_block_till_done() - assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - assert result["type"] == RESULT_TYPE_FORM - assert not result["errors"] - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_TYPE: CONF_TYPE_SYSBUS}, - ) - - assert result["type"] == RESULT_TYPE_FORM - assert result["step_id"] == "mount_dir" - assert not result["errors"] - - # Valid path - with patch( - "homeassistant.components.onewire.onewirehub.os.path.isdir", - return_value=True, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR}, - ) - - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - await hass.async_block_till_done() - assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/onewire/test_diagnostics.py b/tests/components/onewire/test_diagnostics.py index ded4811a586..e279e3a2633 100644 --- a/tests/components/onewire/test_diagnostics.py +++ b/tests/components/onewire/test_diagnostics.py @@ -52,7 +52,6 @@ async def test_entry_diagnostics( "data": { "host": REDACTED, "port": 1234, - "type": "OWServer", }, "options": { "device_options": { diff --git a/tests/components/onewire/test_init.py b/tests/components/onewire/test_init.py index 763cdc9c071..fecade521a8 100644 --- a/tests/components/onewire/test_init.py +++ b/tests/components/onewire/test_init.py @@ -1,5 +1,4 @@ """Tests for 1-Wire config flow.""" -import logging from unittest.mock import MagicMock from pyownet import protocol @@ -11,7 +10,7 @@ from homeassistant.core import HomeAssistant @pytest.mark.usefixtures("owproxy_with_connerror") -async def test_owserver_connect_failure(hass: HomeAssistant, config_entry: ConfigEntry): +async def test_connect_failure(hass: HomeAssistant, config_entry: ConfigEntry): """Test connection failure raises ConfigEntryNotReady.""" await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -21,7 +20,7 @@ async def test_owserver_connect_failure(hass: HomeAssistant, config_entry: Confi assert not hass.data.get(DOMAIN) -async def test_owserver_listing_failure( +async def test_listing_failure( hass: HomeAssistant, config_entry: ConfigEntry, owproxy: MagicMock ): """Test listing failure raises ConfigEntryNotReady.""" @@ -49,34 +48,3 @@ async def test_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): assert config_entry.state is ConfigEntryState.NOT_LOADED assert not hass.data.get(DOMAIN) - - -@pytest.mark.usefixtures("sysbus") -async def test_warning_no_devices( - hass: HomeAssistant, - sysbus_config_entry: ConfigEntry, - caplog: pytest.LogCaptureFixture, -): - """Test warning is generated when no sysbus devices found.""" - with caplog.at_level(logging.WARNING, logger="homeassistant.components.onewire"): - await hass.config_entries.async_setup(sysbus_config_entry.entry_id) - await hass.async_block_till_done() - assert "No onewire sensor found. Check if dtoverlay=w1-gpio" in caplog.text - - -@pytest.mark.usefixtures("sysbus") -async def test_unload_sysbus_entry( - hass: HomeAssistant, sysbus_config_entry: ConfigEntry -): - """Test being able to unload an entry.""" - await hass.config_entries.async_setup(sysbus_config_entry.entry_id) - await hass.async_block_till_done() - - assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - assert sysbus_config_entry.state is ConfigEntryState.LOADED - - assert await hass.config_entries.async_unload(sysbus_config_entry.entry_id) - await hass.async_block_till_done() - - assert sysbus_config_entry.state is ConfigEntryState.NOT_LOADED - assert not hass.data.get(DOMAIN) diff --git a/tests/components/onewire/test_options_flow.py b/tests/components/onewire/test_options_flow.py index 2edb9da7ffc..e27b5a368d9 100644 --- a/tests/components/onewire/test_options_flow.py +++ b/tests/components/onewire/test_options_flow.py @@ -2,8 +2,6 @@ from unittest.mock import MagicMock, patch from homeassistant.components.onewire.const import ( - CONF_TYPE_SYSBUS, - DOMAIN, INPUT_ENTRY_CLEAR_OPTIONS, INPUT_ENTRY_DEVICE_SELECTION, ) @@ -26,13 +24,7 @@ class FakeDevice: name_by_user = "Given Name" -class FakeOWHubSysBus: - """Mock Class for mocking onewire hub.""" - - type = CONF_TYPE_SYSBUS - - -async def test_user_owserver_options_clear( +async def test_user_options_clear( hass: HomeAssistant, config_entry: ConfigEntry, owproxy: MagicMock, @@ -61,7 +53,7 @@ async def test_user_owserver_options_clear( assert result["data"] == {} -async def test_user_owserver_options_empty_selection( +async def test_user_options_empty_selection( hass: HomeAssistant, config_entry: ConfigEntry, owproxy: MagicMock, @@ -91,7 +83,7 @@ async def test_user_owserver_options_empty_selection( assert result["errors"] == {"base": "device_not_selected"} -async def test_user_owserver_options_set_single( +async def test_user_options_set_single( hass: HomeAssistant, config_entry: ConfigEntry, owproxy: MagicMock, @@ -134,7 +126,7 @@ async def test_user_owserver_options_set_single( ) -async def test_user_owserver_options_set_multiple( +async def test_user_options_set_multiple( hass: HomeAssistant, config_entry: ConfigEntry, owproxy: MagicMock, @@ -208,7 +200,7 @@ async def test_user_owserver_options_set_multiple( ) -async def test_user_owserver_options_no_devices( +async def test_user_options_no_devices( hass: HomeAssistant, config_entry: ConfigEntry, owproxy: MagicMock, @@ -223,15 +215,3 @@ async def test_user_owserver_options_no_devices( await hass.async_block_till_done() assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "No configurable devices found." - - -async def test_user_sysbus_options( - hass: HomeAssistant, - config_entry: ConfigEntry, -): - """Test that SysBus options flow aborts on init.""" - hass.data[DOMAIN] = {config_entry.entry_id: FakeOWHubSysBus()} - result = await hass.config_entries.options.async_init(config_entry.entry_id) - await hass.async_block_till_done() - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "SysBus setup does not have any config options." diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py index 6bfc68d85c8..a20714fedfc 100644 --- a/tests/components/onewire/test_sensor.py +++ b/tests/components/onewire/test_sensor.py @@ -1,4 +1,4 @@ -"""Tests for 1-Wire sensor platform.""" +"""Tests for 1-Wire sensors.""" import logging from unittest.mock import MagicMock, patch @@ -14,14 +14,8 @@ from . import ( check_device_registry, check_entities, setup_owproxy_mock_devices, - setup_sysbus_mock_devices, -) -from .const import ( - ATTR_DEVICE_INFO, - ATTR_UNKNOWN_DEVICE, - MOCK_OWPROXY_DEVICES, - MOCK_SYSBUS_DEVICES, ) +from .const import ATTR_DEVICE_INFO, ATTR_UNKNOWN_DEVICE, MOCK_OWPROXY_DEVICES from tests.common import mock_device_registry, mock_registry @@ -33,7 +27,7 @@ def override_platforms(): yield -async def test_owserver_sensor( +async def test_sensors( hass: HomeAssistant, config_entry: ConfigEntry, owproxy: MagicMock, @@ -73,44 +67,3 @@ async def test_owserver_sensor( await hass.async_block_till_done() check_entities(hass, entity_registry, expected_entities) - - -@pytest.mark.usefixtures("sysbus") -@pytest.mark.parametrize("device_id", MOCK_SYSBUS_DEVICES.keys(), indirect=True) -async def test_onewiredirect_setup_valid_device( - hass: HomeAssistant, - sysbus_config_entry: ConfigEntry, - device_id: str, - caplog: pytest.LogCaptureFixture, -): - """Test that sysbus config entry works correctly.""" - device_registry = mock_device_registry(hass) - entity_registry = mock_registry(hass) - - glob_result, read_side_effect = setup_sysbus_mock_devices( - Platform.SENSOR, [device_id] - ) - - mock_device = MOCK_SYSBUS_DEVICES[device_id] - expected_entities = mock_device.get(Platform.SENSOR, []) - expected_devices = ensure_list(mock_device.get(ATTR_DEVICE_INFO)) - - with patch("pi1wire._finder.glob.glob", return_value=glob_result,), patch( - "pi1wire.OneWire.get_temperature", - side_effect=read_side_effect, - ), caplog.at_level( - logging.WARNING, logger="homeassistant.components.onewire" - ), patch( - "homeassistant.components.onewire.sensor.asyncio.sleep" - ): - await hass.config_entries.async_setup(sysbus_config_entry.entry_id) - await hass.async_block_till_done() - assert "No onewire sensor found. Check if dtoverlay=w1-gpio" not in caplog.text - if mock_device.get(ATTR_UNKNOWN_DEVICE): - assert "Ignoring unknown device family" in caplog.text - else: - assert "Ignoring unknown device family" not in caplog.text - - check_device_registry(device_registry, expected_devices) - assert len(entity_registry.entities) == len(expected_entities) - check_entities(hass, entity_registry, expected_entities) diff --git a/tests/components/onewire/test_switch.py b/tests/components/onewire/test_switch.py index 32212f84b34..8b60232330f 100644 --- a/tests/components/onewire/test_switch.py +++ b/tests/components/onewire/test_switch.py @@ -1,4 +1,4 @@ -"""Tests for 1-Wire devices connected on OWServer.""" +"""Tests for 1-Wire switches.""" import logging from unittest.mock import MagicMock, patch @@ -35,7 +35,7 @@ def override_platforms(): yield -async def test_owserver_switch( +async def test_switches( hass: HomeAssistant, config_entry: ConfigEntry, owproxy: MagicMock, diff --git a/tests/components/onvif/test_config_flow.py b/tests/components/onvif/test_config_flow.py index 0760ead2ba1..fec1e2b8132 100644 --- a/tests/components/onvif/test_config_flow.py +++ b/tests/components/onvif/test_config_flow.py @@ -314,7 +314,9 @@ async def test_option_flow(hass): """Test config flow options.""" entry, _, _ = await setup_onvif_integration(hass) - result = await hass.config_entries.options.async_init(entry.entry_id) + result = await hass.config_entries.options.async_init( + entry.entry_id, context={"show_advanced_options": True} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "onvif_devices" @@ -323,12 +325,14 @@ async def test_option_flow(hass): result["flow_id"], user_input={ config_flow.CONF_EXTRA_ARGUMENTS: "", - config_flow.CONF_RTSP_TRANSPORT: config_flow.RTSP_TRANS_PROTOCOLS[1], + config_flow.CONF_RTSP_TRANSPORT: list(config_flow.RTSP_TRANSPORTS)[1], + config_flow.CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"] == { config_flow.CONF_EXTRA_ARGUMENTS: "", - config_flow.CONF_RTSP_TRANSPORT: config_flow.RTSP_TRANS_PROTOCOLS[1], + config_flow.CONF_RTSP_TRANSPORT: list(config_flow.RTSP_TRANSPORTS)[1], + config_flow.CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, } diff --git a/tests/components/picnic/test_sensor.py b/tests/components/picnic/test_sensor.py index 7b1bdeb1d12..8d680327bc8 100644 --- a/tests/components/picnic/test_sensor.py +++ b/tests/components/picnic/test_sensor.py @@ -17,7 +17,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util import dt from tests.common import ( @@ -97,9 +97,7 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self): """Set up things to be run when tests are started.""" self.hass = await async_test_home_assistant(None) - self.entity_registry = ( - await self.hass.helpers.entity_registry.async_get_registry() - ) + self.entity_registry = er.async_get(self.hass) # Patch the api client self.picnic_patcher = patch("homeassistant.components.picnic.PicnicAPI") @@ -498,13 +496,13 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): # Setup platform and default mock responses await self._setup_platform(use_default_responses=True) - device_registry = await self.hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(self.hass) picnic_service = device_registry.async_get_device( identifiers={(const.DOMAIN, DEFAULT_USER_RESPONSE["user_id"])} ) assert picnic_service.model == DEFAULT_USER_RESPONSE["user_id"] assert picnic_service.name == "Picnic: Commonstreet 123a" - assert picnic_service.entry_type is DeviceEntryType.SERVICE + assert picnic_service.entry_type is dr.DeviceEntryType.SERVICE async def test_auth_token_is_saved_on_update(self): """Test that auth-token changes in the session object are reflected by the config entry.""" diff --git a/tests/components/plex/conftest.py b/tests/components/plex/conftest.py index 47e7d96d2fe..506aadcce61 100644 --- a/tests/components/plex/conftest.py +++ b/tests/components/plex/conftest.py @@ -381,7 +381,7 @@ def hubs_music_library_fixture(): @pytest.fixture(name="entry") -def mock_config_entry(): +async def mock_config_entry(): """Return the default mocked config entry.""" return MockConfigEntry( domain=DOMAIN, diff --git a/tests/components/plex/test_device_handling.py b/tests/components/plex/test_device_handling.py index 2c23ee7cb09..38145b27deb 100644 --- a/tests/components/plex/test_device_handling.py +++ b/tests/components/plex/test_device_handling.py @@ -2,14 +2,15 @@ from homeassistant.components.plex.const import DOMAIN from homeassistant.const import Platform +from homeassistant.helpers import device_registry as dr, entity_registry as er async def test_cleanup_orphaned_devices(hass, entry, setup_plex_server): """Test cleaning up orphaned devices on startup.""" test_device_id = {(DOMAIN, "temporary_device_123")} - device_registry = await hass.helpers.device_registry.async_get_registry() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) test_device = device_registry.async_get_or_create( config_entry_id=entry.entry_id, @@ -44,8 +45,8 @@ async def test_migrate_transient_devices( non_plexweb_device_id = {(DOMAIN, "1234567890123456-com-plexapp-android")} plex_client_service_device_id = {(DOMAIN, "plex.tv-clients")} - device_registry = await hass.helpers.device_registry.async_get_registry() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) # Pre-create devices and entities to test device migration plexweb_device = device_registry.async_get_or_create( @@ -87,16 +88,12 @@ async def test_migrate_transient_devices( ) assert ( - len( - hass.helpers.entity_registry.async_entries_for_device( - entity_registry, device_id=plexweb_device.id - ) - ) + len(er.async_entries_for_device(entity_registry, device_id=plexweb_device.id)) == 1 ) assert ( len( - hass.helpers.entity_registry.async_entries_for_device( + er.async_entries_for_device( entity_registry, device_id=non_plexweb_device.id ) ) @@ -112,16 +109,12 @@ async def test_migrate_transient_devices( ) assert ( - len( - hass.helpers.entity_registry.async_entries_for_device( - entity_registry, device_id=plexweb_device.id - ) - ) + len(er.async_entries_for_device(entity_registry, device_id=plexweb_device.id)) == 0 ) assert ( len( - hass.helpers.entity_registry.async_entries_for_device( + er.async_entries_for_device( entity_registry, device_id=non_plexweb_device.id ) ) @@ -129,7 +122,7 @@ async def test_migrate_transient_devices( ) assert ( len( - hass.helpers.entity_registry.async_entries_for_device( + er.async_entries_for_device( entity_registry, device_id=plex_service_device.id ) ) diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json index 4c4776f4063..6ef0716e4b6 100644 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json +++ b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json @@ -12,26 +12,19 @@ }, { "df4a4a8169904cdb9c03d61a21f42140": { - "class": "zone_thermostat", - "fw": "2016-10-27T02:00:00+02:00", - "hw": "255", + "dev_class": "zone_thermostat", + "firmware": "2016-10-27T02:00:00+02:00", + "hardware": "255", "location": "12493538af164a409c6a1c79e38afe1c", - "mac_address": null, "model": "Lisa", "name": "Zone Lisa Bios", + "zigbee_mac_address": "ABCD012345670A06", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 99.9, "resolution": 0.01, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "preset_modes": ["home", "asleep", "away", "no_frost"], "active_preset": "away", - "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0] - }, "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -41,7 +34,6 @@ ], "selected_schedule": "None", "last_used": "Badkamer Schema", - "schedule_temperature": 15.0, "mode": "heat", "sensors": { "temperature": 16.5, @@ -50,13 +42,13 @@ } }, "b310b72a0e354bfab43089919b9a88bf": { - "class": "thermo_sensor", - "fw": "2019-03-27T01:00:00+01:00", - "hw": "1", + "dev_class": "thermo_sensor", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", "location": "c50f167537524366a5af7aa3942feb1e", - "mac_address": null, "model": "Tom/Floor", "name": "Floor kraan", + "zigbee_mac_address": "ABCD012345670A02", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 100.0, @@ -69,13 +61,13 @@ } }, "a2c3583e0a6349358998b760cea82d2a": { - "class": "thermo_sensor", - "fw": "2019-03-27T01:00:00+01:00", - "hw": "1", + "dev_class": "thermo_sensor", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", "location": "12493538af164a409c6a1c79e38afe1c", - "mac_address": null, "model": "Tom/Floor", "name": "Bios Cv Thermostatic Radiator ", + "zigbee_mac_address": "ABCD012345670A09", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 100.0, @@ -89,26 +81,19 @@ } }, "b59bcebaf94b499ea7d46e4a66fb62d8": { - "class": "zone_thermostat", - "fw": "2016-08-02T02:00:00+02:00", - "hw": "255", + "dev_class": "zone_thermostat", + "firmware": "2016-08-02T02:00:00+02:00", + "hardware": "255", "location": "c50f167537524366a5af7aa3942feb1e", - "mac_address": null, "model": "Lisa", "name": "Zone Lisa WK", + "zigbee_mac_address": "ABCD012345670A07", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 99.9, "resolution": 0.01, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "preset_modes": ["home", "asleep", "away", "no_frost"], "active_preset": "home", - "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0] - }, "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -118,7 +103,6 @@ ], "selected_schedule": "GF7 Woonkamer", "last_used": "GF7 Woonkamer", - "schedule_temperature": 20.0, "mode": "auto", "sensors": { "temperature": 20.9, @@ -127,15 +111,15 @@ } }, "fe799307f1624099878210aa0b9f1475": { - "class": "gateway", - "fw": "3.0.15", - "hw": "AME Smile 2.0 board", + "dev_class": "gateway", + "firmware": "3.0.15", + "hardware": "AME Smile 2.0 board", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", "mac_address": "012345670001", "model": "Adam", "name": "Adam", - "vendor": "Plugwise B.V.", "zigbee_mac_address": "ABCD012345670101", + "vendor": "Plugwise B.V.", "regulation_mode": "heating", "regulation_modes": [], "binary_sensors": { @@ -146,13 +130,13 @@ } }, "d3da73bde12a47d5a6b8f9dad971f2ec": { - "class": "thermo_sensor", - "fw": "2019-03-27T01:00:00+01:00", - "hw": "1", + "dev_class": "thermo_sensor", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", "location": "82fa13f017d240daa0d0ea1775420f24", - "mac_address": null, "model": "Tom/Floor", "name": "Thermostatic Radiator Jessie", + "zigbee_mac_address": "ABCD012345670A10", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 100.0, @@ -166,15 +150,13 @@ } }, "21f2b542c49845e6bb416884c55778d6": { - "class": "game_console", - "fw": "2019-06-21T02:00:00+02:00", - "hw": null, + "dev_class": "game_console", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": null, "model": "Plug", "name": "Playstation Smart Plug", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A12", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 82.6, "electricity_consumed_interval": 8.6, @@ -187,15 +169,13 @@ } }, "78d1126fc4c743db81b61c20e88342a7": { - "class": "central_heating_pump", - "fw": "2019-06-21T02:00:00+02:00", - "hw": null, + "dev_class": "central_heating_pump", + "firmware": "2019-06-21T02:00:00+02:00", "location": "c50f167537524366a5af7aa3942feb1e", - "mac_address": null, "model": "Plug", "name": "CV Pomp", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A05", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 35.6, "electricity_consumed_interval": 7.37, @@ -207,14 +187,10 @@ } }, "90986d591dcd426cae3ec3e8111ff730": { - "class": "heater_central", - "fw": null, - "hw": null, + "dev_class": "heater_central", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", - "mac_address": null, "model": "Unknown", "name": "OnOff", - "vendor": null, "binary_sensors": { "heating_state": true }, @@ -225,15 +201,13 @@ } }, "cd0ddb54ef694e11ac18ed1cbce5dbbd": { - "class": "vcr", - "fw": "2019-06-21T02:00:00+02:00", - "hw": null, + "dev_class": "vcr", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": null, "model": "Plug", "name": "NAS", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A14", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 16.5, "electricity_consumed_interval": 0.5, @@ -246,15 +220,13 @@ } }, "4a810418d5394b3f82727340b91ba740": { - "class": "router", - "fw": "2019-06-21T02:00:00+02:00", - "hw": null, + "dev_class": "router", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": null, "model": "Plug", "name": "USG Smart Plug", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A16", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 8.5, "electricity_consumed_interval": 0.0, @@ -267,15 +239,13 @@ } }, "02cf28bfec924855854c544690a609ef": { - "class": "vcr", - "fw": "2019-06-21T02:00:00+02:00", - "hw": null, + "dev_class": "vcr", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": null, "model": "Plug", "name": "NVR", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A15", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 34.0, "electricity_consumed_interval": 9.15, @@ -288,15 +258,13 @@ } }, "a28f588dc4a049a483fd03a30361ad3a": { - "class": "settop", - "fw": "2019-06-21T02:00:00+02:00", - "hw": null, + "dev_class": "settop", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": null, "model": "Plug", "name": "Fibaro HC2", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A13", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 12.5, "electricity_consumed_interval": 3.8, @@ -309,26 +277,19 @@ } }, "6a3bf693d05e48e0b460c815a4fdd09d": { - "class": "zone_thermostat", - "fw": "2016-10-27T02:00:00+02:00", - "hw": "255", + "dev_class": "zone_thermostat", + "firmware": "2016-10-27T02:00:00+02:00", + "hardware": "255", "location": "82fa13f017d240daa0d0ea1775420f24", - "mac_address": null, "model": "Lisa", "name": "Zone Thermostat Jessie", + "zigbee_mac_address": "ABCD012345670A03", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 99.9, "resolution": 0.01, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "preset_modes": ["home", "asleep", "away", "no_frost"], "active_preset": "asleep", - "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0] - }, "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -338,7 +299,6 @@ ], "selected_schedule": "CV Jessie", "last_used": "CV Jessie", - "schedule_temperature": 15.0, "mode": "auto", "sensors": { "temperature": 17.2, @@ -347,13 +307,13 @@ } }, "680423ff840043738f42cc7f1ff97a36": { - "class": "thermo_sensor", - "fw": "2019-03-27T01:00:00+01:00", - "hw": "1", + "dev_class": "thermo_sensor", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", "location": "08963fec7c53423ca5680aa4cb502c63", - "mac_address": null, "model": "Tom/Floor", "name": "Thermostatic Radiator Badkamer", + "zigbee_mac_address": "ABCD012345670A17", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 100.0, @@ -367,26 +327,19 @@ } }, "f1fee6043d3642a9b0a65297455f008e": { - "class": "zone_thermostat", - "fw": "2016-10-27T02:00:00+02:00", - "hw": "255", + "dev_class": "zone_thermostat", + "firmware": "2016-10-27T02:00:00+02:00", + "hardware": "255", "location": "08963fec7c53423ca5680aa4cb502c63", - "mac_address": null, "model": "Lisa", "name": "Zone Thermostat Badkamer", + "zigbee_mac_address": "ABCD012345670A08", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 99.9, "resolution": 0.01, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "preset_modes": ["home", "asleep", "away", "no_frost"], "active_preset": "away", - "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0] - }, "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -396,7 +349,6 @@ ], "selected_schedule": "Badkamer Schema", "last_used": "Badkamer Schema", - "schedule_temperature": 15.0, "mode": "auto", "sensors": { "temperature": 18.9, @@ -405,15 +357,13 @@ } }, "675416a629f343c495449970e2ca37b5": { - "class": "router", - "fw": "2019-06-21T02:00:00+02:00", - "hw": null, + "dev_class": "router", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": null, "model": "Plug", "name": "Ziggo Modem", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A01", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 12.2, "electricity_consumed_interval": 2.97, @@ -426,26 +376,19 @@ } }, "e7693eb9582644e5b865dba8d4447cf1": { - "class": "thermostatic_radiator_valve", - "fw": "2019-03-27T01:00:00+01:00", - "hw": "1", + "dev_class": "thermostatic_radiator_valve", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", "location": "446ac08dd04d4eff8ac57489757b7314", - "mac_address": null, "model": "Tom/Floor", "name": "CV Kraan Garage", + "zigbee_mac_address": "ABCD012345670A11", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 100.0, "resolution": 0.01, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "preset_modes": ["home", "asleep", "away", "no_frost"], "active_preset": "no_frost", - "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0] - }, "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -455,7 +398,6 @@ ], "selected_schedule": "None", "last_used": "Badkamer Schema", - "schedule_temperature": 15.0, "mode": "heat", "sensors": { "temperature": 15.6, diff --git a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json index efc95494cad..60bc4c35668 100644 --- a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json +++ b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json @@ -8,19 +8,16 @@ }, { "1cbf783bb11e4a7c8a6843dee3a86927": { - "class": "heater_central", - "fw": null, - "hw": null, + "dev_class": "heater_central", "location": "a57efe5f145f498c9be62a9b63626fbf", - "mac_address": null, "model": "Generic heater", "name": "OpenTherm", "vendor": "Techneco", "maximum_boiler_temperature": 60.0, - "compressor_state": true, "binary_sensors": { "dhw_state": false, "heating_state": true, + "compressor_state": true, "cooling_state": false, "slave_boiler_state": false, "flame_state": false @@ -38,9 +35,9 @@ } }, "015ae9ea3f964e668e490fa39da3870b": { - "class": "gateway", - "fw": "4.0.15", - "hw": "AME Smile 2.0 board", + "dev_class": "gateway", + "firmware": "4.0.15", + "hardware": "AME Smile 2.0 board", "location": "a57efe5f145f498c9be62a9b63626fbf", "mac_address": "012345670001", "model": "Anna", @@ -54,11 +51,10 @@ } }, "3cb70739631c4d17a86b8b12e8a5161b": { - "class": "thermostat", - "fw": "2018-02-08T11:15:53+01:00", - "hw": "6539-1301-5002", + "dev_class": "thermostat", + "firmware": "2018-02-08T11:15:53+01:00", + "hardware": "6539-1301-5002", "location": "c784ee9fdab44e1395b8dee7d7a497d5", - "mac_address": null, "model": "Anna", "name": "Anna", "vendor": "Plugwise", @@ -67,18 +63,10 @@ "resolution": 0.1, "preset_modes": ["no_frost", "home", "away", "asleep", "vacation"], "active_preset": "home", - "presets": { - "no_frost": [10.0, 30.0], - "home": [21.0, 22.0], - "away": [20.0, 25.0], - "asleep": [20.5, 24.0], - "vacation": [17.0, 28.0] - }, - "available_schedules": ["None"], - "selected_schedule": "None", - "last_used": null, - "schedule_temperature": null, - "mode": "heat", + "available_schedules": ["standaard"], + "selected_schedule": "standaard", + "last_used": "standaard", + "mode": "auto", "sensors": { "temperature": 19.3, "setpoint": 21.0, diff --git a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json index f186335fcc9..fbf5aa63a5f 100644 --- a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json +++ b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json @@ -6,9 +6,9 @@ }, { "e950c7d5e1ee407a858e2a8b5016c8b3": { - "class": "gateway", - "fw": "3.3.9", - "hw": "AME Smile 2.0 board", + "dev_class": "gateway", + "firmware": "3.3.9", + "hardware": "AME Smile 2.0 board", "location": "cd3e822288064775a7c4afcdd70bdda2", "mac_address": "012345670001", "model": "P1", diff --git a/tests/components/plugwise/fixtures/stretch_v31/all_data.json b/tests/components/plugwise/fixtures/stretch_v31/all_data.json index 08ebab7b148..1ff62e9e619 100644 --- a/tests/components/plugwise/fixtures/stretch_v31/all_data.json +++ b/tests/components/plugwise/fixtures/stretch_v31/all_data.json @@ -6,26 +6,24 @@ }, { "0000aaaa0000aaaa0000aaaa0000aa00": { - "class": "gateway", - "fw": "3.1.11", - "hw": null, - "mac_address": "01:23:45:67:89:AB", + "dev_class": "gateway", + "firmware": "3.1.11", "location": "0000aaaa0000aaaa0000aaaa0000aa00", - "vendor": "Plugwise B.V.", + "mac_address": "01:23:45:67:89:AB", "model": "Stretch", "name": "Stretch", + "vendor": "Plugwise B.V.", "zigbee_mac_address": "ABCD012345670101" }, "5871317346d045bc9f6b987ef25ee638": { - "class": "water_heater_vessel", - "fw": "2011-06-27T10:52:18+02:00", - "hw": "6539-0701-4028", + "dev_class": "water_heater_vessel", + "firmware": "2011-06-27T10:52:18+02:00", + "hardware": "6539-0701-4028", "location": "0000aaaa0000aaaa0000aaaa0000aa00", - "mac_address": null, "model": "Circle type F", "name": "Boiler (1EB31)", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A07", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 1.19, "electricity_consumed_interval": 0.0, @@ -37,13 +35,13 @@ } }, "e1c884e7dede431dadee09506ec4f859": { - "class": "refrigerator", - "fw": "2011-06-27T10:47:37+02:00", - "hw": "6539-0700-7330", + "dev_class": "refrigerator", + "firmware": "2011-06-27T10:47:37+02:00", + "hardware": "6539-0700-7330", "location": "0000aaaa0000aaaa0000aaaa0000aa00", - "mac_address": null, "model": "Circle+ type F", "name": "Koelkast (92C4A)", + "zigbee_mac_address": "0123456789AB", "vendor": "Plugwise", "sensors": { "electricity_consumed": 50.5, @@ -56,15 +54,14 @@ } }, "aac7b735042c4832ac9ff33aae4f453b": { - "class": "dishwasher", - "fw": "2011-06-27T10:52:18+02:00", - "hw": "6539-0701-4022", + "dev_class": "dishwasher", + "firmware": "2011-06-27T10:52:18+02:00", + "hardware": "6539-0701-4022", "location": "0000aaaa0000aaaa0000aaaa0000aa00", - "mac_address": null, "model": "Circle type F", "name": "Vaatwasser (2a1ab)", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A02", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 0.0, "electricity_consumed_interval": 0.71, @@ -76,15 +73,14 @@ } }, "cfe95cf3de1948c0b8955125bf754614": { - "class": "dryer", - "fw": "2011-06-27T10:52:18+02:00", - "hw": "0000-0440-0107", + "dev_class": "dryer", + "firmware": "2011-06-27T10:52:18+02:00", + "hardware": "0000-0440-0107", "location": "0000aaaa0000aaaa0000aaaa0000aa00", - "mac_address": null, "model": "Circle type F", "name": "Droger (52559)", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A04", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, @@ -96,15 +92,14 @@ } }, "059e4d03c7a34d278add5c7a4a781d19": { - "class": "washingmachine", - "fw": "2011-06-27T10:52:18+02:00", - "hw": "0000-0440-0107", + "dev_class": "washingmachine", + "firmware": "2011-06-27T10:52:18+02:00", + "hardware": "0000-0440-0107", "location": "0000aaaa0000aaaa0000aaaa0000aa00", - "mac_address": null, "model": "Circle type F", "name": "Wasmachine (52AC1)", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A01", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, @@ -116,22 +111,16 @@ } }, "71e1944f2a944b26ad73323e399efef0": { - "class": "switching", - "fw": null, - "location": null, + "dev_class": "switching", "model": "Switchgroup", "name": "Test", "members": ["5ca521ac179d468e91d772eeeb8a2117"], - "types": ["switch_group"], - "vendor": null, "switches": { "relay": true } }, "d950b314e9d8499f968e6db8d82ef78c": { - "class": "report", - "fw": null, - "location": null, + "dev_class": "report", "model": "Switchgroup", "name": "Stroomvreters", "members": [ @@ -141,24 +130,18 @@ "cfe95cf3de1948c0b8955125bf754614", "e1c884e7dede431dadee09506ec4f859" ], - "types": ["switch_group"], - "vendor": null, "switches": { "relay": true } }, "d03738edfcc947f7b8f4573571d90d2d": { - "class": "switching", - "fw": null, - "location": null, + "dev_class": "switching", "model": "Switchgroup", "name": "Schakel", "members": [ "059e4d03c7a34d278add5c7a4a781d19", "cfe95cf3de1948c0b8955125bf754614" ], - "types": ["switch_group"], - "vendor": null, "switches": { "relay": true } diff --git a/tests/components/plugwise/test_climate.py b/tests/components/plugwise/test_climate.py index 34a7ea50d34..bf110cd8a91 100644 --- a/tests/components/plugwise/test_climate.py +++ b/tests/components/plugwise/test_climate.py @@ -150,10 +150,11 @@ async def test_anna_climate_entity_attributes( """Test creation of anna climate device environment.""" state = hass.states.get("climate.anna") assert state - assert state.state == HVACMode.HEAT + assert state.state == HVACMode.AUTO assert state.attributes["hvac_modes"] == [ HVACMode.HEAT, HVACMode.COOL, + HVACMode.AUTO, ] assert "no_frost" in state.attributes["preset_modes"] assert "home" in state.attributes["preset_modes"] @@ -199,24 +200,12 @@ async def test_anna_climate_entity_climate_changes( await hass.services.async_call( "climate", "set_hvac_mode", - {"entity_id": "climate.anna", "hvac_mode": "heat_cool"}, + {"entity_id": "climate.anna", "hvac_mode": "heat"}, blocking=True, ) assert mock_smile_anna.set_temperature.call_count == 1 assert mock_smile_anna.set_schedule_state.call_count == 1 mock_smile_anna.set_schedule_state.assert_called_with( - "c784ee9fdab44e1395b8dee7d7a497d5", None, "off" + "c784ee9fdab44e1395b8dee7d7a497d5", "standaard", "off" ) - - # Auto mode is not available, no schedules - with pytest.raises(ValueError): - await hass.services.async_call( - "climate", - "set_hvac_mode", - {"entity_id": "climate.anna", "hvac_mode": "auto"}, - blocking=True, - ) - - assert mock_smile_anna.set_temperature.call_count == 1 - assert mock_smile_anna.set_schedule_state.call_count == 1 diff --git a/tests/components/plugwise/test_diagnostics.py b/tests/components/plugwise/test_diagnostics.py index d6d1eebaf72..372f410cd81 100644 --- a/tests/components/plugwise/test_diagnostics.py +++ b/tests/components/plugwise/test_diagnostics.py @@ -32,26 +32,19 @@ async def test_diagnostics( }, "devices": { "df4a4a8169904cdb9c03d61a21f42140": { - "class": "zone_thermostat", - "fw": "2016-10-27T02:00:00+02:00", - "hw": "255", + "dev_class": "zone_thermostat", + "firmware": "2016-10-27T02:00:00+02:00", + "hardware": "255", "location": "12493538af164a409c6a1c79e38afe1c", - "mac_address": None, "model": "Lisa", "name": "Zone Lisa Bios", + "zigbee_mac_address": "ABCD012345670A06", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 99.9, "resolution": 0.01, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "preset_modes": ["home", "asleep", "away", "no_frost"], "active_preset": "away", - "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0], - }, "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -61,18 +54,17 @@ async def test_diagnostics( ], "selected_schedule": "None", "last_used": "Badkamer Schema", - "schedule_temperature": 15.0, "mode": "heat", "sensors": {"temperature": 16.5, "setpoint": 13.0, "battery": 67}, }, "b310b72a0e354bfab43089919b9a88bf": { - "class": "thermo_sensor", - "fw": "2019-03-27T01:00:00+01:00", - "hw": "1", + "dev_class": "thermo_sensor", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", "location": "c50f167537524366a5af7aa3942feb1e", - "mac_address": None, "model": "Tom/Floor", "name": "Floor kraan", + "zigbee_mac_address": "ABCD012345670A02", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 100.0, @@ -85,13 +77,13 @@ async def test_diagnostics( }, }, "a2c3583e0a6349358998b760cea82d2a": { - "class": "thermo_sensor", - "fw": "2019-03-27T01:00:00+01:00", - "hw": "1", + "dev_class": "thermo_sensor", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", "location": "12493538af164a409c6a1c79e38afe1c", - "mac_address": None, "model": "Tom/Floor", "name": "Bios Cv Thermostatic Radiator ", + "zigbee_mac_address": "ABCD012345670A09", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 100.0, @@ -105,26 +97,19 @@ async def test_diagnostics( }, }, "b59bcebaf94b499ea7d46e4a66fb62d8": { - "class": "zone_thermostat", - "fw": "2016-08-02T02:00:00+02:00", - "hw": "255", + "dev_class": "zone_thermostat", + "firmware": "2016-08-02T02:00:00+02:00", + "hardware": "255", "location": "c50f167537524366a5af7aa3942feb1e", - "mac_address": None, "model": "Lisa", "name": "Zone Lisa WK", + "zigbee_mac_address": "ABCD012345670A07", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 99.9, "resolution": 0.01, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "preset_modes": ["home", "asleep", "away", "no_frost"], "active_preset": "home", - "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0], - }, "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -134,33 +119,32 @@ async def test_diagnostics( ], "selected_schedule": "GF7 Woonkamer", "last_used": "GF7 Woonkamer", - "schedule_temperature": 20.0, "mode": "auto", "sensors": {"temperature": 20.9, "setpoint": 21.5, "battery": 34}, }, "fe799307f1624099878210aa0b9f1475": { - "class": "gateway", - "fw": "3.0.15", - "hw": "AME Smile 2.0 board", + "dev_class": "gateway", + "firmware": "3.0.15", + "hardware": "AME Smile 2.0 board", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", "mac_address": "012345670001", "model": "Adam", "name": "Adam", - "vendor": "Plugwise B.V.", "zigbee_mac_address": "ABCD012345670101", + "vendor": "Plugwise B.V.", "regulation_mode": "heating", "regulation_modes": [], "binary_sensors": {"plugwise_notification": True}, "sensors": {"outdoor_temperature": 7.81}, }, "d3da73bde12a47d5a6b8f9dad971f2ec": { - "class": "thermo_sensor", - "fw": "2019-03-27T01:00:00+01:00", - "hw": "1", + "dev_class": "thermo_sensor", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", "location": "82fa13f017d240daa0d0ea1775420f24", - "mac_address": None, "model": "Tom/Floor", "name": "Thermostatic Radiator Jessie", + "zigbee_mac_address": "ABCD012345670A10", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 100.0, @@ -174,15 +158,13 @@ async def test_diagnostics( }, }, "21f2b542c49845e6bb416884c55778d6": { - "class": "game_console", - "fw": "2019-06-21T02:00:00+02:00", - "hw": None, + "dev_class": "game_console", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": None, "model": "Plug", "name": "Playstation Smart Plug", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A12", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 82.6, "electricity_consumed_interval": 8.6, @@ -192,15 +174,13 @@ async def test_diagnostics( "switches": {"relay": True, "lock": False}, }, "78d1126fc4c743db81b61c20e88342a7": { - "class": "central_heating_pump", - "fw": "2019-06-21T02:00:00+02:00", - "hw": None, + "dev_class": "central_heating_pump", + "firmware": "2019-06-21T02:00:00+02:00", "location": "c50f167537524366a5af7aa3942feb1e", - "mac_address": None, "model": "Plug", "name": "CV Pomp", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A05", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 35.6, "electricity_consumed_interval": 7.37, @@ -210,14 +190,10 @@ async def test_diagnostics( "switches": {"relay": True}, }, "90986d591dcd426cae3ec3e8111ff730": { - "class": "heater_central", - "fw": None, - "hw": None, + "dev_class": "heater_central", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", - "mac_address": None, "model": "Unknown", "name": "OnOff", - "vendor": None, "binary_sensors": {"heating_state": True}, "sensors": { "water_temperature": 70.0, @@ -226,15 +202,13 @@ async def test_diagnostics( }, }, "cd0ddb54ef694e11ac18ed1cbce5dbbd": { - "class": "vcr", - "fw": "2019-06-21T02:00:00+02:00", - "hw": None, + "dev_class": "vcr", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": None, "model": "Plug", "name": "NAS", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A14", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 16.5, "electricity_consumed_interval": 0.5, @@ -244,15 +218,13 @@ async def test_diagnostics( "switches": {"relay": True, "lock": True}, }, "4a810418d5394b3f82727340b91ba740": { - "class": "router", - "fw": "2019-06-21T02:00:00+02:00", - "hw": None, + "dev_class": "router", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": None, "model": "Plug", "name": "USG Smart Plug", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A16", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 8.5, "electricity_consumed_interval": 0.0, @@ -262,15 +234,13 @@ async def test_diagnostics( "switches": {"relay": True, "lock": True}, }, "02cf28bfec924855854c544690a609ef": { - "class": "vcr", - "fw": "2019-06-21T02:00:00+02:00", - "hw": None, + "dev_class": "vcr", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": None, "model": "Plug", "name": "NVR", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A15", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 34.0, "electricity_consumed_interval": 9.15, @@ -280,15 +250,13 @@ async def test_diagnostics( "switches": {"relay": True, "lock": True}, }, "a28f588dc4a049a483fd03a30361ad3a": { - "class": "settop", - "fw": "2019-06-21T02:00:00+02:00", - "hw": None, + "dev_class": "settop", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": None, "model": "Plug", "name": "Fibaro HC2", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A13", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 12.5, "electricity_consumed_interval": 3.8, @@ -298,26 +266,19 @@ async def test_diagnostics( "switches": {"relay": True, "lock": True}, }, "6a3bf693d05e48e0b460c815a4fdd09d": { - "class": "zone_thermostat", - "fw": "2016-10-27T02:00:00+02:00", - "hw": "255", + "dev_class": "zone_thermostat", + "firmware": "2016-10-27T02:00:00+02:00", + "hardware": "255", "location": "82fa13f017d240daa0d0ea1775420f24", - "mac_address": None, "model": "Lisa", "name": "Zone Thermostat Jessie", + "zigbee_mac_address": "ABCD012345670A03", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 99.9, "resolution": 0.01, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "preset_modes": ["home", "asleep", "away", "no_frost"], "active_preset": "asleep", - "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0], - }, "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -327,18 +288,17 @@ async def test_diagnostics( ], "selected_schedule": "CV Jessie", "last_used": "CV Jessie", - "schedule_temperature": 15.0, "mode": "auto", "sensors": {"temperature": 17.2, "setpoint": 15.0, "battery": 37}, }, "680423ff840043738f42cc7f1ff97a36": { - "class": "thermo_sensor", - "fw": "2019-03-27T01:00:00+01:00", - "hw": "1", + "dev_class": "thermo_sensor", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", "location": "08963fec7c53423ca5680aa4cb502c63", - "mac_address": None, "model": "Tom/Floor", "name": "Thermostatic Radiator Badkamer", + "zigbee_mac_address": "ABCD012345670A17", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 100.0, @@ -352,26 +312,19 @@ async def test_diagnostics( }, }, "f1fee6043d3642a9b0a65297455f008e": { - "class": "zone_thermostat", - "fw": "2016-10-27T02:00:00+02:00", - "hw": "255", + "dev_class": "zone_thermostat", + "firmware": "2016-10-27T02:00:00+02:00", + "hardware": "255", "location": "08963fec7c53423ca5680aa4cb502c63", - "mac_address": None, "model": "Lisa", "name": "Zone Thermostat Badkamer", + "zigbee_mac_address": "ABCD012345670A08", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 99.9, "resolution": 0.01, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "preset_modes": ["home", "asleep", "away", "no_frost"], "active_preset": "away", - "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0], - }, "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -381,20 +334,17 @@ async def test_diagnostics( ], "selected_schedule": "Badkamer Schema", "last_used": "Badkamer Schema", - "schedule_temperature": 15.0, "mode": "auto", "sensors": {"temperature": 18.9, "setpoint": 14.0, "battery": 92}, }, "675416a629f343c495449970e2ca37b5": { - "class": "router", - "fw": "2019-06-21T02:00:00+02:00", - "hw": None, + "dev_class": "router", + "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", - "mac_address": None, "model": "Plug", "name": "Ziggo Modem", - "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A01", + "vendor": "Plugwise", "sensors": { "electricity_consumed": 12.2, "electricity_consumed_interval": 2.97, @@ -404,26 +354,19 @@ async def test_diagnostics( "switches": {"relay": True, "lock": True}, }, "e7693eb9582644e5b865dba8d4447cf1": { - "class": "thermostatic_radiator_valve", - "fw": "2019-03-27T01:00:00+01:00", - "hw": "1", + "dev_class": "thermostatic_radiator_valve", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", "location": "446ac08dd04d4eff8ac57489757b7314", - "mac_address": None, "model": "Tom/Floor", "name": "CV Kraan Garage", + "zigbee_mac_address": "ABCD012345670A11", "vendor": "Plugwise", "lower_bound": 0.0, "upper_bound": 100.0, "resolution": 0.01, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "preset_modes": ["home", "asleep", "away", "no_frost"], "active_preset": "no_frost", - "presets": { - "home": [20.0, 22.0], - "asleep": [17.0, 24.0], - "away": [15.0, 25.0], - "vacation": [15.0, 28.0], - "no_frost": [10.0, 30.0], - }, "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -433,7 +376,6 @@ async def test_diagnostics( ], "selected_schedule": "None", "last_used": "Badkamer Schema", - "schedule_temperature": 15.0, "mode": "heat", "sensors": { "temperature": 15.6, diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index 811018db7e2..557680cb060 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -2,6 +2,7 @@ import asyncio from unittest.mock import MagicMock +import aiohttp from plugwise.exceptions import ( ConnectionFailedError, PlugwiseException, @@ -49,6 +50,7 @@ async def test_load_unload_config_entry( (PlugwiseException), (XMLDataMissingError), (asyncio.TimeoutError), + (aiohttp.ClientConnectionError), ], ) async def test_config_entry_not_ready( diff --git a/tests/components/powerwall/test_init.py b/tests/components/powerwall/test_init.py new file mode 100644 index 00000000000..d5fa92fc23f --- /dev/null +++ b/tests/components/powerwall/test_init.py @@ -0,0 +1,66 @@ +"""Tests for the PowerwallDataManager.""" + +import datetime +from unittest.mock import MagicMock, patch + +from tesla_powerwall import AccessDeniedError, LoginResponse + +from homeassistant.components.powerwall.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD +from homeassistant.core import HomeAssistant +from homeassistant.util.dt import utcnow + +from .mocks import _mock_powerwall_with_fixtures + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_update_data_reauthenticate_on_access_denied(hass: HomeAssistant): + """Test if _update_data of PowerwallDataManager reauthenticates on AccessDeniedError.""" + + mock_powerwall = await _mock_powerwall_with_fixtures(hass) + # login responses for the different tests: + # 1. login success on entry setup + # 2. login success after reauthentication + # 3. login failure after reauthentication + mock_powerwall.login = MagicMock(name="login", return_value=LoginResponse({})) + mock_powerwall.get_charge = MagicMock(name="get_charge", return_value=90.0) + mock_powerwall.is_authenticated = MagicMock( + name="is_authenticated", return_value=True + ) + mock_powerwall.logout = MagicMock(name="logout") + + config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4", CONF_PASSWORD: "password"} + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), patch( + "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + mock_powerwall.login.reset_mock(return_value=True) + mock_powerwall.get_charge.side_effect = [AccessDeniedError("test"), 90.0] + + async_fire_time_changed(hass, utcnow() + datetime.timedelta(minutes=1)) + await hass.async_block_till_done() + flows = hass.config_entries.flow.async_progress(DOMAIN) + assert len(flows) == 0 + + mock_powerwall.login.reset_mock() + mock_powerwall.login.side_effect = AccessDeniedError("test") + mock_powerwall.get_charge.side_effect = [AccessDeniedError("test"), 90.0] + + async_fire_time_changed(hass, utcnow() + datetime.timedelta(minutes=1)) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.LOADED + + flows = hass.config_entries.flow.async_progress(DOMAIN) + assert len(flows) == 1 + reauth_flow = flows[0] + assert reauth_flow["context"]["source"] == "reauth" diff --git a/tests/components/prosegur/test_alarm_control_panel.py b/tests/components/prosegur/test_alarm_control_panel.py index 9ab0c0d37de..8a50319047e 100644 --- a/tests/components/prosegur/test_alarm_control_panel.py +++ b/tests/components/prosegur/test_alarm_control_panel.py @@ -18,7 +18,7 @@ from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_UNAVAILABLE, ) -from homeassistant.helpers import entity_component +from homeassistant.helpers import entity_component, entity_registry as er from .common import CONTRACT, setup_platform @@ -49,7 +49,7 @@ def mock_status(request): async def test_entity_registry(hass, mock_auth, mock_status): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(PROSEGUR_ALARM_ENTITY) # Prosegur alarm device unique_id is the contract id associated to the alarm account diff --git a/tests/components/ps4/test_init.py b/tests/components/ps4/test_init.py index 8c43bd5df90..a84adba2a70 100644 --- a/tests/components/ps4/test_init.py +++ b/tests/components/ps4/test_init.py @@ -155,7 +155,7 @@ async def test_config_flow_entry_migrate(hass): "homeassistant.util.location.async_detect_location_info", return_value=MOCK_LOCATION, ), patch( - "homeassistant.helpers.entity_registry.async_get_registry", + "homeassistant.helpers.entity_registry.async_get", return_value=mock_e_registry, ): await ps4.async_migrate_entry(hass, mock_entry) diff --git a/tests/components/qnap_qsw/test_binary_sensor.py b/tests/components/qnap_qsw/test_binary_sensor.py new file mode 100644 index 00000000000..a36a34f02be --- /dev/null +++ b/tests/components/qnap_qsw/test_binary_sensor.py @@ -0,0 +1,17 @@ +"""The binary sensor tests for the QNAP QSW platform.""" + +from homeassistant.components.qnap_qsw.const import ATTR_MESSAGE +from homeassistant.const import STATE_OFF +from homeassistant.core import HomeAssistant + +from .util import async_init_integration + + +async def test_qnap_qsw_create_binary_sensors(hass: HomeAssistant) -> None: + """Test creation of binary sensors.""" + + await async_init_integration(hass) + + state = hass.states.get("binary_sensor.qsw_m408_4c_anomaly") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_MESSAGE) is None diff --git a/tests/components/qnap_qsw/test_button.py b/tests/components/qnap_qsw/test_button.py new file mode 100644 index 00000000000..5423c9686d4 --- /dev/null +++ b/tests/components/qnap_qsw/test_button.py @@ -0,0 +1,37 @@ +"""The sensor tests for the QNAP QSW platform.""" + +from unittest.mock import patch + +from homeassistant.components.button.const import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN +from homeassistant.core import HomeAssistant + +from .util import SYSTEM_COMMAND_MOCK, USERS_VERIFICATION_MOCK, async_init_integration + + +async def test_qnap_buttons(hass: HomeAssistant) -> None: + """Test buttons.""" + + await async_init_integration(hass) + + state = hass.states.get("button.qsw_m408_4c_reboot") + assert state + assert state.state == STATE_UNKNOWN + + with patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification", + return_value=USERS_VERIFICATION_MOCK, + ) as mock_users_verification, patch( + "homeassistant.components.qnap_qsw.QnapQswApi.post_system_command", + return_value=SYSTEM_COMMAND_MOCK, + ) as mock_post_system_command: + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: "button.qsw_m408_4c_reboot"}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_users_verification.assert_called_once() + mock_post_system_command.assert_called_once() diff --git a/tests/components/qnap_qsw/test_diagnostics.py b/tests/components/qnap_qsw/test_diagnostics.py new file mode 100644 index 00000000000..a4b5c1658e1 --- /dev/null +++ b/tests/components/qnap_qsw/test_diagnostics.py @@ -0,0 +1,123 @@ +"""The diagnostics tests for the QNAP QSW platform.""" + +from aiohttp import ClientSession +from aioqsw.const import ( + API_ANOMALY, + API_BUILD_NUMBER, + API_FAN1_SPEED, + API_MAX_SWITCH_TEMP, + API_NUMBER, + API_PRODUCT, + API_RESULT, + API_SWITCH_TEMP, + API_UPTIME, + API_VERSION, + QSD_ANOMALY, + QSD_BUILD_NUMBER, + QSD_FAN1_SPEED, + QSD_FIRMWARE_CONDITION, + QSD_FIRMWARE_INFO, + QSD_MAC, + QSD_NUMBER, + QSD_PRODUCT, + QSD_SERIAL, + QSD_SYSTEM_BOARD, + QSD_SYSTEM_SENSOR, + QSD_SYSTEM_TIME, + QSD_TEMP, + QSD_TEMP_MAX, + QSD_UPTIME, + QSD_VERSION, +) + +from homeassistant.components.diagnostics.const import REDACTED +from homeassistant.components.qnap_qsw.const import DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from .util import ( + CONFIG, + FIRMWARE_CONDITION_MOCK, + FIRMWARE_INFO_MOCK, + SYSTEM_BOARD_MOCK, + SYSTEM_SENSOR_MOCK, + SYSTEM_TIME_MOCK, + async_init_integration, +) + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_config_entry_diagnostics( + hass: HomeAssistant, hass_client: ClientSession +) -> None: + """Test config entry diagnostics.""" + await async_init_integration(hass) + assert hass.data[DOMAIN] + + config_entry = hass.config_entries.async_entries(DOMAIN)[0] + diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) + + assert ( + diag["config_entry"].items() + >= { + "data": { + CONF_PASSWORD: REDACTED, + CONF_URL: CONFIG[CONF_URL], + CONF_USERNAME: REDACTED, + }, + "domain": DOMAIN, + "unique_id": REDACTED, + }.items() + ) + + fw_cond_diag = diag["coord_data"][QSD_FIRMWARE_CONDITION] + fw_cond_mock = FIRMWARE_CONDITION_MOCK[API_RESULT] + assert ( + fw_cond_diag.items() + >= { + QSD_ANOMALY: fw_cond_mock[API_ANOMALY], + }.items() + ) + + fw_info_diag = diag["coord_data"][QSD_FIRMWARE_INFO] + fw_info_mock = FIRMWARE_INFO_MOCK[API_RESULT] + assert ( + fw_info_diag.items() + >= { + QSD_BUILD_NUMBER: fw_info_mock[API_BUILD_NUMBER], + QSD_NUMBER: fw_info_mock[API_NUMBER], + QSD_VERSION: fw_info_mock[API_VERSION], + }.items() + ) + + sys_board_diag = diag["coord_data"][QSD_SYSTEM_BOARD] + sys_board_mock = SYSTEM_BOARD_MOCK[API_RESULT] + assert ( + sys_board_diag.items() + >= { + QSD_MAC: REDACTED, + QSD_PRODUCT: sys_board_mock[API_PRODUCT], + QSD_SERIAL: REDACTED, + }.items() + ) + + sys_sensor_diag = diag["coord_data"][QSD_SYSTEM_SENSOR] + sys_sensor_mock = SYSTEM_SENSOR_MOCK[API_RESULT] + assert ( + sys_sensor_diag.items() + >= { + QSD_FAN1_SPEED: sys_sensor_mock[API_FAN1_SPEED], + QSD_TEMP: sys_sensor_mock[API_SWITCH_TEMP], + QSD_TEMP_MAX: sys_sensor_mock[API_MAX_SWITCH_TEMP], + }.items() + ) + + sys_time_diag = diag["coord_data"][QSD_SYSTEM_TIME] + sys_time_mock = SYSTEM_TIME_MOCK[API_RESULT] + assert ( + sys_time_diag.items() + >= { + QSD_UPTIME: sys_time_mock[API_UPTIME], + }.items() + ) diff --git a/tests/components/qnap_qsw/util.py b/tests/components/qnap_qsw/util.py index 501c31f55e9..28e7f7881d5 100644 --- a/tests/components/qnap_qsw/util.py +++ b/tests/components/qnap_qsw/util.py @@ -90,6 +90,12 @@ FIRMWARE_INFO_MOCK = { }, } +SYSTEM_COMMAND_MOCK = { + API_ERROR_CODE: 200, + API_ERROR_MESSAGE: "OK", + API_RESULT: "None", +} + SYSTEM_SENSOR_MOCK = { API_ERROR_CODE: 200, API_ERROR_MESSAGE: "OK", @@ -127,8 +133,12 @@ async def async_init_integration( ) -> None: """Set up the QNAP QSW integration in Home Assistant.""" - entry = MockConfigEntry(domain=DOMAIN, data=CONFIG) - entry.add_to_hass(hass) + config_entry = MockConfigEntry( + data=CONFIG, + domain=DOMAIN, + unique_id="qsw_unique_id", + ) + config_entry.add_to_hass(hass) with patch( "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_condition", @@ -149,5 +159,5 @@ async def async_init_integration( "homeassistant.components.qnap_qsw.QnapQswApi.post_users_login", return_value=USERS_LOGIN_MOCK, ): - await hass.config_entries.async_setup(entry.entry_id) + await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/raspberry_pi/__init__.py b/tests/components/raspberry_pi/__init__.py new file mode 100644 index 00000000000..0b10b50c6f0 --- /dev/null +++ b/tests/components/raspberry_pi/__init__.py @@ -0,0 +1 @@ +"""Tests for the Raspberry Pi integration.""" diff --git a/tests/components/raspberry_pi/test_config_flow.py b/tests/components/raspberry_pi/test_config_flow.py new file mode 100644 index 00000000000..dfad1100cad --- /dev/null +++ b/tests/components/raspberry_pi/test_config_flow.py @@ -0,0 +1,58 @@ +"""Test the Raspberry Pi config flow.""" +from unittest.mock import patch + +from homeassistant.components.raspberry_pi.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_CREATE_ENTRY + +from tests.common import MockConfigEntry, MockModule, mock_integration + + +async def test_config_flow(hass: HomeAssistant) -> None: + """Test the config flow.""" + mock_integration(hass, MockModule("hassio")) + + with patch( + "homeassistant.components.raspberry_pi.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "system"} + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "Raspberry Pi" + assert result["data"] == {} + assert result["options"] == {} + assert len(mock_setup_entry.mock_calls) == 1 + + config_entry = hass.config_entries.async_entries(DOMAIN)[0] + assert config_entry.data == {} + assert config_entry.options == {} + assert config_entry.title == "Raspberry Pi" + + +async def test_config_flow_single_entry(hass: HomeAssistant) -> None: + """Test only a single entry is allowed.""" + mock_integration(hass, MockModule("hassio")) + + # Setup the config entry + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={}, + title="Raspberry Pi", + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.raspberry_pi.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "system"} + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "single_instance_allowed" + mock_setup_entry.assert_not_called() diff --git a/tests/components/raspberry_pi/test_hardware.py b/tests/components/raspberry_pi/test_hardware.py new file mode 100644 index 00000000000..748972c8d60 --- /dev/null +++ b/tests/components/raspberry_pi/test_hardware.py @@ -0,0 +1,57 @@ +"""Test the Raspberry Pi hardware platform.""" +import pytest + +from homeassistant.components.hassio import DATA_OS_INFO +from homeassistant.components.raspberry_pi.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import MockModule, mock_integration + + +async def test_hardware_info(hass: HomeAssistant, hass_ws_client) -> None: + """Test we can get the board info.""" + mock_integration(hass, MockModule("hassio")) + hass.data[DATA_OS_INFO] = {"board": "rpi"} + + assert await async_setup_component(hass, DOMAIN, {}) + + client = await hass_ws_client(hass) + + await client.send_json({"id": 1, "type": "hardware/info"}) + msg = await client.receive_json() + + assert msg["id"] == 1 + assert msg["success"] + assert msg["result"] == { + "hardware": [ + { + "board": { + "hassio_board_id": "rpi", + "manufacturer": "raspberry_pi", + "model": "1", + "revision": None, + }, + "name": "Raspberry Pi", + "url": None, + } + ] + } + + +@pytest.mark.parametrize("os_info", [None, {"board": None}, {"board": "other"}]) +async def test_hardware_info_fail(hass: HomeAssistant, hass_ws_client, os_info) -> None: + """Test async_info raises if os_info is not as expected.""" + mock_integration(hass, MockModule("hassio")) + hass.data[DATA_OS_INFO] = os_info + + assert await async_setup_component(hass, DOMAIN, {}) + + client = await hass_ws_client(hass) + + await client.send_json({"id": 1, "type": "hardware/info"}) + msg = await client.receive_json() + + assert msg["id"] == 1 + assert msg["success"] + assert msg["result"] == {"hardware": []} diff --git a/tests/components/raspberry_pi/test_init.py b/tests/components/raspberry_pi/test_init.py new file mode 100644 index 00000000000..dd86da7bce0 --- /dev/null +++ b/tests/components/raspberry_pi/test_init.py @@ -0,0 +1,72 @@ +"""Test the Raspberry Pi integration.""" +from unittest.mock import patch + +from homeassistant.components.raspberry_pi.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, MockModule, mock_integration + + +async def test_setup_entry(hass: HomeAssistant) -> None: + """Test setup of a config entry.""" + mock_integration(hass, MockModule("hassio")) + + # Setup the config entry + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={}, + title="Raspberry Pi", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.raspberry_pi.get_os_info", + return_value={"board": "rpi"}, + ) as mock_get_os_info: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert len(mock_get_os_info.mock_calls) == 1 + + +async def test_setup_entry_wrong_board(hass: HomeAssistant) -> None: + """Test setup of a config entry with wrong board type.""" + mock_integration(hass, MockModule("hassio")) + + # Setup the config entry + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={}, + title="Raspberry Pi", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.raspberry_pi.get_os_info", + return_value={"board": "generic-x86-64"}, + ) as mock_get_os_info: + assert not await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert len(mock_get_os_info.mock_calls) == 1 + + +async def test_setup_entry_wait_hassio(hass: HomeAssistant) -> None: + """Test setup of a config entry when hassio has not fetched os_info.""" + mock_integration(hass, MockModule("hassio")) + + # Setup the config entry + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={}, + title="Raspberry Pi", + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.raspberry_pi.get_os_info", + return_value=None, + ) as mock_get_os_info: + assert not await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert len(mock_get_os_info.mock_calls) == 1 + assert config_entry.state == ConfigEntryState.SETUP_RETRY diff --git a/tests/components/recorder/common.py b/tests/components/recorder/common.py index a7472a6289e..39cde4c2e7c 100644 --- a/tests/components/recorder/common.py +++ b/tests/components/recorder/common.py @@ -1,15 +1,21 @@ """Common test utils for working with recorder.""" from __future__ import annotations +import asyncio +from dataclasses import dataclass from datetime import datetime, timedelta -from typing import cast +import time +from typing import Any, cast from sqlalchemy import create_engine from sqlalchemy.orm.session import Session from homeassistant import core as ha from homeassistant.components import recorder +from homeassistant.components.recorder import get_instance, statistics +from homeassistant.components.recorder.core import Recorder from homeassistant.components.recorder.models import RecorderRuns +from homeassistant.components.recorder.tasks import RecorderTask, StatisticsTask from homeassistant.core import HomeAssistant from homeassistant.util import dt as dt_util @@ -19,6 +25,38 @@ from tests.components.recorder import models_schema_0 DEFAULT_PURGE_TASKS = 3 +@dataclass +class BlockRecorderTask(RecorderTask): + """A task to block the recorder for testing only.""" + + event: asyncio.Event + seconds: float + + def run(self, instance: Recorder) -> None: + """Block the recorders event loop.""" + instance.hass.loop.call_soon_threadsafe(self.event.set) + time.sleep(self.seconds) + + +async def async_block_recorder(hass: HomeAssistant, seconds: float) -> None: + """Block the recorders event loop for testing. + + Returns as soon as the recorder has started the block. + + Does not wait for the block to finish. + """ + event = asyncio.Event() + get_instance(hass).queue_task(BlockRecorderTask(event, seconds)) + await event.wait() + + +def do_adhoc_statistics(hass: HomeAssistant, **kwargs: Any) -> None: + """Trigger an adhoc statistics run.""" + if not (start := kwargs.get("start")): + start = statistics.get_start_time() + get_instance(hass).queue_task(StatisticsTask(start)) + + def wait_recording_done(hass: HomeAssistant) -> None: """Block till recording is done.""" hass.block_till_done() diff --git a/tests/components/recorder/models_schema_23_with_newer_columns.py b/tests/components/recorder/models_schema_23_with_newer_columns.py new file mode 100644 index 00000000000..a086aa588d4 --- /dev/null +++ b/tests/components/recorder/models_schema_23_with_newer_columns.py @@ -0,0 +1,613 @@ +"""Models for SQLAlchemy. + +This file contains the model definitions for schema version 23 +used by Home Assistant Core 2021.11.0, which adds the name column +to statistics_meta. + +The v23 schema as been slightly modified to add the EventData table to +allow the recorder to startup successfully. + +It is used to test the schema migration logic. +""" +from __future__ import annotations + +from datetime import datetime, timedelta +import json +import logging +from typing import TypedDict, overload + +from sqlalchemy import ( + BigInteger, + Boolean, + Column, + DateTime, + Float, + ForeignKey, + Identity, + Index, + Integer, + SmallInteger, + String, + Text, + distinct, +) +from sqlalchemy.dialects import mysql, oracle, postgresql +from sqlalchemy.ext.declarative import declared_attr +from sqlalchemy.orm import declarative_base, relationship +from sqlalchemy.orm.session import Session + +from homeassistant.const import ( + MAX_LENGTH_EVENT_CONTEXT_ID, + MAX_LENGTH_EVENT_EVENT_TYPE, + MAX_LENGTH_EVENT_ORIGIN, + MAX_LENGTH_STATE_DOMAIN, + MAX_LENGTH_STATE_ENTITY_ID, + MAX_LENGTH_STATE_STATE, +) +from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id +from homeassistant.helpers.json import JSONEncoder +import homeassistant.util.dt as dt_util + +# SQLAlchemy Schema +# pylint: disable=invalid-name +Base = declarative_base() + +SCHEMA_VERSION = 23 + +_LOGGER = logging.getLogger(__name__) + +DB_TIMEZONE = "+00:00" + +TABLE_EVENTS = "events" +TABLE_STATES = "states" +TABLE_RECORDER_RUNS = "recorder_runs" +TABLE_SCHEMA_CHANGES = "schema_changes" +TABLE_STATISTICS = "statistics" +TABLE_STATISTICS_META = "statistics_meta" +TABLE_STATISTICS_RUNS = "statistics_runs" +TABLE_STATISTICS_SHORT_TERM = "statistics_short_term" +TABLE_EVENT_DATA = "event_data" + +ALL_TABLES = [ + TABLE_STATES, + TABLE_EVENTS, + TABLE_RECORDER_RUNS, + TABLE_SCHEMA_CHANGES, + TABLE_STATISTICS, + TABLE_STATISTICS_META, + TABLE_STATISTICS_RUNS, + TABLE_STATISTICS_SHORT_TERM, +] + +DATETIME_TYPE = DateTime(timezone=True).with_variant( + mysql.DATETIME(timezone=True, fsp=6), "mysql" +) +DOUBLE_TYPE = ( + Float() + .with_variant(mysql.DOUBLE(asdecimal=False), "mysql") + .with_variant(oracle.DOUBLE_PRECISION(), "oracle") + .with_variant(postgresql.DOUBLE_PRECISION(), "postgresql") +) + + +class Events(Base): # type: ignore + """Event history data.""" + + __table_args__ = ( + # Used for fetching events at a specific time + # see logbook + Index("ix_events_event_type_time_fired", "event_type", "time_fired"), + {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, + ) + __tablename__ = TABLE_EVENTS + event_id = Column(Integer, Identity(), primary_key=True) + event_type = Column(String(MAX_LENGTH_EVENT_EVENT_TYPE)) + event_data = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) + origin = Column(String(MAX_LENGTH_EVENT_ORIGIN)) + origin_idx = Column( + SmallInteger + ) # *** Not originally in v23, only added for recorder to startup ok + time_fired = Column(DATETIME_TYPE, index=True) + created = Column(DATETIME_TYPE, default=dt_util.utcnow) + context_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) + context_user_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) + context_parent_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) + data_id = Column( + Integer, ForeignKey("event_data.data_id"), index=True + ) # *** Not originally in v23, only added for recorder to startup ok + event_data_rel = relationship( + "EventData" + ) # *** Not originally in v23, only added for recorder to startup ok + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + @staticmethod + def from_event(event, event_data=None): + """Create an event database object from a native event.""" + return Events( + event_type=event.event_type, + event_data=event_data + or json.dumps(event.data, cls=JSONEncoder, separators=(",", ":")), + origin=str(event.origin.value), + time_fired=event.time_fired, + context_id=event.context.id, + context_user_id=event.context.user_id, + context_parent_id=event.context.parent_id, + ) + + def to_native(self, validate_entity_id=True): + """Convert to a native HA Event.""" + context = Context( + id=self.context_id, + user_id=self.context_user_id, + parent_id=self.context_parent_id, + ) + try: + return Event( + self.event_type, + json.loads(self.event_data), + EventOrigin(self.origin), + process_timestamp(self.time_fired), + context=context, + ) + except ValueError: + # When json.loads fails + _LOGGER.exception("Error converting to event: %s", self) + return None + + +# *** Not originally in v23, only added for recorder to startup ok +# This is not being tested by the v23 statistics migration tests +class EventData(Base): # type: ignore[misc,valid-type] + """Event data history.""" + + __table_args__ = ( + {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, + ) + __tablename__ = TABLE_EVENT_DATA + data_id = Column(Integer, Identity(), primary_key=True) + hash = Column(BigInteger, index=True) + # Note that this is not named attributes to avoid confusion with the states table + shared_data = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) + + +class States(Base): # type: ignore + """State change history.""" + + __table_args__ = ( + # Used for fetching the state of entities at a specific time + # (get_states in history.py) + Index("ix_states_entity_id_last_updated", "entity_id", "last_updated"), + {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, + ) + __tablename__ = TABLE_STATES + state_id = Column(Integer, Identity(), primary_key=True) + domain = Column(String(MAX_LENGTH_STATE_DOMAIN)) + entity_id = Column(String(MAX_LENGTH_STATE_ENTITY_ID)) + state = Column(String(MAX_LENGTH_STATE_STATE)) + attributes = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) + event_id = Column( + Integer, ForeignKey("events.event_id", ondelete="CASCADE"), index=True + ) + last_changed = Column(DATETIME_TYPE, default=dt_util.utcnow) + last_updated = Column(DATETIME_TYPE, default=dt_util.utcnow, index=True) + created = Column(DATETIME_TYPE, default=dt_util.utcnow) + old_state_id = Column(Integer, ForeignKey("states.state_id"), index=True) + event = relationship("Events", uselist=False) + old_state = relationship("States", remote_side=[state_id]) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + @staticmethod + def from_event(event): + """Create object from a state_changed event.""" + entity_id = event.data["entity_id"] + state = event.data.get("new_state") + + dbstate = States(entity_id=entity_id) + + # State got deleted + if state is None: + dbstate.state = "" + dbstate.domain = split_entity_id(entity_id)[0] + dbstate.attributes = "{}" + dbstate.last_changed = event.time_fired + dbstate.last_updated = event.time_fired + else: + dbstate.domain = state.domain + dbstate.state = state.state + dbstate.attributes = json.dumps( + dict(state.attributes), cls=JSONEncoder, separators=(",", ":") + ) + dbstate.last_changed = state.last_changed + dbstate.last_updated = state.last_updated + + return dbstate + + def to_native(self, validate_entity_id=True): + """Convert to an HA state object.""" + try: + return State( + self.entity_id, + self.state, + json.loads(self.attributes), + process_timestamp(self.last_changed), + process_timestamp(self.last_updated), + # Join the events table on event_id to get the context instead + # as it will always be there for state_changed events + context=Context(id=None), + validate_entity_id=validate_entity_id, + ) + except ValueError: + # When json.loads fails + _LOGGER.exception("Error converting row to state: %s", self) + return None + + +class StatisticResult(TypedDict): + """Statistic result data class. + + Allows multiple datapoints for the same statistic_id. + """ + + meta: StatisticMetaData + stat: StatisticData + + +class StatisticDataBase(TypedDict): + """Mandatory fields for statistic data class.""" + + start: datetime + + +class StatisticData(StatisticDataBase, total=False): + """Statistic data class.""" + + mean: float + min: float + max: float + last_reset: datetime | None + state: float + sum: float + + +class StatisticsBase: + """Statistics base class.""" + + id = Column(Integer, Identity(), primary_key=True) + created = Column(DATETIME_TYPE, default=dt_util.utcnow) + + @declared_attr + def metadata_id(self): + """Define the metadata_id column for sub classes.""" + return Column( + Integer, + ForeignKey(f"{TABLE_STATISTICS_META}.id", ondelete="CASCADE"), + index=True, + ) + + start = Column(DATETIME_TYPE, index=True) + mean = Column(DOUBLE_TYPE) + min = Column(DOUBLE_TYPE) + max = Column(DOUBLE_TYPE) + last_reset = Column(DATETIME_TYPE) + state = Column(DOUBLE_TYPE) + sum = Column(DOUBLE_TYPE) + + @classmethod + def from_stats(cls, metadata_id: int, stats: StatisticData): + """Create object from a statistics.""" + return cls( # type: ignore + metadata_id=metadata_id, + **stats, + ) + + +class Statistics(Base, StatisticsBase): # type: ignore + """Long term statistics.""" + + duration = timedelta(hours=1) + + __table_args__ = ( + # Used for fetching statistics for a certain entity at a specific time + Index("ix_statistics_statistic_id_start", "metadata_id", "start"), + ) + __tablename__ = TABLE_STATISTICS + + +class StatisticsShortTerm(Base, StatisticsBase): # type: ignore + """Short term statistics.""" + + duration = timedelta(minutes=5) + + __table_args__ = ( + # Used for fetching statistics for a certain entity at a specific time + Index("ix_statistics_short_term_statistic_id_start", "metadata_id", "start"), + ) + __tablename__ = TABLE_STATISTICS_SHORT_TERM + + +class StatisticMetaData(TypedDict): + """Statistic meta data class.""" + + has_mean: bool + has_sum: bool + name: str | None + source: str + statistic_id: str + unit_of_measurement: str | None + + +class StatisticsMeta(Base): # type: ignore + """Statistics meta data.""" + + __table_args__ = ( + {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, + ) + __tablename__ = TABLE_STATISTICS_META + id = Column(Integer, Identity(), primary_key=True) + statistic_id = Column(String(255), index=True) + source = Column(String(32)) + unit_of_measurement = Column(String(255)) + has_mean = Column(Boolean) + has_sum = Column(Boolean) + name = Column(String(255)) + + @staticmethod + def from_meta(meta: StatisticMetaData) -> StatisticsMeta: + """Create object from meta data.""" + return StatisticsMeta(**meta) + + +class RecorderRuns(Base): # type: ignore + """Representation of recorder run.""" + + __table_args__ = (Index("ix_recorder_runs_start_end", "start", "end"),) + __tablename__ = TABLE_RECORDER_RUNS + run_id = Column(Integer, Identity(), primary_key=True) + start = Column(DateTime(timezone=True), default=dt_util.utcnow) + end = Column(DateTime(timezone=True)) + closed_incorrect = Column(Boolean, default=False) + created = Column(DateTime(timezone=True), default=dt_util.utcnow) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + end = ( + f"'{self.end.isoformat(sep=' ', timespec='seconds')}'" if self.end else None + ) + return ( + f"" + ) + + def entity_ids(self, point_in_time=None): + """Return the entity ids that existed in this run. + + Specify point_in_time if you want to know which existed at that point + in time inside the run. + """ + session = Session.object_session(self) + + assert session is not None, "RecorderRuns need to be persisted" + + query = session.query(distinct(States.entity_id)).filter( + States.last_updated >= self.start + ) + + if point_in_time is not None: + query = query.filter(States.last_updated < point_in_time) + elif self.end is not None: + query = query.filter(States.last_updated < self.end) + + return [row[0] for row in query] + + def to_native(self, validate_entity_id=True): + """Return self, native format is this model.""" + return self + + +class SchemaChanges(Base): # type: ignore + """Representation of schema version changes.""" + + __tablename__ = TABLE_SCHEMA_CHANGES + change_id = Column(Integer, Identity(), primary_key=True) + schema_version = Column(Integer) + changed = Column(DateTime(timezone=True), default=dt_util.utcnow) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + +class StatisticsRuns(Base): # type: ignore + """Representation of statistics run.""" + + __tablename__ = TABLE_STATISTICS_RUNS + run_id = Column(Integer, Identity(), primary_key=True) + start = Column(DateTime(timezone=True)) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + +@overload +def process_timestamp(ts: None) -> None: + ... + + +@overload +def process_timestamp(ts: datetime) -> datetime: + ... + + +def process_timestamp(ts: datetime | None) -> datetime | None: + """Process a timestamp into datetime object.""" + if ts is None: + return None + if ts.tzinfo is None: + return ts.replace(tzinfo=dt_util.UTC) + + return dt_util.as_utc(ts) + + +@overload +def process_timestamp_to_utc_isoformat(ts: None) -> None: + ... + + +@overload +def process_timestamp_to_utc_isoformat(ts: datetime) -> str: + ... + + +def process_timestamp_to_utc_isoformat(ts: datetime | None) -> str | None: + """Process a timestamp into UTC isotime.""" + if ts is None: + return None + if ts.tzinfo == dt_util.UTC: + return ts.isoformat() + if ts.tzinfo is None: + return f"{ts.isoformat()}{DB_TIMEZONE}" + return ts.astimezone(dt_util.UTC).isoformat() + + +class LazyState(State): + """A lazy version of core State.""" + + __slots__ = [ + "_row", + "entity_id", + "state", + "_attributes", + "_last_changed", + "_last_updated", + "_context", + ] + + def __init__(self, row): # pylint: disable=super-init-not-called + """Init the lazy state.""" + self._row = row + self.entity_id = self._row.entity_id + self.state = self._row.state or "" + self._attributes = None + self._last_changed = None + self._last_updated = None + self._context = None + + @property # type: ignore + def attributes(self): + """State attributes.""" + if not self._attributes: + try: + self._attributes = json.loads(self._row.attributes) + except ValueError: + # When json.loads fails + _LOGGER.exception("Error converting row to state: %s", self._row) + self._attributes = {} + return self._attributes + + @attributes.setter + def attributes(self, value): + """Set attributes.""" + self._attributes = value + + @property # type: ignore + def context(self): + """State context.""" + if not self._context: + self._context = Context(id=None) + return self._context + + @context.setter + def context(self, value): + """Set context.""" + self._context = value + + @property # type: ignore + def last_changed(self): + """Last changed datetime.""" + if not self._last_changed: + self._last_changed = process_timestamp(self._row.last_changed) + return self._last_changed + + @last_changed.setter + def last_changed(self, value): + """Set last changed datetime.""" + self._last_changed = value + + @property # type: ignore + def last_updated(self): + """Last updated datetime.""" + if not self._last_updated: + self._last_updated = process_timestamp(self._row.last_updated) + return self._last_updated + + @last_updated.setter + def last_updated(self, value): + """Set last updated datetime.""" + self._last_updated = value + + def as_dict(self): + """Return a dict representation of the LazyState. + + Async friendly. + + To be used for JSON serialization. + """ + if self._last_changed: + last_changed_isoformat = self._last_changed.isoformat() + else: + last_changed_isoformat = process_timestamp_to_utc_isoformat( + self._row.last_changed + ) + if self._last_updated: + last_updated_isoformat = self._last_updated.isoformat() + else: + last_updated_isoformat = process_timestamp_to_utc_isoformat( + self._row.last_updated + ) + return { + "entity_id": self.entity_id, + "state": self.state, + "attributes": self._attributes or self.attributes, + "last_changed": last_changed_isoformat, + "last_updated": last_updated_isoformat, + } + + def __eq__(self, other): + """Return the comparison.""" + return ( + other.__class__ in [self.__class__, State] + and self.entity_id == other.entity_id + and self.state == other.state + and self.attributes == other.attributes + ) diff --git a/tests/components/recorder/models_schema_28.py b/tests/components/recorder/models_schema_28.py new file mode 100644 index 00000000000..8d2de0432ac --- /dev/null +++ b/tests/components/recorder/models_schema_28.py @@ -0,0 +1,753 @@ +"""Models for SQLAlchemy. + +This file contains the model definitions for schema version 28. +It is used to test the schema migration logic. +""" +from __future__ import annotations + +from datetime import datetime, timedelta +import json +import logging +from typing import Any, TypedDict, cast, overload + +from fnvhash import fnv1a_32 +from sqlalchemy import ( + BigInteger, + Boolean, + Column, + DateTime, + Float, + ForeignKey, + Identity, + Index, + Integer, + SmallInteger, + String, + Text, + distinct, +) +from sqlalchemy.dialects import mysql, oracle, postgresql +from sqlalchemy.engine.row import Row +from sqlalchemy.ext.declarative import declared_attr +from sqlalchemy.orm import declarative_base, relationship +from sqlalchemy.orm.session import Session + +from homeassistant.components.recorder.const import ALL_DOMAIN_EXCLUDE_ATTRS, JSON_DUMP +from homeassistant.const import ( + MAX_LENGTH_EVENT_CONTEXT_ID, + MAX_LENGTH_EVENT_EVENT_TYPE, + MAX_LENGTH_EVENT_ORIGIN, + MAX_LENGTH_STATE_ENTITY_ID, + MAX_LENGTH_STATE_STATE, +) +from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id +import homeassistant.util.dt as dt_util + +# SQLAlchemy Schema +# pylint: disable=invalid-name +Base = declarative_base() + +SCHEMA_VERSION = 28 + +_LOGGER = logging.getLogger(__name__) + +DB_TIMEZONE = "+00:00" + +TABLE_EVENTS = "events" +TABLE_EVENT_DATA = "event_data" +TABLE_STATES = "states" +TABLE_STATE_ATTRIBUTES = "state_attributes" +TABLE_RECORDER_RUNS = "recorder_runs" +TABLE_SCHEMA_CHANGES = "schema_changes" +TABLE_STATISTICS = "statistics" +TABLE_STATISTICS_META = "statistics_meta" +TABLE_STATISTICS_RUNS = "statistics_runs" +TABLE_STATISTICS_SHORT_TERM = "statistics_short_term" + +ALL_TABLES = [ + TABLE_STATES, + TABLE_STATE_ATTRIBUTES, + TABLE_EVENTS, + TABLE_EVENT_DATA, + TABLE_RECORDER_RUNS, + TABLE_SCHEMA_CHANGES, + TABLE_STATISTICS, + TABLE_STATISTICS_META, + TABLE_STATISTICS_RUNS, + TABLE_STATISTICS_SHORT_TERM, +] + +TABLES_TO_CHECK = [ + TABLE_STATES, + TABLE_EVENTS, + TABLE_RECORDER_RUNS, + TABLE_SCHEMA_CHANGES, +] + + +EMPTY_JSON_OBJECT = "{}" + + +DATETIME_TYPE = DateTime(timezone=True).with_variant( + mysql.DATETIME(timezone=True, fsp=6), "mysql" +) +DOUBLE_TYPE = ( + Float() + .with_variant(mysql.DOUBLE(asdecimal=False), "mysql") + .with_variant(oracle.DOUBLE_PRECISION(), "oracle") + .with_variant(postgresql.DOUBLE_PRECISION(), "postgresql") +) +EVENT_ORIGIN_ORDER = [EventOrigin.local, EventOrigin.remote] +EVENT_ORIGIN_TO_IDX = {origin: idx for idx, origin in enumerate(EVENT_ORIGIN_ORDER)} + + +class Events(Base): # type: ignore[misc,valid-type] + """Event history data.""" + + __table_args__ = ( + # Used for fetching events at a specific time + # see logbook + Index("ix_events_event_type_time_fired", "event_type", "time_fired"), + {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, + ) + __tablename__ = TABLE_EVENTS + event_id = Column(Integer, Identity(), primary_key=True) # no longer used + event_type = Column(String(MAX_LENGTH_EVENT_EVENT_TYPE)) + event_data = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) + origin = Column(String(MAX_LENGTH_EVENT_ORIGIN)) # no longer used + origin_idx = Column(SmallInteger) + time_fired = Column(DATETIME_TYPE, index=True) + context_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) + context_user_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID)) + context_parent_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID)) + data_id = Column(Integer, ForeignKey("event_data.data_id"), index=True) + event_data_rel = relationship("EventData") + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + @staticmethod + def from_event(event: Event) -> Events: + """Create an event database object from a native event.""" + return Events( + event_type=event.event_type, + event_data=None, + origin_idx=EVENT_ORIGIN_TO_IDX.get(event.origin), + time_fired=event.time_fired, + context_id=event.context.id, + context_user_id=event.context.user_id, + context_parent_id=event.context.parent_id, + ) + + def to_native(self, validate_entity_id: bool = True) -> Event | None: + """Convert to a native HA Event.""" + context = Context( + id=self.context_id, + user_id=self.context_user_id, + parent_id=self.context_parent_id, + ) + try: + return Event( + self.event_type, + json.loads(self.event_data) if self.event_data else {}, + EventOrigin(self.origin) + if self.origin + else EVENT_ORIGIN_ORDER[self.origin_idx], + process_timestamp(self.time_fired), + context=context, + ) + except ValueError: + # When json.loads fails + _LOGGER.exception("Error converting to event: %s", self) + return None + + +class EventData(Base): # type: ignore[misc,valid-type] + """Event data history.""" + + __table_args__ = ( + {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, + ) + __tablename__ = TABLE_EVENT_DATA + data_id = Column(Integer, Identity(), primary_key=True) + hash = Column(BigInteger, index=True) + # Note that this is not named attributes to avoid confusion with the states table + shared_data = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + @staticmethod + def from_event(event: Event) -> EventData: + """Create object from an event.""" + shared_data = JSON_DUMP(event.data) + return EventData( + shared_data=shared_data, hash=EventData.hash_shared_data(shared_data) + ) + + @staticmethod + def shared_data_from_event(event: Event) -> str: + """Create shared_attrs from an event.""" + return JSON_DUMP(event.data) + + @staticmethod + def hash_shared_data(shared_data: str) -> int: + """Return the hash of json encoded shared data.""" + return cast(int, fnv1a_32(shared_data.encode("utf-8"))) + + def to_native(self) -> dict[str, Any]: + """Convert to an HA state object.""" + try: + return cast(dict[str, Any], json.loads(self.shared_data)) + except ValueError: + _LOGGER.exception("Error converting row to event data: %s", self) + return {} + + +class States(Base): # type: ignore[misc,valid-type] + """State change history.""" + + __table_args__ = ( + # Used for fetching the state of entities at a specific time + # (get_states in history.py) + Index("ix_states_entity_id_last_updated", "entity_id", "last_updated"), + {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, + ) + __tablename__ = TABLE_STATES + state_id = Column(Integer, Identity(), primary_key=True) + entity_id = Column(String(MAX_LENGTH_STATE_ENTITY_ID)) + state = Column(String(MAX_LENGTH_STATE_STATE)) + attributes = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) + event_id = Column( + Integer, ForeignKey("events.event_id", ondelete="CASCADE"), index=True + ) + last_changed = Column(DATETIME_TYPE, default=dt_util.utcnow) + last_updated = Column(DATETIME_TYPE, default=dt_util.utcnow, index=True) + old_state_id = Column(Integer, ForeignKey("states.state_id"), index=True) + attributes_id = Column( + Integer, ForeignKey("state_attributes.attributes_id"), index=True + ) + context_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) + context_user_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID)) + context_parent_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID)) + origin_idx = Column(SmallInteger) # 0 is local, 1 is remote + old_state = relationship("States", remote_side=[state_id]) + state_attributes = relationship("StateAttributes") + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + @staticmethod + def from_event(event: Event) -> States: + """Create object from a state_changed event.""" + entity_id = event.data["entity_id"] + state: State | None = event.data.get("new_state") + dbstate = States( + entity_id=entity_id, + attributes=None, + context_id=event.context.id, + context_user_id=event.context.user_id, + context_parent_id=event.context.parent_id, + origin_idx=EVENT_ORIGIN_TO_IDX.get(event.origin), + ) + + # None state means the state was removed from the state machine + if state is None: + dbstate.state = "" + dbstate.last_changed = event.time_fired + dbstate.last_updated = event.time_fired + else: + dbstate.state = state.state + dbstate.last_changed = state.last_changed + dbstate.last_updated = state.last_updated + + return dbstate + + def to_native(self, validate_entity_id: bool = True) -> State | None: + """Convert to an HA state object.""" + context = Context( + id=self.context_id, + user_id=self.context_user_id, + parent_id=self.context_parent_id, + ) + try: + return State( + self.entity_id, + self.state, + # Join the state_attributes table on attributes_id to get the attributes + # for newer states + json.loads(self.attributes) if self.attributes else {}, + process_timestamp(self.last_changed), + process_timestamp(self.last_updated), + context=context, + validate_entity_id=validate_entity_id, + ) + except ValueError: + # When json.loads fails + _LOGGER.exception("Error converting row to state: %s", self) + return None + + +class StateAttributes(Base): # type: ignore[misc,valid-type] + """State attribute change history.""" + + __table_args__ = ( + {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, + ) + __tablename__ = TABLE_STATE_ATTRIBUTES + attributes_id = Column(Integer, Identity(), primary_key=True) + hash = Column(BigInteger, index=True) + # Note that this is not named attributes to avoid confusion with the states table + shared_attrs = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + @staticmethod + def from_event(event: Event) -> StateAttributes: + """Create object from a state_changed event.""" + state: State | None = event.data.get("new_state") + # None state means the state was removed from the state machine + dbstate = StateAttributes( + shared_attrs="{}" if state is None else JSON_DUMP(state.attributes) + ) + dbstate.hash = StateAttributes.hash_shared_attrs(dbstate.shared_attrs) + return dbstate + + @staticmethod + def shared_attrs_from_event( + event: Event, exclude_attrs_by_domain: dict[str, set[str]] + ) -> str: + """Create shared_attrs from a state_changed event.""" + state: State | None = event.data.get("new_state") + # None state means the state was removed from the state machine + if state is None: + return "{}" + domain = split_entity_id(state.entity_id)[0] + exclude_attrs = ( + exclude_attrs_by_domain.get(domain, set()) | ALL_DOMAIN_EXCLUDE_ATTRS + ) + return JSON_DUMP( + {k: v for k, v in state.attributes.items() if k not in exclude_attrs} + ) + + @staticmethod + def hash_shared_attrs(shared_attrs: str) -> int: + """Return the hash of json encoded shared attributes.""" + return cast(int, fnv1a_32(shared_attrs.encode("utf-8"))) + + def to_native(self) -> dict[str, Any]: + """Convert to an HA state object.""" + try: + return cast(dict[str, Any], json.loads(self.shared_attrs)) + except ValueError: + # When json.loads fails + _LOGGER.exception("Error converting row to state attributes: %s", self) + return {} + + +class StatisticResult(TypedDict): + """Statistic result data class. + + Allows multiple datapoints for the same statistic_id. + """ + + meta: StatisticMetaData + stat: StatisticData + + +class StatisticDataBase(TypedDict): + """Mandatory fields for statistic data class.""" + + start: datetime + + +class StatisticData(StatisticDataBase, total=False): + """Statistic data class.""" + + mean: float + min: float + max: float + last_reset: datetime | None + state: float + sum: float + + +class StatisticsBase: + """Statistics base class.""" + + id = Column(Integer, Identity(), primary_key=True) + created = Column(DATETIME_TYPE, default=dt_util.utcnow) + + @declared_attr # type: ignore[misc] + def metadata_id(self) -> Column: + """Define the metadata_id column for sub classes.""" + return Column( + Integer, + ForeignKey(f"{TABLE_STATISTICS_META}.id", ondelete="CASCADE"), + index=True, + ) + + start = Column(DATETIME_TYPE, index=True) + mean = Column(DOUBLE_TYPE) + min = Column(DOUBLE_TYPE) + max = Column(DOUBLE_TYPE) + last_reset = Column(DATETIME_TYPE) + state = Column(DOUBLE_TYPE) + sum = Column(DOUBLE_TYPE) + + @classmethod + def from_stats(cls, metadata_id: int, stats: StatisticData) -> StatisticsBase: + """Create object from a statistics.""" + return cls( # type: ignore[call-arg,misc] + metadata_id=metadata_id, + **stats, + ) + + +class Statistics(Base, StatisticsBase): # type: ignore[misc,valid-type] + """Long term statistics.""" + + duration = timedelta(hours=1) + + __table_args__ = ( + # Used for fetching statistics for a certain entity at a specific time + Index("ix_statistics_statistic_id_start", "metadata_id", "start", unique=True), + ) + __tablename__ = TABLE_STATISTICS + + +class StatisticsShortTerm(Base, StatisticsBase): # type: ignore[misc,valid-type] + """Short term statistics.""" + + duration = timedelta(minutes=5) + + __table_args__ = ( + # Used for fetching statistics for a certain entity at a specific time + Index( + "ix_statistics_short_term_statistic_id_start", + "metadata_id", + "start", + unique=True, + ), + ) + __tablename__ = TABLE_STATISTICS_SHORT_TERM + + +class StatisticMetaData(TypedDict): + """Statistic meta data class.""" + + has_mean: bool + has_sum: bool + name: str | None + source: str + statistic_id: str + unit_of_measurement: str | None + + +class StatisticsMeta(Base): # type: ignore[misc,valid-type] + """Statistics meta data.""" + + __table_args__ = ( + {"mysql_default_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci"}, + ) + __tablename__ = TABLE_STATISTICS_META + id = Column(Integer, Identity(), primary_key=True) + statistic_id = Column(String(255), index=True) + source = Column(String(32)) + unit_of_measurement = Column(String(255)) + has_mean = Column(Boolean) + has_sum = Column(Boolean) + name = Column(String(255)) + + @staticmethod + def from_meta(meta: StatisticMetaData) -> StatisticsMeta: + """Create object from meta data.""" + return StatisticsMeta(**meta) + + +class RecorderRuns(Base): # type: ignore[misc,valid-type] + """Representation of recorder run.""" + + __table_args__ = (Index("ix_recorder_runs_start_end", "start", "end"),) + __tablename__ = TABLE_RECORDER_RUNS + run_id = Column(Integer, Identity(), primary_key=True) + start = Column(DateTime(timezone=True), default=dt_util.utcnow) + end = Column(DateTime(timezone=True)) + closed_incorrect = Column(Boolean, default=False) + created = Column(DateTime(timezone=True), default=dt_util.utcnow) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + end = ( + f"'{self.end.isoformat(sep=' ', timespec='seconds')}'" if self.end else None + ) + return ( + f"" + ) + + def entity_ids(self, point_in_time: datetime | None = None) -> list[str]: + """Return the entity ids that existed in this run. + + Specify point_in_time if you want to know which existed at that point + in time inside the run. + """ + session = Session.object_session(self) + + assert session is not None, "RecorderRuns need to be persisted" + + query = session.query(distinct(States.entity_id)).filter( + States.last_updated >= self.start + ) + + if point_in_time is not None: + query = query.filter(States.last_updated < point_in_time) + elif self.end is not None: + query = query.filter(States.last_updated < self.end) + + return [row[0] for row in query] + + def to_native(self, validate_entity_id: bool = True) -> RecorderRuns: + """Return self, native format is this model.""" + return self + + +class SchemaChanges(Base): # type: ignore[misc,valid-type] + """Representation of schema version changes.""" + + __tablename__ = TABLE_SCHEMA_CHANGES + change_id = Column(Integer, Identity(), primary_key=True) + schema_version = Column(Integer) + changed = Column(DateTime(timezone=True), default=dt_util.utcnow) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + +class StatisticsRuns(Base): # type: ignore[misc,valid-type] + """Representation of statistics run.""" + + __tablename__ = TABLE_STATISTICS_RUNS + run_id = Column(Integer, Identity(), primary_key=True) + start = Column(DateTime(timezone=True), index=True) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + +@overload +def process_timestamp(ts: None) -> None: + ... + + +@overload +def process_timestamp(ts: datetime) -> datetime: + ... + + +def process_timestamp(ts: datetime | None) -> datetime | None: + """Process a timestamp into datetime object.""" + if ts is None: + return None + if ts.tzinfo is None: + return ts.replace(tzinfo=dt_util.UTC) + + return dt_util.as_utc(ts) + + +@overload +def process_timestamp_to_utc_isoformat(ts: None) -> None: + ... + + +@overload +def process_timestamp_to_utc_isoformat(ts: datetime) -> str: + ... + + +def process_timestamp_to_utc_isoformat(ts: datetime | None) -> str | None: + """Process a timestamp into UTC isotime.""" + if ts is None: + return None + if ts.tzinfo == dt_util.UTC: + return ts.isoformat() + if ts.tzinfo is None: + return f"{ts.isoformat()}{DB_TIMEZONE}" + return ts.astimezone(dt_util.UTC).isoformat() + + +class LazyState(State): + """A lazy version of core State.""" + + __slots__ = [ + "_row", + "_attributes", + "_last_changed", + "_last_updated", + "_context", + "_attr_cache", + ] + + def __init__( # pylint: disable=super-init-not-called + self, row: Row, attr_cache: dict[str, dict[str, Any]] | None = None + ) -> None: + """Init the lazy state.""" + self._row = row + self.entity_id: str = self._row.entity_id + self.state = self._row.state or "" + self._attributes: dict[str, Any] | None = None + self._last_changed: datetime | None = None + self._last_updated: datetime | None = None + self._context: Context | None = None + self._attr_cache = attr_cache + + @property # type: ignore[override] + def attributes(self) -> dict[str, Any]: # type: ignore[override] + """State attributes.""" + if self._attributes is None: + source = self._row.shared_attrs or self._row.attributes + if self._attr_cache is not None and ( + attributes := self._attr_cache.get(source) + ): + self._attributes = attributes + return attributes + if source == EMPTY_JSON_OBJECT or source is None: + self._attributes = {} + return self._attributes + try: + self._attributes = json.loads(source) + except ValueError: + # When json.loads fails + _LOGGER.exception( + "Error converting row to state attributes: %s", self._row + ) + self._attributes = {} + if self._attr_cache is not None: + self._attr_cache[source] = self._attributes + return self._attributes + + @attributes.setter + def attributes(self, value: dict[str, Any]) -> None: + """Set attributes.""" + self._attributes = value + + @property # type: ignore[override] + def context(self) -> Context: # type: ignore[override] + """State context.""" + if self._context is None: + self._context = Context(id=None) # type: ignore[arg-type] + return self._context + + @context.setter + def context(self, value: Context) -> None: + """Set context.""" + self._context = value + + @property # type: ignore[override] + def last_changed(self) -> datetime: # type: ignore[override] + """Last changed datetime.""" + if self._last_changed is None: + self._last_changed = process_timestamp(self._row.last_changed) + return self._last_changed + + @last_changed.setter + def last_changed(self, value: datetime) -> None: + """Set last changed datetime.""" + self._last_changed = value + + @property # type: ignore[override] + def last_updated(self) -> datetime: # type: ignore[override] + """Last updated datetime.""" + if self._last_updated is None: + 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 + def last_updated(self, value: datetime) -> None: + """Set last updated datetime.""" + self._last_updated = value + + def as_dict(self) -> dict[str, Any]: # type: ignore[override] + """Return a dict representation of the LazyState. + + Async friendly. + + To be used for JSON serialization. + """ + if self._last_changed is None and self._last_updated is None: + last_changed_isoformat = process_timestamp_to_utc_isoformat( + self._row.last_changed + ) + 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( + self._row.last_updated + ) + else: + last_changed_isoformat = self.last_changed.isoformat() + if self.last_changed == self.last_updated: + last_updated_isoformat = last_changed_isoformat + else: + last_updated_isoformat = self.last_updated.isoformat() + return { + "entity_id": self.entity_id, + "state": self.state, + "attributes": self._attributes or self.attributes, + "last_changed": last_changed_isoformat, + "last_updated": last_updated_isoformat, + } + + def __eq__(self, other: Any) -> bool: + """Return the comparison.""" + return ( + other.__class__ in [self.__class__, State] + and self.entity_id == other.entity_id + and self.state == other.state + and self.attributes == other.attributes + ) diff --git a/tests/components/recorder/test_filters.py b/tests/components/recorder/test_filters.py new file mode 100644 index 00000000000..fa80df6e345 --- /dev/null +++ b/tests/components/recorder/test_filters.py @@ -0,0 +1,114 @@ +"""The tests for recorder filters.""" + +from homeassistant.components.recorder.filters import ( + extract_include_exclude_filter_conf, + merge_include_exclude_filters, +) +from homeassistant.helpers.entityfilter import ( + CONF_DOMAINS, + CONF_ENTITIES, + CONF_ENTITY_GLOBS, + CONF_EXCLUDE, + CONF_INCLUDE, +) + +SIMPLE_INCLUDE_FILTER = { + CONF_INCLUDE: { + CONF_DOMAINS: ["homeassistant"], + CONF_ENTITIES: ["sensor.one"], + CONF_ENTITY_GLOBS: ["climate.*"], + } +} +SIMPLE_INCLUDE_FILTER_DIFFERENT_ENTITIES = { + CONF_INCLUDE: { + CONF_DOMAINS: ["other"], + CONF_ENTITIES: ["not_sensor.one"], + CONF_ENTITY_GLOBS: ["not_climate.*"], + } +} +SIMPLE_EXCLUDE_FILTER = { + CONF_EXCLUDE: { + CONF_DOMAINS: ["homeassistant"], + CONF_ENTITIES: ["sensor.one"], + CONF_ENTITY_GLOBS: ["climate.*"], + } +} +SIMPLE_INCLUDE_EXCLUDE_FILTER = {**SIMPLE_INCLUDE_FILTER, **SIMPLE_EXCLUDE_FILTER} + + +def test_extract_include_exclude_filter_conf(): + """Test we can extract a filter from configuration without altering it.""" + include_filter = extract_include_exclude_filter_conf(SIMPLE_INCLUDE_FILTER) + assert include_filter == { + CONF_EXCLUDE: { + CONF_DOMAINS: set(), + CONF_ENTITIES: set(), + CONF_ENTITY_GLOBS: set(), + }, + CONF_INCLUDE: { + CONF_DOMAINS: {"homeassistant"}, + CONF_ENTITIES: {"sensor.one"}, + CONF_ENTITY_GLOBS: {"climate.*"}, + }, + } + + exclude_filter = extract_include_exclude_filter_conf(SIMPLE_EXCLUDE_FILTER) + assert exclude_filter == { + CONF_INCLUDE: { + CONF_DOMAINS: set(), + CONF_ENTITIES: set(), + CONF_ENTITY_GLOBS: set(), + }, + CONF_EXCLUDE: { + CONF_DOMAINS: {"homeassistant"}, + CONF_ENTITIES: {"sensor.one"}, + CONF_ENTITY_GLOBS: {"climate.*"}, + }, + } + + include_exclude_filter = extract_include_exclude_filter_conf( + SIMPLE_INCLUDE_EXCLUDE_FILTER + ) + assert include_exclude_filter == { + CONF_INCLUDE: { + CONF_DOMAINS: {"homeassistant"}, + CONF_ENTITIES: {"sensor.one"}, + CONF_ENTITY_GLOBS: {"climate.*"}, + }, + CONF_EXCLUDE: { + CONF_DOMAINS: {"homeassistant"}, + CONF_ENTITIES: {"sensor.one"}, + CONF_ENTITY_GLOBS: {"climate.*"}, + }, + } + + include_exclude_filter[CONF_EXCLUDE][CONF_ENTITIES] = {"cover.altered"} + # verify it really is a copy + assert SIMPLE_INCLUDE_EXCLUDE_FILTER[CONF_EXCLUDE][CONF_ENTITIES] != { + "cover.altered" + } + + +def test_merge_include_exclude_filters(): + """Test we can merge two filters together.""" + include_exclude_filter_base = extract_include_exclude_filter_conf( + SIMPLE_INCLUDE_EXCLUDE_FILTER + ) + include_filter_add = extract_include_exclude_filter_conf( + SIMPLE_INCLUDE_FILTER_DIFFERENT_ENTITIES + ) + merged_filter = merge_include_exclude_filters( + include_exclude_filter_base, include_filter_add + ) + assert merged_filter == { + CONF_EXCLUDE: { + CONF_DOMAINS: {"homeassistant"}, + CONF_ENTITIES: {"sensor.one"}, + CONF_ENTITY_GLOBS: {"climate.*"}, + }, + CONF_INCLUDE: { + CONF_DOMAINS: {"other", "homeassistant"}, + CONF_ENTITIES: {"not_sensor.one", "sensor.one"}, + CONF_ENTITY_GLOBS: {"climate.*", "not_climate.*"}, + }, + } diff --git a/tests/components/recorder/test_history.py b/tests/components/recorder/test_history.py index c4952d8355b..da6c3a8af35 100644 --- a/tests/components/recorder/test_history.py +++ b/tests/components/recorder/test_history.py @@ -14,6 +14,7 @@ from homeassistant.components import recorder from homeassistant.components.recorder import history from homeassistant.components.recorder.models import ( Events, + LazyState, RecorderRuns, StateAttributes, States, @@ -21,12 +22,15 @@ from homeassistant.components.recorder.models import ( ) from homeassistant.components.recorder.util import session_scope import homeassistant.core as ha -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, State from homeassistant.helpers.json import JSONEncoder import homeassistant.util.dt as dt_util from tests.common import SetupRecorderInstanceT, mock_state_change_event -from tests.components.recorder.common import wait_recording_done +from tests.components.recorder.common import ( + async_wait_recording_done, + wait_recording_done, +) async def _async_get_states( @@ -40,9 +44,19 @@ async def _async_get_states( def _get_states_with_session(): with session_scope(hass=hass) as session: - return history._get_states_with_session( - hass, session, utc_point_in_time, entity_ids, run, None, no_attributes - ) + attr_cache = {} + return [ + LazyState(row, attr_cache) + for row in history._get_rows_with_session( + hass, + session, + utc_point_in_time, + entity_ids, + run, + None, + no_attributes, + ) + ] return await recorder.get_instance(hass).async_add_executor_job( _get_states_with_session @@ -52,7 +66,7 @@ async def _async_get_states( def _add_db_entries( hass: ha.HomeAssistant, point: datetime, entity_ids: list[str] ) -> None: - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: for idx, entity_id in enumerate(entity_ids): session.add( Events( @@ -68,7 +82,7 @@ def _add_db_entries( entity_id=entity_id, state="on", attributes='{"name":"the light"}', - last_changed=point, + last_changed=None, last_updated=point, event_id=1001 + idx, attributes_id=1002 + idx, @@ -87,7 +101,9 @@ def _setup_get_states(hass): """Set up for testing get_states.""" states = [] now = dt_util.utcnow() - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=now): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=now + ): for i in range(5): state = ha.State( f"test.point_in_time_{i % 5}", @@ -102,7 +118,9 @@ def _setup_get_states(hass): wait_recording_done(hass) future = now + timedelta(seconds=1) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=future): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=future + ): for i in range(5): state = ha.State( f"test.point_in_time_{i % 5}", @@ -122,7 +140,7 @@ def test_get_full_significant_states_with_session_entity_no_matches(hass_recorde hass = hass_recorder() now = dt_util.utcnow() time_before_recorder_ran = now - timedelta(days=1000) - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: assert ( history.get_full_significant_states_with_session( hass, session, time_before_recorder_ran, now, entity_ids=["demo.id"] @@ -148,7 +166,7 @@ def test_significant_states_with_session_entity_minimal_response_no_matches( hass = hass_recorder() now = dt_util.utcnow() time_before_recorder_ran = now - timedelta(days=1000) - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: assert ( history.get_significant_states_with_session( hass, @@ -197,11 +215,15 @@ def test_state_changes_during_period(hass_recorder, attributes, no_attributes, l point = start + timedelta(seconds=1) end = point + timedelta(seconds=1) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=start + ): set_state("idle") set_state("YouTube") - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=point + ): states = [ set_state("idle"), set_state("Netflix"), @@ -209,7 +231,9 @@ def test_state_changes_during_period(hass_recorder, attributes, no_attributes, l set_state("YouTube"), ] - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=end): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=end + ): set_state("Netflix") set_state("Plex") @@ -235,11 +259,15 @@ def test_state_changes_during_period_descending(hass_recorder): point = start + timedelta(seconds=1) end = point + timedelta(seconds=1) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=start + ): set_state("idle") set_state("YouTube") - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=point + ): states = [ set_state("idle"), set_state("Netflix"), @@ -247,7 +275,9 @@ def test_state_changes_during_period_descending(hass_recorder): set_state("YouTube"), ] - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=end): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=end + ): set_state("Netflix") set_state("Plex") @@ -277,14 +307,20 @@ def test_get_last_state_changes(hass_recorder): point = start + timedelta(minutes=1) point2 = point + timedelta(minutes=1) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=start + ): set_state("1") states = [] - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=point + ): states.append(set_state("2")) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point2): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=point2 + ): states.append(set_state("3")) hist = history.get_last_state_changes(hass, 2, entity_id) @@ -310,10 +346,14 @@ def test_ensure_state_can_be_copied(hass_recorder): start = dt_util.utcnow() - timedelta(minutes=2) point = start + timedelta(minutes=1) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=start + ): set_state("1") - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=point + ): set_state("2") hist = history.get_last_state_changes(hass, 2, entity_id) @@ -348,23 +388,30 @@ def test_get_significant_states_minimal_response(hass_recorder): hass = hass_recorder() zero, four, states = record_states(hass) hist = history.get_significant_states(hass, zero, four, minimal_response=True) + entites_with_reducable_states = [ + "media_player.test", + "media_player.test3", + ] - # The second media_player.test state is reduced + # All states for media_player.test state are reduced # down to last_changed and state when minimal_response + # is set except for the first state. # is set. We use JSONEncoder to make sure that are # pre-encoded last_changed is always the same as what # will happen with encoding a native state - input_state = states["media_player.test"][1] - orig_last_changed = json.dumps( - process_timestamp(input_state.last_changed), - cls=JSONEncoder, - ).replace('"', "") - orig_state = input_state.state - states["media_player.test"][1] = { - "last_changed": orig_last_changed, - "state": orig_state, - } - + for entity_id in entites_with_reducable_states: + entity_states = states[entity_id] + for state_idx in range(1, len(entity_states)): + input_state = entity_states[state_idx] + orig_last_changed = orig_last_changed = json.dumps( + process_timestamp(input_state.last_changed), + cls=JSONEncoder, + ).replace('"', "") + orig_state = input_state.state + entity_states[state_idx] = { + "last_changed": orig_last_changed, + "state": orig_state, + } assert states == hist @@ -486,23 +533,28 @@ def test_get_significant_states_only(hass_recorder): points.append(start + timedelta(minutes=i)) states = [] - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=start + ): set_state("123", attributes={"attribute": 10.64}) with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[0] + "homeassistant.components.recorder.core.dt_util.utcnow", + return_value=points[0], ): # Attributes are different, state not states.append(set_state("123", attributes={"attribute": 21.42})) with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[1] + "homeassistant.components.recorder.core.dt_util.utcnow", + return_value=points[1], ): # state is different, attributes not states.append(set_state("32", attributes={"attribute": 21.42})) with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[2] + "homeassistant.components.recorder.core.dt_util.utcnow", + return_value=points[2], ): # everything is different states.append(set_state("412", attributes={"attribute": 54.23})) @@ -520,7 +572,7 @@ def test_get_significant_states_only(hass_recorder): assert states == hist[entity_id] -def record_states(hass): +def record_states(hass) -> tuple[datetime, datetime, dict[str, list[State]]]: """Record some test states. We inject a bunch of state updates from media player, zone and @@ -547,7 +599,9 @@ def record_states(hass): four = three + timedelta(seconds=1) states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []} - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=one): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=one + ): states[mp].append( set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) ) @@ -564,7 +618,9 @@ def record_states(hass): set_state(therm, 20, attributes={"current_temperature": 19.5}) ) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=two): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=two + ): # This state will be skipped only different in time set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}) # This state will be skipped because domain is excluded @@ -579,7 +635,9 @@ def record_states(hass): set_state(therm2, 20, attributes={"current_temperature": 19}) ) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=three): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=three + ): states[mp].append( set_state(mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}) ) @@ -737,3 +795,86 @@ async def test_get_states_query_during_migration_to_schema_25_multiple_entities( ) assert hist[0].attributes == {"name": "the light"} assert hist[1].attributes == {"name": "the light"} + + +async def test_get_full_significant_states_handles_empty_last_changed( + hass: ha.HomeAssistant, + async_setup_recorder_instance: SetupRecorderInstanceT, +): + """Test getting states when last_changed is null.""" + await async_setup_recorder_instance(hass, {}) + + now = dt_util.utcnow() + hass.states.async_set("sensor.one", "on", {"attr": "original"}) + state0 = hass.states.get("sensor.one") + await hass.async_block_till_done() + hass.states.async_set("sensor.one", "on", {"attr": "new"}) + state1 = hass.states.get("sensor.one") + + assert state0.last_changed == state1.last_changed + assert state0.last_updated != state1.last_updated + await async_wait_recording_done(hass) + + def _get_entries(): + with session_scope(hass=hass) as session: + return history.get_full_significant_states_with_session( + hass, + session, + now, + dt_util.utcnow(), + entity_ids=["sensor.one"], + significant_changes_only=False, + ) + + states = await recorder.get_instance(hass).async_add_executor_job(_get_entries) + sensor_one_states: list[State] = states["sensor.one"] + assert sensor_one_states[0] == state0 + assert sensor_one_states[1] == state1 + assert sensor_one_states[0].last_changed == sensor_one_states[1].last_changed + assert sensor_one_states[0].last_updated != sensor_one_states[1].last_updated + + def _fetch_native_states() -> list[State]: + with session_scope(hass=hass) as session: + native_states = [] + db_state_attributes = { + state_attributes.attributes_id: state_attributes + for state_attributes in session.query(StateAttributes) + } + for db_state in session.query(States): + state = db_state.to_native() + state.attributes = db_state_attributes[ + db_state.attributes_id + ].to_native() + native_states.append(state) + return native_states + + native_sensor_one_states = await recorder.get_instance(hass).async_add_executor_job( + _fetch_native_states + ) + assert native_sensor_one_states[0] == state0 + assert native_sensor_one_states[1] == state1 + assert ( + native_sensor_one_states[0].last_changed + == native_sensor_one_states[1].last_changed + ) + assert ( + native_sensor_one_states[0].last_updated + != native_sensor_one_states[1].last_updated + ) + + def _fetch_db_states() -> list[State]: + with session_scope(hass=hass) as session: + states = list(session.query(States)) + session.expunge_all() + return states + + db_sensor_one_states = await recorder.get_instance(hass).async_add_executor_job( + _fetch_db_states + ) + assert db_sensor_one_states[0].last_changed is None + assert ( + process_timestamp(db_sensor_one_states[1].last_changed) == state0.last_changed + ) + assert db_sensor_one_states[0].last_updated is not None + assert db_sensor_one_states[1].last_updated is not None + assert db_sensor_one_states[0].last_updated != db_sensor_one_states[1].last_updated diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 0a0194f4ddf..41c4428ee5e 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -6,11 +6,11 @@ import asyncio from datetime import datetime, timedelta import sqlite3 import threading +from typing import cast from unittest.mock import Mock, patch import pytest from sqlalchemy.exc import DatabaseError, OperationalError, SQLAlchemyError -from sqlalchemy.ext import baked from homeassistant.components import recorder from homeassistant.components.recorder import ( @@ -20,18 +20,14 @@ from homeassistant.components.recorder import ( CONF_DB_URL, CONFIG_SCHEMA, DOMAIN, - KEEPALIVE_TIME, - SERVICE_DISABLE, - SERVICE_ENABLE, - SERVICE_PURGE, - SERVICE_PURGE_ENTITIES, SQLITE_URL_PREFIX, Recorder, get_instance, ) -from homeassistant.components.recorder.const import DATA_INSTANCE +from homeassistant.components.recorder.const import DATA_INSTANCE, KEEPALIVE_TIME from homeassistant.components.recorder.models import ( SCHEMA_VERSION, + EventData, Events, RecorderRuns, StateAttributes, @@ -39,6 +35,12 @@ from homeassistant.components.recorder.models import ( StatisticsRuns, process_timestamp, ) +from homeassistant.components.recorder.services import ( + SERVICE_DISABLE, + SERVICE_ENABLE, + SERVICE_PURGE, + SERVICE_PURGE_ENTITIES, +) from homeassistant.components.recorder.util import session_scope from homeassistant.const import ( EVENT_HOMEASSISTANT_FINAL_WRITE, @@ -48,11 +50,12 @@ from homeassistant.const import ( STATE_LOCKED, STATE_UNLOCKED, ) -from homeassistant.core import Context, CoreState, HomeAssistant, callback +from homeassistant.core import CoreState, Event, HomeAssistant, callback from homeassistant.setup import async_setup_component, setup_component from homeassistant.util import dt as dt_util from .common import ( + async_block_recorder, async_wait_recording_done, corrupt_db_file, run_information_with_session, @@ -81,7 +84,6 @@ def _default_recorder(hass): entity_filter=CONFIG_SCHEMA({DOMAIN: {}}), exclude_t=[], exclude_attributes_by_domain={}, - bakery=baked.bakery(), ) @@ -137,6 +139,8 @@ async def test_shutdown_closes_connections(hass, recorder_mock): await hass.async_block_till_done() assert len(pool.shutdown.mock_calls) == 1 + with pytest.raises(RuntimeError): + assert instance.get_session() async def test_state_gets_saved_when_set_before_start_event( @@ -161,7 +165,7 @@ async def test_state_gets_saved_when_set_before_start_event( with session_scope(hass=hass) as session: db_states = list(session.query(States)) assert len(db_states) == 1 - assert db_states[0].event_id > 0 + assert db_states[0].event_id is None async def test_saving_state(hass: HomeAssistant, recorder_mock): @@ -181,9 +185,9 @@ async def test_saving_state(hass: HomeAssistant, recorder_mock): state = db_state.to_native() state.attributes = db_state_attributes.to_native() assert len(db_states) == 1 - assert db_states[0].event_id > 0 + assert db_states[0].event_id is None - assert state == _state_empty_context(hass, entity_id) + assert state == _state_with_context(hass, entity_id) async def test_saving_many_states( @@ -197,7 +201,7 @@ async def test_saving_many_states( with patch.object( hass.data[DATA_INSTANCE].event_session, "expire_all" - ) as expire_all, patch.object(recorder, "EXPIRE_AFTER_COMMITS", 2): + ) as expire_all, patch.object(recorder.core, "EXPIRE_AFTER_COMMITS", 2): for _ in range(3): hass.states.async_set(entity_id, "on", attributes) await async_wait_recording_done(hass) @@ -209,7 +213,7 @@ async def test_saving_many_states( with session_scope(hass=hass) as session: db_states = list(session.query(States)) assert len(db_states) == 6 - assert db_states[0].event_id > 0 + assert db_states[0].event_id is None async def test_saving_state_with_intermixed_time_changes( @@ -233,7 +237,7 @@ async def test_saving_state_with_intermixed_time_changes( with session_scope(hass=hass) as session: db_states = list(session.query(States)) assert len(db_states) == 2 - assert db_states[0].event_id > 0 + assert db_states[0].event_id is None def test_saving_state_with_exception(hass, hass_recorder, caplog): @@ -364,14 +368,25 @@ def test_saving_event(hass, hass_recorder): wait_recording_done(hass) assert len(events) == 1 - event = events[0] + event: Event = events[0] hass.data[DATA_INSTANCE].block_till_done() + events: list[Event] = [] with session_scope(hass=hass) as session: - db_events = list(session.query(Events).filter_by(event_type=event_type)) - assert len(db_events) == 1 - db_event = db_events[0].to_native() + for select_event, event_data in ( + session.query(Events, EventData) + .filter_by(event_type=event_type) + .outerjoin(EventData, Events.data_id == EventData.data_id) + ): + select_event = cast(Events, select_event) + event_data = cast(EventData, event_data) + + native_event = select_event.to_native() + native_event.data = event_data.to_native() + events.append(native_event) + + db_event = events[0] assert event.event_type == db_event.event_type assert event.data == db_event.data @@ -399,7 +414,7 @@ def test_saving_state_with_commit_interval_zero(hass_recorder): with session_scope(hass=hass) as session: db_states = list(session.query(States)) assert len(db_states) == 1 - assert db_states[0].event_id > 0 + assert db_states[0].event_id is None def _add_entities(hass, entity_ids): @@ -428,15 +443,24 @@ def _add_events(hass, events): wait_recording_done(hass) with session_scope(hass=hass) as session: - return [ev.to_native() for ev in session.query(Events)] + events = [] + for event, event_data in session.query(Events, EventData).outerjoin( + EventData, Events.data_id == EventData.data_id + ): + event = cast(Events, event) + event_data = cast(EventData, event_data) + + native_event = event.to_native() + if event_data: + native_event.data = event_data.to_native() + events.append(native_event) + return events -def _state_empty_context(hass, entity_id): +def _state_with_context(hass, entity_id): # We don't restore context unless we need it by joining the # events table on the event_id for state_changed events - state = hass.states.get(entity_id) - state.context = Context(id=None) - return state + return hass.states.get(entity_id) def test_setup_without_migration(hass_recorder): @@ -451,7 +475,7 @@ def test_saving_state_include_domains(hass_recorder): hass = hass_recorder({"include": {"domains": "test2"}}) states = _add_entities(hass, ["test.recorder", "test2.recorder"]) assert len(states) == 1 - assert _state_empty_context(hass, "test2.recorder") == states[0] + assert _state_with_context(hass, "test2.recorder") == states[0] def test_saving_state_include_domains_globs(hass_recorder): @@ -463,8 +487,8 @@ def test_saving_state_include_domains_globs(hass_recorder): hass, ["test.recorder", "test2.recorder", "test3.included_entity"] ) assert len(states) == 2 - assert _state_empty_context(hass, "test2.recorder") == states[0] - assert _state_empty_context(hass, "test3.included_entity") == states[1] + assert _state_with_context(hass, "test2.recorder") == states[0] + assert _state_with_context(hass, "test3.included_entity") == states[1] def test_saving_state_incl_entities(hass_recorder): @@ -472,7 +496,7 @@ def test_saving_state_incl_entities(hass_recorder): hass = hass_recorder({"include": {"entities": "test2.recorder"}}) states = _add_entities(hass, ["test.recorder", "test2.recorder"]) assert len(states) == 1 - assert _state_empty_context(hass, "test2.recorder") == states[0] + assert _state_with_context(hass, "test2.recorder") == states[0] def test_saving_event_exclude_event_type(hass_recorder): @@ -501,7 +525,7 @@ def test_saving_state_exclude_domains(hass_recorder): hass = hass_recorder({"exclude": {"domains": "test"}}) states = _add_entities(hass, ["test.recorder", "test2.recorder"]) assert len(states) == 1 - assert _state_empty_context(hass, "test2.recorder") == states[0] + assert _state_with_context(hass, "test2.recorder") == states[0] def test_saving_state_exclude_domains_globs(hass_recorder): @@ -513,7 +537,7 @@ def test_saving_state_exclude_domains_globs(hass_recorder): hass, ["test.recorder", "test2.recorder", "test2.excluded_entity"] ) assert len(states) == 1 - assert _state_empty_context(hass, "test2.recorder") == states[0] + assert _state_with_context(hass, "test2.recorder") == states[0] def test_saving_state_exclude_entities(hass_recorder): @@ -521,7 +545,7 @@ def test_saving_state_exclude_entities(hass_recorder): hass = hass_recorder({"exclude": {"entities": "test.recorder"}}) states = _add_entities(hass, ["test.recorder", "test2.recorder"]) assert len(states) == 1 - assert _state_empty_context(hass, "test2.recorder") == states[0] + assert _state_with_context(hass, "test2.recorder") == states[0] def test_saving_state_exclude_domain_include_entity(hass_recorder): @@ -554,8 +578,8 @@ def test_saving_state_include_domain_exclude_entity(hass_recorder): ) states = _add_entities(hass, ["test.recorder", "test2.recorder", "test.ok"]) assert len(states) == 1 - assert _state_empty_context(hass, "test.ok") == states[0] - assert _state_empty_context(hass, "test.ok").state == "state2" + assert _state_with_context(hass, "test.ok") == states[0] + assert _state_with_context(hass, "test.ok").state == "state2" def test_saving_state_include_domain_glob_exclude_entity(hass_recorder): @@ -570,8 +594,8 @@ def test_saving_state_include_domain_glob_exclude_entity(hass_recorder): hass, ["test.recorder", "test2.recorder", "test.ok", "test2.included_entity"] ) assert len(states) == 1 - assert _state_empty_context(hass, "test.ok") == states[0] - assert _state_empty_context(hass, "test.ok").state == "state2" + assert _state_with_context(hass, "test.ok") == states[0] + assert _state_with_context(hass, "test.ok").state == "state2" def test_saving_state_and_removing_entity(hass, hass_recorder): @@ -598,7 +622,7 @@ def test_saving_state_and_removing_entity(hass, hass_recorder): def test_recorder_setup_failure(hass): """Test some exceptions.""" with patch.object(Recorder, "_setup_connection") as setup, patch( - "homeassistant.components.recorder.time.sleep" + "homeassistant.components.recorder.core.time.sleep" ): setup.side_effect = ImportError("driver not found") rec = _default_recorder(hass) @@ -612,7 +636,7 @@ def test_recorder_setup_failure(hass): def test_recorder_setup_failure_without_event_listener(hass): """Test recorder setup failure when the event listener is not setup.""" with patch.object(Recorder, "_setup_connection") as setup, patch( - "homeassistant.components.recorder.time.sleep" + "homeassistant.components.recorder.core.time.sleep" ): setup.side_effect = ImportError("driver not found") rec = _default_recorder(hass) @@ -672,7 +696,7 @@ def test_auto_purge(hass_recorder): with patch( "homeassistant.components.recorder.purge.purge_old_data", return_value=True ) as purge_old_data, patch( - "homeassistant.components.recorder.periodic_db_cleanups" + "homeassistant.components.recorder.tasks.periodic_db_cleanups" ) as periodic_db_cleanups: # Advance one day, and the purge task should run test_time = test_time + timedelta(days=1) @@ -728,11 +752,11 @@ def test_auto_purge_auto_repack_on_second_sunday(hass_recorder): run_tasks_at_time(hass, test_time) with patch( - "homeassistant.components.recorder.is_second_sunday", return_value=True + "homeassistant.components.recorder.core.is_second_sunday", return_value=True ), patch( "homeassistant.components.recorder.purge.purge_old_data", return_value=True ) as purge_old_data, patch( - "homeassistant.components.recorder.periodic_db_cleanups" + "homeassistant.components.recorder.tasks.periodic_db_cleanups" ) as periodic_db_cleanups: # Advance one day, and the purge task should run test_time = test_time + timedelta(days=1) @@ -766,11 +790,11 @@ def test_auto_purge_auto_repack_disabled_on_second_sunday(hass_recorder): run_tasks_at_time(hass, test_time) with patch( - "homeassistant.components.recorder.is_second_sunday", return_value=True + "homeassistant.components.recorder.core.is_second_sunday", return_value=True ), patch( "homeassistant.components.recorder.purge.purge_old_data", return_value=True ) as purge_old_data, patch( - "homeassistant.components.recorder.periodic_db_cleanups" + "homeassistant.components.recorder.tasks.periodic_db_cleanups" ) as periodic_db_cleanups: # Advance one day, and the purge task should run test_time = test_time + timedelta(days=1) @@ -804,11 +828,12 @@ def test_auto_purge_no_auto_repack_on_not_second_sunday(hass_recorder): run_tasks_at_time(hass, test_time) with patch( - "homeassistant.components.recorder.is_second_sunday", return_value=False + "homeassistant.components.recorder.core.is_second_sunday", + return_value=False, ), patch( "homeassistant.components.recorder.purge.purge_old_data", return_value=True ) as purge_old_data, patch( - "homeassistant.components.recorder.periodic_db_cleanups" + "homeassistant.components.recorder.tasks.periodic_db_cleanups" ) as periodic_db_cleanups: # Advance one day, and the purge task should run test_time = test_time + timedelta(days=1) @@ -843,7 +868,7 @@ def test_auto_purge_disabled(hass_recorder): with patch( "homeassistant.components.recorder.purge.purge_old_data", return_value=True ) as purge_old_data, patch( - "homeassistant.components.recorder.periodic_db_cleanups" + "homeassistant.components.recorder.tasks.periodic_db_cleanups" ) as periodic_db_cleanups: # Advance one day, and the purge task should run test_time = test_time + timedelta(days=1) @@ -911,7 +936,9 @@ def test_auto_statistics(hass_recorder): def test_statistics_runs_initiated(hass_recorder): """Test statistics_runs is initiated when DB is created.""" now = dt_util.utcnow() - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=now): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=now + ): hass = hass_recorder() wait_recording_done(hass) @@ -931,7 +958,9 @@ def test_compile_missing_statistics(tmpdir): test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=now): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=now + ): hass = get_test_home_assistant() setup_component(hass, DOMAIN, {DOMAIN: {CONF_DB_URL: dburl}}) @@ -950,7 +979,7 @@ def test_compile_missing_statistics(tmpdir): hass.stop() with patch( - "homeassistant.components.recorder.dt_util.utcnow", + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=now + timedelta(hours=1), ): @@ -1080,11 +1109,22 @@ def test_service_disable_events_not_recording(hass, hass_recorder): assert events[0] != events[1] assert events[0].data != events[1].data + db_events = [] with session_scope(hass=hass) as session: - db_events = list(session.query(Events).filter_by(event_type=event_type)) - assert len(db_events) == 1 - db_event = db_events[0].to_native() + for select_event, event_data in ( + session.query(Events, EventData) + .filter_by(event_type=event_type) + .outerjoin(EventData, Events.data_id == EventData.data_id) + ): + select_event = cast(Events, select_event) + event_data = cast(EventData, event_data) + native_event = select_event.to_native() + native_event.data = event_data.to_native() + db_events.append(native_event) + + assert len(db_events) == 1 + db_event = db_events[0] event = events[1] assert event.event_type == db_event.event_type @@ -1125,8 +1165,8 @@ def test_service_disable_states_not_recording(hass, hass_recorder): with session_scope(hass=hass) as session: db_states = list(session.query(States)) assert len(db_states) == 1 - assert db_states[0].event_id > 0 - assert db_states[0].to_native() == _state_empty_context(hass, "test.two") + assert db_states[0].event_id is None + assert db_states[0].to_native() == _state_with_context(hass, "test.two") def test_service_disable_run_information_recorded(tmpdir): @@ -1229,7 +1269,7 @@ async def test_database_corruption_while_running(hass, tmpdir, caplog): with session_scope(hass=hass) as session: db_states = list(session.query(States)) assert len(db_states) == 1 - assert db_states[0].event_id > 0 + assert db_states[0].event_id is None return db_states[0].to_native() state = await hass.async_add_executor_job(_get_last_state) @@ -1280,7 +1320,9 @@ def test_entity_id_filter(hass_recorder): async def test_database_lock_and_unlock( - hass: HomeAssistant, async_setup_recorder_instance: SetupRecorderInstanceT, tmp_path + hass: HomeAssistant, + async_setup_recorder_instance: SetupRecorderInstanceT, + tmp_path, ): """Test writing events during lock getting written after unlocking.""" # Use file DB, in memory DB cannot do write locks. @@ -1291,6 +1333,10 @@ async def test_database_lock_and_unlock( await async_setup_recorder_instance(hass, config) await hass.async_block_till_done() + def _get_db_events(): + with session_scope(hass=hass) as session: + return list(session.query(Events).filter_by(event_type=event_type)) + instance: Recorder = hass.data[DATA_INSTANCE] assert await instance.lock_database() @@ -1305,21 +1351,20 @@ async def test_database_lock_and_unlock( # Recording can't be finished while lock is held with pytest.raises(asyncio.TimeoutError): await asyncio.wait_for(asyncio.shield(task), timeout=1) - - with session_scope(hass=hass) as session: - db_events = list(session.query(Events).filter_by(event_type=event_type)) + db_events = await hass.async_add_executor_job(_get_db_events) assert len(db_events) == 0 assert instance.unlock_database() await task - with session_scope(hass=hass) as session: - db_events = list(session.query(Events).filter_by(event_type=event_type)) - assert len(db_events) == 1 + db_events = await hass.async_add_executor_job(_get_db_events) + assert len(db_events) == 1 async def test_database_lock_and_overflow( - hass: HomeAssistant, async_setup_recorder_instance: SetupRecorderInstanceT, tmp_path + hass: HomeAssistant, + async_setup_recorder_instance: SetupRecorderInstanceT, + tmp_path, ): """Test writing events during lock leading to overflow the queue causes the database to unlock.""" # Use file DB, in memory DB cannot do write locks. @@ -1330,10 +1375,14 @@ async def test_database_lock_and_overflow( await async_setup_recorder_instance(hass, config) await hass.async_block_till_done() + def _get_db_events(): + with session_scope(hass=hass) as session: + return list(session.query(Events).filter_by(event_type=event_type)) + instance: Recorder = hass.data[DATA_INSTANCE] - with patch.object(recorder, "MAX_QUEUE_BACKLOG", 1), patch.object( - recorder, "DB_LOCK_QUEUE_CHECK_TIMEOUT", 0.1 + with patch.object(recorder.core, "MAX_QUEUE_BACKLOG", 1), patch.object( + recorder.core, "DB_LOCK_QUEUE_CHECK_TIMEOUT", 0.1 ): await instance.lock_database() @@ -1345,9 +1394,8 @@ async def test_database_lock_and_overflow( # even before unlocking. await async_wait_recording_done(hass) - with session_scope(hass=hass) as session: - db_events = list(session.query(Events).filter_by(event_type=event_type)) - assert len(db_events) == 1 + db_events = await hass.async_add_executor_job(_get_db_events) + assert len(db_events) == 1 assert not instance.unlock_database() @@ -1358,15 +1406,15 @@ async def test_database_lock_timeout(hass, recorder_mock): instance: Recorder = hass.data[DATA_INSTANCE] - class BlockQueue(recorder.RecorderTask): + class BlockQueue(recorder.tasks.RecorderTask): event: threading.Event = threading.Event() def run(self, instance: Recorder) -> None: self.event.wait() block_task = BlockQueue() - instance.queue.put(block_task) - with patch.object(recorder, "DB_LOCK_TIMEOUT", 0.1): + instance.queue_task(block_task) + with patch.object(recorder.core, "DB_LOCK_TIMEOUT", 0.1): try: with pytest.raises(TimeoutError): await instance.lock_database() @@ -1401,9 +1449,7 @@ async def test_database_connection_keep_alive( caplog: pytest.LogCaptureFixture, ): """Test we keep alive socket based dialects.""" - with patch( - "homeassistant.components.recorder.Recorder.using_sqlite", return_value=False - ): + with patch("homeassistant.components.recorder.Recorder.dialect_name"): instance = await async_setup_recorder_instance(hass) # We have to mock this since we don't have a mock # MySQL server available in tests. @@ -1411,7 +1457,7 @@ async def test_database_connection_keep_alive( await instance.async_recorder_ready.wait() async_fire_time_changed( - hass, dt_util.utcnow() + timedelta(seconds=recorder.KEEPALIVE_TIME) + hass, dt_util.utcnow() + timedelta(seconds=recorder.core.KEEPALIVE_TIME) ) await async_wait_recording_done(hass) assert "Sending keepalive" in caplog.text @@ -1428,7 +1474,89 @@ async def test_database_connection_keep_alive_disabled_on_sqlite( await instance.async_recorder_ready.wait() async_fire_time_changed( - hass, dt_util.utcnow() + timedelta(seconds=recorder.KEEPALIVE_TIME) + hass, dt_util.utcnow() + timedelta(seconds=recorder.core.KEEPALIVE_TIME) ) await async_wait_recording_done(hass) assert "Sending keepalive" not in caplog.text + + +def test_deduplication_event_data_inside_commit_interval(hass_recorder, caplog): + """Test deduplication of event data inside the commit interval.""" + hass = hass_recorder() + + for _ in range(10): + hass.bus.fire("this_event", {"de": "dupe"}) + wait_recording_done(hass) + for _ in range(10): + hass.bus.fire("this_event", {"de": "dupe"}) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + events = list( + session.query(Events) + .filter(Events.event_type == "this_event") + .outerjoin(EventData, (Events.data_id == EventData.data_id)) + ) + assert len(events) == 20 + first_data_id = events[0].data_id + assert all(event.data_id == first_data_id for event in events) + + +# Patch STATE_ATTRIBUTES_ID_CACHE_SIZE since otherwise +# the CI can fail because the test takes too long to run +@patch("homeassistant.components.recorder.core.STATE_ATTRIBUTES_ID_CACHE_SIZE", 5) +def test_deduplication_state_attributes_inside_commit_interval(hass_recorder, caplog): + """Test deduplication of state attributes inside the commit interval.""" + hass = hass_recorder() + + entity_id = "test.recorder" + attributes = {"test_attr": 5, "test_attr_10": "nice"} + + hass.states.set(entity_id, "on", attributes) + hass.states.set(entity_id, "off", attributes) + + # Now exaust the cache to ensure we go back to the db + for attr_id in range(5): + hass.states.set(entity_id, "on", {"test_attr": attr_id}) + hass.states.set(entity_id, "off", {"test_attr": attr_id}) + + wait_recording_done(hass) + for _ in range(5): + hass.states.set(entity_id, "on", attributes) + hass.states.set(entity_id, "off", attributes) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + states = list( + session.query(States) + .filter(States.entity_id == entity_id) + .outerjoin( + StateAttributes, (States.attributes_id == StateAttributes.attributes_id) + ) + ) + assert len(states) == 22 + first_attributes_id = states[0].attributes_id + last_attributes_id = states[-1].attributes_id + assert first_attributes_id == last_attributes_id + + +async def test_async_block_till_done(hass, async_setup_recorder_instance): + """Test we can block until recordering is done.""" + instance = await async_setup_recorder_instance(hass) + await async_wait_recording_done(hass) + + entity_id = "test.recorder" + attributes = {"test_attr": 5, "test_attr_10": "nice"} + + hass.states.async_set(entity_id, "on", attributes) + hass.states.async_set(entity_id, "off", attributes) + + def _fetch_states(): + with session_scope(hass=hass) as session: + return list(session.query(States).filter(States.entity_id == entity_id)) + + await async_block_recorder(hass, 0.1) + await instance.async_block_till_done() + states = await instance.async_add_executor_job(_fetch_states) + assert len(states) == 2 + await hass.async_block_till_done() diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index 464c3a8b4a3..fcc35938088 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -48,7 +48,8 @@ async def test_schema_update_calls(hass): assert recorder.util.async_migration_in_progress(hass) is False with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.create_engine", new=create_engine_test + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, ), patch( "homeassistant.components.recorder.migration._apply_update", wraps=migration._apply_update, @@ -59,9 +60,12 @@ async def test_schema_update_calls(hass): await async_wait_recording_done(hass) assert recorder.util.async_migration_in_progress(hass) is False + instance = recorder.get_instance(hass) + engine = instance.engine + session_maker = instance.get_session update.assert_has_calls( [ - call(hass.data[DATA_INSTANCE], version + 1, 0) + call(hass, engine, session_maker, version + 1, 0) for version in range(0, models.SCHEMA_VERSION) ] ) @@ -71,10 +75,10 @@ async def test_migration_in_progress(hass): """Test that we can check for migration in progress.""" assert recorder.util.async_migration_in_progress(hass) is False - with patch( - "homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", - True, - ), patch("homeassistant.components.recorder.create_engine", new=create_engine_test): + with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True,), patch( + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, + ): await async_setup_component( hass, "recorder", {"recorder": {"db_url": "sqlite://"}} ) @@ -91,7 +95,8 @@ async def test_database_migration_failed(hass): assert recorder.util.async_migration_in_progress(hass) is False with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.create_engine", new=create_engine_test + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, ), patch( "homeassistant.components.recorder.migration._apply_update", side_effect=ValueError, @@ -130,7 +135,7 @@ async def test_database_migration_encounters_corruption(hass): "homeassistant.components.recorder.migration.migrate_schema", side_effect=sqlite3_exception, ), patch( - "homeassistant.components.recorder.move_away_broken_database" + "homeassistant.components.recorder.core.move_away_broken_database" ) as move_away: await async_setup_component( hass, "recorder", {"recorder": {"db_url": "sqlite://"}} @@ -154,7 +159,7 @@ async def test_database_migration_encounters_corruption_not_sqlite(hass): "homeassistant.components.recorder.migration.migrate_schema", side_effect=DatabaseError("statement", {}, []), ), patch( - "homeassistant.components.recorder.move_away_broken_database" + "homeassistant.components.recorder.core.move_away_broken_database" ) as move_away, patch( "homeassistant.components.persistent_notification.create", side_effect=pn.create ) as mock_create, patch( @@ -181,10 +186,10 @@ async def test_events_during_migration_are_queued(hass): assert recorder.util.async_migration_in_progress(hass) is False - with patch( - "homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", - True, - ), patch("homeassistant.components.recorder.create_engine", new=create_engine_test): + with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True,), patch( + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, + ): await async_setup_component( hass, "recorder", @@ -212,8 +217,9 @@ async def test_events_during_migration_queue_exhausted(hass): assert recorder.util.async_migration_in_progress(hass) is False with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.create_engine", new=create_engine_test - ), patch.object(recorder, "MAX_QUEUE_BACKLOG", 1): + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, + ), patch.object(recorder.core, "MAX_QUEUE_BACKLOG", 1): await async_setup_component( hass, "recorder", @@ -301,7 +307,8 @@ async def test_schema_migrate(hass, start_version): migration_done.set() with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.create_engine", new=_create_engine_test + "homeassistant.components.recorder.core.create_engine", + new=_create_engine_test, ), patch( "homeassistant.components.recorder.Recorder._setup_run", side_effect=_mock_setup_run, @@ -323,10 +330,10 @@ async def test_schema_migrate(hass, start_version): assert recorder.util.async_migration_in_progress(hass) is not True -def test_invalid_update(): +def test_invalid_update(hass): """Test that an invalid new version raises an exception.""" with pytest.raises(ValueError): - migration._apply_update(Mock(), -1, 0) + migration._apply_update(hass, Mock(), Mock(), -1, 0) @pytest.mark.parametrize( @@ -347,7 +354,9 @@ def test_modify_column(engine_type, substr): instance.get_session = Mock(return_value=session) engine = Mock() engine.dialect.name = engine_type - migration._modify_columns(instance, engine, "events", ["event_type VARCHAR(64)"]) + migration._modify_columns( + instance.get_session, engine, "events", ["event_type VARCHAR(64)"] + ) if substr: assert substr in connection.execute.call_args[0][0].text else: @@ -361,8 +370,12 @@ def test_forgiving_add_column(): session.execute(text("CREATE TABLE hello (id int)")) instance = Mock() instance.get_session = Mock(return_value=session) - migration._add_columns(instance, "hello", ["context_id CHARACTER(36)"]) - migration._add_columns(instance, "hello", ["context_id CHARACTER(36)"]) + migration._add_columns( + instance.get_session, "hello", ["context_id CHARACTER(36)"] + ) + migration._add_columns( + instance.get_session, "hello", ["context_id CHARACTER(36)"] + ) def test_forgiving_add_index(): @@ -372,7 +385,7 @@ def test_forgiving_add_index(): with Session(engine) as session: instance = Mock() instance.get_session = Mock(return_value=session) - migration._create_index(instance, "states", "ix_states_context_id") + migration._create_index(instance.get_session, "states", "ix_states_context_id") @pytest.mark.parametrize( diff --git a/tests/components/recorder/test_models.py b/tests/components/recorder/test_models.py index 8382afe4353..9d07c33a17a 100644 --- a/tests/components/recorder/test_models.py +++ b/tests/components/recorder/test_models.py @@ -2,17 +2,20 @@ from datetime import datetime, timedelta from unittest.mock import PropertyMock +from freezegun import freeze_time import pytest from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from homeassistant.components.recorder.models import ( Base, + EventData, Events, LazyState, RecorderRuns, StateAttributes, States, + process_datetime_to_timestamp, process_timestamp, process_timestamp_to_utc_isoformat, ) @@ -25,7 +28,9 @@ from homeassistant.util import dt, dt as dt_util def test_from_event_to_db_event(): """Test converting event to db event.""" event = ha.Event("test_event", {"some_data": 15}) - assert event == Events.from_event(event).to_native() + db_event = Events.from_event(event) + db_event.event_data = EventData.from_event(event).shared_data + assert event == db_event.to_native() def test_from_event_to_db_state(): @@ -36,9 +41,6 @@ def test_from_event_to_db_state(): {"entity_id": "sensor.temperature", "old_state": None, "new_state": state}, context=state.context, ) - # We don't restore context unless we need it by joining the - # events table on the event_id for state_changed events - state.context = ha.Context(id=None) assert state == States.from_event(event).to_native() @@ -77,7 +79,7 @@ def test_from_event_to_delete_state(): assert db_state.entity_id == "sensor.temperature" assert db_state.state == "" - assert db_state.last_changed == event.time_fired + assert db_state.last_changed is None assert db_state.last_updated == event.time_fired @@ -231,10 +233,12 @@ async def test_event_to_db_model(): event = ha.Event( "state_changed", {"some": "attr"}, ha.EventOrigin.local, dt_util.utcnow() ) - native = Events.from_event(event).to_native() + db_event = Events.from_event(event) + db_event.event_data = EventData.from_event(event).shared_data + native = db_event.to_native() assert native == event - native = Events.from_event(event, event_data="{}").to_native() + native = Events.from_event(event).to_native() event.data = {} assert native == event @@ -245,7 +249,7 @@ async def test_lazy_state_handles_include_json(caplog): entity_id="sensor.invalid", shared_attrs="{INVALID_JSON}", ) - assert LazyState(row).attributes == {} + assert LazyState(row, {}).attributes == {} assert "Error converting row to state attributes" in caplog.text @@ -256,7 +260,7 @@ async def test_lazy_state_prefers_shared_attrs_over_attrs(caplog): shared_attrs='{"shared":true}', attributes='{"shared":false}', ) - assert LazyState(row).attributes == {"shared": True} + assert LazyState(row, {}).attributes == {"shared": True} async def test_lazy_state_handles_different_last_updated_and_last_changed(caplog): @@ -269,7 +273,7 @@ async def test_lazy_state_handles_different_last_updated_and_last_changed(caplog last_updated=now, last_changed=now - timedelta(seconds=60), ) - lstate = LazyState(row) + lstate = LazyState(row, {}) assert lstate.as_dict() == { "attributes": {"shared": True}, "entity_id": "sensor.valid", @@ -298,7 +302,7 @@ async def test_lazy_state_handles_same_last_updated_and_last_changed(caplog): last_updated=now, last_changed=now, ) - lstate = LazyState(row) + lstate = LazyState(row, {}) assert lstate.as_dict() == { "attributes": {"shared": True}, "entity_id": "sensor.valid", @@ -331,3 +335,73 @@ async def test_lazy_state_handles_same_last_updated_and_last_changed(caplog): "last_updated": "2020-06-12T03:04:01.000323+00:00", "state": "off", } + + +@pytest.mark.parametrize( + "time_zone", ["Europe/Berlin", "America/Chicago", "US/Hawaii", "UTC"] +) +def test_process_datetime_to_timestamp(time_zone, hass): + """Test we can handle processing database datatimes to timestamps.""" + hass.config.set_time_zone(time_zone) + utc_now = dt_util.utcnow() + assert process_datetime_to_timestamp(utc_now) == utc_now.timestamp() + now = dt_util.now() + assert process_datetime_to_timestamp(now) == now.timestamp() + + +@pytest.mark.parametrize( + "time_zone", ["Europe/Berlin", "America/Chicago", "US/Hawaii", "UTC"] +) +def test_process_datetime_to_timestamp_freeze_time(time_zone, hass): + """Test we can handle processing database datatimes to timestamps. + + This test freezes time to make sure everything matches. + """ + hass.config.set_time_zone(time_zone) + utc_now = dt_util.utcnow() + with freeze_time(utc_now): + epoch = utc_now.timestamp() + assert process_datetime_to_timestamp(dt_util.utcnow()) == epoch + now = dt_util.now() + assert process_datetime_to_timestamp(now) == epoch + + +@pytest.mark.parametrize( + "time_zone", ["Europe/Berlin", "America/Chicago", "US/Hawaii", "UTC"] +) +async def test_process_datetime_to_timestamp_mirrors_utc_isoformat_behavior( + time_zone, hass +): + """Test process_datetime_to_timestamp mirrors process_timestamp_to_utc_isoformat.""" + hass.config.set_time_zone(time_zone) + datetime_with_tzinfo = datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC) + datetime_without_tzinfo = datetime(2016, 7, 9, 11, 0, 0) + est = dt_util.get_time_zone("US/Eastern") + datetime_est_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=est) + est = dt_util.get_time_zone("US/Eastern") + datetime_est_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=est) + nst = dt_util.get_time_zone("Canada/Newfoundland") + datetime_nst_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=nst) + hst = dt_util.get_time_zone("US/Hawaii") + datetime_hst_timezone = datetime(2016, 7, 9, 11, 0, 0, tzinfo=hst) + + assert ( + process_datetime_to_timestamp(datetime_with_tzinfo) + == dt_util.parse_datetime("2016-07-09T11:00:00+00:00").timestamp() + ) + assert ( + process_datetime_to_timestamp(datetime_without_tzinfo) + == dt_util.parse_datetime("2016-07-09T11:00:00+00:00").timestamp() + ) + assert ( + process_datetime_to_timestamp(datetime_est_timezone) + == dt_util.parse_datetime("2016-07-09T15:00:00+00:00").timestamp() + ) + assert ( + process_datetime_to_timestamp(datetime_nst_timezone) + == dt_util.parse_datetime("2016-07-09T13:30:00+00:00").timestamp() + ) + assert ( + process_datetime_to_timestamp(datetime_hst_timezone) + == dt_util.parse_datetime("2016-07-09T21:00:00+00:00").timestamp() + ) diff --git a/tests/components/recorder/test_pool.py b/tests/components/recorder/test_pool.py index ca6a88d84a7..aa47ce5eb3c 100644 --- a/tests/components/recorder/test_pool.py +++ b/tests/components/recorder/test_pool.py @@ -1,6 +1,7 @@ """Test pool.""" import threading +import pytest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker @@ -8,6 +9,13 @@ from homeassistant.components.recorder.const import DB_WORKER_PREFIX from homeassistant.components.recorder.pool import RecorderPool +async def test_recorder_pool_called_from_event_loop(): + """Test we raise an exception when calling from the event loop.""" + engine = create_engine("sqlite://", poolclass=RecorderPool) + with pytest.raises(RuntimeError): + sessionmaker(bind=engine)().connection() + + def test_recorder_pool(caplog): """Test RecorderPool gives the same connection in the creating thread.""" @@ -28,30 +36,26 @@ def test_recorder_pool(caplog): connections.append(session.connection().connection.connection) session.close() - _get_connection_twice() - assert "accesses the database without the database executor" in caplog.text - assert connections[0] != connections[1] - caplog.clear() new_thread = threading.Thread(target=_get_connection_twice) new_thread.start() new_thread.join() assert "accesses the database without the database executor" in caplog.text - assert connections[2] != connections[3] + assert connections[0] != connections[1] caplog.clear() new_thread = threading.Thread(target=_get_connection_twice, name=DB_WORKER_PREFIX) new_thread.start() new_thread.join() assert "accesses the database without the database executor" not in caplog.text - assert connections[4] == connections[5] + assert connections[2] == connections[3] caplog.clear() new_thread = threading.Thread(target=_get_connection_twice, name="Recorder") new_thread.start() new_thread.join() assert "accesses the database without the database executor" not in caplog.text - assert connections[6] == connections[7] + assert connections[4] == connections[5] shutdown = True caplog.clear() @@ -59,4 +63,4 @@ def test_recorder_pool(caplog): new_thread.start() new_thread.join() assert "accesses the database without the database executor" not in caplog.text - assert connections[8] != connections[9] + assert connections[6] != connections[7] diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 1f0f3c87ea5..f4e998c5388 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -9,9 +9,9 @@ from sqlalchemy.exc import DatabaseError, OperationalError from sqlalchemy.orm.session import Session from homeassistant.components import recorder -from homeassistant.components.recorder import PurgeTask -from homeassistant.components.recorder.const import MAX_ROWS_TO_PURGE +from homeassistant.components.recorder.const import MAX_ROWS_TO_PURGE, SupportedDialect from homeassistant.components.recorder.models import ( + EventData, Events, RecorderRuns, StateAttributes, @@ -20,8 +20,13 @@ from homeassistant.components.recorder.models import ( StatisticsShortTerm, ) from homeassistant.components.recorder.purge import purge_old_data +from homeassistant.components.recorder.services import ( + SERVICE_PURGE, + SERVICE_PURGE_ENTITIES, +) +from homeassistant.components.recorder.tasks import PurgeTask from homeassistant.components.recorder.util import session_scope -from homeassistant.const import EVENT_STATE_CHANGED, STATE_ON +from homeassistant.const import EVENT_STATE_CHANGED, EVENT_THEMES_UPDATED, STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util @@ -39,8 +44,10 @@ from tests.common import SetupRecorderInstanceT def mock_use_sqlite(request): """Pytest fixture to switch purge method.""" with patch( - "homeassistant.components.recorder.Recorder.using_sqlite", - return_value=request.param, + "homeassistant.components.recorder.core.Recorder.dialect_name", + return_value=SupportedDialect.SQLITE + if request.param + else SupportedDialect.MYSQL, ): yield @@ -64,13 +71,19 @@ async def test_purge_old_states( assert state_attributes.count() == 3 events = session.query(Events).filter(Events.event_type == "state_changed") - assert events.count() == 6 + assert events.count() == 0 assert "test.recorder2" in instance._old_states purge_before = dt_util.utcnow() - timedelta(days=4) # run purge_old_data() - finished = purge_old_data(instance, purge_before, repack=False) + finished = purge_old_data( + instance, + purge_before, + states_batch_size=1, + events_batch_size=1, + repack=False, + ) assert not finished assert states.count() == 2 assert state_attributes.count() == 1 @@ -90,7 +103,13 @@ async def test_purge_old_states( # run purge_old_data again purge_before = dt_util.utcnow() - finished = purge_old_data(instance, purge_before, repack=False) + finished = purge_old_data( + instance, + purge_before, + states_batch_size=1, + events_batch_size=1, + repack=False, + ) assert not finished assert states.count() == 0 assert state_attributes.count() == 0 @@ -108,7 +127,7 @@ async def test_purge_old_states( assert states[5].old_state_id == states[4].state_id events = session.query(Events).filter(Events.event_type == "state_changed") - assert events.count() == 6 + assert events.count() == 0 assert "test.recorder2" in instance._old_states state_attributes = session.query(StateAttributes) @@ -128,14 +147,12 @@ async def test_purge_old_states_encouters_database_corruption( sqlite3_exception.__cause__ = sqlite3.DatabaseError() with patch( - "homeassistant.components.recorder.move_away_broken_database" + "homeassistant.components.recorder.core.move_away_broken_database" ) as move_away, patch( "homeassistant.components.recorder.purge.purge_old_data", side_effect=sqlite3_exception, ): - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, {"keep_days": 0} - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, {"keep_days": 0}) await hass.async_block_till_done() await async_wait_recording_done(hass) @@ -169,9 +186,7 @@ async def test_purge_old_states_encounters_temporary_mysql_error( ), patch.object( instance.engine.dialect, "name", "mysql" ): - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, {"keep_days": 0} - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, {"keep_days": 0}) await hass.async_block_till_done() await async_wait_recording_done(hass) await async_wait_recording_done(hass) @@ -197,9 +212,7 @@ async def test_purge_old_states_encounters_operational_error( "homeassistant.components.recorder.purge._purge_old_recorder_runs", side_effect=exception, ): - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, {"keep_days": 0} - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, {"keep_days": 0}) await hass.async_block_till_done() await async_wait_recording_done(hass) await async_wait_recording_done(hass) @@ -223,12 +236,24 @@ async def test_purge_old_events( purge_before = dt_util.utcnow() - timedelta(days=4) # run purge_old_data() - finished = purge_old_data(instance, purge_before, repack=False) + finished = purge_old_data( + instance, + purge_before, + repack=False, + events_batch_size=1, + states_batch_size=1, + ) assert not finished assert events.count() == 2 # we should only have 2 events left - finished = purge_old_data(instance, purge_before, repack=False) + finished = purge_old_data( + instance, + purge_before, + repack=False, + events_batch_size=1, + states_batch_size=1, + ) assert finished assert events.count() == 2 @@ -249,10 +274,22 @@ async def test_purge_old_recorder_runs( purge_before = dt_util.utcnow() # run purge_old_data() - finished = purge_old_data(instance, purge_before, repack=False) + finished = purge_old_data( + instance, + purge_before, + repack=False, + events_batch_size=1, + states_batch_size=1, + ) assert not finished - finished = purge_old_data(instance, purge_before, repack=False) + finished = purge_old_data( + instance, + purge_before, + repack=False, + events_batch_size=1, + states_batch_size=1, + ) assert finished assert recorder_runs.count() == 1 @@ -406,7 +443,7 @@ async def test_purge_edge_case( """Test states and events are purged even if they occurred shortly before purge_before.""" async def _add_db_entries(hass: HomeAssistant, timestamp: datetime) -> None: - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: session.add( Events( event_id=1001, @@ -452,9 +489,7 @@ async def test_purge_edge_case( events = session.query(Events).filter(Events.event_type == "EVENT_TEST_PURGE") assert events.count() == 1 - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await hass.async_block_till_done() await async_recorder_block_till_done(hass) @@ -477,7 +512,7 @@ async def test_purge_cutoff_date( timestamp_keep = cutoff timestamp_purge = cutoff - timedelta(microseconds=1) - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: session.add( Events( event_id=1000, @@ -561,7 +596,7 @@ async def test_purge_cutoff_date( assert events.filter(Events.event_type == "PURGE").count() == rows - 1 assert events.filter(Events.event_type == "KEEP").count() == 1 - instance.queue.put(PurgeTask(cutoff, repack=False, apply_filter=False)) + instance.queue_task(PurgeTask(cutoff, repack=False, apply_filter=False)) await hass.async_block_till_done() await async_recorder_block_till_done(hass) await async_wait_purge_done(hass) @@ -592,7 +627,7 @@ async def test_purge_cutoff_date( assert events.filter(Events.event_type == "KEEP").count() == 1 # Make sure we can purge everything - instance.queue.put(PurgeTask(dt_util.utcnow(), repack=False, apply_filter=False)) + instance.queue_task(PurgeTask(dt_util.utcnow(), repack=False, apply_filter=False)) await async_recorder_block_till_done(hass) await async_wait_purge_done(hass) @@ -603,7 +638,7 @@ async def test_purge_cutoff_date( assert state_attributes.count() == 0 # Make sure we can purge everything when the db is already empty - instance.queue.put(PurgeTask(dt_util.utcnow(), repack=False, apply_filter=False)) + instance.queue_task(PurgeTask(dt_util.utcnow(), repack=False, apply_filter=False)) await async_recorder_block_till_done(hass) await async_wait_purge_done(hass) @@ -626,7 +661,7 @@ async def test_purge_filtered_states( assert instance.entity_filter("sensor.excluded") is False def _add_db_entries(hass: HomeAssistant) -> None: - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: # Add states and state_changed events that should be purged for days in range(1, 4): timestamp = dt_util.utcnow() - timedelta(days=days) @@ -722,9 +757,7 @@ async def test_purge_filtered_states( assert events_keep.count() == 1 # Normal purge doesn't remove excluded entities - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await hass.async_block_till_done() await async_recorder_block_till_done(hass) @@ -742,9 +775,7 @@ async def test_purge_filtered_states( # Test with 'apply_filter' = True service_data["apply_filter"] = True - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await hass.async_block_till_done() await async_recorder_block_till_done(hass) @@ -780,9 +811,7 @@ async def test_purge_filtered_states( assert session.query(StateAttributes).count() == 11 # Do it again to make sure nothing changes - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await async_recorder_block_till_done(hass) await async_wait_purge_done(hass) @@ -793,11 +822,8 @@ async def test_purge_filtered_states( assert session.query(StateAttributes).count() == 11 - # Finally make sure we can delete them all except for the ones missing an event_id service_data = {"keep_days": 0} - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await async_recorder_block_till_done(hass) await async_wait_purge_done(hass) @@ -805,8 +831,8 @@ async def test_purge_filtered_states( remaining = list(session.query(States)) for state in remaining: assert state.event_id is None - assert len(remaining) == 3 - assert session.query(StateAttributes).count() == 1 + assert len(remaining) == 0 + assert session.query(StateAttributes).count() == 0 @pytest.mark.parametrize("use_sqlite", (True, False), indirect=True) @@ -821,7 +847,7 @@ async def test_purge_filtered_states_to_empty( assert instance.entity_filter("sensor.excluded") is False def _add_db_entries(hass: HomeAssistant) -> None: - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: # Add states and state_changed events that should be purged for days in range(1, 4): timestamp = dt_util.utcnow() - timedelta(days=days) @@ -845,9 +871,7 @@ async def test_purge_filtered_states_to_empty( # Test with 'apply_filter' = True service_data["apply_filter"] = True - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await async_recorder_block_till_done(hass) await async_wait_purge_done(hass) @@ -859,9 +883,7 @@ async def test_purge_filtered_states_to_empty( # Do it again to make sure nothing changes # Why do we do this? Should we check the end result? - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await async_recorder_block_till_done(hass) await async_wait_purge_done(hass) @@ -878,7 +900,7 @@ async def test_purge_without_state_attributes_filtered_states_to_empty( assert instance.entity_filter("sensor.old_format") is False def _add_db_entries(hass: HomeAssistant) -> None: - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: # Add states and state_changed events that should be purged # in the legacy format timestamp = dt_util.utcnow() - timedelta(days=5) @@ -903,6 +925,15 @@ async def test_purge_without_state_attributes_filtered_states_to_empty( time_fired=timestamp, ) ) + session.add( + Events( + event_id=event_id + 1, + event_type=EVENT_THEMES_UPDATED, + event_data="{}", + origin="LOCAL", + time_fired=timestamp, + ) + ) service_data = {"keep_days": 10} _add_db_entries(hass) @@ -915,9 +946,7 @@ async def test_purge_without_state_attributes_filtered_states_to_empty( # Test with 'apply_filter' = True service_data["apply_filter"] = True - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await async_recorder_block_till_done(hass) await async_wait_purge_done(hass) @@ -929,9 +958,7 @@ async def test_purge_without_state_attributes_filtered_states_to_empty( # Do it again to make sure nothing changes # Why do we do this? Should we check the end result? - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await async_recorder_block_till_done(hass) await async_wait_purge_done(hass) @@ -945,7 +972,7 @@ async def test_purge_filtered_events( await async_setup_recorder_instance(hass, config) def _add_db_entries(hass: HomeAssistant) -> None: - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: # Add events that should be purged for days in range(1, 4): timestamp = dt_util.utcnow() - timedelta(days=days) @@ -986,9 +1013,7 @@ async def test_purge_filtered_events( assert states.count() == 10 # Normal purge doesn't remove excluded events - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await hass.async_block_till_done() await async_recorder_block_till_done(hass) @@ -1006,9 +1031,7 @@ async def test_purge_filtered_events( # Test with 'apply_filter' = True service_data["apply_filter"] = True - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await hass.async_block_till_done() await async_recorder_block_till_done(hass) @@ -1039,7 +1062,7 @@ async def test_purge_filtered_events_state_changed( assert instance.entity_filter("sensor.excluded") is True def _add_db_entries(hass: HomeAssistant) -> None: - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: # Add states and state_changed events that should be purged for days in range(1, 4): timestamp = dt_util.utcnow() - timedelta(days=days) @@ -1106,9 +1129,7 @@ async def test_purge_filtered_events_state_changed( assert events_purge.count() == 60 assert states.count() == 63 - await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE, service_data - ) + await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, service_data) await hass.async_block_till_done() await async_recorder_block_till_done(hass) @@ -1147,7 +1168,7 @@ async def test_purge_entities( } await hass.services.async_call( - recorder.DOMAIN, recorder.SERVICE_PURGE_ENTITIES, service_data + recorder.DOMAIN, SERVICE_PURGE_ENTITIES, service_data ) await hass.async_block_till_done() @@ -1155,7 +1176,7 @@ async def test_purge_entities( await async_wait_purge_done(hass) def _add_purge_records(hass: HomeAssistant) -> None: - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: # Add states and state_changed events that should be purged for days in range(1, 4): timestamp = dt_util.utcnow() - timedelta(days=days) @@ -1187,7 +1208,7 @@ async def test_purge_entities( ) def _add_keep_records(hass: HomeAssistant) -> None: - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: # Add states and state_changed events that should be kept timestamp = dt_util.utcnow() - timedelta(days=2) for event_id in range(200, 210): @@ -1290,12 +1311,13 @@ async def _add_test_states(hass: HomeAssistant): attributes = {"dontpurgeme": True, **base_attributes} with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=timestamp + "homeassistant.components.recorder.core.dt_util.utcnow", + return_value=timestamp, ): await set_state("test.recorder2", state, attributes=attributes) -async def _add_test_events(hass: HomeAssistant): +async def _add_test_events(hass: HomeAssistant, iterations: int = 1): """Add a few events for testing.""" utcnow = dt_util.utcnow() five_days_ago = utcnow - timedelta(days=5) @@ -1305,26 +1327,65 @@ async def _add_test_events(hass: HomeAssistant): await hass.async_block_till_done() await async_wait_recording_done(hass) - with recorder.session_scope(hass=hass) as session: - for event_id in range(6): - if event_id < 2: - timestamp = eleven_days_ago - event_type = "EVENT_TEST_AUTOPURGE" - elif event_id < 4: - timestamp = five_days_ago - event_type = "EVENT_TEST_PURGE" - else: - timestamp = utcnow - event_type = "EVENT_TEST" + with session_scope(hass=hass) as session: + for _ in range(iterations): + for event_id in range(6): + if event_id < 2: + timestamp = eleven_days_ago + event_type = "EVENT_TEST_AUTOPURGE" + elif event_id < 4: + timestamp = five_days_ago + event_type = "EVENT_TEST_PURGE" + else: + timestamp = utcnow + event_type = "EVENT_TEST" - session.add( - Events( - event_type=event_type, - event_data=json.dumps(event_data), - origin="LOCAL", - time_fired=timestamp, + session.add( + Events( + event_type=event_type, + event_data=json.dumps(event_data), + origin="LOCAL", + time_fired=timestamp, + ) + ) + + +async def _add_events_with_event_data(hass: HomeAssistant, iterations: int = 1): + """Add a few events with linked event_data for testing.""" + utcnow = dt_util.utcnow() + five_days_ago = utcnow - timedelta(days=5) + eleven_days_ago = utcnow - timedelta(days=11) + event_data = {"test_attr": 5, "test_attr_10": "nice"} + + await hass.async_block_till_done() + await async_wait_recording_done(hass) + + with session_scope(hass=hass) as session: + for _ in range(iterations): + for event_id in range(6): + if event_id < 2: + timestamp = eleven_days_ago + event_type = "EVENT_TEST_AUTOPURGE_WITH_EVENT_DATA" + shared_data = '{"type":{"EVENT_TEST_AUTOPURGE_WITH_EVENT_DATA"}' + elif event_id < 4: + timestamp = five_days_ago + event_type = "EVENT_TEST_PURGE_WITH_EVENT_DATA" + shared_data = '{"type":{"EVENT_TEST_PURGE_WITH_EVENT_DATA"}' + else: + timestamp = utcnow + event_type = "EVENT_TEST_WITH_EVENT_DATA" + shared_data = '{"type":{"EVENT_TEST_WITH_EVENT_DATA"}' + + event_data = EventData(hash=1234, shared_data=shared_data) + + session.add( + Events( + event_type=event_type, + origin="LOCAL", + time_fired=timestamp, + event_data_rel=event_data, + ) ) - ) async def _add_test_statistics(hass: HomeAssistant): @@ -1336,7 +1397,7 @@ async def _add_test_statistics(hass: HomeAssistant): await hass.async_block_till_done() await async_wait_recording_done(hass) - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: for event_id in range(6): if event_id < 2: timestamp = eleven_days_ago @@ -1365,7 +1426,7 @@ async def _add_test_recorder_runs(hass: HomeAssistant): await hass.async_block_till_done() await async_wait_recording_done(hass) - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: for rec_id in range(6): if rec_id < 2: timestamp = eleven_days_ago @@ -1392,7 +1453,7 @@ async def _add_test_statistics_runs(hass: HomeAssistant): await hass.async_block_till_done() await async_wait_recording_done(hass) - with recorder.session_scope(hass=hass) as session: + with session_scope(hass=hass) as session: for rec_id in range(6): if rec_id < 2: timestamp = eleven_days_ago @@ -1408,6 +1469,29 @@ async def _add_test_statistics_runs(hass: HomeAssistant): ) +def _add_state_without_event_linkage( + session: Session, + entity_id: str, + state: str, + timestamp: datetime, +): + state_attrs = StateAttributes( + hash=1234, shared_attrs=json.dumps({entity_id: entity_id}) + ) + session.add(state_attrs) + session.add( + States( + entity_id=entity_id, + state=state, + attributes=None, + last_changed=timestamp, + last_updated=timestamp, + event_id=None, + state_attributes=state_attrs, + ) + ) + + def _add_state_and_state_changed_event( session: Session, entity_id: str, @@ -1440,3 +1524,149 @@ def _add_state_and_state_changed_event( time_fired=timestamp, ) ) + + +async def test_purge_many_old_events( + hass: HomeAssistant, async_setup_recorder_instance: SetupRecorderInstanceT +): + """Test deleting old events.""" + instance = await async_setup_recorder_instance(hass) + + await _add_test_events(hass, MAX_ROWS_TO_PURGE) + + with session_scope(hass=hass) as session: + events = session.query(Events).filter(Events.event_type.like("EVENT_TEST%")) + event_datas = session.query(EventData) + assert events.count() == MAX_ROWS_TO_PURGE * 6 + assert event_datas.count() == 5 + + purge_before = dt_util.utcnow() - timedelta(days=4) + + # run purge_old_data() + finished = purge_old_data( + instance, + purge_before, + repack=False, + states_batch_size=3, + events_batch_size=3, + ) + assert not finished + assert events.count() == MAX_ROWS_TO_PURGE * 3 + assert event_datas.count() == 5 + + # we should only have 2 groups of events left + finished = purge_old_data( + instance, + purge_before, + repack=False, + states_batch_size=3, + events_batch_size=3, + ) + assert finished + assert events.count() == MAX_ROWS_TO_PURGE * 2 + assert event_datas.count() == 5 + + # we should now purge everything + finished = purge_old_data( + instance, + dt_util.utcnow(), + repack=False, + states_batch_size=20, + events_batch_size=20, + ) + assert finished + assert events.count() == 0 + assert event_datas.count() == 0 + + +async def test_purge_can_mix_legacy_and_new_format( + hass: HomeAssistant, async_setup_recorder_instance: SetupRecorderInstanceT +): + """Test purging with legacy a new events.""" + instance = await async_setup_recorder_instance(hass) + utcnow = dt_util.utcnow() + eleven_days_ago = utcnow - timedelta(days=11) + with session_scope(hass=hass) as session: + broken_state_no_time = States( + event_id=None, + entity_id="orphened.state", + last_updated=None, + last_changed=None, + ) + session.add(broken_state_no_time) + start_id = 50000 + for event_id in range(start_id, start_id + 50): + _add_state_and_state_changed_event( + session, + "sensor.excluded", + "purgeme", + eleven_days_ago, + event_id, + ) + await _add_test_events(hass, 50) + await _add_events_with_event_data(hass, 50) + with session_scope(hass=hass) as session: + for _ in range(50): + _add_state_without_event_linkage( + session, "switch.random", "on", eleven_days_ago + ) + states_with_event_id = session.query(States).filter( + States.event_id.is_not(None) + ) + states_without_event_id = session.query(States).filter( + States.event_id.is_(None) + ) + + assert states_with_event_id.count() == 50 + assert states_without_event_id.count() == 51 + + purge_before = dt_util.utcnow() - timedelta(days=4) + finished = purge_old_data( + instance, + purge_before, + repack=False, + ) + assert not finished + assert states_with_event_id.count() == 0 + assert states_without_event_id.count() == 51 + # At this point all the legacy states are gone + # and we switch methods + purge_before = dt_util.utcnow() - timedelta(days=4) + finished = purge_old_data( + instance, + purge_before, + repack=False, + events_batch_size=1, + states_batch_size=1, + ) + # Since we only allow one iteration, we won't + # check if we are finished this loop similar + # to the legacy method + assert not finished + assert states_with_event_id.count() == 0 + assert states_without_event_id.count() == 1 + finished = purge_old_data( + instance, + purge_before, + repack=False, + events_batch_size=100, + states_batch_size=100, + ) + assert finished + assert states_with_event_id.count() == 0 + assert states_without_event_id.count() == 1 + _add_state_without_event_linkage( + session, "switch.random", "on", eleven_days_ago + ) + assert states_with_event_id.count() == 0 + assert states_without_event_id.count() == 2 + finished = purge_old_data( + instance, + purge_before, + repack=False, + ) + assert finished + # The broken state without a timestamp + # does not prevent future purges. Its ignored. + assert states_with_event_id.count() == 0 + assert states_without_event_id.count() == 1 diff --git a/tests/components/recorder/test_statistics.py b/tests/components/recorder/test_statistics.py index 8a73716e482..882f00d2940 100644 --- a/tests/components/recorder/test_statistics.py +++ b/tests/components/recorder/test_statistics.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access,invalid-name from datetime import timedelta import importlib -import json import sys from unittest.mock import patch, sentinel @@ -12,15 +11,16 @@ from sqlalchemy import create_engine from sqlalchemy.orm import Session from homeassistant.components import recorder -from homeassistant.components.recorder import SQLITE_URL_PREFIX, history, statistics -from homeassistant.components.recorder.const import DATA_INSTANCE +from homeassistant.components.recorder import history, statistics +from homeassistant.components.recorder.const import DATA_INSTANCE, SQLITE_URL_PREFIX from homeassistant.components.recorder.models import ( StatisticsShortTerm, process_timestamp_to_utc_isoformat, ) from homeassistant.components.recorder.statistics import ( async_add_external_statistics, - delete_duplicates, + delete_statistics_duplicates, + delete_statistics_meta_duplicates, get_last_short_term_statistics, get_last_statistics, get_latest_short_term_statistics, @@ -35,7 +35,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util -from .common import async_wait_recording_done +from .common import async_wait_recording_done, do_adhoc_statistics from tests.common import get_test_home_assistant, mock_registry from tests.components.recorder.common import wait_recording_done @@ -62,8 +62,8 @@ def test_compile_hourly_statistics(hass_recorder): stats = get_last_short_term_statistics(hass, 0, "sensor.test1", True) assert stats == {} - recorder.do_adhoc_statistics(start=zero) - recorder.do_adhoc_statistics(start=four) + do_adhoc_statistics(hass, start=zero) + do_adhoc_statistics(hass, start=four) wait_recording_done(hass) expected_1 = { "statistic_id": "sensor.test1", @@ -202,12 +202,11 @@ def test_compile_periodic_statistics_exception( """Test exception handling when compiling periodic statistics.""" hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) now = dt_util.utcnow() - recorder.do_adhoc_statistics(start=now) - recorder.do_adhoc_statistics(start=now + timedelta(minutes=5)) + do_adhoc_statistics(hass, start=now) + do_adhoc_statistics(hass, start=now + timedelta(minutes=5)) wait_recording_done(hass) expected_1 = { "statistic_id": "sensor.test1", @@ -254,7 +253,6 @@ def test_compile_periodic_statistics_exception( def test_rename_entity(hass_recorder): """Test statistics is migrated when entity_id is changed.""" hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) entity_reg = mock_registry(hass) @@ -282,7 +280,7 @@ def test_rename_entity(hass_recorder): stats = get_last_short_term_statistics(hass, 0, "sensor.test1", True) assert stats == {} - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) expected_1 = { "statistic_id": "sensor.test1", @@ -313,16 +311,95 @@ def test_rename_entity(hass_recorder): entity_reg.async_update_entity("sensor.test1", new_entity_id="sensor.test99") hass.add_job(rename_entry) - hass.block_till_done() + wait_recording_done(hass) stats = statistics_during_period(hass, zero, period="5minute") assert stats == {"sensor.test99": expected_stats99, "sensor.test2": expected_stats2} +def test_rename_entity_collision(hass_recorder, caplog): + """Test statistics is migrated when entity_id is changed.""" + hass = hass_recorder() + setup_component(hass, "sensor", {}) + + entity_reg = mock_registry(hass) + + @callback + def add_entry(): + reg_entry = entity_reg.async_get_or_create( + "sensor", + "test", + "unique_0000", + suggested_object_id="test1", + ) + assert reg_entry.entity_id == "sensor.test1" + + hass.add_job(add_entry) + hass.block_till_done() + + zero, four, states = record_states(hass) + hist = history.get_significant_states(hass, zero, four) + assert dict(states) == dict(hist) + + for kwargs in ({}, {"statistic_ids": ["sensor.test1"]}): + stats = statistics_during_period(hass, zero, period="5minute", **kwargs) + assert stats == {} + stats = get_last_short_term_statistics(hass, 0, "sensor.test1", True) + assert stats == {} + + do_adhoc_statistics(hass, start=zero) + wait_recording_done(hass) + expected_1 = { + "statistic_id": "sensor.test1", + "start": process_timestamp_to_utc_isoformat(zero), + "end": process_timestamp_to_utc_isoformat(zero + timedelta(minutes=5)), + "mean": approx(14.915254237288135), + "min": approx(10.0), + "max": approx(20.0), + "last_reset": None, + "state": None, + "sum": None, + } + expected_stats1 = [ + {**expected_1, "statistic_id": "sensor.test1"}, + ] + expected_stats2 = [ + {**expected_1, "statistic_id": "sensor.test2"}, + ] + + stats = statistics_during_period(hass, zero, period="5minute") + assert stats == {"sensor.test1": expected_stats1, "sensor.test2": expected_stats2} + + # Insert metadata for sensor.test99 + metadata_1 = { + "has_mean": True, + "has_sum": False, + "name": "Total imported energy", + "source": "test", + "statistic_id": "sensor.test99", + "unit_of_measurement": "kWh", + } + + with session_scope(hass=hass) as session: + session.add(recorder.models.StatisticsMeta.from_meta(metadata_1)) + + # Rename entity sensor.test1 to sensor.test99 + @callback + def rename_entry(): + entity_reg.async_update_entity("sensor.test1", new_entity_id="sensor.test99") + + hass.add_job(rename_entry) + wait_recording_done(hass) + + # Statistics failed to migrate due to the collision + stats = statistics_during_period(hass, zero, period="5minute") + assert stats == {"sensor.test1": expected_stats1, "sensor.test2": expected_stats2} + assert "Blocked attempt to insert duplicated statistic rows" in caplog.text + + def test_statistics_duplicated(hass_recorder, caplog): """Test statistics with same start time is not compiled.""" hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) zero, four, states = record_states(hass) hist = history.get_significant_states(hass, zero, four) @@ -336,7 +413,7 @@ def test_statistics_duplicated(hass_recorder, caplog): "homeassistant.components.sensor.recorder.compile_statistics", return_value=statistics.PlatformCompiledStatistics([], {}), ) as compile_statistics: - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) assert compile_statistics.called compile_statistics.reset_mock() @@ -344,7 +421,7 @@ def test_statistics_duplicated(hass_recorder, caplog): assert "Statistics already compiled" not in caplog.text caplog.clear() - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) assert not compile_statistics.called compile_statistics.reset_mock() @@ -740,615 +817,12 @@ def test_monthly_statistics(hass_recorder, caplog, timezone): dt_util.set_default_time_zone(dt_util.get_time_zone("UTC")) -def _create_engine_test(*args, **kwargs): - """Test version of create_engine that initializes with old schema. - - This simulates an existing db with the old schema. - """ - module = "tests.components.recorder.models_schema_23" - importlib.import_module(module) - old_models = sys.modules[module] - engine = create_engine(*args, **kwargs) - old_models.Base.metadata.create_all(engine) - with Session(engine) as session: - session.add(recorder.models.StatisticsRuns(start=statistics.get_start_time())) - session.add( - recorder.models.SchemaChanges(schema_version=old_models.SCHEMA_VERSION) - ) - session.commit() - return engine - - -def test_delete_duplicates(caplog, tmpdir): - """Test removal of duplicated statistics.""" - test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") - dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" - - module = "tests.components.recorder.models_schema_23" - importlib.import_module(module) - old_models = sys.modules[module] - - period1 = dt_util.as_utc(dt_util.parse_datetime("2021-09-01 00:00:00")) - period2 = dt_util.as_utc(dt_util.parse_datetime("2021-09-30 23:00:00")) - period3 = dt_util.as_utc(dt_util.parse_datetime("2021-10-01 00:00:00")) - period4 = dt_util.as_utc(dt_util.parse_datetime("2021-10-31 23:00:00")) - - external_energy_statistics_1 = ( - { - "start": period1, - "last_reset": None, - "state": 0, - "sum": 2, - }, - { - "start": period2, - "last_reset": None, - "state": 1, - "sum": 3, - }, - { - "start": period3, - "last_reset": None, - "state": 2, - "sum": 4, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 5, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 5, - }, - ) - external_energy_metadata_1 = { - "has_mean": False, - "has_sum": True, - "name": "Total imported energy", - "source": "test", - "statistic_id": "test:total_energy_import_tariff_1", - "unit_of_measurement": "kWh", - } - external_energy_statistics_2 = ( - { - "start": period1, - "last_reset": None, - "state": 0, - "sum": 20, - }, - { - "start": period2, - "last_reset": None, - "state": 1, - "sum": 30, - }, - { - "start": period3, - "last_reset": None, - "state": 2, - "sum": 40, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 50, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 50, - }, - ) - external_energy_metadata_2 = { - "has_mean": False, - "has_sum": True, - "name": "Total imported energy", - "source": "test", - "statistic_id": "test:total_energy_import_tariff_2", - "unit_of_measurement": "kWh", - } - external_co2_statistics = ( - { - "start": period1, - "last_reset": None, - "mean": 10, - }, - { - "start": period2, - "last_reset": None, - "mean": 30, - }, - { - "start": period3, - "last_reset": None, - "mean": 60, - }, - { - "start": period4, - "last_reset": None, - "mean": 90, - }, - ) - external_co2_metadata = { - "has_mean": True, - "has_sum": False, - "name": "Fossil percentage", - "source": "test", - "statistic_id": "test:fossil_percentage", - "unit_of_measurement": "%", - } - - # Create some duplicated statistics with schema version 23 - with patch.object(recorder, "models", old_models), patch.object( - recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION - ), patch( - "homeassistant.components.recorder.create_engine", new=_create_engine_test - ): - hass = get_test_home_assistant() - setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) - wait_recording_done(hass) - wait_recording_done(hass) - - with session_scope(hass=hass) as session: - session.add( - recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) - ) - session.add( - recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) - ) - session.add(recorder.models.StatisticsMeta.from_meta(external_co2_metadata)) - with session_scope(hass=hass) as session: - for stat in external_energy_statistics_1: - session.add(recorder.models.Statistics.from_stats(1, stat)) - for stat in external_energy_statistics_2: - session.add(recorder.models.Statistics.from_stats(2, stat)) - for stat in external_co2_statistics: - session.add(recorder.models.Statistics.from_stats(3, stat)) - - hass.stop() - dt_util.DEFAULT_TIME_ZONE = ORIG_TZ - - # Test that the duplicates are removed during migration from schema 23 - hass = get_test_home_assistant() - setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) - hass.start() - wait_recording_done(hass) - wait_recording_done(hass) - hass.stop() - dt_util.DEFAULT_TIME_ZONE = ORIG_TZ - - assert "Deleted 2 duplicated statistics rows" in caplog.text - assert "Found non identical" not in caplog.text - assert "Found duplicated" not in caplog.text - - -def test_delete_duplicates_many(caplog, tmpdir): - """Test removal of duplicated statistics.""" - test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") - dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" - - module = "tests.components.recorder.models_schema_23" - importlib.import_module(module) - old_models = sys.modules[module] - - period1 = dt_util.as_utc(dt_util.parse_datetime("2021-09-01 00:00:00")) - period2 = dt_util.as_utc(dt_util.parse_datetime("2021-09-30 23:00:00")) - period3 = dt_util.as_utc(dt_util.parse_datetime("2021-10-01 00:00:00")) - period4 = dt_util.as_utc(dt_util.parse_datetime("2021-10-31 23:00:00")) - - external_energy_statistics_1 = ( - { - "start": period1, - "last_reset": None, - "state": 0, - "sum": 2, - }, - { - "start": period2, - "last_reset": None, - "state": 1, - "sum": 3, - }, - { - "start": period3, - "last_reset": None, - "state": 2, - "sum": 4, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 5, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 5, - }, - ) - external_energy_metadata_1 = { - "has_mean": False, - "has_sum": True, - "name": "Total imported energy", - "source": "test", - "statistic_id": "test:total_energy_import_tariff_1", - "unit_of_measurement": "kWh", - } - external_energy_statistics_2 = ( - { - "start": period1, - "last_reset": None, - "state": 0, - "sum": 20, - }, - { - "start": period2, - "last_reset": None, - "state": 1, - "sum": 30, - }, - { - "start": period3, - "last_reset": None, - "state": 2, - "sum": 40, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 50, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 50, - }, - ) - external_energy_metadata_2 = { - "has_mean": False, - "has_sum": True, - "name": "Total imported energy", - "source": "test", - "statistic_id": "test:total_energy_import_tariff_2", - "unit_of_measurement": "kWh", - } - external_co2_statistics = ( - { - "start": period1, - "last_reset": None, - "mean": 10, - }, - { - "start": period2, - "last_reset": None, - "mean": 30, - }, - { - "start": period3, - "last_reset": None, - "mean": 60, - }, - { - "start": period4, - "last_reset": None, - "mean": 90, - }, - ) - external_co2_metadata = { - "has_mean": True, - "has_sum": False, - "name": "Fossil percentage", - "source": "test", - "statistic_id": "test:fossil_percentage", - "unit_of_measurement": "%", - } - - # Create some duplicated statistics with schema version 23 - with patch.object(recorder, "models", old_models), patch.object( - recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION - ), patch( - "homeassistant.components.recorder.create_engine", new=_create_engine_test - ): - hass = get_test_home_assistant() - setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) - wait_recording_done(hass) - wait_recording_done(hass) - - with session_scope(hass=hass) as session: - session.add( - recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) - ) - session.add( - recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) - ) - session.add(recorder.models.StatisticsMeta.from_meta(external_co2_metadata)) - with session_scope(hass=hass) as session: - for stat in external_energy_statistics_1: - session.add(recorder.models.Statistics.from_stats(1, stat)) - for _ in range(3000): - session.add( - recorder.models.Statistics.from_stats( - 1, external_energy_statistics_1[-1] - ) - ) - for stat in external_energy_statistics_2: - session.add(recorder.models.Statistics.from_stats(2, stat)) - for stat in external_co2_statistics: - session.add(recorder.models.Statistics.from_stats(3, stat)) - - hass.stop() - dt_util.DEFAULT_TIME_ZONE = ORIG_TZ - - # Test that the duplicates are removed during migration from schema 23 - hass = get_test_home_assistant() - setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) - hass.start() - wait_recording_done(hass) - wait_recording_done(hass) - hass.stop() - dt_util.DEFAULT_TIME_ZONE = ORIG_TZ - - assert "Deleted 3002 duplicated statistics rows" in caplog.text - assert "Found non identical" not in caplog.text - assert "Found duplicated" not in caplog.text - - -@pytest.mark.freeze_time("2021-08-01 00:00:00+00:00") -def test_delete_duplicates_non_identical(caplog, tmpdir): - """Test removal of duplicated statistics.""" - test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") - dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" - - module = "tests.components.recorder.models_schema_23" - importlib.import_module(module) - old_models = sys.modules[module] - - period1 = dt_util.as_utc(dt_util.parse_datetime("2021-09-01 00:00:00")) - period2 = dt_util.as_utc(dt_util.parse_datetime("2021-09-30 23:00:00")) - period3 = dt_util.as_utc(dt_util.parse_datetime("2021-10-01 00:00:00")) - period4 = dt_util.as_utc(dt_util.parse_datetime("2021-10-31 23:00:00")) - - external_energy_statistics_1 = ( - { - "start": period1, - "last_reset": None, - "state": 0, - "sum": 2, - }, - { - "start": period2, - "last_reset": None, - "state": 1, - "sum": 3, - }, - { - "start": period3, - "last_reset": None, - "state": 2, - "sum": 4, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 5, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 6, - }, - ) - external_energy_metadata_1 = { - "has_mean": False, - "has_sum": True, - "name": "Total imported energy", - "source": "test", - "statistic_id": "test:total_energy_import_tariff_1", - "unit_of_measurement": "kWh", - } - external_energy_statistics_2 = ( - { - "start": period1, - "last_reset": None, - "state": 0, - "sum": 20, - }, - { - "start": period2, - "last_reset": None, - "state": 1, - "sum": 30, - }, - { - "start": period3, - "last_reset": None, - "state": 2, - "sum": 40, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 50, - }, - { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 50, - }, - ) - external_energy_metadata_2 = { - "has_mean": False, - "has_sum": True, - "name": "Total imported energy", - "source": "test", - "statistic_id": "test:total_energy_import_tariff_2", - "unit_of_measurement": "kWh", - } - - # Create some duplicated statistics with schema version 23 - with patch.object(recorder, "models", old_models), patch.object( - recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION - ), patch( - "homeassistant.components.recorder.create_engine", new=_create_engine_test - ): - hass = get_test_home_assistant() - setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) - wait_recording_done(hass) - wait_recording_done(hass) - - with session_scope(hass=hass) as session: - session.add( - recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) - ) - session.add( - recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) - ) - with session_scope(hass=hass) as session: - for stat in external_energy_statistics_1: - session.add(recorder.models.Statistics.from_stats(1, stat)) - for stat in external_energy_statistics_2: - session.add(recorder.models.Statistics.from_stats(2, stat)) - - hass.stop() - dt_util.DEFAULT_TIME_ZONE = ORIG_TZ - - # Test that the duplicates are removed during migration from schema 23 - hass = get_test_home_assistant() - hass.config.config_dir = tmpdir - setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) - hass.start() - wait_recording_done(hass) - wait_recording_done(hass) - hass.stop() - dt_util.DEFAULT_TIME_ZONE = ORIG_TZ - - assert "Deleted 2 duplicated statistics rows" in caplog.text - assert "Deleted 1 non identical" in caplog.text - assert "Found duplicated" not in caplog.text - - isotime = dt_util.utcnow().isoformat() - backup_file_name = f".storage/deleted_statistics.{isotime}.json" - - with open(hass.config.path(backup_file_name)) as backup_file: - backup = json.load(backup_file) - - assert backup == [ - { - "duplicate": { - "created": "2021-08-01T00:00:00", - "id": 4, - "last_reset": None, - "max": None, - "mean": None, - "metadata_id": 1, - "min": None, - "start": "2021-10-31T23:00:00", - "state": 3.0, - "sum": 5.0, - }, - "original": { - "created": "2021-08-01T00:00:00", - "id": 5, - "last_reset": None, - "max": None, - "mean": None, - "metadata_id": 1, - "min": None, - "start": "2021-10-31T23:00:00", - "state": 3.0, - "sum": 6.0, - }, - } - ] - - -def test_delete_duplicates_short_term(caplog, tmpdir): - """Test removal of duplicated statistics.""" - test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") - dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" - - module = "tests.components.recorder.models_schema_23" - importlib.import_module(module) - old_models = sys.modules[module] - - period4 = dt_util.as_utc(dt_util.parse_datetime("2021-10-31 23:00:00")) - - external_energy_metadata_1 = { - "has_mean": False, - "has_sum": True, - "name": "Total imported energy", - "source": "test", - "statistic_id": "test:total_energy_import_tariff_1", - "unit_of_measurement": "kWh", - } - statistic_row = { - "start": period4, - "last_reset": None, - "state": 3, - "sum": 5, - } - - # Create some duplicated statistics with schema version 23 - with patch.object(recorder, "models", old_models), patch.object( - recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION - ), patch( - "homeassistant.components.recorder.create_engine", new=_create_engine_test - ): - hass = get_test_home_assistant() - setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) - wait_recording_done(hass) - wait_recording_done(hass) - - with session_scope(hass=hass) as session: - session.add( - recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) - ) - with session_scope(hass=hass) as session: - session.add( - recorder.models.StatisticsShortTerm.from_stats(1, statistic_row) - ) - session.add( - recorder.models.StatisticsShortTerm.from_stats(1, statistic_row) - ) - - hass.stop() - dt_util.DEFAULT_TIME_ZONE = ORIG_TZ - - # Test that the duplicates are removed during migration from schema 23 - hass = get_test_home_assistant() - hass.config.config_dir = tmpdir - setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) - hass.start() - wait_recording_done(hass) - wait_recording_done(hass) - hass.stop() - dt_util.DEFAULT_TIME_ZONE = ORIG_TZ - - assert "duplicated statistics rows" not in caplog.text - assert "Found non identical" not in caplog.text - assert "Deleted duplicated short term statistic" in caplog.text - - def test_delete_duplicates_no_duplicates(hass_recorder, caplog): """Test removal of duplicated statistics.""" hass = hass_recorder() wait_recording_done(hass) with session_scope(hass=hass) as session: - delete_duplicates(hass.data[DATA_INSTANCE], session) + delete_statistics_duplicates(hass, session) assert "duplicated statistics rows" not in caplog.text assert "Found non identical" not in caplog.text assert "Found duplicated" not in caplog.text @@ -1411,6 +885,208 @@ def test_duplicate_statistics_handle_integrity_error(hass_recorder, caplog): assert "Blocked attempt to insert duplicated statistic rows" in caplog.text +def _create_engine_28(*args, **kwargs): + """Test version of create_engine that initializes with old schema. + + This simulates an existing db with the old schema. + """ + module = "tests.components.recorder.models_schema_28" + importlib.import_module(module) + old_models = sys.modules[module] + engine = create_engine(*args, **kwargs) + old_models.Base.metadata.create_all(engine) + with Session(engine) as session: + session.add(recorder.models.StatisticsRuns(start=statistics.get_start_time())) + session.add( + recorder.models.SchemaChanges(schema_version=old_models.SCHEMA_VERSION) + ) + session.commit() + return engine + + +def test_delete_metadata_duplicates(caplog, tmpdir): + """Test removal of duplicated statistics.""" + test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") + dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" + + module = "tests.components.recorder.models_schema_28" + importlib.import_module(module) + old_models = sys.modules[module] + + external_energy_metadata_1 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_1", + "unit_of_measurement": "kWh", + } + external_energy_metadata_2 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_1", + "unit_of_measurement": "kWh", + } + external_co2_metadata = { + "has_mean": True, + "has_sum": False, + "name": "Fossil percentage", + "source": "test", + "statistic_id": "test:fossil_percentage", + "unit_of_measurement": "%", + } + + # Create some duplicated statistics_meta with schema version 28 + with patch.object(recorder, "models", old_models), patch.object( + recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION + ), patch( + "homeassistant.components.recorder.core.create_engine", new=_create_engine_28 + ): + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + wait_recording_done(hass) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) + ) + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) + ) + session.add(recorder.models.StatisticsMeta.from_meta(external_co2_metadata)) + + with session_scope(hass=hass) as session: + tmp = session.query(recorder.models.StatisticsMeta).all() + assert len(tmp) == 3 + assert tmp[0].id == 1 + assert tmp[0].statistic_id == "test:total_energy_import_tariff_1" + assert tmp[1].id == 2 + assert tmp[1].statistic_id == "test:total_energy_import_tariff_1" + assert tmp[2].id == 3 + assert tmp[2].statistic_id == "test:fossil_percentage" + + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + # Test that the duplicates are removed during migration from schema 28 + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + hass.start() + wait_recording_done(hass) + wait_recording_done(hass) + + assert "Deleted 1 duplicated statistics_meta rows" in caplog.text + with session_scope(hass=hass) as session: + tmp = session.query(recorder.models.StatisticsMeta).all() + assert len(tmp) == 2 + assert tmp[0].id == 2 + assert tmp[0].statistic_id == "test:total_energy_import_tariff_1" + assert tmp[1].id == 3 + assert tmp[1].statistic_id == "test:fossil_percentage" + + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + +def test_delete_metadata_duplicates_many(caplog, tmpdir): + """Test removal of duplicated statistics.""" + test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") + dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" + + module = "tests.components.recorder.models_schema_28" + importlib.import_module(module) + old_models = sys.modules[module] + + external_energy_metadata_1 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_1", + "unit_of_measurement": "kWh", + } + external_energy_metadata_2 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_2", + "unit_of_measurement": "kWh", + } + external_co2_metadata = { + "has_mean": True, + "has_sum": False, + "name": "Fossil percentage", + "source": "test", + "statistic_id": "test:fossil_percentage", + "unit_of_measurement": "%", + } + + # Create some duplicated statistics with schema version 28 + with patch.object(recorder, "models", old_models), patch.object( + recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION + ), patch( + "homeassistant.components.recorder.core.create_engine", new=_create_engine_28 + ): + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + wait_recording_done(hass) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) + ) + for _ in range(3000): + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) + ) + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) + ) + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) + ) + session.add(recorder.models.StatisticsMeta.from_meta(external_co2_metadata)) + session.add(recorder.models.StatisticsMeta.from_meta(external_co2_metadata)) + + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + # Test that the duplicates are removed during migration from schema 28 + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + hass.start() + wait_recording_done(hass) + wait_recording_done(hass) + + assert "Deleted 3002 duplicated statistics_meta rows" in caplog.text + with session_scope(hass=hass) as session: + tmp = session.query(recorder.models.StatisticsMeta).all() + assert len(tmp) == 3 + assert tmp[0].id == 3001 + assert tmp[0].statistic_id == "test:total_energy_import_tariff_1" + assert tmp[1].id == 3003 + assert tmp[1].statistic_id == "test:total_energy_import_tariff_2" + assert tmp[2].id == 3005 + assert tmp[2].statistic_id == "test:fossil_percentage" + + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + +def test_delete_metadata_duplicates_no_duplicates(hass_recorder, caplog): + """Test removal of duplicated statistics.""" + hass = hass_recorder() + wait_recording_done(hass) + with session_scope(hass=hass) as session: + delete_statistics_meta_duplicates(session) + assert "duplicated statistics_meta rows" not in caplog.text + + def record_states(hass): """Record some test states. @@ -1447,7 +1123,9 @@ def record_states(hass): four = three + timedelta(seconds=15 * 5) states = {mp: [], sns1: [], sns2: [], sns3: [], sns4: []} - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=one): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=one + ): states[mp].append( set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) ) @@ -1459,13 +1137,17 @@ def record_states(hass): states[sns3].append(set_state(sns3, "10", attributes=sns3_attr)) states[sns4].append(set_state(sns4, "10", attributes=sns4_attr)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=two): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=two + ): states[sns1].append(set_state(sns1, "15", attributes=sns1_attr)) states[sns2].append(set_state(sns2, "15", attributes=sns2_attr)) states[sns3].append(set_state(sns3, "15", attributes=sns3_attr)) states[sns4].append(set_state(sns4, "15", attributes=sns4_attr)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=three): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=three + ): states[sns1].append(set_state(sns1, "20", attributes=sns1_attr)) states[sns2].append(set_state(sns2, "20", attributes=sns2_attr)) states[sns3].append(set_state(sns3, "20", attributes=sns3_attr)) diff --git a/tests/components/recorder/test_statistics_v23_migration.py b/tests/components/recorder/test_statistics_v23_migration.py new file mode 100644 index 00000000000..d487743a87f --- /dev/null +++ b/tests/components/recorder/test_statistics_v23_migration.py @@ -0,0 +1,618 @@ +"""The tests for sensor recorder platform migrating statistics from v23. + +The v23 schema used for these tests has been slightly modified to add the +EventData table to allow the recorder to startup successfully. +""" +# pylint: disable=protected-access,invalid-name +import importlib +import json +import sys +from unittest.mock import patch + +import pytest +from sqlalchemy import create_engine +from sqlalchemy.orm import Session + +from homeassistant.components import recorder +from homeassistant.components.recorder import SQLITE_URL_PREFIX, statistics +from homeassistant.components.recorder.util import session_scope +from homeassistant.setup import setup_component +import homeassistant.util.dt as dt_util + +from tests.common import get_test_home_assistant +from tests.components.recorder.common import wait_recording_done + +ORIG_TZ = dt_util.DEFAULT_TIME_ZONE + +CREATE_ENGINE_TARGET = "homeassistant.components.recorder.core.create_engine" +SCHEMA_MODULE = "tests.components.recorder.models_schema_23_with_newer_columns" + + +def _create_engine_test(*args, **kwargs): + """Test version of create_engine that initializes with old schema. + + This simulates an existing db with the old schema. + """ + importlib.import_module(SCHEMA_MODULE) + old_models = sys.modules[SCHEMA_MODULE] + engine = create_engine(*args, **kwargs) + old_models.Base.metadata.create_all(engine) + with Session(engine) as session: + session.add(recorder.models.StatisticsRuns(start=statistics.get_start_time())) + session.add( + recorder.models.SchemaChanges(schema_version=old_models.SCHEMA_VERSION) + ) + session.commit() + return engine + + +def test_delete_duplicates(caplog, tmpdir): + """Test removal of duplicated statistics.""" + test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") + dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" + + importlib.import_module(SCHEMA_MODULE) + old_models = sys.modules[SCHEMA_MODULE] + + period1 = dt_util.as_utc(dt_util.parse_datetime("2021-09-01 00:00:00")) + period2 = dt_util.as_utc(dt_util.parse_datetime("2021-09-30 23:00:00")) + period3 = dt_util.as_utc(dt_util.parse_datetime("2021-10-01 00:00:00")) + period4 = dt_util.as_utc(dt_util.parse_datetime("2021-10-31 23:00:00")) + + external_energy_statistics_1 = ( + { + "start": period1, + "last_reset": None, + "state": 0, + "sum": 2, + }, + { + "start": period2, + "last_reset": None, + "state": 1, + "sum": 3, + }, + { + "start": period3, + "last_reset": None, + "state": 2, + "sum": 4, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 5, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 5, + }, + ) + external_energy_metadata_1 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_1", + "unit_of_measurement": "kWh", + } + external_energy_statistics_2 = ( + { + "start": period1, + "last_reset": None, + "state": 0, + "sum": 20, + }, + { + "start": period2, + "last_reset": None, + "state": 1, + "sum": 30, + }, + { + "start": period3, + "last_reset": None, + "state": 2, + "sum": 40, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 50, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 50, + }, + ) + external_energy_metadata_2 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_2", + "unit_of_measurement": "kWh", + } + external_co2_statistics = ( + { + "start": period1, + "last_reset": None, + "mean": 10, + }, + { + "start": period2, + "last_reset": None, + "mean": 30, + }, + { + "start": period3, + "last_reset": None, + "mean": 60, + }, + { + "start": period4, + "last_reset": None, + "mean": 90, + }, + ) + external_co2_metadata = { + "has_mean": True, + "has_sum": False, + "name": "Fossil percentage", + "source": "test", + "statistic_id": "test:fossil_percentage", + "unit_of_measurement": "%", + } + + # Create some duplicated statistics with schema version 23 + with patch.object(recorder, "models", old_models), patch.object( + recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION + ), patch(CREATE_ENGINE_TARGET, new=_create_engine_test): + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + wait_recording_done(hass) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) + ) + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) + ) + session.add(recorder.models.StatisticsMeta.from_meta(external_co2_metadata)) + with session_scope(hass=hass) as session: + for stat in external_energy_statistics_1: + session.add(recorder.models.Statistics.from_stats(1, stat)) + for stat in external_energy_statistics_2: + session.add(recorder.models.Statistics.from_stats(2, stat)) + for stat in external_co2_statistics: + session.add(recorder.models.Statistics.from_stats(3, stat)) + + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + # Test that the duplicates are removed during migration from schema 23 + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + hass.start() + wait_recording_done(hass) + wait_recording_done(hass) + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + assert "Deleted 2 duplicated statistics rows" in caplog.text + assert "Found non identical" not in caplog.text + assert "Found duplicated" not in caplog.text + + +def test_delete_duplicates_many(caplog, tmpdir): + """Test removal of duplicated statistics.""" + test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") + dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" + + importlib.import_module(SCHEMA_MODULE) + old_models = sys.modules[SCHEMA_MODULE] + + period1 = dt_util.as_utc(dt_util.parse_datetime("2021-09-01 00:00:00")) + period2 = dt_util.as_utc(dt_util.parse_datetime("2021-09-30 23:00:00")) + period3 = dt_util.as_utc(dt_util.parse_datetime("2021-10-01 00:00:00")) + period4 = dt_util.as_utc(dt_util.parse_datetime("2021-10-31 23:00:00")) + + external_energy_statistics_1 = ( + { + "start": period1, + "last_reset": None, + "state": 0, + "sum": 2, + }, + { + "start": period2, + "last_reset": None, + "state": 1, + "sum": 3, + }, + { + "start": period3, + "last_reset": None, + "state": 2, + "sum": 4, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 5, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 5, + }, + ) + external_energy_metadata_1 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_1", + "unit_of_measurement": "kWh", + } + external_energy_statistics_2 = ( + { + "start": period1, + "last_reset": None, + "state": 0, + "sum": 20, + }, + { + "start": period2, + "last_reset": None, + "state": 1, + "sum": 30, + }, + { + "start": period3, + "last_reset": None, + "state": 2, + "sum": 40, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 50, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 50, + }, + ) + external_energy_metadata_2 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_2", + "unit_of_measurement": "kWh", + } + external_co2_statistics = ( + { + "start": period1, + "last_reset": None, + "mean": 10, + }, + { + "start": period2, + "last_reset": None, + "mean": 30, + }, + { + "start": period3, + "last_reset": None, + "mean": 60, + }, + { + "start": period4, + "last_reset": None, + "mean": 90, + }, + ) + external_co2_metadata = { + "has_mean": True, + "has_sum": False, + "name": "Fossil percentage", + "source": "test", + "statistic_id": "test:fossil_percentage", + "unit_of_measurement": "%", + } + + # Create some duplicated statistics with schema version 23 + with patch.object(recorder, "models", old_models), patch.object( + recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION + ), patch(CREATE_ENGINE_TARGET, new=_create_engine_test): + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + wait_recording_done(hass) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) + ) + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) + ) + session.add(recorder.models.StatisticsMeta.from_meta(external_co2_metadata)) + with session_scope(hass=hass) as session: + for stat in external_energy_statistics_1: + session.add(recorder.models.Statistics.from_stats(1, stat)) + for _ in range(3000): + session.add( + recorder.models.Statistics.from_stats( + 1, external_energy_statistics_1[-1] + ) + ) + for stat in external_energy_statistics_2: + session.add(recorder.models.Statistics.from_stats(2, stat)) + for stat in external_co2_statistics: + session.add(recorder.models.Statistics.from_stats(3, stat)) + + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + # Test that the duplicates are removed during migration from schema 23 + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + hass.start() + wait_recording_done(hass) + wait_recording_done(hass) + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + assert "Deleted 3002 duplicated statistics rows" in caplog.text + assert "Found non identical" not in caplog.text + assert "Found duplicated" not in caplog.text + + +@pytest.mark.freeze_time("2021-08-01 00:00:00+00:00") +def test_delete_duplicates_non_identical(caplog, tmpdir): + """Test removal of duplicated statistics.""" + test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") + dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" + + importlib.import_module(SCHEMA_MODULE) + old_models = sys.modules[SCHEMA_MODULE] + + period1 = dt_util.as_utc(dt_util.parse_datetime("2021-09-01 00:00:00")) + period2 = dt_util.as_utc(dt_util.parse_datetime("2021-09-30 23:00:00")) + period3 = dt_util.as_utc(dt_util.parse_datetime("2021-10-01 00:00:00")) + period4 = dt_util.as_utc(dt_util.parse_datetime("2021-10-31 23:00:00")) + + external_energy_statistics_1 = ( + { + "start": period1, + "last_reset": None, + "state": 0, + "sum": 2, + }, + { + "start": period2, + "last_reset": None, + "state": 1, + "sum": 3, + }, + { + "start": period3, + "last_reset": None, + "state": 2, + "sum": 4, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 5, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 6, + }, + ) + external_energy_metadata_1 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_1", + "unit_of_measurement": "kWh", + } + external_energy_statistics_2 = ( + { + "start": period1, + "last_reset": None, + "state": 0, + "sum": 20, + }, + { + "start": period2, + "last_reset": None, + "state": 1, + "sum": 30, + }, + { + "start": period3, + "last_reset": None, + "state": 2, + "sum": 40, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 50, + }, + { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 50, + }, + ) + external_energy_metadata_2 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_2", + "unit_of_measurement": "kWh", + } + + # Create some duplicated statistics with schema version 23 + with patch.object(recorder, "models", old_models), patch.object( + recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION + ), patch(CREATE_ENGINE_TARGET, new=_create_engine_test): + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + wait_recording_done(hass) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) + ) + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_2) + ) + with session_scope(hass=hass) as session: + for stat in external_energy_statistics_1: + session.add(recorder.models.Statistics.from_stats(1, stat)) + for stat in external_energy_statistics_2: + session.add(recorder.models.Statistics.from_stats(2, stat)) + + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + # Test that the duplicates are removed during migration from schema 23 + hass = get_test_home_assistant() + hass.config.config_dir = tmpdir + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + hass.start() + wait_recording_done(hass) + wait_recording_done(hass) + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + assert "Deleted 2 duplicated statistics rows" in caplog.text + assert "Deleted 1 non identical" in caplog.text + assert "Found duplicated" not in caplog.text + + isotime = dt_util.utcnow().isoformat() + backup_file_name = f".storage/deleted_statistics.{isotime}.json" + + with open(hass.config.path(backup_file_name)) as backup_file: + backup = json.load(backup_file) + + assert backup == [ + { + "duplicate": { + "created": "2021-08-01T00:00:00", + "id": 4, + "last_reset": None, + "max": None, + "mean": None, + "metadata_id": 1, + "min": None, + "start": "2021-10-31T23:00:00", + "state": 3.0, + "sum": 5.0, + }, + "original": { + "created": "2021-08-01T00:00:00", + "id": 5, + "last_reset": None, + "max": None, + "mean": None, + "metadata_id": 1, + "min": None, + "start": "2021-10-31T23:00:00", + "state": 3.0, + "sum": 6.0, + }, + } + ] + + +def test_delete_duplicates_short_term(caplog, tmpdir): + """Test removal of duplicated statistics.""" + test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") + dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" + + importlib.import_module(SCHEMA_MODULE) + old_models = sys.modules[SCHEMA_MODULE] + + period4 = dt_util.as_utc(dt_util.parse_datetime("2021-10-31 23:00:00")) + + external_energy_metadata_1 = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy", + "source": "test", + "statistic_id": "test:total_energy_import_tariff_1", + "unit_of_measurement": "kWh", + } + statistic_row = { + "start": period4, + "last_reset": None, + "state": 3, + "sum": 5, + } + + # Create some duplicated statistics with schema version 23 + with patch.object(recorder, "models", old_models), patch.object( + recorder.migration, "SCHEMA_VERSION", old_models.SCHEMA_VERSION + ), patch(CREATE_ENGINE_TARGET, new=_create_engine_test): + hass = get_test_home_assistant() + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + wait_recording_done(hass) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + session.add( + recorder.models.StatisticsMeta.from_meta(external_energy_metadata_1) + ) + with session_scope(hass=hass) as session: + session.add( + recorder.models.StatisticsShortTerm.from_stats(1, statistic_row) + ) + session.add( + recorder.models.StatisticsShortTerm.from_stats(1, statistic_row) + ) + + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + # Test that the duplicates are removed during migration from schema 23 + hass = get_test_home_assistant() + hass.config.config_dir = tmpdir + setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) + hass.start() + wait_recording_done(hass) + wait_recording_done(hass) + hass.stop() + dt_util.DEFAULT_TIME_ZONE = ORIG_TZ + + assert "duplicated statistics rows" not in caplog.text + assert "Found non identical" not in caplog.text + assert "Deleted duplicated short term statistic" in caplog.text diff --git a/tests/components/recorder/test_system_health.py b/tests/components/recorder/test_system_health.py new file mode 100644 index 00000000000..b465ee89ebe --- /dev/null +++ b/tests/components/recorder/test_system_health.py @@ -0,0 +1,102 @@ +"""Test recorder system health.""" + +from unittest.mock import ANY, Mock, patch + +import pytest + +from homeassistant.components.recorder import get_instance +from homeassistant.components.recorder.const import SupportedDialect +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from .common import async_wait_recording_done + +from tests.common import SetupRecorderInstanceT, get_system_health_info + + +async def test_recorder_system_health(hass, recorder_mock): + """Test recorder system health.""" + assert await async_setup_component(hass, "system_health", {}) + await async_wait_recording_done(hass) + info = await get_system_health_info(hass, "recorder") + instance = get_instance(hass) + assert info == { + "current_recorder_run": instance.run_history.current.start, + "oldest_recorder_run": instance.run_history.first.start, + "estimated_db_size": ANY, + "database_engine": SupportedDialect.SQLITE.value, + "database_version": ANY, + } + + +@pytest.mark.parametrize( + "dialect_name", [SupportedDialect.MYSQL, SupportedDialect.POSTGRESQL] +) +async def test_recorder_system_health_alternate_dbms(hass, recorder_mock, dialect_name): + """Test recorder system health.""" + assert await async_setup_component(hass, "system_health", {}) + await async_wait_recording_done(hass) + with patch( + "homeassistant.components.recorder.core.Recorder.dialect_name", dialect_name + ), patch( + "sqlalchemy.orm.session.Session.execute", + return_value=Mock(first=Mock(return_value=("1048576",))), + ): + info = await get_system_health_info(hass, "recorder") + instance = get_instance(hass) + assert info == { + "current_recorder_run": instance.run_history.current.start, + "oldest_recorder_run": instance.run_history.first.start, + "estimated_db_size": "1.00 MiB", + "database_engine": dialect_name.value, + "database_version": ANY, + } + + +@pytest.mark.parametrize( + "dialect_name", [SupportedDialect.MYSQL, SupportedDialect.POSTGRESQL] +) +async def test_recorder_system_health_db_url_missing_host( + hass, recorder_mock, dialect_name +): + """Test recorder system health with a db_url without a hostname.""" + assert await async_setup_component(hass, "system_health", {}) + await async_wait_recording_done(hass) + + instance = get_instance(hass) + with patch( + "homeassistant.components.recorder.core.Recorder.dialect_name", dialect_name + ), patch.object( + instance, + "db_url", + "postgresql://homeassistant:blabla@/home_assistant?host=/config/socket", + ), patch( + "sqlalchemy.orm.session.Session.execute", + return_value=Mock(first=Mock(return_value=("1048576",))), + ): + info = await get_system_health_info(hass, "recorder") + assert info == { + "current_recorder_run": instance.run_history.current.start, + "oldest_recorder_run": instance.run_history.first.start, + "estimated_db_size": "1.00 MiB", + "database_engine": dialect_name.value, + "database_version": ANY, + } + + +async def test_recorder_system_health_crashed_recorder_runs_table( + hass: HomeAssistant, async_setup_recorder_instance: SetupRecorderInstanceT +): + """Test recorder system health with crashed recorder runs table.""" + with patch("homeassistant.components.recorder.run_history.RunHistory.load_from_db"): + assert await async_setup_component(hass, "system_health", {}) + instance = await async_setup_recorder_instance(hass) + await async_wait_recording_done(hass) + info = await get_system_health_info(hass, "recorder") + assert info == { + "current_recorder_run": instance.run_history.current.start, + "oldest_recorder_run": instance.run_history.current.start, + "estimated_db_size": ANY, + "database_engine": SupportedDialect.SQLITE.value, + "database_version": ANY, + } diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index f6d2fa0a99d..343c57045cf 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -2,16 +2,19 @@ from datetime import datetime, timedelta import os import sqlite3 -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, Mock, patch import pytest from sqlalchemy import text +from sqlalchemy.engine.result import ChunkedIteratorResult +from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.sql.elements import TextClause +from sqlalchemy.sql.lambdas import StatementLambdaElement from homeassistant.components import recorder -from homeassistant.components.recorder import util +from homeassistant.components.recorder import history, util from homeassistant.components.recorder.const import DATA_INSTANCE, SQLITE_URL_PREFIX -from homeassistant.components.recorder.models import RecorderRuns +from homeassistant.components.recorder.models import RecorderRuns, UnsupportedDialect from homeassistant.components.recorder.util import ( end_incomplete_runs, is_second_sunday, @@ -24,6 +27,7 @@ from homeassistant.util import dt as dt_util from .common import corrupt_db_file, run_information_with_session from tests.common import SetupRecorderInstanceT, async_test_home_assistant +from tests.components.recorder.common import wait_recording_done def test_session_scope_not_setup(hass_recorder): @@ -45,7 +49,7 @@ def test_recorder_bad_commit(hass_recorder): session.execute(text("select * from notthere")) with patch( - "homeassistant.components.recorder.time.sleep" + "homeassistant.components.recorder.core.time.sleep" ) as e_mock, util.session_scope(hass=hass) as session: res = util.commit(session, work) assert res is False @@ -66,7 +70,7 @@ def test_recorder_bad_execute(hass_recorder): mck1.to_native = to_native with pytest.raises(SQLAlchemyError), patch( - "homeassistant.components.recorder.time.sleep" + "homeassistant.components.recorder.core.time.sleep" ) as e_mock: util.execute((mck1,), to_native=True) @@ -148,7 +152,7 @@ async def test_last_run_was_recently_clean( "homeassistant.components.recorder.util.last_run_was_recently_clean", wraps=_last_run_was_recently_clean, ) as last_run_was_recently_clean_mock, patch( - "homeassistant.components.recorder.dt_util.utcnow", + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=thirty_min_future_time, ): hass = await async_test_home_assistant(None) @@ -162,17 +166,12 @@ async def test_last_run_was_recently_clean( @pytest.mark.parametrize( - "mysql_version, db_supports_row_number", - [ - ("10.2.0-MariaDB", True), - ("10.1.0-MariaDB", False), - ("5.8.0", True), - ("5.7.0", False), - ], + "mysql_version", + ["10.3.0-MariaDB", "8.0.0"], ) -def test_setup_connection_for_dialect_mysql(mysql_version, db_supports_row_number): +def test_setup_connection_for_dialect_mysql(mysql_version): """Test setting up the connection for a mysql dialect.""" - instance_mock = MagicMock(_db_supports_row_number=True) + instance_mock = MagicMock() execute_args = [] close_mock = MagicMock() @@ -197,19 +196,14 @@ def test_setup_connection_for_dialect_mysql(mysql_version, db_supports_row_numbe assert execute_args[0] == "SET session wait_timeout=28800" assert execute_args[1] == "SELECT VERSION()" - assert instance_mock._db_supports_row_number == db_supports_row_number - @pytest.mark.parametrize( - "sqlite_version, db_supports_row_number", - [ - ("3.25.0", True), - ("3.24.0", False), - ], + "sqlite_version", + ["3.31.0"], ) -def test_setup_connection_for_dialect_sqlite(sqlite_version, db_supports_row_number): +def test_setup_connection_for_dialect_sqlite(sqlite_version): """Test setting up the connection for a sqlite dialect.""" - instance_mock = MagicMock(_db_supports_row_number=True) + instance_mock = MagicMock() execute_args = [] close_mock = MagicMock() @@ -230,20 +224,65 @@ def test_setup_connection_for_dialect_sqlite(sqlite_version, db_supports_row_num util.setup_connection_for_dialect(instance_mock, "sqlite", dbapi_connection, True) - assert len(execute_args) == 4 + assert len(execute_args) == 5 assert execute_args[0] == "PRAGMA journal_mode=WAL" assert execute_args[1] == "SELECT sqlite_version()" - assert execute_args[2] == "PRAGMA cache_size = -8192" - assert execute_args[3] == "PRAGMA foreign_keys=ON" + assert execute_args[2] == "PRAGMA cache_size = -16384" + assert execute_args[3] == "PRAGMA synchronous=NORMAL" + assert execute_args[4] == "PRAGMA foreign_keys=ON" execute_args = [] util.setup_connection_for_dialect(instance_mock, "sqlite", dbapi_connection, False) - assert len(execute_args) == 2 - assert execute_args[0] == "PRAGMA cache_size = -8192" - assert execute_args[1] == "PRAGMA foreign_keys=ON" + assert len(execute_args) == 3 + assert execute_args[0] == "PRAGMA cache_size = -16384" + assert execute_args[1] == "PRAGMA synchronous=NORMAL" + assert execute_args[2] == "PRAGMA foreign_keys=ON" - assert instance_mock._db_supports_row_number == db_supports_row_number + +@pytest.mark.parametrize( + "sqlite_version", + ["3.31.0"], +) +def test_setup_connection_for_dialect_sqlite_zero_commit_interval( + sqlite_version, +): + """Test setting up the connection for a sqlite dialect with a zero commit interval.""" + instance_mock = MagicMock(commit_interval=0) + execute_args = [] + close_mock = MagicMock() + + def execute_mock(statement): + nonlocal execute_args + execute_args.append(statement) + + def fetchall_mock(): + nonlocal execute_args + if execute_args[-1] == "SELECT sqlite_version()": + return [[sqlite_version]] + return None + + def _make_cursor_mock(*_): + return MagicMock(execute=execute_mock, close=close_mock, fetchall=fetchall_mock) + + dbapi_connection = MagicMock(cursor=_make_cursor_mock) + + util.setup_connection_for_dialect(instance_mock, "sqlite", dbapi_connection, True) + + assert len(execute_args) == 5 + assert execute_args[0] == "PRAGMA journal_mode=WAL" + assert execute_args[1] == "SELECT sqlite_version()" + assert execute_args[2] == "PRAGMA cache_size = -16384" + assert execute_args[3] == "PRAGMA synchronous=FULL" + assert execute_args[4] == "PRAGMA foreign_keys=ON" + + execute_args = [] + util.setup_connection_for_dialect(instance_mock, "sqlite", dbapi_connection, False) + + assert len(execute_args) == 3 + assert execute_args[0] == "PRAGMA cache_size = -16384" + assert execute_args[1] == "PRAGMA synchronous=FULL" + assert execute_args[2] == "PRAGMA foreign_keys=ON" @pytest.mark.parametrize( @@ -263,9 +302,9 @@ def test_setup_connection_for_dialect_sqlite(sqlite_version, db_supports_row_num ), ], ) -def test_warn_outdated_mysql(caplog, mysql_version, message): +def test_fail_outdated_mysql(caplog, mysql_version, message): """Test setting up the connection for an outdated mysql version.""" - instance_mock = MagicMock(_db_supports_row_number=True) + instance_mock = MagicMock() execute_args = [] close_mock = MagicMock() @@ -284,7 +323,10 @@ def test_warn_outdated_mysql(caplog, mysql_version, message): dbapi_connection = MagicMock(cursor=_make_cursor_mock) - util.setup_connection_for_dialect(instance_mock, "mysql", dbapi_connection, True) + with pytest.raises(UnsupportedDialect): + util.setup_connection_for_dialect( + instance_mock, "mysql", dbapi_connection, True + ) assert message in caplog.text @@ -298,7 +340,7 @@ def test_warn_outdated_mysql(caplog, mysql_version, message): ) def test_supported_mysql(caplog, mysql_version): """Test setting up the connection for a supported mysql version.""" - instance_mock = MagicMock(_db_supports_row_number=True) + instance_mock = MagicMock() execute_args = [] close_mock = MagicMock() @@ -339,9 +381,9 @@ def test_supported_mysql(caplog, mysql_version): ), ], ) -def test_warn_outdated_pgsql(caplog, pgsql_version, message): +def test_fail_outdated_pgsql(caplog, pgsql_version, message): """Test setting up the connection for an outdated PostgreSQL version.""" - instance_mock = MagicMock(_db_supports_row_number=True) + instance_mock = MagicMock() execute_args = [] close_mock = MagicMock() @@ -360,9 +402,10 @@ def test_warn_outdated_pgsql(caplog, pgsql_version, message): dbapi_connection = MagicMock(cursor=_make_cursor_mock) - util.setup_connection_for_dialect( - instance_mock, "postgresql", dbapi_connection, True - ) + with pytest.raises(UnsupportedDialect): + util.setup_connection_for_dialect( + instance_mock, "postgresql", dbapi_connection, True + ) assert message in caplog.text @@ -373,7 +416,7 @@ def test_warn_outdated_pgsql(caplog, pgsql_version, message): ) def test_supported_pgsql(caplog, pgsql_version): """Test setting up the connection for a supported PostgreSQL version.""" - instance_mock = MagicMock(_db_supports_row_number=True) + instance_mock = MagicMock() execute_args = [] close_mock = MagicMock() @@ -416,9 +459,9 @@ def test_supported_pgsql(caplog, pgsql_version): ), ], ) -def test_warn_outdated_sqlite(caplog, sqlite_version, message): +def test_fail_outdated_sqlite(caplog, sqlite_version, message): """Test setting up the connection for an outdated sqlite version.""" - instance_mock = MagicMock(_db_supports_row_number=True) + instance_mock = MagicMock() execute_args = [] close_mock = MagicMock() @@ -437,7 +480,10 @@ def test_warn_outdated_sqlite(caplog, sqlite_version, message): dbapi_connection = MagicMock(cursor=_make_cursor_mock) - util.setup_connection_for_dialect(instance_mock, "sqlite", dbapi_connection, True) + with pytest.raises(UnsupportedDialect): + util.setup_connection_for_dialect( + instance_mock, "sqlite", dbapi_connection, True + ) assert message in caplog.text @@ -451,7 +497,7 @@ def test_warn_outdated_sqlite(caplog, sqlite_version, message): ) def test_supported_sqlite(caplog, sqlite_version): """Test setting up the connection for a supported sqlite version.""" - instance_mock = MagicMock(_db_supports_row_number=True) + instance_mock = MagicMock() execute_args = [] close_mock = MagicMock() @@ -488,7 +534,10 @@ def test_warn_unsupported_dialect(caplog, dialect, message): instance_mock = MagicMock() dbapi_connection = MagicMock() - util.setup_connection_for_dialect(instance_mock, dialect, dbapi_connection, True) + with pytest.raises(UnsupportedDialect): + util.setup_connection_for_dialect( + instance_mock, dialect, dbapi_connection, True + ) assert message in caplog.text @@ -510,8 +559,10 @@ def test_basic_sanity_check(hass_recorder): def test_combined_checks(hass_recorder, caplog): """Run Checks on the open database.""" hass = hass_recorder() + instance = recorder.get_instance(hass) + instance.db_retry_wait = 0 - cursor = hass.data[DATA_INSTANCE].engine.raw_connection().cursor() + cursor = instance.engine.raw_connection().cursor() assert util.run_checks_on_open_db("fake_db_path", cursor) is None assert "could not validate that the sqlite3 database" in caplog.text @@ -597,8 +648,12 @@ def test_periodic_db_cleanups(hass_recorder): assert str(text_obj) == "PRAGMA wal_checkpoint(TRUNCATE);" +@patch("homeassistant.components.recorder.pool.check_loop") async def test_write_lock_db( - hass: HomeAssistant, async_setup_recorder_instance: SetupRecorderInstanceT, tmp_path + skip_check_loop, + hass: HomeAssistant, + async_setup_recorder_instance: SetupRecorderInstanceT, + tmp_path, ): """Test database write lock.""" from sqlalchemy.exc import OperationalError @@ -637,3 +692,71 @@ def test_is_second_sunday(): assert is_second_sunday(datetime(2022, 5, 8, 0, 0, 0, tzinfo=dt_util.UTC)) is True assert is_second_sunday(datetime(2022, 1, 10, 0, 0, 0, tzinfo=dt_util.UTC)) is False + + +def test_build_mysqldb_conv(): + """Test building the MySQLdb connect conv param.""" + mock_converters = Mock(conversions={"original": "preserved"}) + mock_constants = Mock(FIELD_TYPE=Mock(DATETIME="DATETIME")) + with patch.dict( + "sys.modules", + **{"MySQLdb.constants": mock_constants, "MySQLdb.converters": mock_converters}, + ): + conv = util.build_mysqldb_conv() + + assert conv["original"] == "preserved" + assert conv["DATETIME"]("INVALID") is None + assert conv["DATETIME"]("2022-05-13T22:33:12.741") == datetime( + 2022, 5, 13, 22, 33, 12, 741000, tzinfo=None + ) + + +@patch("homeassistant.components.recorder.util.QUERY_RETRY_WAIT", 0) +def test_execute_stmt_lambda_element(hass_recorder): + """Test executing with execute_stmt_lambda_element.""" + hass = hass_recorder() + instance = recorder.get_instance(hass) + hass.states.set("sensor.on", "on") + new_state = hass.states.get("sensor.on") + wait_recording_done(hass) + now = dt_util.utcnow() + tomorrow = now + timedelta(days=1) + one_week_from_now = now + timedelta(days=7) + + class MockExecutor: + def __init__(self, stmt): + assert isinstance(stmt, StatementLambdaElement) + self.calls = 0 + + def all(self): + self.calls += 1 + if self.calls == 2: + return ["mock_row"] + raise SQLAlchemyError + + with session_scope(hass=hass) as session: + # No time window, we always get a list + stmt = history._get_single_entity_states_stmt( + instance.schema_version, dt_util.utcnow(), "sensor.on", False + ) + rows = util.execute_stmt_lambda_element(session, stmt) + assert isinstance(rows, list) + assert rows[0].state == new_state.state + assert rows[0].entity_id == new_state.entity_id + + # Time window >= 2 days, we get a ChunkedIteratorResult + rows = util.execute_stmt_lambda_element(session, stmt, now, one_week_from_now) + assert isinstance(rows, ChunkedIteratorResult) + row = next(rows) + assert row.state == new_state.state + assert row.entity_id == new_state.entity_id + + # Time window < 2 days, we get a list + rows = util.execute_stmt_lambda_element(session, stmt, now, tomorrow) + assert isinstance(rows, list) + assert rows[0].state == new_state.state + assert rows[0].entity_id == new_state.entity_id + + with patch.object(session, "execute", MockExecutor): + rows = util.execute_stmt_lambda_element(session, stmt, now, tomorrow) + assert rows == ["mock_row"] diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index 08427e60911..55bbb13898e 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -18,6 +18,7 @@ from .common import ( async_recorder_block_till_done, async_wait_recording_done, create_engine_test, + do_adhoc_statistics, ) from tests.common import async_fire_time_changed @@ -84,7 +85,7 @@ async def test_clear_statistics(hass, hass_ws_client, recorder_mock): hass.states.async_set("sensor.test3", state * 3, attributes=attributes) await async_wait_recording_done(hass) - hass.data[DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) client = await hass_ws_client() @@ -208,7 +209,7 @@ async def test_update_statistics_metadata( hass.states.async_set("sensor.test", state, attributes=attributes) await async_wait_recording_done(hass) - hass.data[DATA_INSTANCE].do_adhoc_statistics(period="hourly", start=now) + do_adhoc_statistics(hass, period="hourly", start=now) await async_recorder_block_till_done(hass) client = await hass_ws_client() @@ -323,9 +324,10 @@ async def test_recorder_info_migration_queue_exhausted(hass, hass_ws_client): with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( "homeassistant.components.recorder.Recorder.async_periodic_statistics" ), patch( - "homeassistant.components.recorder.create_engine", new=create_engine_test + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, ), patch.object( - recorder, "MAX_QUEUE_BACKLOG", 1 + recorder.core, "MAX_QUEUE_BACKLOG", 1 ), patch( "homeassistant.components.recorder.migration.migrate_schema", wraps=stalled_migration, @@ -384,7 +386,7 @@ async def test_backup_start_timeout( # Ensure there are no queued events await async_wait_recording_done(hass) - with patch.object(recorder, "DB_LOCK_TIMEOUT", 0): + with patch.object(recorder.core, "DB_LOCK_TIMEOUT", 0): try: await client.send_json({"id": 1, "type": "backup/start"}) response = await client.receive_json() @@ -520,7 +522,7 @@ async def test_get_statistics_metadata( } ] - hass.data[recorder.DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) # Remove the state, statistics will now be fetched from the database hass.states.async_remove("sensor.test") diff --git a/tests/components/ring/test_siren.py b/tests/components/ring/test_siren.py new file mode 100644 index 00000000000..bb282769e57 --- /dev/null +++ b/tests/components/ring/test_siren.py @@ -0,0 +1,132 @@ +"""The tests for the Ring button platform.""" + +from homeassistant.const import Platform +from homeassistant.helpers import entity_registry as er + +from .common import setup_platform + + +async def test_entity_registry(hass, requests_mock): + """Tests that the devices are registered in the entity registry.""" + await setup_platform(hass, Platform.SIREN) + entity_registry = er.async_get(hass) + + entry = entity_registry.async_get("siren.downstairs_siren") + assert entry.unique_id == "123456-siren" + + +async def test_sirens_report_correctly(hass, requests_mock): + """Tests that the initial state of a device that should be on is correct.""" + await setup_platform(hass, Platform.SIREN) + + state = hass.states.get("siren.downstairs_siren") + assert state.attributes.get("friendly_name") == "Downstairs Siren" + assert state.state == "unknown" + + +async def test_default_ding_chime_can_be_played(hass, requests_mock): + """Tests the play chime request is sent correctly.""" + await setup_platform(hass, Platform.SIREN) + + # Mocks the response for playing a test sound + requests_mock.post( + "https://api.ring.com/clients_api/chimes/123456/play_sound", + text="SUCCESS", + ) + await hass.services.async_call( + "siren", + "turn_on", + {"entity_id": "siren.downstairs_siren"}, + blocking=True, + ) + + await hass.async_block_till_done() + + assert requests_mock.request_history[-1].url.startswith( + "https://api.ring.com/clients_api/chimes/123456/play_sound?" + ) + assert "kind=ding" in requests_mock.request_history[-1].url + + state = hass.states.get("siren.downstairs_siren") + assert state.state == "unknown" + + +async def test_toggle_plays_default_chime(hass, requests_mock): + """Tests the play chime request is sent correctly when toggled.""" + await setup_platform(hass, Platform.SIREN) + + # Mocks the response for playing a test sound + requests_mock.post( + "https://api.ring.com/clients_api/chimes/123456/play_sound", + text="SUCCESS", + ) + await hass.services.async_call( + "siren", + "toggle", + {"entity_id": "siren.downstairs_siren"}, + blocking=True, + ) + + await hass.async_block_till_done() + + assert requests_mock.request_history[-1].url.startswith( + "https://api.ring.com/clients_api/chimes/123456/play_sound?" + ) + assert "kind=ding" in requests_mock.request_history[-1].url + + state = hass.states.get("siren.downstairs_siren") + assert state.state == "unknown" + + +async def test_explicit_ding_chime_can_be_played(hass, requests_mock): + """Tests the play chime request is sent correctly.""" + await setup_platform(hass, Platform.SIREN) + + # Mocks the response for playing a test sound + requests_mock.post( + "https://api.ring.com/clients_api/chimes/123456/play_sound", + text="SUCCESS", + ) + await hass.services.async_call( + "siren", + "turn_on", + {"entity_id": "siren.downstairs_siren", "tone": "ding"}, + blocking=True, + ) + + await hass.async_block_till_done() + + assert requests_mock.request_history[-1].url.startswith( + "https://api.ring.com/clients_api/chimes/123456/play_sound?" + ) + assert "kind=ding" in requests_mock.request_history[-1].url + + state = hass.states.get("siren.downstairs_siren") + assert state.state == "unknown" + + +async def test_motion_chime_can_be_played(hass, requests_mock): + """Tests the play chime request is sent correctly.""" + await setup_platform(hass, Platform.SIREN) + + # Mocks the response for playing a test sound + requests_mock.post( + "https://api.ring.com/clients_api/chimes/123456/play_sound", + text="SUCCESS", + ) + await hass.services.async_call( + "siren", + "turn_on", + {"entity_id": "siren.downstairs_siren", "tone": "motion"}, + blocking=True, + ) + + await hass.async_block_till_done() + + assert requests_mock.request_history[-1].url.startswith( + "https://api.ring.com/clients_api/chimes/123456/play_sound?" + ) + assert "kind=motion" in requests_mock.request_history[-1].url + + state = hass.states.get("siren.downstairs_siren") + assert state.state == "unknown" diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 21fd2e861b6..c95eda2288a 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -52,7 +52,7 @@ from homeassistant.components.roku.const import ( DOMAIN, SERVICE_SEARCH, ) -from homeassistant.components.stream.const import FORMAT_CONTENT_TYPE, HLS_PROVIDER +from homeassistant.components.stream import FORMAT_CONTENT_TYPE, HLS_PROVIDER from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.config import async_process_ha_core_config from homeassistant.const import ( diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 40397a68d7d..11aaf12d9ee 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -1420,6 +1420,36 @@ async def test_update_missing_mac_unique_id_added_from_zeroconf( assert entry.unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4" +@pytest.mark.usefixtures("remote", "rest_api_failing") +async def test_update_missing_model_added_from_ssdp(hass: HomeAssistant) -> None: + """Test missing model added via ssdp on legacy models.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_OLD_ENTRY, + unique_id=None, + ) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.samsungtv.async_setup", + return_value=True, + ) as mock_setup, patch( + "homeassistant.components.samsungtv.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=MOCK_SSDP_DATA, + ) + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + assert entry.data[CONF_MODEL] == "fake_model" + + @pytest.mark.usefixtures("remotews", "rest_api", "remoteencws_failing") async def test_update_missing_mac_unique_id_ssdp_location_added_from_ssdp( hass: HomeAssistant, diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index ca0cdb97592..4ef4cd9d2b0 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -6,7 +6,7 @@ from unittest.mock import Mock, patch import pytest -from homeassistant.components import logbook, script +from homeassistant.components import script from homeassistant.components.script import DOMAIN, EVENT_SCRIPT_STARTED from homeassistant.const import ( ATTR_ENTITY_ID, @@ -41,7 +41,7 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed, async_mock_service, mock_restore_cache -from tests.components.logbook.test_init import MockLazyEventPartialState +from tests.components.logbook.common import MockRow, mock_humanify ENTITY_ID = "script.test" @@ -377,7 +377,7 @@ async def test_async_get_descriptions_script(hass): } await async_setup_component(hass, DOMAIN, script_config) - descriptions = await hass.helpers.service.async_get_all_descriptions() + descriptions = await async_get_all_descriptions(hass) assert descriptions[DOMAIN]["test1"]["description"] == "" assert not descriptions[DOMAIN]["test1"]["fields"] @@ -526,24 +526,19 @@ async def test_logbook_humanify_script_started_event(hass): hass.config.components.add("recorder") await async_setup_component(hass, DOMAIN, {}) await async_setup_component(hass, "logbook", {}) - entity_attr_cache = logbook.EntityAttributeCache(hass) - event1, event2 = list( - logbook.humanify( - hass, - [ - MockLazyEventPartialState( - EVENT_SCRIPT_STARTED, - {ATTR_ENTITY_ID: "script.hello", ATTR_NAME: "Hello Script"}, - ), - MockLazyEventPartialState( - EVENT_SCRIPT_STARTED, - {ATTR_ENTITY_ID: "script.bye", ATTR_NAME: "Bye Script"}, - ), - ], - entity_attr_cache, - {}, - ) + event1, event2 = mock_humanify( + hass, + [ + MockRow( + EVENT_SCRIPT_STARTED, + {ATTR_ENTITY_ID: "script.hello", ATTR_NAME: "Hello Script"}, + ), + MockRow( + EVENT_SCRIPT_STARTED, + {ATTR_ENTITY_ID: "script.bye", ATTR_NAME: "Bye Script"}, + ), + ], ) assert event1["name"] == "Hello Script" diff --git a/tests/components/sensibo/__init__.py b/tests/components/sensibo/__init__.py index 8dd2ed661bc..da585f8d1e8 100644 --- a/tests/components/sensibo/__init__.py +++ b/tests/components/sensibo/__init__.py @@ -1 +1,6 @@ """Tests for the Sensibo integration.""" +from __future__ import annotations + +from homeassistant.const import CONF_API_KEY + +ENTRY_CONFIG = {CONF_API_KEY: "1234567890"} diff --git a/tests/components/sensibo/conftest.py b/tests/components/sensibo/conftest.py new file mode 100644 index 00000000000..48c9317a5cb --- /dev/null +++ b/tests/components/sensibo/conftest.py @@ -0,0 +1,71 @@ +"""Fixtures for the Sensibo integration.""" +from __future__ import annotations + +import json +from typing import Any +from unittest.mock import patch + +from pysensibo import SensiboClient +from pysensibo.model import SensiboData +import pytest + +from homeassistant.components.sensibo.const import DOMAIN +from homeassistant.config_entries import SOURCE_USER +from homeassistant.core import HomeAssistant + +from . import ENTRY_CONFIG + +from tests.common import MockConfigEntry, load_fixture +from tests.test_util.aiohttp import AiohttpClientMocker + + +@pytest.fixture +async def load_int(hass: HomeAssistant, get_data: SensiboData) -> MockConfigEntry: + """Set up the Sensibo integration in Home Assistant.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data=ENTRY_CONFIG, + entry_id="1", + unique_id="username", + version=2, + ) + + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + return config_entry + + +@pytest.fixture(name="get_data") +async def get_data_from_library( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, load_json: dict[str, Any] +) -> SensiboData: + """Retrieve data from upstream Sensibo library.""" + + client = SensiboClient("123467890", aioclient_mock.create_session(hass.loop)) + with patch("pysensibo.SensiboClient.async_get_devices", return_value=load_json): + output = await client.async_get_devices_data() + await client._session.close() # pylint: disable=protected-access + return output + + +@pytest.fixture(name="load_json", scope="session") +def load_json_from_fixture() -> SensiboData: + """Load fixture with json data and return.""" + + data_fixture = load_fixture("data.json", "sensibo") + json_data: dict[str, Any] = json.loads(data_fixture) + return json_data diff --git a/tests/components/sensibo/fixtures/data.json b/tests/components/sensibo/fixtures/data.json new file mode 100644 index 00000000000..c787ea5592c --- /dev/null +++ b/tests/components/sensibo/fixtures/data.json @@ -0,0 +1,551 @@ +{ + "status": "success", + "result": [ + { + "isGeofenceOnEnterEnabledForThisUser": false, + "isClimateReactGeofenceOnEnterEnabledForThisUser": false, + "isMotionGeofenceOnEnterEnabled": false, + "isOwner": true, + "id": "ABC999111", + "qrId": "AAAAAAAAAA", + "temperatureUnit": "C", + "room": { + "uid": "99TT99TT", + "name": "Hallway", + "icon": "Lounge" + }, + "acState": { + "timestamp": { + "time": "2022-04-30T11:23:30.019722Z", + "secondsAgo": -1 + }, + "on": true, + "mode": "heat", + "fanLevel": "high", + "targetTemperature": 25, + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on" + }, + "lastStateChange": { + "time": "2022-04-30T11:21:30Z", + "secondsAgo": 119 + }, + "lastStateChangeToOn": { + "time": "2022-04-30T11:20:28Z", + "secondsAgo": 181 + }, + "lastStateChangeToOff": { + "time": "2022-04-30T11:21:30Z", + "secondsAgo": 119 + }, + "location": { + "id": "ZZZZZZZZZZZZ", + "name": "Home", + "latLon": [58.9806976, 20.5864297], + "address": ["Sealand 99", "Some county"], + "country": "United Country", + "createTime": { + "time": "2020-03-21T15:44:15Z", + "secondsAgo": 66543240 + } + }, + "connectionStatus": { + "isAlive": true, + "lastSeen": { + "time": "2022-04-30T11:22:57.894846Z", + "secondsAgo": 32 + } + }, + "firmwareVersion": "SKY30046", + "firmwareType": "esp8266ex", + "productModel": "skyv2", + "configGroup": "stable", + "currentlyAvailableFirmwareVersion": "SKY30048", + "cleanFiltersNotificationEnabled": false, + "shouldShowFilterCleaningNotification": false, + "isGeofenceOnExitEnabled": false, + "isClimateReactGeofenceOnExitEnabled": false, + "isMotionGeofenceOnExitEnabled": false, + "serial": "1234567890", + "sensorsCalibration": { + "temperature": 0.1, + "humidity": 0.0 + }, + "motionSensors": [ + { + "configGroup": "stable", + "connectionStatus": { + "isAlive": true, + "lastSeen": { + "secondsAgo": 86, + "time": "2022-01-01T18:59:48.665878Z" + } + }, + "firmwareType": "nrf52", + "firmwareVersion": "V17", + "id": "AABBCC", + "isMainSensor": true, + "macAddress": "00:04:00:B6:00:00", + "measurements": { + "batteryVoltage": 3000, + "humidity": 57, + "motion": true, + "rssi": -72, + "temperature": 23.9, + "time": { + "secondsAgo": 86, + "time": "2022-01-01T18:59:48.665878Z" + } + }, + "parentDeviceUid": "ABC999111", + "productModel": "motion_sensor", + "qrId": "AAZZAAZZ", + "serial": "12123434" + } + ], + "tags": [], + "timer": null, + "schedules": [ + { + "id": "11", + "isEnabled": false, + "acState": { + "on": false, + "targetTemperature": 21, + "temperatureUnit": "C", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "extra": { + "scheduler": { + "climate_react": null, + "motion": null, + "on": false, + "climate_react_settings": null, + "pure_boost": null + } + }, + "horizontalSwing": "stopped", + "light": "on" + }, + "createTime": "2022-04-17T15:41:05", + "createTimeSecondsAgo": 1107745, + "recurringDays": ["Wednesday", "Thursday"], + "targetTimeLocal": "17:40", + "timezone": "Europe/Stockholm", + "podUid": "ABC999111", + "nextTime": "2022-05-04T15:40:00", + "nextTimeSecondsFromNow": 360989 + } + ], + "motionConfig": { + "enabled": true, + "onEnterACChange": false, + "onEnterACState": null, + "onEnterCRChange": true, + "onExitACChange": true, + "onExitACState": null, + "onExitCRChange": true, + "onExitDelayMinutes": 20 + }, + "filtersCleaning": { + "acOnSecondsSinceLastFiltersClean": 667991, + "filtersCleanSecondsThreshold": 1080000, + "lastFiltersCleanTime": { + "time": "2022-03-12T15:24:26Z", + "secondsAgo": 4219143 + }, + "shouldCleanFilters": false + }, + "serviceSubscriptions": [], + "roomIsOccupied": true, + "mainMeasurementsSensor": { + "configGroup": "stable", + "connectionStatus": { + "isAlive": true, + "lastSeen": { + "secondsAgo": 86, + "time": "2022-01-01T18:59:48.665878Z" + } + }, + "firmwareType": "nrf52", + "firmwareVersion": "V17", + "id": "AABBCC", + "isMainSensor": true, + "macAddress": "00:04:00:B6:00:00", + "measurements": { + "batteryVoltage": 3000, + "humidity": 32.9, + "motion": false, + "rssi": -72, + "temperature": 21.2, + "time": { + "secondsAgo": 86, + "time": "2022-01-01T18:59:48.665878Z" + } + }, + "parentDeviceUid": "ABC999111", + "productModel": "motion_sensor", + "qrId": "AAZZAAZZ", + "serial": "12123434" + }, + "pureBoostConfig": null, + "warrantyEligible": "no", + "warrantyEligibleUntil": { + "time": "2020-04-18T15:43:08Z", + "secondsAgo": 64093221 + }, + "features": ["softShowPlus", "optimusTrial"], + "runningHealthcheck": null, + "lastHealthcheck": null, + "lastACStateChange": { + "id": "11", + "time": { + "time": "2022-04-30T11:21:29Z", + "secondsAgo": 120 + }, + "status": "Success", + "acState": { + "timestamp": { + "time": "2022-04-30T11:23:30.062645Z", + "secondsAgo": -1 + }, + "on": false, + "mode": "fan", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on" + }, + "resultingAcState": { + "timestamp": { + "time": "2022-04-30T11:23:30.062688Z", + "secondsAgo": -1 + }, + "on": false, + "mode": "fan", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on" + }, + "changedProperties": ["on"], + "reason": "UserRequest", + "failureReason": null, + "resolveTime": { + "time": "2022-04-30T11:21:30Z", + "secondsAgo": 119 + }, + "causedByScheduleId": null, + "causedByScheduleType": null + }, + "homekitSupported": false, + "remoteCapabilities": { + "modes": { + "cool": { + "temperatures": { + "F": { + "isNative": false, + "values": [64, 66, 68] + }, + "C": { + "isNative": true, + "values": [18, 19, 20] + } + }, + "fanLevels": ["quiet", "low", "medium"], + "swing": ["stopped", "fixedTop", "fixedMiddleTop"], + "horizontalSwing": ["stopped", "fixedLeft", "fixedCenterLeft"], + "light": ["on", "off"] + }, + "heat": { + "temperatures": { + "F": { + "isNative": false, + "values": [63, 64, 66] + }, + "C": { + "isNative": true, + "values": [17, 18, 19, 20] + } + }, + "fanLevels": ["quiet", "low", "medium"], + "swing": ["stopped", "fixedTop", "fixedMiddleTop"], + "horizontalSwing": ["stopped", "fixedLeft", "fixedCenterLeft"], + "light": ["on", "off"] + }, + "dry": { + "temperatures": { + "F": { + "isNative": false, + "values": [64, 66, 68] + }, + "C": { + "isNative": true, + "values": [18, 19, 20] + } + }, + "swing": ["stopped", "fixedTop", "fixedMiddleTop"], + "horizontalSwing": ["stopped", "fixedLeft", "fixedCenterLeft"], + "light": ["on", "off"] + }, + "auto": { + "temperatures": { + "F": { + "isNative": false, + "values": [64, 66, 68] + }, + "C": { + "isNative": true, + "values": [18, 19, 20, 21] + } + }, + "fanLevels": ["quiet", "low", "medium"], + "swing": ["stopped", "fixedTop", "fixedMiddleTop"], + "horizontalSwing": ["stopped", "fixedLeft", "fixedCenterLeft"], + "light": ["on", "off"] + }, + "fan": { + "temperatures": {}, + "fanLevels": ["quiet", "low", "medium"], + "swing": ["stopped", "fixedTop", "fixedMiddleTop"], + "horizontalSwing": ["stopped", "fixedLeft", "fixedCenterLeft"], + "light": ["on", "off"] + } + } + }, + "remote": { + "toggle": false, + "window": false + }, + "remoteFlavor": "Curious Sea Cucumber", + "remoteAlternatives": ["_mitsubishi2_night_heat"], + "smartMode": { + "enabled": false, + "type": "temperature", + "deviceUid": "ABC999111", + "lowTemperatureThreshold": 0.0, + "highTemperatureThreshold": 27.5, + "lowTemperatureState": { + "on": true, + "targetTemperature": 21, + "temperatureUnit": "C", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on" + }, + "highTemperatureState": { + "on": true, + "targetTemperature": 21, + "temperatureUnit": "C", + "mode": "cool", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on" + }, + "lowTemperatureWebhook": null, + "highTemperatureWebhook": null + }, + "measurements": { + "time": { + "time": "2022-04-30T11:22:57.894846Z", + "secondsAgo": 32 + }, + "temperature": 21.2, + "humidity": 32.9, + "motion": true, + "roomIsOccupied": true, + "feelsLike": 21.2, + "rssi": -45 + }, + "accessPoint": { + "ssid": "Sensibo-1234567890", + "password": null + }, + "macAddress": "00:02:00:B6:00:00", + "autoOffMinutes": null, + "autoOffEnabled": false, + "antiMoldTimer": null, + "antiMoldConfig": null + }, + { + "isGeofenceOnEnterEnabledForThisUser": false, + "isClimateReactGeofenceOnEnterEnabledForThisUser": false, + "isMotionGeofenceOnEnterEnabled": false, + "isOwner": true, + "id": "AAZZAAZZ", + "qrId": "AAAAAAAABB", + "temperatureUnit": "C", + "room": { + "uid": "99TT99ZZ", + "name": "Kitchen", + "icon": "Diningroom" + }, + "acState": { + "timestamp": { + "time": "2022-04-30T11:23:30.067312Z", + "secondsAgo": -1 + }, + "on": false, + "mode": "fan", + "fanLevel": "low", + "light": "on" + }, + "lastStateChange": { + "time": "2022-04-30T11:21:41Z", + "secondsAgo": 108 + }, + "lastStateChangeToOn": { + "time": "2022-04-30T09:43:26Z", + "secondsAgo": 6003 + }, + "lastStateChangeToOff": { + "time": "2022-04-30T11:21:37Z", + "secondsAgo": 112 + }, + "location": { + "id": "ZZZZZZZZZZZZ", + "name": "Home", + "latLon": [58.9806976, 20.5864297], + "address": ["Sealand 99", "Some county"], + "country": "United Country", + "createTime": { + "time": "2020-03-21T15:44:15Z", + "secondsAgo": 66543240 + } + }, + "connectionStatus": { + "isAlive": true, + "lastSeen": { + "time": "2022-04-30T11:23:20.642798Z", + "secondsAgo": 9 + } + }, + "firmwareVersion": "PUR00111", + "firmwareType": "pure-esp32", + "productModel": "pure", + "configGroup": "stable", + "currentlyAvailableFirmwareVersion": "PUR00111", + "cleanFiltersNotificationEnabled": false, + "shouldShowFilterCleaningNotification": false, + "isGeofenceOnExitEnabled": false, + "isClimateReactGeofenceOnExitEnabled": false, + "isMotionGeofenceOnExitEnabled": false, + "serial": "0987654321", + "sensorsCalibration": { + "temperature": 0.0, + "humidity": 0.0 + }, + "motionSensors": [], + "tags": [], + "timer": null, + "schedules": [], + "motionConfig": null, + "filtersCleaning": { + "acOnSecondsSinceLastFiltersClean": 415560, + "filtersCleanSecondsThreshold": 14256000, + "lastFiltersCleanTime": { + "time": "2022-04-23T15:58:45Z", + "secondsAgo": 588284 + }, + "shouldCleanFilters": false + }, + "serviceSubscriptions": [], + "roomIsOccupied": null, + "mainMeasurementsSensor": null, + "pureBoostConfig": { + "enabled": false, + "sensitivity": "N", + "measurements_integration": true, + "ac_integration": false, + "geo_integration": false, + "prime_integration": false + }, + "warrantyEligible": "no", + "warrantyEligibleUntil": { + "time": "2022-04-10T09:58:58Z", + "secondsAgo": 1733071 + }, + "features": ["optimusTrial", "softShowPlus"], + "runningHealthcheck": null, + "lastHealthcheck": null, + "lastACStateChange": { + "id": "AA22", + "time": { + "time": "2022-04-30T11:21:37Z", + "secondsAgo": 112 + }, + "status": "Success", + "acState": { + "timestamp": { + "time": "2022-04-30T11:23:30.090144Z", + "secondsAgo": -1 + }, + "on": false, + "mode": "fan", + "fanLevel": "low", + "light": "on" + }, + "resultingAcState": { + "timestamp": { + "time": "2022-04-30T11:23:30.090185Z", + "secondsAgo": -1 + }, + "on": false, + "mode": "fan", + "fanLevel": "low", + "light": "on" + }, + "changedProperties": ["on"], + "reason": "UserRequest", + "failureReason": null, + "resolveTime": { + "time": "2022-04-30T11:21:37Z", + "secondsAgo": 112 + }, + "causedByScheduleId": null, + "causedByScheduleType": null + }, + "homekitSupported": true, + "remoteCapabilities": { + "modes": { + "fan": { + "temperatures": {}, + "fanLevels": ["low", "high"], + "light": ["on", "dim", "off"] + } + } + }, + "remote": { + "toggle": false, + "window": false + }, + "remoteFlavor": "Eccentric Eagle", + "remoteAlternatives": [], + "smartMode": null, + "measurements": { + "time": { + "time": "2022-04-30T11:23:20.642798Z", + "secondsAgo": 9 + }, + "rssi": -58, + "pm25": 1, + "motion": false, + "roomIsOccupied": null + }, + "accessPoint": { + "ssid": "Sensibo-09876", + "password": null + }, + "macAddress": "00:01:00:01:00:01", + "autoOffMinutes": null, + "autoOffEnabled": false, + "antiMoldTimer": null, + "antiMoldConfig": null + } + ] +} diff --git a/tests/components/sensibo/test_binary_sensor.py b/tests/components/sensibo/test_binary_sensor.py new file mode 100644 index 00000000000..3a84dc99ca5 --- /dev/null +++ b/tests/components/sensibo/test_binary_sensor.py @@ -0,0 +1,54 @@ +"""The test for the sensibo binary sensor platform.""" +from __future__ import annotations + +from datetime import timedelta +from unittest.mock import patch + +from pysensibo.model import SensiboData +from pytest import MonkeyPatch + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.util import dt + +from tests.common import async_fire_time_changed + + +async def test_binary_sensor( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo binary sensor.""" + + state1 = hass.states.get("binary_sensor.hallway_motion_sensor_alive") + state2 = hass.states.get("binary_sensor.hallway_motion_sensor_main_sensor") + state3 = hass.states.get("binary_sensor.hallway_motion_sensor_motion") + state4 = hass.states.get("binary_sensor.hallway_room_occupied") + assert state1.state == "on" + assert state2.state == "on" + assert state3.state == "on" + assert state4.state == "on" + + monkeypatch.setattr( + get_data.parsed["ABC999111"].motion_sensors["AABBCC"], "alive", False + ) + monkeypatch.setattr( + get_data.parsed["ABC999111"].motion_sensors["AABBCC"], "motion", False + ) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("binary_sensor.hallway_motion_sensor_alive") + state3 = hass.states.get("binary_sensor.hallway_motion_sensor_motion") + assert state1.state == "off" + assert state3.state == "off" diff --git a/tests/components/sensibo/test_climate.py b/tests/components/sensibo/test_climate.py new file mode 100644 index 00000000000..0b6c043240c --- /dev/null +++ b/tests/components/sensibo/test_climate.py @@ -0,0 +1,626 @@ +"""The test for the sensibo binary sensor platform.""" +from __future__ import annotations + +from datetime import timedelta +from unittest.mock import patch + +from pysensibo.model import SensiboData +import pytest +from voluptuous import MultipleInvalid + +from homeassistant.components.climate.const import ( + ATTR_FAN_MODE, + ATTR_HVAC_MODE, + ATTR_SWING_MODE, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + DOMAIN as CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_SWING_MODE, + SERVICE_SET_TEMPERATURE, +) +from homeassistant.components.sensibo.climate import SERVICE_ASSUME_STATE +from homeassistant.components.sensibo.const import DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_STATE, + ATTR_TEMPERATURE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.util import dt + +from tests.common import async_fire_time_changed + + +async def test_climate( + hass: HomeAssistant, load_int: ConfigEntry, get_data: SensiboData +) -> None: + """Test the Sensibo climate.""" + + state1 = hass.states.get("climate.hallway") + state2 = hass.states.get("climate.kitchen") + + assert state1.state == "heat" + assert state1.attributes == { + "hvac_modes": [ + "cool", + "heat", + "dry", + "heat_cool", + "fan_only", + "off", + ], + "min_temp": 17, + "max_temp": 20, + "target_temp_step": 1, + "fan_modes": ["quiet", "low", "medium"], + "swing_modes": [ + "stopped", + "fixedTop", + "fixedMiddleTop", + ], + "current_temperature": 21.2, + "temperature": 25, + "current_humidity": 32.9, + "fan_mode": "high", + "swing_mode": "stopped", + "friendly_name": "Hallway", + "supported_features": 41, + } + + assert state2.state == "off" + + +async def test_climate_fan( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: pytest.MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo climate fan service.""" + + state1 = hass.states.get("climate.hallway") + assert state1.attributes["fan_mode"] == "high" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_FAN_MODE: "low"}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.attributes["fan_mode"] == "low" + + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "active_features", + [ + "timestamp", + "on", + "mode", + "swing", + "targetTemperature", + "horizontalSwing", + "light", + ], + ) + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + ): + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_FAN_MODE: "low"}, + blocking=True, + ) + await hass.async_block_till_done() + + state3 = hass.states.get("climate.hallway") + assert state3.attributes["fan_mode"] == "low" + + +async def test_climate_swing( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: pytest.MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo climate swing service.""" + + state1 = hass.states.get("climate.hallway") + assert state1.attributes["swing_mode"] == "stopped" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_SWING_MODE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_SWING_MODE: "fixedTop"}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.attributes["swing_mode"] == "fixedTop" + + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "active_features", + [ + "timestamp", + "on", + "mode", + "targetTemperature", + "horizontalSwing", + "light", + ], + ) + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + ): + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_SWING_MODE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_SWING_MODE: "fixedTop"}, + blocking=True, + ) + await hass.async_block_till_done() + + state3 = hass.states.get("climate.hallway") + assert state3.attributes["swing_mode"] == "fixedTop" + + +async def test_climate_temperatures( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: pytest.MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo climate temperature service.""" + + state1 = hass.states.get("climate.hallway") + assert state1.attributes["temperature"] == 25 + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_TEMPERATURE: 20}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.attributes["temperature"] == 20 + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_TEMPERATURE: 15}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.attributes["temperature"] == 17 + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + with pytest.raises(ValueError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_TEMPERATURE: 18.5}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.attributes["temperature"] == 17 + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_TEMPERATURE: 24}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.attributes["temperature"] == 20 + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_TEMPERATURE: 20}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.attributes["temperature"] == 20 + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + with pytest.raises(MultipleInvalid): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: state1.entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.attributes["temperature"] == 20 + + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "active_features", + [ + "timestamp", + "on", + "mode", + "swing", + "horizontalSwing", + "light", + ], + ) + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_TEMPERATURE: 20}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.attributes["temperature"] == 20 + + +async def test_climate_temperature_is_none( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: pytest.MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo climate temperature service no temperature provided.""" + + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "active_features", + [ + "timestamp", + "on", + "mode", + "fanLevel", + "targetTemperature", + "swing", + "horizontalSwing", + "light", + ], + ) + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "target_temp", + 25, + ) + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("climate.hallway") + assert state1.attributes["temperature"] == 25 + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + ): + with pytest.raises(ValueError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: state1.entity_id, + ATTR_TARGET_TEMP_HIGH: 30, + ATTR_TARGET_TEMP_LOW: 20, + }, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.attributes["temperature"] == 25 + + +async def test_climate_hvac_mode( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: pytest.MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo climate hvac mode service.""" + + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "active_features", + [ + "timestamp", + "on", + "mode", + "fanLevel", + "targetTemperature", + "swing", + "horizontalSwing", + "light", + ], + ) + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("climate.hallway") + assert state1.state == "heat" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_HVAC_MODE: "off"}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.state == "off" + + monkeypatch.setattr(get_data.parsed["ABC999111"], "device_on", False) + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_HVAC_MODE: "heat"}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.state == "heat" + + +async def test_climate_on_off( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: pytest.MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo climate on/off service.""" + + monkeypatch.setattr(get_data.parsed["ABC999111"], "hvac_mode", "heat") + monkeypatch.setattr(get_data.parsed["ABC999111"], "device_on", True) + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("climate.hallway") + assert state1.state == "heat" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: state1.entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.state == "off" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: state1.entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.state == "heat" + + +async def test_climate_service_failed( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: pytest.MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo climate service failed.""" + + monkeypatch.setattr(get_data.parsed["ABC999111"], "hvac_mode", "heat") + monkeypatch.setattr(get_data.parsed["ABC999111"], "device_on", True) + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("climate.hallway") + assert state1.state == "heat" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Error", "failureReason": "Did not work"}}, + ): + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: state1.entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.state == "heat" + + +async def test_climate_assumed_state( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: pytest.MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo climate assumed state service.""" + + monkeypatch.setattr(get_data.parsed["ABC999111"], "hvac_mode", "heat") + monkeypatch.setattr(get_data.parsed["ABC999111"], "device_on", True) + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("climate.hallway") + assert state1.state == "heat" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + DOMAIN, + SERVICE_ASSUME_STATE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_STATE: "off"}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("climate.hallway") + assert state2.state == "off" diff --git a/tests/components/sensibo/test_coordinator.py b/tests/components/sensibo/test_coordinator.py new file mode 100644 index 00000000000..f0b9d60f112 --- /dev/null +++ b/tests/components/sensibo/test_coordinator.py @@ -0,0 +1,90 @@ +"""The test for the sensibo coordinator.""" +from __future__ import annotations + +from datetime import timedelta +from unittest.mock import patch + +from pysensibo.exceptions import AuthenticationError, SensiboError +from pysensibo.model import SensiboData +import pytest + +from homeassistant.components.sensibo.const import DOMAIN +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.core import HomeAssistant +from homeassistant.util import dt + +from . import ENTRY_CONFIG + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_coordinator( + hass: HomeAssistant, monkeypatch: pytest.MonkeyPatch, get_data: SensiboData +) -> None: + """Test the Sensibo coordinator with errors.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data=ENTRY_CONFIG, + entry_id="1", + unique_id="username", + version=2, + ) + + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + ) as mock_data, patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ): + monkeypatch.setattr(get_data.parsed["ABC999111"], "hvac_mode", "heat") + monkeypatch.setattr(get_data.parsed["ABC999111"], "device_on", True) + mock_data.return_value = get_data + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + mock_data.assert_called_once() + state = hass.states.get("climate.hallway") + assert state.state == "heat" + mock_data.reset_mock() + + mock_data.side_effect = SensiboError("info") + async_fire_time_changed(hass, dt.utcnow() + timedelta(minutes=1)) + await hass.async_block_till_done() + mock_data.assert_called_once() + state = hass.states.get("climate.hallway") + assert state.state == STATE_UNAVAILABLE + mock_data.reset_mock() + + mock_data.return_value = SensiboData(raw={}, parsed={}) + mock_data.side_effect = None + async_fire_time_changed(hass, dt.utcnow() + timedelta(minutes=3)) + await hass.async_block_till_done() + mock_data.assert_called_once() + state = hass.states.get("climate.hallway") + assert state.state == STATE_UNAVAILABLE + mock_data.reset_mock() + + monkeypatch.setattr(get_data.parsed["ABC999111"], "hvac_mode", "heat") + monkeypatch.setattr(get_data.parsed["ABC999111"], "device_on", True) + + mock_data.return_value = get_data + mock_data.side_effect = None + async_fire_time_changed(hass, dt.utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + mock_data.assert_called_once() + state = hass.states.get("climate.hallway") + assert state.state == "heat" + mock_data.reset_mock() + + mock_data.side_effect = AuthenticationError("info") + async_fire_time_changed(hass, dt.utcnow() + timedelta(minutes=7)) + await hass.async_block_till_done() + mock_data.assert_called_once() + state = hass.states.get("climate.hallway") + assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/sensibo/test_diagnostics.py b/tests/components/sensibo/test_diagnostics.py new file mode 100644 index 00000000000..636fefa5054 --- /dev/null +++ b/tests/components/sensibo/test_diagnostics.py @@ -0,0 +1,26 @@ +"""Test Sensibo diagnostics.""" +from __future__ import annotations + +import aiohttp + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics( + hass: HomeAssistant, hass_client: aiohttp.client, load_int: ConfigEntry +): + """Test generating diagnostics for a config entry.""" + entry = load_int + + diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) + + assert diag["status"] == "success" + for device in diag["result"]: + assert device["id"] == "**REDACTED**" + assert device["qrId"] == "**REDACTED**" + assert device["macAddress"] == "**REDACTED**" + assert device["location"] == "**REDACTED**" + assert device["productModel"] in ["skyv2", "pure"] diff --git a/tests/components/sensibo/test_entity.py b/tests/components/sensibo/test_entity.py new file mode 100644 index 00000000000..bf512f9f220 --- /dev/null +++ b/tests/components/sensibo/test_entity.py @@ -0,0 +1,119 @@ +"""The test for the sensibo entity.""" +from __future__ import annotations + +from unittest.mock import AsyncMock, patch + +from pysensibo.model import SensiboData +import pytest + +from homeassistant.components.climate.const import ( + ATTR_FAN_MODE, + DOMAIN as CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, +) +from homeassistant.components.number.const import ( + ATTR_VALUE, + DOMAIN as NUMBER_DOMAIN, + SERVICE_SET_VALUE, +) +from homeassistant.components.sensibo.const import SENSIBO_ERRORS +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import device_registry as dr, entity_registry as er + + +async def test_entity( + hass: HomeAssistant, load_int: ConfigEntry, get_data: SensiboData +) -> None: + """Test the Sensibo climate.""" + + state1 = hass.states.get("climate.hallway") + assert state1 + + dr_reg = dr.async_get(hass) + dr_entries = dr.async_entries_for_config_entry(dr_reg, load_int.entry_id) + dr_entry: dr.DeviceEntry + for dr_entry in dr_entries: + if dr_entry.name == "Hallway": + assert dr_entry.identifiers == {("sensibo", "ABC999111")} + device_id = dr_entry.id + + er_reg = er.async_get(hass) + er_entries = er.async_entries_for_device( + er_reg, device_id, include_disabled_entities=True + ) + er_entry: er.RegistryEntry + for er_entry in er_entries: + if er_entry.name == "Hallway": + assert er_entry.unique_id == "Hallway" + + +@pytest.mark.parametrize("p_error", SENSIBO_ERRORS) +async def test_entity_send_command( + hass: HomeAssistant, + p_error: Exception, + load_int: ConfigEntry, + get_data: SensiboData, +) -> None: + """Test the Sensibo send command with error.""" + + state = hass.states.get("climate.hallway") + assert state + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: state.entity_id, ATTR_FAN_MODE: "low"}, + blocking=True, + ) + await hass.async_block_till_done() + + state = hass.states.get("climate.hallway") + assert state.attributes["fan_mode"] == "low" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + side_effect=p_error, + ): + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: state.entity_id, ATTR_FAN_MODE: "low"}, + blocking=True, + ) + + state = hass.states.get("climate.hallway") + assert state.attributes["fan_mode"] == "low" + + +async def test_entity_send_command_calibration( + hass: HomeAssistant, + entity_registry_enabled_by_default: AsyncMock, + load_int: ConfigEntry, + get_data: SensiboData, +) -> None: + """Test the Sensibo send command for calibration.""" + + state = hass.states.get("number.hallway_temperature_calibration") + assert state.state == "0.1" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_calibration", + return_value={"status": "success"}, + ): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: state.entity_id, ATTR_VALUE: 0.2}, + blocking=True, + ) + + state = hass.states.get("number.hallway_temperature_calibration") + assert state.state == "0.2" diff --git a/tests/components/sensibo/test_init.py b/tests/components/sensibo/test_init.py new file mode 100644 index 00000000000..505816e3f41 --- /dev/null +++ b/tests/components/sensibo/test_init.py @@ -0,0 +1,133 @@ +"""Test for Sensibo component Init.""" +from __future__ import annotations + +from unittest.mock import patch + +from pysensibo.model import SensiboData + +from homeassistant import config_entries +from homeassistant.components.sensibo.const import DOMAIN +from homeassistant.components.sensibo.util import NoUsernameError +from homeassistant.config_entries import SOURCE_USER +from homeassistant.core import HomeAssistant + +from . import ENTRY_CONFIG + +from tests.common import MockConfigEntry + + +async def test_setup_entry(hass: HomeAssistant, get_data: SensiboData) -> None: + """Test setup entry.""" + entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data=ENTRY_CONFIG, + entry_id="1", + unique_id="12", + version=2, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == config_entries.ConfigEntryState.LOADED + + +async def test_migrate_entry(hass: HomeAssistant, get_data: SensiboData) -> None: + """Test migrate entry unique id.""" + entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data=ENTRY_CONFIG, + entry_id="1", + unique_id="12", + version=1, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == config_entries.ConfigEntryState.LOADED + assert entry.version == 2 + assert entry.unique_id == "username" + + +async def test_migrate_entry_fails(hass: HomeAssistant, get_data: SensiboData) -> None: + """Test migrate entry unique id.""" + entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data=ENTRY_CONFIG, + entry_id="1", + unique_id="12", + version=1, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + side_effect=NoUsernameError("No username returned"), + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == config_entries.ConfigEntryState.MIGRATION_ERROR + assert entry.version == 1 + assert entry.unique_id == "12" + + +async def test_unload_entry(hass: HomeAssistant, get_data: SensiboData) -> None: + """Test unload an entry.""" + entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data=ENTRY_CONFIG, + entry_id="1", + unique_id="12", + version="2", + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == config_entries.ConfigEntryState.LOADED + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert entry.state is config_entries.ConfigEntryState.NOT_LOADED diff --git a/tests/components/sensibo/test_number.py b/tests/components/sensibo/test_number.py new file mode 100644 index 00000000000..ed60d6653e9 --- /dev/null +++ b/tests/components/sensibo/test_number.py @@ -0,0 +1,101 @@ +"""The test for the sensibo number platform.""" +from __future__ import annotations + +from datetime import timedelta +from unittest.mock import AsyncMock, patch + +from pysensibo.model import SensiboData +import pytest +from pytest import MonkeyPatch + +from homeassistant.components.number.const import ( + ATTR_VALUE, + DOMAIN as NUMBER_DOMAIN, + SERVICE_SET_VALUE, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.util import dt + +from tests.common import async_fire_time_changed + + +async def test_number( + hass: HomeAssistant, + entity_registry_enabled_by_default: AsyncMock, + load_int: ConfigEntry, + monkeypatch: MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo number.""" + + state1 = hass.states.get("number.hallway_temperature_calibration") + state2 = hass.states.get("number.hallway_humidity_calibration") + assert state1.state == "0.1" + assert state2.state == "0.0" + + monkeypatch.setattr(get_data.parsed["ABC999111"], "calibration_temp", 0.2) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("number.hallway_temperature_calibration") + assert state1.state == "0.2" + + +async def test_number_set_value( + hass: HomeAssistant, + entity_registry_enabled_by_default: AsyncMock, + load_int: ConfigEntry, + get_data: SensiboData, +) -> None: + """Test the Sensibo number service.""" + + state1 = hass.states.get("number.hallway_temperature_calibration") + assert state1.state == "0.1" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_calibration", + return_value={"status": "failure"}, + ): + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_VALUE: "0.2"}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("number.hallway_temperature_calibration") + assert state2.state == "0.1" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_calibration", + return_value={"status": "success"}, + ): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_VALUE: "0.2"}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("number.hallway_temperature_calibration") + assert state2.state == "0.2" diff --git a/tests/components/sensibo/test_select.py b/tests/components/sensibo/test_select.py new file mode 100644 index 00000000000..ce361e224c9 --- /dev/null +++ b/tests/components/sensibo/test_select.py @@ -0,0 +1,163 @@ +"""The test for the sensibo select platform.""" +from __future__ import annotations + +from datetime import timedelta +from unittest.mock import patch + +from pysensibo.model import SensiboData +import pytest +from pytest import MonkeyPatch + +from homeassistant.components.select.const import ( + ATTR_OPTION, + DOMAIN as SELECT_DOMAIN, + SERVICE_SELECT_OPTION, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.util import dt + +from tests.common import async_fire_time_changed + + +async def test_select( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo select.""" + + state1 = hass.states.get("select.hallway_horizontal_swing") + assert state1.state == "stopped" + + monkeypatch.setattr( + get_data.parsed["ABC999111"], "horizontal_swing_mode", "fixedLeft" + ) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("select.hallway_horizontal_swing") + assert state1.state == "fixedLeft" + + +async def test_select_set_option( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo select service.""" + + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "active_features", + [ + "timestamp", + "on", + "mode", + "targetTemperature", + "light", + ], + ) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("select.hallway_horizontal_swing") + assert state1.state == "stopped" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "failed"}}, + ): + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_OPTION: "fixedLeft"}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("select.hallway_horizontal_swing") + assert state2.state == "stopped" + + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "active_features", + [ + "timestamp", + "on", + "mode", + "targetTemperature", + "horizontalSwing", + "light", + ], + ) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Failed", "failureReason": "No connection"}}, + ): + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_OPTION: "fixedLeft"}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("select.hallway_horizontal_swing") + assert state2.state == "stopped" + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: state1.entity_id, ATTR_OPTION: "fixedLeft"}, + blocking=True, + ) + await hass.async_block_till_done() + + state2 = hass.states.get("select.hallway_horizontal_swing") + assert state2.state == "fixedLeft" diff --git a/tests/components/sensibo/test_sensor.py b/tests/components/sensibo/test_sensor.py new file mode 100644 index 00000000000..413b62f6b9f --- /dev/null +++ b/tests/components/sensibo/test_sensor.py @@ -0,0 +1,50 @@ +"""The test for the sensibo select platform.""" +from __future__ import annotations + +from datetime import timedelta +from unittest.mock import patch + +from pysensibo.model import SensiboData +from pytest import MonkeyPatch + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.util import dt + +from tests.common import async_fire_time_changed + + +async def test_sensor( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo sensor.""" + + state1 = hass.states.get("sensor.hallway_motion_sensor_battery_voltage") + state2 = hass.states.get("sensor.kitchen_pm2_5") + assert state1.state == "3000" + assert state2.state == "1" + assert state2.attributes == { + "state_class": "measurement", + "unit_of_measurement": "µg/m³", + "device_class": "pm25", + "icon": "mdi:air-filter", + "friendly_name": "Kitchen PM2.5", + } + + monkeypatch.setattr(get_data.parsed["AAZZAAZZ"], "pm25", 2) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("sensor.kitchen_pm2_5") + assert state1.state == "2" diff --git a/tests/components/sensibo/test_update.py b/tests/components/sensibo/test_update.py new file mode 100644 index 00000000000..11b019d111e --- /dev/null +++ b/tests/components/sensibo/test_update.py @@ -0,0 +1,47 @@ +"""The test for the sensibo update platform.""" +from __future__ import annotations + +from datetime import timedelta +from unittest.mock import patch + +from pysensibo.model import SensiboData +from pytest import MonkeyPatch + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import HomeAssistant +from homeassistant.util import dt + +from tests.common import async_fire_time_changed + + +async def test_select( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo update.""" + + state1 = hass.states.get("update.hallway_update_available") + state2 = hass.states.get("update.kitchen_update_available") + assert state1.state == STATE_ON + assert state1.attributes["installed_version"] == "SKY30046" + assert state1.attributes["latest_version"] == "SKY30048" + assert state1.attributes["title"] == "skyv2" + assert state2.state == STATE_OFF + + monkeypatch.setattr(get_data.parsed["ABC999111"], "fw_ver", "SKY30048") + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("update.hallway_update_available") + assert state1.state == STATE_OFF diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index 58a991c5f53..66ed0032201 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -1,7 +1,6 @@ """The tests for sensor recorder platform.""" # pylint: disable=protected-access,invalid-name from datetime import timedelta -from functools import partial import math from statistics import mean from unittest.mock import patch @@ -30,6 +29,7 @@ from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM from tests.components.recorder.common import ( async_recorder_block_till_done, async_wait_recording_done, + do_adhoc_statistics, wait_recording_done, ) @@ -101,7 +101,6 @@ def test_compile_hourly_statistics( """Test compiling hourly statistics.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -113,7 +112,7 @@ def test_compile_hourly_statistics( hist = history.get_significant_states(hass, zero, four) assert dict(states) == dict(hist) - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -157,7 +156,6 @@ def test_compile_hourly_statistics_purged_state_changes( """Test compiling hourly statistics.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -172,14 +170,16 @@ def test_compile_hourly_statistics_purged_state_changes( mean = min = max = float(hist["sensor.test1"][-1].state) # Purge all states from the database - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=four): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=four + ): hass.services.call("recorder", "purge", {"keep_days": 0}) hass.block_till_done() wait_recording_done(hass) hist = history.get_significant_states(hass, zero, four) assert not hist - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -216,7 +216,6 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes """Test compiling hourly statistics for unsupported sensor.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added four, states = record_states(hass, zero, "sensor.test1", attributes) @@ -248,7 +247,7 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes hist = history.get_significant_states(hass, zero, four) assert dict(states) == dict(hist) - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -359,7 +358,6 @@ async def test_compile_hourly_sum_statistics_amount( period2_end = period0 + timedelta(minutes=15) client = await hass_ws_client() hass.config.units = units - recorder = hass.data[DATA_INSTANCE] await async_setup_component(hass, "sensor", {}) # Wait for the sensor recorder platform to be added await async_recorder_block_till_done(hass) @@ -380,17 +378,11 @@ async def test_compile_hourly_sum_statistics_amount( ) assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"] - await hass.async_add_executor_job( - partial(recorder.do_adhoc_statistics, start=period0) - ) + do_adhoc_statistics(hass, start=period0) await async_wait_recording_done(hass) - await hass.async_add_executor_job( - partial(recorder.do_adhoc_statistics, start=period1) - ) + do_adhoc_statistics(hass, start=period1) await async_wait_recording_done(hass) - await hass.async_add_executor_job( - partial(recorder.do_adhoc_statistics, start=period2) - ) + do_adhoc_statistics(hass, start=period2) await async_wait_recording_done(hass) statistic_ids = await hass.async_add_executor_job(list_statistic_ids, hass) assert statistic_ids == [ @@ -523,7 +515,6 @@ def test_compile_hourly_sum_statistics_amount_reset_every_state_change( """Test compiling hourly statistics.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -571,8 +562,8 @@ def test_compile_hourly_sum_statistics_amount_reset_every_state_change( ) assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"] - recorder.do_adhoc_statistics(start=zero) - recorder.do_adhoc_statistics(start=zero + timedelta(minutes=5)) + do_adhoc_statistics(hass, start=zero) + do_adhoc_statistics(hass, start=zero + timedelta(minutes=5)) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -630,7 +621,6 @@ def test_compile_hourly_sum_statistics_amount_invalid_last_reset( """Test compiling hourly statistics.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -664,7 +654,7 @@ def test_compile_hourly_sum_statistics_amount_invalid_last_reset( ) assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"] - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -710,7 +700,6 @@ def test_compile_hourly_sum_statistics_nan_inf_state( """Test compiling hourly statistics with nan and inf states.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -740,7 +729,7 @@ def test_compile_hourly_sum_statistics_nan_inf_state( ) assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"] - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -815,7 +804,6 @@ def test_compile_hourly_sum_statistics_negative_state( zero = dt_util.utcnow() hass = hass_recorder() hass.data.pop(loader.DATA_CUSTOM_COMPONENTS) - recorder = hass.data[DATA_INSTANCE] platform = getattr(hass.components, "test.sensor") platform.init(empty=True) @@ -853,7 +841,7 @@ def test_compile_hourly_sum_statistics_negative_state( ) assert dict(states)[entity_id] == dict(hist)[entity_id] - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert { @@ -909,7 +897,6 @@ def test_compile_hourly_sum_statistics_total_no_reset( period1_end = period2 = period0 + timedelta(minutes=10) period2_end = period0 + timedelta(minutes=15) hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -928,11 +915,11 @@ def test_compile_hourly_sum_statistics_total_no_reset( ) assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"] - recorder.do_adhoc_statistics(start=period0) + do_adhoc_statistics(hass, start=period0) wait_recording_done(hass) - recorder.do_adhoc_statistics(start=period1) + do_adhoc_statistics(hass, start=period1) wait_recording_done(hass) - recorder.do_adhoc_statistics(start=period2) + do_adhoc_statistics(hass, start=period2) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -1004,7 +991,6 @@ def test_compile_hourly_sum_statistics_total_increasing( period1_end = period2 = period0 + timedelta(minutes=10) period2_end = period0 + timedelta(minutes=15) hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -1023,11 +1009,11 @@ def test_compile_hourly_sum_statistics_total_increasing( ) assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"] - recorder.do_adhoc_statistics(start=period0) + do_adhoc_statistics(hass, start=period0) wait_recording_done(hass) - recorder.do_adhoc_statistics(start=period1) + do_adhoc_statistics(hass, start=period1) wait_recording_done(hass) - recorder.do_adhoc_statistics(start=period2) + do_adhoc_statistics(hass, start=period2) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -1097,7 +1083,6 @@ def test_compile_hourly_sum_statistics_total_increasing_small_dip( period1_end = period2 = period0 + timedelta(minutes=10) period2_end = period0 + timedelta(minutes=15) hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -1116,15 +1101,15 @@ def test_compile_hourly_sum_statistics_total_increasing_small_dip( ) assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"] - recorder.do_adhoc_statistics(start=period0) + do_adhoc_statistics(hass, start=period0) wait_recording_done(hass) - recorder.do_adhoc_statistics(start=period1) + do_adhoc_statistics(hass, start=period1) wait_recording_done(hass) assert ( "Entity sensor.test1 has state class total_increasing, but its state is not " "strictly increasing." ) not in caplog.text - recorder.do_adhoc_statistics(start=period2) + do_adhoc_statistics(hass, start=period2) wait_recording_done(hass) state = states["sensor.test1"][6].state previous_state = float(states["sensor.test1"][5].state) @@ -1194,7 +1179,6 @@ def test_compile_hourly_energy_statistics_unsupported(hass_recorder, caplog): period1_end = period2 = period0 + timedelta(minutes=10) period2_end = period0 + timedelta(minutes=15) hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added sns1_attr = { @@ -1223,11 +1207,11 @@ def test_compile_hourly_energy_statistics_unsupported(hass_recorder, caplog): ) assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"] - recorder.do_adhoc_statistics(start=period0) + do_adhoc_statistics(hass, start=period0) wait_recording_done(hass) - recorder.do_adhoc_statistics(start=period1) + do_adhoc_statistics(hass, start=period1) wait_recording_done(hass) - recorder.do_adhoc_statistics(start=period2) + do_adhoc_statistics(hass, start=period2) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -1288,7 +1272,6 @@ def test_compile_hourly_energy_statistics_multiple(hass_recorder, caplog): period1_end = period2 = period0 + timedelta(minutes=10) period2_end = period0 + timedelta(minutes=15) hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added sns1_attr = {**ENERGY_SENSOR_ATTRIBUTES, "last_reset": None} @@ -1315,11 +1298,11 @@ def test_compile_hourly_energy_statistics_multiple(hass_recorder, caplog): ) assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"] - recorder.do_adhoc_statistics(start=period0) + do_adhoc_statistics(hass, start=period0) wait_recording_done(hass) - recorder.do_adhoc_statistics(start=period1) + do_adhoc_statistics(hass, start=period1) wait_recording_done(hass) - recorder.do_adhoc_statistics(start=period2) + do_adhoc_statistics(hass, start=period2) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -1481,7 +1464,6 @@ def test_compile_hourly_statistics_unchanged( """Test compiling hourly statistics, with no changes during the hour.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -1493,7 +1475,7 @@ def test_compile_hourly_statistics_unchanged( hist = history.get_significant_states(hass, zero, four) assert dict(states) == dict(hist) - recorder.do_adhoc_statistics(start=four) + do_adhoc_statistics(hass, start=four) wait_recording_done(hass) stats = statistics_during_period(hass, four, period="5minute") assert stats == { @@ -1518,7 +1500,6 @@ def test_compile_hourly_statistics_partially_unavailable(hass_recorder, caplog): """Test compiling hourly statistics, with the sensor being partially unavailable.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added four, states = record_states_partially_unavailable( @@ -1527,7 +1508,7 @@ def test_compile_hourly_statistics_partially_unavailable(hass_recorder, caplog): hist = history.get_significant_states(hass, zero, four) assert dict(states) == dict(hist) - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) stats = statistics_during_period(hass, zero, period="5minute") assert stats == { @@ -1570,7 +1551,6 @@ def test_compile_hourly_statistics_unavailable( """Test compiling hourly statistics, with the sensor being unavailable.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -1586,7 +1566,7 @@ def test_compile_hourly_statistics_unavailable( hist = history.get_significant_states(hass, zero, four) assert dict(states) == dict(hist) - recorder.do_adhoc_statistics(start=four) + do_adhoc_statistics(hass, start=four) wait_recording_done(hass) stats = statistics_during_period(hass, four, period="5minute") assert stats == { @@ -1611,14 +1591,13 @@ def test_compile_hourly_statistics_fails(hass_recorder, caplog): """Test compiling hourly statistics throws.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added with patch( "homeassistant.components.sensor.recorder.compile_statistics", side_effect=Exception, ): - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) assert "Error while processing event StatisticsTask" in caplog.text @@ -1735,7 +1714,6 @@ def test_compile_hourly_statistics_changing_units_1( """Test compiling hourly statistics where units change from one hour to the next.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -1756,7 +1734,7 @@ def test_compile_hourly_statistics_changing_units_1( hist = history.get_significant_states(hass, zero, four) assert dict(states) == dict(hist) - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) assert "does not match the unit of already compiled" not in caplog.text statistic_ids = list_statistic_ids(hass) @@ -1787,7 +1765,7 @@ def test_compile_hourly_statistics_changing_units_1( ] } - recorder.do_adhoc_statistics(start=zero + timedelta(minutes=10)) + do_adhoc_statistics(hass, start=zero + timedelta(minutes=10)) wait_recording_done(hass) assert ( "The unit of sensor.test1 (cats) does not match the unit of already compiled " @@ -1838,7 +1816,6 @@ def test_compile_hourly_statistics_changing_units_2( """Test compiling hourly statistics where units change during an hour.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -1855,7 +1832,7 @@ def test_compile_hourly_statistics_changing_units_2( hist = history.get_significant_states(hass, zero, four) assert dict(states) == dict(hist) - recorder.do_adhoc_statistics(start=zero + timedelta(seconds=30 * 5)) + do_adhoc_statistics(hass, start=zero + timedelta(seconds=30 * 5)) wait_recording_done(hass) assert "The unit of sensor.test1 is changing" in caplog.text assert "and matches the unit of already compiled statistics" not in caplog.text @@ -1891,7 +1868,6 @@ def test_compile_hourly_statistics_changing_units_3( """Test compiling hourly statistics where units change from one hour to the next.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -1912,7 +1888,7 @@ def test_compile_hourly_statistics_changing_units_3( hist = history.get_significant_states(hass, zero, four) assert dict(states) == dict(hist) - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) assert "does not match the unit of already compiled" not in caplog.text statistic_ids = list_statistic_ids(hass) @@ -1943,7 +1919,7 @@ def test_compile_hourly_statistics_changing_units_3( ] } - recorder.do_adhoc_statistics(start=zero + timedelta(minutes=10)) + do_adhoc_statistics(hass, start=zero + timedelta(minutes=10)) wait_recording_done(hass) assert "The unit of sensor.test1 is changing" in caplog.text assert f"matches the unit of already compiled statistics ({unit})" in caplog.text @@ -1989,7 +1965,6 @@ def test_compile_hourly_statistics_changing_device_class_1( """Test compiling hourly statistics where device class changes from one hour to the next.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added @@ -2000,7 +1975,7 @@ def test_compile_hourly_statistics_changing_device_class_1( } four, states = record_states(hass, zero, "sensor.test1", attributes) - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) assert "does not match the unit of already compiled" not in caplog.text statistic_ids = list_statistic_ids(hass) @@ -2045,7 +2020,7 @@ def test_compile_hourly_statistics_changing_device_class_1( assert dict(states) == dict(hist) # Run statistics again, we get a warning, and no additional statistics is generated - recorder.do_adhoc_statistics(start=zero + timedelta(minutes=10)) + do_adhoc_statistics(hass, start=zero + timedelta(minutes=10)) wait_recording_done(hass) assert ( f"The normalized unit of sensor.test1 ({statistic_unit}) does not match the " @@ -2093,7 +2068,6 @@ def test_compile_hourly_statistics_changing_device_class_2( """Test compiling hourly statistics where device class changes from one hour to the next.""" zero = dt_util.utcnow() hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added @@ -2105,7 +2079,7 @@ def test_compile_hourly_statistics_changing_device_class_2( } four, states = record_states(hass, zero, "sensor.test1", attributes) - recorder.do_adhoc_statistics(start=zero) + do_adhoc_statistics(hass, start=zero) wait_recording_done(hass) assert "does not match the unit of already compiled" not in caplog.text statistic_ids = list_statistic_ids(hass) @@ -2150,7 +2124,7 @@ def test_compile_hourly_statistics_changing_device_class_2( assert dict(states) == dict(hist) # Run statistics again, we get a warning, and no additional statistics is generated - recorder.do_adhoc_statistics(start=zero + timedelta(minutes=10)) + do_adhoc_statistics(hass, start=zero + timedelta(minutes=10)) wait_recording_done(hass) assert ( f"The unit of sensor.test1 ({state_unit}) does not match the " @@ -2200,7 +2174,6 @@ def test_compile_hourly_statistics_changing_statistics( period0_end = period1 = period0 + timedelta(minutes=5) period1_end = period0 + timedelta(minutes=10) hass = hass_recorder() - recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes_1 = { @@ -2214,7 +2187,7 @@ def test_compile_hourly_statistics_changing_statistics( "unit_of_measurement": unit, } four, states = record_states(hass, period0, "sensor.test1", attributes_1) - recorder.do_adhoc_statistics(start=period0) + do_adhoc_statistics(hass, start=period0) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -2248,7 +2221,7 @@ def test_compile_hourly_statistics_changing_statistics( hist = history.get_significant_states(hass, period0, four) assert dict(states) == dict(hist) - recorder.do_adhoc_statistics(start=period1) + do_adhoc_statistics(hass, start=period1) wait_recording_done(hass) statistic_ids = list_statistic_ids(hass) assert statistic_ids == [ @@ -2306,13 +2279,7 @@ def test_compile_hourly_statistics_changing_statistics( assert "Error while processing event StatisticsTask" not in caplog.text -@pytest.mark.parametrize( - "db_supports_row_number,in_log,not_in_log", - [(True, "row_number", None), (False, None, "row_number")], -) -def test_compile_statistics_hourly_daily_monthly_summary( - hass_recorder, caplog, db_supports_row_number, in_log, not_in_log -): +def test_compile_statistics_hourly_daily_monthly_summary(hass_recorder, caplog): """Test compiling hourly statistics + monthly and daily summary.""" zero = dt_util.utcnow() # August 31st, 23:00 local time @@ -2326,7 +2293,6 @@ def test_compile_statistics_hourly_daily_monthly_summary( # Remove this after dropping the use of the hass_recorder fixture hass.config.set_time_zone("America/Regina") recorder = hass.data[DATA_INSTANCE] - recorder._db_supports_row_number = db_supports_row_number setup_component(hass, "sensor", {}) wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { @@ -2445,7 +2411,7 @@ def test_compile_statistics_hourly_daily_monthly_summary( # Generate 5-minute statistics for two hours start = zero for i in range(24): - recorder.do_adhoc_statistics(start=start) + do_adhoc_statistics(hass, start=start) wait_recording_done(hass) start += timedelta(minutes=5) @@ -2720,10 +2686,6 @@ def test_compile_statistics_hourly_daily_monthly_summary( assert stats == expected_stats assert "Error while processing event StatisticsTask" not in caplog.text - if in_log: - assert in_log in caplog.text - if not_in_log: - assert not_in_log not in caplog.text def record_states(hass, zero, entity_id, attributes, seq=None): @@ -2747,17 +2709,23 @@ def record_states(hass, zero, entity_id, attributes, seq=None): four = three + timedelta(seconds=10 * 5) states = {entity_id: []} - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=one): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=one + ): states[entity_id].append( set_state(entity_id, str(seq[0]), attributes=attributes) ) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=two): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=two + ): states[entity_id].append( set_state(entity_id, str(seq[1]), attributes=attributes) ) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=three): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=three + ): states[entity_id].append( set_state(entity_id, str(seq[2]), attributes=attributes) ) @@ -2833,7 +2801,7 @@ async def test_validate_statistics_supported_device_class( # Statistics has run, invalid state - expect error await async_recorder_block_till_done(hass) - hass.data[DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) hass.states.async_set( "sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": "dogs"}} ) @@ -2848,7 +2816,7 @@ async def test_validate_statistics_supported_device_class( await assert_validation_result(client, {}) # Valid state, statistic runs again - empty response - hass.data[DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -2907,7 +2875,7 @@ async def test_validate_statistics_supported_device_class_2( await assert_validation_result(client, {}) # Statistics has run, device class set - expect error - hass.data[DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) hass.states.async_set("sensor.test", 12, attributes=attributes) await hass.async_block_till_done() @@ -2996,7 +2964,7 @@ async def test_validate_statistics_unsupported_state_class( await assert_validation_result(client, {}) # Statistics has run, empty response - hass.data[DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -3060,7 +3028,7 @@ async def test_validate_statistics_sensor_no_longer_recorded( await assert_validation_result(client, {}) # Statistics has run, empty response - hass.data[DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -3133,7 +3101,7 @@ async def test_validate_statistics_sensor_not_recorded( await assert_validation_result(client, expected) # Statistics has run, expect same error - hass.data[DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) await assert_validation_result(client, expected) @@ -3179,7 +3147,7 @@ async def test_validate_statistics_sensor_removed( await assert_validation_result(client, {}) # Statistics has run, empty response - hass.data[DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -3235,7 +3203,6 @@ async def test_validate_statistics_unsupported_device_class( await async_setup_component(hass, "sensor", {}) await async_recorder_block_till_done(hass) client = await hass_ws_client() - rec = hass.data[DATA_INSTANCE] # No statistics, no state - empty response await assert_validation_result(client, {}) @@ -3252,7 +3219,7 @@ async def test_validate_statistics_unsupported_device_class( # Run statistics, no statistics will be generated because of conflicting units await async_recorder_block_till_done(hass) - rec.do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) await assert_statistic_ids([]) @@ -3264,7 +3231,7 @@ async def test_validate_statistics_unsupported_device_class( # Run statistics one hour later, only the "dogs" state will be considered await async_recorder_block_till_done(hass) - rec.do_adhoc_statistics(start=now + timedelta(hours=1)) + do_adhoc_statistics(hass, start=now + timedelta(hours=1)) await async_recorder_block_till_done(hass) await assert_statistic_ids( [{"statistic_id": "sensor.test", "unit_of_measurement": "dogs"}] @@ -3297,7 +3264,7 @@ async def test_validate_statistics_unsupported_device_class( # Valid state, statistic runs again - empty response await async_recorder_block_till_done(hass) - hass.data[DATA_INSTANCE].do_adhoc_statistics(start=now) + do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -3339,35 +3306,53 @@ def record_meter_states(hass, zero, entity_id, _attributes, seq): attributes["last_reset"] = zero.isoformat() states = {entity_id: []} - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=zero): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=zero + ): states[entity_id].append(set_state(entity_id, seq[0], attributes=attributes)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=one): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=one + ): states[entity_id].append(set_state(entity_id, seq[1], attributes=attributes)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=two): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=two + ): states[entity_id].append(set_state(entity_id, seq[2], attributes=attributes)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=three): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=three + ): states[entity_id].append(set_state(entity_id, seq[3], attributes=attributes)) attributes = dict(_attributes) if "last_reset" in _attributes: attributes["last_reset"] = four.isoformat() - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=four): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=four + ): states[entity_id].append(set_state(entity_id, seq[4], attributes=attributes)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=five): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=five + ): states[entity_id].append(set_state(entity_id, seq[5], attributes=attributes)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=six): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=six + ): states[entity_id].append(set_state(entity_id, seq[6], attributes=attributes)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=seven): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=seven + ): states[entity_id].append(set_state(entity_id, seq[7], attributes=attributes)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=eight): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=eight + ): states[entity_id].append(set_state(entity_id, seq[8], attributes=attributes)) return four, eight, states @@ -3386,7 +3371,9 @@ def record_meter_state(hass, zero, entity_id, attributes, seq): return hass.states.get(entity_id) states = {entity_id: []} - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=zero): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=zero + ): states[entity_id].append(set_state(entity_id, seq[0], attributes=attributes)) return states @@ -3410,13 +3397,19 @@ def record_states_partially_unavailable(hass, zero, entity_id, attributes): four = three + timedelta(seconds=15 * 5) states = {entity_id: []} - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=one): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=one + ): states[entity_id].append(set_state(entity_id, "10", attributes=attributes)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=two): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=two + ): states[entity_id].append(set_state(entity_id, "25", attributes=attributes)) - with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=three): + with patch( + "homeassistant.components.recorder.core.dt_util.utcnow", return_value=three + ): states[entity_id].append( set_state(entity_id, STATE_UNAVAILABLE, attributes=attributes) ) diff --git a/tests/components/senz/test_config_flow.py b/tests/components/senz/test_config_flow.py index 5143acea60a..3906f2c0320 100644 --- a/tests/components/senz/test_config_flow.py +++ b/tests/components/senz/test_config_flow.py @@ -24,7 +24,6 @@ async def test_full_flow( "senz", { "senz": {"client_id": CLIENT_ID, "client_secret": CLIENT_SECRET}, - "http": {"base_url": "https://example.com"}, }, ) diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 713999de36f..145bcbb3566 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -58,7 +58,7 @@ async def test_form(hass, gen): "aioshelly.rpc_device.RpcDevice.create", new=AsyncMock( return_value=Mock( - model="SHSW-1", + shelly={"model": "SHSW-1", "gen": gen}, config=MOCK_CONFIG, shutdown=AsyncMock(), ) @@ -175,7 +175,7 @@ async def test_form_auth(hass, test_data): "aioshelly.rpc_device.RpcDevice.create", new=AsyncMock( return_value=Mock( - model="SHSW-1", + shelly={"model": "SHSW-1", "gen": gen}, config=MOCK_CONFIG, shutdown=AsyncMock(), ) @@ -225,19 +225,23 @@ async def test_form_errors_get_info(hass, error): assert result2["errors"] == {"base": base_error} -@pytest.mark.parametrize("error", [(KeyError, "firmware_not_fully_provisioned")]) -async def test_form_missing_key_get_info(hass, error): - """Test we handle missing key.""" - exc, base_error = error +async def test_form_missing_model_key(hass): + """Test we handle missing Shelly model key.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) with patch( "aioshelly.common.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False, "gen": "2"}, + return_value={"mac": "test-mac", "auth": False, "gen": "2"}, ), patch( - "homeassistant.components.shelly.config_flow.validate_input", - side_effect=KeyError, + "aioshelly.rpc_device.RpcDevice.create", + new=AsyncMock( + return_value=Mock( + shelly={"gen": 2}, + config=MOCK_CONFIG, + shutdown=AsyncMock(), + ) + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -245,7 +249,77 @@ async def test_form_missing_key_get_info(hass, error): ) assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["errors"] == {"base": base_error} + assert result2["errors"] == {"base": "firmware_not_fully_provisioned"} + + +async def test_form_missing_model_key_auth_enabled(hass): + """Test we handle missing Shelly model key when auth enabled.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "aioshelly.common.get_info", + return_value={"mac": "test-mac", "auth": True, "gen": 2}, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"host": "1.1.1.1"}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "aioshelly.rpc_device.RpcDevice.create", + new=AsyncMock( + return_value=Mock( + shelly={"gen": 2}, + config=MOCK_CONFIG, + shutdown=AsyncMock(), + ) + ), + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {"password": "1234"} + ) + + assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result3["errors"] == {"base": "firmware_not_fully_provisioned"} + + +async def test_form_missing_model_key_zeroconf(hass, caplog): + """Test we handle missing Shelly model key via zeroconf.""" + + with patch( + "aioshelly.common.get_info", + return_value={"mac": "test-mac", "auth": False, "gen": 2}, + ), patch( + "aioshelly.rpc_device.RpcDevice.create", + new=AsyncMock( + return_value=Mock( + shelly={"gen": 2}, + config=MOCK_CONFIG, + shutdown=AsyncMock(), + ) + ), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=DISCOVERY_INFO, + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "firmware_not_fully_provisioned"} + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] == {"base": "firmware_not_fully_provisioned"} @pytest.mark.parametrize( diff --git a/tests/components/shelly/test_logbook.py b/tests/components/shelly/test_logbook.py index 9ece9590cbb..5e267dcfd8f 100644 --- a/tests/components/shelly/test_logbook.py +++ b/tests/components/shelly/test_logbook.py @@ -1,5 +1,4 @@ """The tests for Shelly logbook.""" -from homeassistant.components import logbook from homeassistant.components.shelly.const import ( ATTR_CHANNEL, ATTR_CLICK_TYPE, @@ -10,7 +9,7 @@ from homeassistant.components.shelly.const import ( from homeassistant.const import ATTR_DEVICE_ID from homeassistant.setup import async_setup_component -from tests.components.logbook.test_init import MockLazyEventPartialState +from tests.components.logbook.common import MockRow, mock_humanify async def test_humanify_shelly_click_event_block_device(hass, coap_wrapper): @@ -18,48 +17,43 @@ async def test_humanify_shelly_click_event_block_device(hass, coap_wrapper): assert coap_wrapper hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) - entity_attr_cache = logbook.EntityAttributeCache(hass) - event1, event2 = list( - logbook.humanify( - hass, - [ - MockLazyEventPartialState( - EVENT_SHELLY_CLICK, - { - ATTR_DEVICE_ID: coap_wrapper.device_id, - ATTR_DEVICE: "shellyix3-12345678", - ATTR_CLICK_TYPE: "single", - ATTR_CHANNEL: 1, - }, - ), - MockLazyEventPartialState( - EVENT_SHELLY_CLICK, - { - ATTR_DEVICE_ID: "no_device_id", - ATTR_DEVICE: "shellyswitch25-12345678", - ATTR_CLICK_TYPE: "long", - ATTR_CHANNEL: 2, - }, - ), - ], - entity_attr_cache, - {}, - ) + event1, event2 = mock_humanify( + hass, + [ + MockRow( + EVENT_SHELLY_CLICK, + { + ATTR_DEVICE_ID: coap_wrapper.device_id, + ATTR_DEVICE: "shellyix3-12345678", + ATTR_CLICK_TYPE: "single", + ATTR_CHANNEL: 1, + }, + ), + MockRow( + EVENT_SHELLY_CLICK, + { + ATTR_DEVICE_ID: "no_device_id", + ATTR_DEVICE: "shellyswitch25-12345678", + ATTR_CLICK_TYPE: "long", + ATTR_CHANNEL: 2, + }, + ), + ], ) assert event1["name"] == "Shelly" assert event1["domain"] == DOMAIN assert ( event1["message"] - == "'single' click event for Test name channel 1 Input was fired." + == "'single' click event for Test name channel 1 Input was fired" ) assert event2["name"] == "Shelly" assert event2["domain"] == DOMAIN assert ( event2["message"] - == "'long' click event for shellyswitch25-12345678 channel 2 Input was fired." + == "'long' click event for shellyswitch25-12345678 channel 2 Input was fired" ) @@ -68,46 +62,41 @@ async def test_humanify_shelly_click_event_rpc_device(hass, rpc_wrapper): assert rpc_wrapper hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) - entity_attr_cache = logbook.EntityAttributeCache(hass) - event1, event2 = list( - logbook.humanify( - hass, - [ - MockLazyEventPartialState( - EVENT_SHELLY_CLICK, - { - ATTR_DEVICE_ID: rpc_wrapper.device_id, - ATTR_DEVICE: "shellyplus1pm-12345678", - ATTR_CLICK_TYPE: "single_push", - ATTR_CHANNEL: 1, - }, - ), - MockLazyEventPartialState( - EVENT_SHELLY_CLICK, - { - ATTR_DEVICE_ID: "no_device_id", - ATTR_DEVICE: "shellypro4pm-12345678", - ATTR_CLICK_TYPE: "btn_down", - ATTR_CHANNEL: 2, - }, - ), - ], - entity_attr_cache, - {}, - ) + event1, event2 = mock_humanify( + hass, + [ + MockRow( + EVENT_SHELLY_CLICK, + { + ATTR_DEVICE_ID: rpc_wrapper.device_id, + ATTR_DEVICE: "shellyplus1pm-12345678", + ATTR_CLICK_TYPE: "single_push", + ATTR_CHANNEL: 1, + }, + ), + MockRow( + EVENT_SHELLY_CLICK, + { + ATTR_DEVICE_ID: "no_device_id", + ATTR_DEVICE: "shellypro4pm-12345678", + ATTR_CLICK_TYPE: "btn_down", + ATTR_CHANNEL: 2, + }, + ), + ], ) assert event1["name"] == "Shelly" assert event1["domain"] == DOMAIN assert ( event1["message"] - == "'single_push' click event for test switch_0 Input was fired." + == "'single_push' click event for test switch_0 Input was fired" ) assert event2["name"] == "Shelly" assert event2["domain"] == DOMAIN assert ( event2["message"] - == "'btn_down' click event for shellypro4pm-12345678 channel 2 Input was fired." + == "'btn_down' click event for shellypro4pm-12345678 channel 2 Input was fired" ) diff --git a/tests/components/slack/__init__.py b/tests/components/slack/__init__.py index b32ec5ef7b1..6a258ce9027 100644 --- a/tests/components/slack/__init__.py +++ b/tests/components/slack/__init__.py @@ -1 +1,72 @@ -"""Slack notification tests.""" +"""Tests for the Slack integration.""" +from __future__ import annotations + +import json + +from homeassistant.components.slack.const import CONF_DEFAULT_CHANNEL, DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_NAME +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, load_fixture +from tests.test_util.aiohttp import AiohttpClientMocker + +AUTH_URL = "https://www.slack.com/api/auth.test" + +TOKEN = "abc123" +TEAM_NAME = "Test Team" +TEAM_ID = "abc123def" + +CONF_INPUT = {CONF_API_KEY: TOKEN, CONF_DEFAULT_CHANNEL: "test_channel"} + +CONF_DATA = CONF_INPUT | {CONF_NAME: TEAM_NAME} + + +def create_entry(hass: HomeAssistant) -> ConfigEntry: + """Add config entry in Home Assistant.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=CONF_DATA, + unique_id=TEAM_ID, + ) + entry.add_to_hass(hass) + return entry + + +def mock_connection( + aioclient_mock: AiohttpClientMocker, error: str | None = None +) -> None: + """Mock connection.""" + if error is not None: + if error == "invalid_auth": + aioclient_mock.post( + AUTH_URL, + text=json.dumps({"ok": False, "error": "invalid_auth"}), + ) + else: + aioclient_mock.post( + AUTH_URL, + text=json.dumps({"ok": False, "error": "cannot_connect"}), + ) + else: + aioclient_mock.post( + AUTH_URL, + text=load_fixture("slack/auth_test.json"), + ) + + +async def async_init_integration( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + skip_setup: bool = False, + error: str | None = None, +) -> ConfigEntry: + """Set up the Slack integration in Home Assistant.""" + entry = create_entry(hass) + mock_connection(aioclient_mock, error) + + if not skip_setup: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry diff --git a/tests/components/slack/fixtures/auth_test.json b/tests/components/slack/fixtures/auth_test.json new file mode 100644 index 00000000000..b7b6ee3e9bd --- /dev/null +++ b/tests/components/slack/fixtures/auth_test.json @@ -0,0 +1,10 @@ +{ + "ok": true, + "url": "https://newscorp-tech.slack.com/", + "team": "Test Team", + "user": "user.name", + "team_id": "ABC123DEF", + "user_id": "ABCDEF12345", + "enterprise_id": "123ABCDEF", + "is_enterprise_install": false +} diff --git a/tests/components/slack/test_config_flow.py b/tests/components/slack/test_config_flow.py new file mode 100644 index 00000000000..850690783e8 --- /dev/null +++ b/tests/components/slack/test_config_flow.py @@ -0,0 +1,140 @@ +"""Test Slack config flow.""" +from unittest.mock import patch + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components.slack.const import DOMAIN +from homeassistant.core import HomeAssistant + +from . import CONF_DATA, CONF_INPUT, TEAM_NAME, create_entry, mock_connection + +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_flow_user( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test user initialized flow.""" + mock_connection(aioclient_mock) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=CONF_INPUT, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == TEAM_NAME + assert result["data"] == CONF_DATA + + +async def test_flow_user_already_configured( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test user initialized flow with duplicate server.""" + create_entry(hass) + mock_connection(aioclient_mock) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=CONF_INPUT, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_flow_user_invalid_auth( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test user initialized flow with invalid token.""" + mock_connection(aioclient_mock, "invalid_auth") + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data=CONF_DATA, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "invalid_auth"} + + +async def test_flow_user_cannot_connect( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test user initialized flow with unreachable server.""" + mock_connection(aioclient_mock, "cannot_connect") + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data=CONF_DATA, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_flow_user_unknown_error(hass: HomeAssistant) -> None: + """Test user initialized flow with unreachable server.""" + with patch( + "homeassistant.components.slack.config_flow.WebClient.auth_test" + ) as mock: + mock.side_effect = Exception + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data=CONF_DATA, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "unknown"} + + +async def test_flow_import( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test an import flow.""" + mock_connection(aioclient_mock) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=CONF_DATA, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == TEAM_NAME + assert result["data"] == CONF_DATA + + +async def test_flow_import_no_name( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test import flow with no name in config.""" + mock_connection(aioclient_mock) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=CONF_INPUT, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == TEAM_NAME + assert result["data"] == CONF_DATA + + +async def test_flow_import_already_configured( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test an import flow already configured.""" + create_entry(hass) + mock_connection(aioclient_mock) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=CONF_DATA, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" diff --git a/tests/components/slack/test_init.py b/tests/components/slack/test_init.py new file mode 100644 index 00000000000..487a65b8ef0 --- /dev/null +++ b/tests/components/slack/test_init.py @@ -0,0 +1,39 @@ +"""Test Slack integration.""" +from homeassistant.components.slack.const import DOMAIN +from homeassistant.config_entries import ConfigEntry, ConfigEntryState +from homeassistant.core import HomeAssistant + +from . import CONF_DATA, async_init_integration + +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_setup(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None: + """Test Slack setup.""" + entry: ConfigEntry = await async_init_integration(hass, aioclient_mock) + await hass.async_block_till_done() + assert entry.state == ConfigEntryState.LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert entry.data == CONF_DATA + + +async def test_async_setup_entry_not_ready( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test that it throws ConfigEntryNotReady when exception occurs during setup.""" + entry: ConfigEntry = await async_init_integration( + hass, aioclient_mock, error="cannot_connect" + ) + await hass.async_block_till_done() + assert entry.state == ConfigEntryState.SETUP_RETRY + + +async def test_async_setup_entry_invalid_auth( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test invalid auth during setup.""" + entry: ConfigEntry = await async_init_integration( + hass, aioclient_mock, error="invalid_auth" + ) + await hass.async_block_till_done() + assert entry.state == ConfigEntryState.SETUP_ERROR diff --git a/tests/components/slack/test_notify.py b/tests/components/slack/test_notify.py index f10673cced4..b5fa08fa54f 100644 --- a/tests/components/slack/test_notify.py +++ b/tests/components/slack/test_notify.py @@ -1,13 +1,10 @@ """Test slack notifications.""" from __future__ import annotations -import copy import logging -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock, Mock from _pytest.logging import LogCaptureFixture -import aiohttp -from slack.errors import SlackApiError from homeassistant.components import notify from homeassistant.components.slack import DOMAIN @@ -15,15 +12,9 @@ from homeassistant.components.slack.notify import ( CONF_DEFAULT_CHANNEL, SlackNotificationService, ) -from homeassistant.const import ( - CONF_API_KEY, - CONF_ICON, - CONF_NAME, - CONF_PLATFORM, - CONF_USERNAME, -) -from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component +from homeassistant.const import ATTR_ICON, CONF_API_KEY, CONF_NAME, CONF_PLATFORM + +from . import CONF_DATA MODULE_PATH = "homeassistant.components.slack.notify" SERVICE_NAME = f"notify_{DOMAIN}" @@ -47,74 +38,14 @@ def filter_log_records(caplog: LogCaptureFixture) -> list[logging.LogRecord]: ] -async def test_setup(hass: HomeAssistant, caplog: LogCaptureFixture): - """Test setup slack notify.""" - config = DEFAULT_CONFIG - - with patch( - MODULE_PATH + ".aiohttp_client", - **{"async_get_clientsession.return_value": (session := Mock())}, - ), patch( - MODULE_PATH + ".WebClient", - return_value=(client := AsyncMock()), - ) as mock_client: - - await async_setup_component(hass, notify.DOMAIN, config) - await hass.async_block_till_done() - assert hass.services.has_service(notify.DOMAIN, SERVICE_NAME) - caplog_records_slack = filter_log_records(caplog) - assert len(caplog_records_slack) == 0 - mock_client.assert_called_with(token="12345", run_async=True, session=session) - client.auth_test.assert_called_once_with() - - -async def test_setup_clientError(hass: HomeAssistant, caplog: LogCaptureFixture): - """Test setup slack notify with aiohttp.ClientError exception.""" - config = copy.deepcopy(DEFAULT_CONFIG) - config[notify.DOMAIN][0].update({CONF_USERNAME: "user", CONF_ICON: "icon"}) - - with patch( - MODULE_PATH + ".aiohttp_client", - **{"async_get_clientsession.return_value": Mock()}, - ), patch(MODULE_PATH + ".WebClient", return_value=(client := AsyncMock())): - - client.auth_test.side_effect = [aiohttp.ClientError] - await async_setup_component(hass, notify.DOMAIN, config) - await hass.async_block_till_done() - assert hass.services.has_service(notify.DOMAIN, SERVICE_NAME) - caplog_records_slack = filter_log_records(caplog) - assert len(caplog_records_slack) == 1 - record = caplog_records_slack[0] - assert record.levelno == logging.WARNING - assert aiohttp.ClientError.__qualname__ in record.message - - -async def test_setup_slackApiError(hass: HomeAssistant, caplog: LogCaptureFixture): - """Test setup slack notify with SlackApiError exception.""" - config = DEFAULT_CONFIG - - with patch( - MODULE_PATH + ".aiohttp_client", - **{"async_get_clientsession.return_value": Mock()}, - ), patch(MODULE_PATH + ".WebClient", return_value=(client := AsyncMock())): - - client.auth_test.side_effect = [err := SlackApiError("msg", "resp")] - await async_setup_component(hass, notify.DOMAIN, config) - await hass.async_block_till_done() - assert hass.services.has_service(notify.DOMAIN, SERVICE_NAME) is False - caplog_records_slack = filter_log_records(caplog) - assert len(caplog_records_slack) == 1 - record = caplog_records_slack[0] - assert record.levelno == logging.ERROR - assert err.__class__.__qualname__ in record.message - - async def test_message_includes_default_emoji(): """Tests that default icon is used when no message icon is given.""" mock_client = Mock() mock_client.chat_postMessage = AsyncMock() expected_icon = ":robot_face:" - service = SlackNotificationService(None, mock_client, "_", "_", expected_icon) + service = SlackNotificationService( + None, mock_client, CONF_DATA | {ATTR_ICON: expected_icon} + ) await service.async_send_message("test") @@ -128,7 +59,9 @@ async def test_message_emoji_overrides_default(): """Tests that overriding the default icon emoji when sending a message works.""" mock_client = Mock() mock_client.chat_postMessage = AsyncMock() - service = SlackNotificationService(None, mock_client, "_", "_", "default_icon") + service = SlackNotificationService( + None, mock_client, CONF_DATA | {ATTR_ICON: "default_icon"} + ) expected_icon = ":new:" await service.async_send_message("test", data={"icon": expected_icon}) @@ -144,7 +77,9 @@ async def test_message_includes_default_icon_url(): mock_client = Mock() mock_client.chat_postMessage = AsyncMock() expected_icon = "https://example.com/hass.png" - service = SlackNotificationService(None, mock_client, "_", "_", expected_icon) + service = SlackNotificationService( + None, mock_client, CONF_DATA | {ATTR_ICON: expected_icon} + ) await service.async_send_message("test") @@ -158,10 +93,12 @@ async def test_message_icon_url_overrides_default(): """Tests that overriding the default icon url when sending a message works.""" mock_client = Mock() mock_client.chat_postMessage = AsyncMock() - service = SlackNotificationService(None, mock_client, "_", "_", "default_icon") + service = SlackNotificationService( + None, mock_client, CONF_DATA | {ATTR_ICON: "default_icon"} + ) expected_icon = "https://example.com/hass.png" - await service.async_send_message("test", data={"icon": expected_icon}) + await service.async_send_message("test", data={ATTR_ICON: expected_icon}) mock_fn = mock_client.chat_postMessage mock_fn.assert_called_once() diff --git a/tests/components/smartthings/test_light.py b/tests/components/smartthings/test_light.py index 166b0606b66..0fff1403985 100644 --- a/tests/components/smartthings/test_light.py +++ b/tests/components/smartthings/test_light.py @@ -16,7 +16,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - SUPPORT_TRANSITION, + LightEntityFeature, ) from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE from homeassistant.config_entries import ConfigEntryState @@ -82,7 +82,7 @@ async def test_entity_state(hass, light_devices): assert state.state == "on" assert ( state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + == SUPPORT_BRIGHTNESS | LightEntityFeature.TRANSITION ) assert isinstance(state.attributes[ATTR_BRIGHTNESS], int) assert state.attributes[ATTR_BRIGHTNESS] == 255 @@ -92,7 +92,7 @@ async def test_entity_state(hass, light_devices): assert state.state == "off" assert ( state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_COLOR + == SUPPORT_BRIGHTNESS | LightEntityFeature.TRANSITION | SUPPORT_COLOR ) # Color Dimmer 2 @@ -100,7 +100,10 @@ async def test_entity_state(hass, light_devices): assert state.state == "on" assert ( state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_COLOR | SUPPORT_COLOR_TEMP + == SUPPORT_BRIGHTNESS + | LightEntityFeature.TRANSITION + | SUPPORT_COLOR + | SUPPORT_COLOR_TEMP ) assert state.attributes[ATTR_BRIGHTNESS] == 255 assert state.attributes[ATTR_HS_COLOR] == (273.6, 55.0) diff --git a/tests/components/smtp/test_notify.py b/tests/components/smtp/test_notify.py index 38f48c169ac..ac742e10ea1 100644 --- a/tests/components/smtp/test_notify.py +++ b/tests/components/smtp/test_notify.py @@ -76,6 +76,7 @@ def message(): ["recip1@example.com", "testrecip@test.com"], "Home Assistant", 0, + True, ) yield mailer diff --git a/tests/components/spotify/test_config_flow.py b/tests/components/spotify/test_config_flow.py index e2f1878c04d..3b1e4851ff1 100644 --- a/tests/components/spotify/test_config_flow.py +++ b/tests/components/spotify/test_config_flow.py @@ -31,14 +31,14 @@ async def test_abort_if_no_configuration(hass): ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "missing_configuration" + assert result["reason"] == "missing_credentials" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=BLANK_ZEROCONF_INFO ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "missing_configuration" + assert result["reason"] == "missing_credentials" async def test_zeroconf_abort_if_existing_entry(hass): diff --git a/tests/components/sql/test_config_flow.py b/tests/components/sql/test_config_flow.py index 3e065df0ebd..47957ead98e 100644 --- a/tests/components/sql/test_config_flow.py +++ b/tests/components/sql/test_config_flow.py @@ -42,7 +42,6 @@ async def test_form(hass: HomeAssistant) -> None: ENTRY_CONFIG, ) await hass.async_block_till_done() - print(ENTRY_CONFIG) assert result2["type"] == RESULT_TYPE_CREATE_ENTRY assert result2["title"] == "Get Value" diff --git a/tests/components/sql/test_sensor.py b/tests/components/sql/test_sensor.py index 0eb3bf70683..588e1c824b7 100644 --- a/tests/components/sql/test_sensor.py +++ b/tests/components/sql/test_sensor.py @@ -115,7 +115,30 @@ async def test_query_no_value( state = hass.states.get("sensor.count_tables") assert state.state == STATE_UNKNOWN - text = "SELECT 5 as value where 1=2 returned no results" + text = "SELECT 5 as value where 1=2 LIMIT 1; returned no results" + assert text in caplog.text + + +async def test_query_mssql_no_result( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test the SQL sensor with a query that returns no value.""" + config = { + "db_url": "mssql://", + "query": "SELECT 5 as value where 1=2", + "column": "value", + "name": "count_tables", + } + with patch("homeassistant.components.sql.sensor.sqlalchemy"), patch( + "homeassistant.components.sql.sensor.sqlalchemy.text", + return_value="SELECT TOP 1 5 as value where 1=2", + ): + await init_integration(hass, config) + + state = hass.states.get("sensor.count_tables") + assert state.state == STATE_UNKNOWN + + text = "SELECT TOP 1 5 AS VALUE WHERE 1=2 returned no results" assert text in caplog.text diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index b947ce4269a..bf88f45acf9 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -75,17 +75,49 @@ async def test_ssdp_flow_dispatched_on_st(mock_get_ssdp, hass, caplog, mock_flow assert mock_call_data.x_homeassistant_matching_domains == {"mock-domain"} assert mock_call_data.upnp == {ssdp.ATTR_UPNP_UDN: "uuid:mock-udn"} assert "Failed to fetch ssdp data" not in caplog.text - # Compatibility with old dict access (to be removed after 2022.6) - assert mock_call_data[ssdp.ATTR_SSDP_ST] == "mock-st" - assert mock_call_data[ssdp.ATTR_SSDP_LOCATION] == "http://1.1.1.1" - assert mock_call_data[ssdp.ATTR_SSDP_USN] == "uuid:mock-udn::mock-st" - assert mock_call_data[ssdp.ATTR_SSDP_SERVER] == "mock-server" - assert mock_call_data[ssdp.ATTR_SSDP_EXT] == "" - assert mock_call_data[ssdp.ATTR_UPNP_UDN] == "uuid:mock-udn" - assert mock_call_data[ssdp.ATTR_SSDP_UDN] == ANY - assert mock_call_data["_timestamp"] == ANY - assert mock_call_data[ssdp.ATTR_HA_MATCHING_DOMAINS] == {"mock-domain"} - # End compatibility checks + + +@patch( + "homeassistant.components.ssdp.async_get_ssdp", + return_value={"mock-domain": [{"manufacturerURL": "mock-url"}]}, +) +@pytest.mark.usefixtures("mock_get_source_ip") +async def test_ssdp_flow_dispatched_on_manufacturer_url( + mock_get_ssdp, hass, caplog, mock_flow_init +): + """Test matching based on manufacturerURL.""" + mock_ssdp_search_response = _ssdp_headers( + { + "st": "mock-st", + "manufacturerURL": "mock-url", + "location": "http://1.1.1.1", + "usn": "uuid:mock-udn::mock-st", + "server": "mock-server", + "ext": "", + } + ) + ssdp_listener = await init_ssdp_component(hass) + await ssdp_listener._on_search(mock_ssdp_search_response) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert len(mock_flow_init.mock_calls) == 1 + assert mock_flow_init.mock_calls[0][1][0] == "mock-domain" + assert mock_flow_init.mock_calls[0][2]["context"] == { + "source": config_entries.SOURCE_SSDP + } + mock_call_data: ssdp.SsdpServiceInfo = mock_flow_init.mock_calls[0][2]["data"] + assert mock_call_data.ssdp_st == "mock-st" + assert mock_call_data.ssdp_location == "http://1.1.1.1" + assert mock_call_data.ssdp_usn == "uuid:mock-udn::mock-st" + assert mock_call_data.ssdp_server == "mock-server" + assert mock_call_data.ssdp_ext == "" + assert mock_call_data.ssdp_udn == ANY + assert mock_call_data.ssdp_headers["_timestamp"] == ANY + assert mock_call_data.x_homeassistant_matching_domains == {"mock-domain"} + assert mock_call_data.upnp == {ssdp.ATTR_UPNP_UDN: "uuid:mock-udn"} + assert "Failed to fetch ssdp data" not in caplog.text @pytest.mark.usefixtures("mock_get_source_ip") @@ -372,18 +404,6 @@ async def test_discovery_from_advertisement_sets_ssdp_st( ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", ssdp.ATTR_UPNP_UDN: "uuid:mock-udn", } - # Compatibility with old dict access (to be removed after 2022.6) - assert discovery_info[ssdp.ATTR_SSDP_LOCATION] == "http://1.1.1.1" - assert discovery_info[ssdp.ATTR_SSDP_NT] == "mock-st" - # Set by ssdp component, not in original advertisement. - assert discovery_info[ssdp.ATTR_SSDP_ST] == "mock-st" - assert discovery_info[ssdp.ATTR_SSDP_USN] == "uuid:mock-udn::mock-st" - assert discovery_info[ssdp.ATTR_UPNP_UDN] == "uuid:mock-udn" - assert discovery_info[ssdp.ATTR_UPNP_DEVICE_TYPE] == "Paulus" - assert discovery_info[ssdp.ATTR_SSDP_UDN] == ANY - assert discovery_info["nts"] == "ssdp:alive" - assert discovery_info["_timestamp"] == ANY - # End compatibility checks @patch( @@ -493,25 +513,6 @@ async def test_scan_with_registered_callback( ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", ssdp.ATTR_UPNP_UDN: "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL", } - # Compatibility with old dict access (to be removed after 2022.6) - assert mock_call_data[ssdp.ATTR_UPNP_DEVICE_TYPE] == "Paulus" - assert mock_call_data[ssdp.ATTR_SSDP_EXT] == "" - assert mock_call_data[ssdp.ATTR_SSDP_LOCATION] == "http://1.1.1.1" - assert mock_call_data[ssdp.ATTR_SSDP_SERVER] == "mock-server" - assert mock_call_data[ssdp.ATTR_SSDP_ST] == "mock-st" - assert ( - mock_call_data[ssdp.ATTR_SSDP_USN] - == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL::mock-st" - ) - assert ( - mock_call_data[ssdp.ATTR_UPNP_UDN] - == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL" - ) - assert mock_call_data["x-rincon-bootseq"] == "55" - assert mock_call_data[ssdp.ATTR_SSDP_UDN] == ANY - assert mock_call_data["_timestamp"] == ANY - assert mock_call_data[ssdp.ATTR_HA_MATCHING_DOMAINS] == set() - # End of compatibility checks assert "Failed to callback info" in caplog.text async_integration_callback_from_cache = AsyncMock() @@ -568,23 +569,6 @@ async def test_getting_existing_headers( ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", ssdp.ATTR_UPNP_UDN: "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL", } - # Compatibility with old dict access (to be removed after 2022.6) - assert discovery_info_by_st[ssdp.ATTR_SSDP_EXT] == "" - assert discovery_info_by_st[ssdp.ATTR_SSDP_LOCATION] == "http://1.1.1.1" - assert discovery_info_by_st[ssdp.ATTR_SSDP_SERVER] == "mock-server" - assert discovery_info_by_st[ssdp.ATTR_SSDP_ST] == "mock-st" - assert ( - discovery_info_by_st[ssdp.ATTR_SSDP_USN] - == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL::urn:mdx-netflix-com:service:target:3" - ) - assert ( - discovery_info_by_st[ssdp.ATTR_UPNP_UDN] - == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL" - ) - assert discovery_info_by_st[ssdp.ATTR_UPNP_DEVICE_TYPE] == "Paulus" - assert discovery_info_by_st[ssdp.ATTR_SSDP_UDN] == ANY - assert discovery_info_by_st["_timestamp"] == ANY - # End of compatibility checks discovery_info_by_udn = await ssdp.async_get_discovery_info_by_udn( hass, "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL" @@ -604,23 +588,6 @@ async def test_getting_existing_headers( ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", ssdp.ATTR_UPNP_UDN: "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL", } - # Compatibility with old dict access (to be removed after 2022.6) - assert discovery_info_by_udn[ssdp.ATTR_SSDP_EXT] == "" - assert discovery_info_by_udn[ssdp.ATTR_SSDP_LOCATION] == "http://1.1.1.1" - assert discovery_info_by_udn[ssdp.ATTR_SSDP_SERVER] == "mock-server" - assert discovery_info_by_udn[ssdp.ATTR_SSDP_ST] == "mock-st" - assert ( - discovery_info_by_udn[ssdp.ATTR_SSDP_USN] - == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL::urn:mdx-netflix-com:service:target:3" - ) - assert ( - discovery_info_by_udn[ssdp.ATTR_UPNP_UDN] - == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL" - ) - assert discovery_info_by_udn[ssdp.ATTR_UPNP_DEVICE_TYPE] == "Paulus" - assert discovery_info_by_udn[ssdp.ATTR_SSDP_UDN] == ANY - assert discovery_info_by_udn["_timestamp"] == ANY - # End of compatibility checks discovery_info_by_udn_st = await ssdp.async_get_discovery_info_by_udn_st( hass, "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL", "mock-st" @@ -639,23 +606,6 @@ async def test_getting_existing_headers( ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", ssdp.ATTR_UPNP_UDN: "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL", } - # Compatibility with old dict access (to be removed after 2022.6) - assert discovery_info_by_udn_st[ssdp.ATTR_SSDP_EXT] == "" - assert discovery_info_by_udn_st[ssdp.ATTR_SSDP_LOCATION] == "http://1.1.1.1" - assert discovery_info_by_udn_st[ssdp.ATTR_SSDP_SERVER] == "mock-server" - assert discovery_info_by_udn_st[ssdp.ATTR_SSDP_ST] == "mock-st" - assert ( - discovery_info_by_udn_st[ssdp.ATTR_SSDP_USN] - == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL::urn:mdx-netflix-com:service:target:3" - ) - assert ( - discovery_info_by_udn_st[ssdp.ATTR_UPNP_UDN] - == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL" - ) - assert discovery_info_by_udn_st[ssdp.ATTR_UPNP_DEVICE_TYPE] == "Paulus" - assert discovery_info_by_udn_st[ssdp.ATTR_SSDP_UDN] == ANY - assert discovery_info_by_udn_st["_timestamp"] == ANY - # End of compatibility checks assert ( await ssdp.async_get_discovery_info_by_udn_st(hass, "wrong", "mock-st") is None @@ -791,61 +741,3 @@ async def test_ipv4_does_additional_search_for_sonos( ), ) assert ssdp_listener.async_search.call_args[1] == {} - - -@pytest.mark.usefixtures("mock_integration_frame") -async def test_service_info_compatibility(hass, caplog): - """Test compatibility with old-style dict. - - To be removed in 2022.6 - """ - discovery_info = ssdp.SsdpServiceInfo( - ssdp_st="mock-st", - ssdp_location="http://1.1.1.1", - ssdp_usn="uuid:mock-udn::mock-st", - ssdp_server="mock-server", - ssdp_ext="", - ssdp_headers=_ssdp_headers( - { - "st": "mock-st", - "location": "http://1.1.1.1", - "usn": "uuid:mock-udn::mock-st", - "server": "mock-server", - "ext": "", - } - ), - upnp={ssdp.ATTR_UPNP_DEVICE_TYPE: "ABC"}, - ) - - with patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()): - assert discovery_info["ssdp_st"] == "mock-st" - assert "Detected integration that accessed discovery_info['ssdp_st']" in caplog.text - - with patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()): - assert discovery_info.get("ssdp_location") == "http://1.1.1.1" - assert ( - "Detected integration that accessed discovery_info.get('ssdp_location')" - in caplog.text - ) - - with patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()): - assert "ssdp_usn" in discovery_info - assert ( - "Detected integration that accessed discovery_info.__contains__('ssdp_usn')" - in caplog.text - ) - - # Root item - assert discovery_info["ssdp_usn"] == "uuid:mock-udn::mock-st" - assert discovery_info.get("ssdp_usn") == "uuid:mock-udn::mock-st" - assert "ssdp_usn" in discovery_info - - # SSDP header - assert discovery_info["st"] == "mock-st" - assert discovery_info.get("st") == "mock-st" - assert "st" in discovery_info - - # UPnP item - assert discovery_info[ssdp.ATTR_UPNP_DEVICE_TYPE] == "ABC" - assert discovery_info.get(ssdp.ATTR_UPNP_DEVICE_TYPE) == "ABC" - assert ssdp.ATTR_UPNP_DEVICE_TYPE in discovery_info diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index dbcb3b1b8e7..56255216f52 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -687,6 +687,22 @@ async def test_state_characteristics(hass: HomeAssistant): "value_9": (start_datetime + timedelta(minutes=1)).isoformat(), "unit": None, }, + { + "source_sensor_domain": "sensor", + "name": "datetime_value_max", + "value_0": STATE_UNKNOWN, + "value_1": (start_datetime + timedelta(minutes=9)).isoformat(), + "value_9": (start_datetime + timedelta(minutes=2)).isoformat(), + "unit": None, + }, + { + "source_sensor_domain": "sensor", + "name": "datetime_value_min", + "value_0": STATE_UNKNOWN, + "value_1": (start_datetime + timedelta(minutes=9)).isoformat(), + "value_9": (start_datetime + timedelta(minutes=5)).isoformat(), + "unit": None, + }, { "source_sensor_domain": "sensor", "name": "distance_95_percent_of_values", @@ -811,6 +827,38 @@ async def test_state_characteristics(hass: HomeAssistant): "value_9": len(VALUES_BINARY), "unit": None, }, + { + "source_sensor_domain": "binary_sensor", + "name": "count_on", + "value_0": 0, + "value_1": 1, + "value_9": VALUES_BINARY.count("on"), + "unit": None, + }, + { + "source_sensor_domain": "binary_sensor", + "name": "count_off", + "value_0": 0, + "value_1": 0, + "value_9": VALUES_BINARY.count("off"), + "unit": None, + }, + { + "source_sensor_domain": "binary_sensor", + "name": "datetime_newest", + "value_0": STATE_UNKNOWN, + "value_1": (start_datetime + timedelta(minutes=9)).isoformat(), + "value_9": (start_datetime + timedelta(minutes=9)).isoformat(), + "unit": None, + }, + { + "source_sensor_domain": "binary_sensor", + "name": "datetime_oldest", + "value_0": STATE_UNKNOWN, + "value_1": (start_datetime + timedelta(minutes=9)).isoformat(), + "value_9": (start_datetime + timedelta(minutes=1)).isoformat(), + "unit": None, + }, { "source_sensor_domain": "binary_sensor", "name": "mean", diff --git a/tests/components/steam_online/__init__.py b/tests/components/steam_online/__init__.py index 27958b76576..4c8c398502f 100644 --- a/tests/components/steam_online/__init__.py +++ b/tests/components/steam_online/__init__.py @@ -1,6 +1,8 @@ """Tests for Steam integration.""" from unittest.mock import patch +import steam + from homeassistant.components.steam_online import DOMAIN from homeassistant.components.steam_online.const import CONF_ACCOUNT, CONF_ACCOUNTS from homeassistant.const import CONF_API_KEY @@ -96,11 +98,24 @@ class MockedInterface(dict): return {"response": {"player_level": 10}} +class MockedInterfacePrivate(MockedInterface): + """Mocked interface for private friends list.""" + + def GetFriendList(self, steamid: str) -> None: + """Get friend list.""" + raise steam.api.HTTPError + + def patch_interface() -> MockedInterface: """Patch interface.""" return patch("steam.api.interface", return_value=MockedInterface()) +def patch_interface_private() -> MockedInterfacePrivate: + """Patch interface for private friends list.""" + return patch("steam.api.interface", return_value=MockedInterfacePrivate()) + + def patch_user_interface_null() -> MockedUserInterfaceNull: """Patch player interface with no players.""" return patch("steam.api.interface", return_value=MockedUserInterfaceNull()) diff --git a/tests/components/steam_online/test_config_flow.py b/tests/components/steam_online/test_config_flow.py index 6d8a16f35f7..c4504bf1641 100644 --- a/tests/components/steam_online/test_config_flow.py +++ b/tests/components/steam_online/test_config_flow.py @@ -21,6 +21,7 @@ from . import ( CONF_OPTIONS_2, create_entry, patch_interface, + patch_interface_private, patch_user_interface_null, ) @@ -225,3 +226,22 @@ async def test_options_flow_timeout(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"] == CONF_OPTIONS + + +async def test_options_flow_unauthorized(hass: HomeAssistant) -> None: + """Test updating options when user's friends list is not public.""" + entry = create_entry(hass) + with patch_interface_private(): + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_ACCOUNTS: [ACCOUNT_1]}, + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"] == CONF_OPTIONS diff --git a/tests/components/steam_online/test_init.py b/tests/components/steam_online/test_init.py index 2a015a4ed36..435a5ac6f5a 100644 --- a/tests/components/steam_online/test_init.py +++ b/tests/components/steam_online/test_init.py @@ -41,7 +41,7 @@ async def test_device_info(hass: HomeAssistant) -> None: entry = create_entry(hass) with patch_interface(): await hass.config_entries.async_setup(entry.entry_id) - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) await hass.async_block_till_done() device = device_registry.async_get_device({(DOMAIN, entry.entry_id)}) diff --git a/tests/components/stream/conftest.py b/tests/components/stream/conftest.py index 407b144267b..a3d2da8bd52 100644 --- a/tests/components/stream/conftest.py +++ b/tests/components/stream/conftest.py @@ -176,7 +176,7 @@ class HLSSync: self.check_requests_ready() return self._original_not_found() - def response(self, body, headers, status=HTTPStatus.OK): + def response(self, body, headers=None, status=HTTPStatus.OK): """Intercept the Response call so we know when the web handler is finished.""" self._num_finished += 1 self.check_requests_ready() diff --git a/tests/components/subaru/test_lock.py b/tests/components/subaru/test_lock.py index 19918ba205c..7ccc1e5fdf5 100644 --- a/tests/components/subaru/test_lock.py +++ b/tests/components/subaru/test_lock.py @@ -13,6 +13,7 @@ from homeassistant.components.subaru.const import ( ) from homeassistant.const import ATTR_ENTITY_ID, SERVICE_LOCK, SERVICE_UNLOCK from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_registry as er from .conftest import MOCK_API @@ -23,7 +24,7 @@ DEVICE_ID = "lock.test_vehicle_2_door_locks" async def test_device_exists(hass, ev_entry): """Test subaru lock entity exists.""" - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(DEVICE_ID) assert entry diff --git a/tests/components/tankerkoenig/test_config_flow.py b/tests/components/tankerkoenig/test_config_flow.py index 0a90b424b73..f48a09fd64b 100644 --- a/tests/components/tankerkoenig/test_config_flow.py +++ b/tests/components/tankerkoenig/test_config_flow.py @@ -42,6 +42,15 @@ MOCK_STATIONS_DATA = { ], } +MOCK_OPTIONS_DATA = { + **MOCK_USER_DATA, + CONF_STATIONS: [ + "3bcd61da-xxxx-xxxx-xxxx-19d5523a7ae8", + "36b4b812-xxxx-xxxx-xxxx-c51735325858", + "54e2b642-xxxx-xxxx-xxxx-87cd4e9867f1", + ], +} + MOCK_IMPORT_DATA = { CONF_API_KEY: "269534f6-xxxx-xxxx-xxxx-yyyyzzzzxxxx", CONF_FUEL_TYPES: ["e5"], @@ -86,7 +95,7 @@ async def test_user(hass: HomeAssistant): assert result["step_id"] == "user" with patch( - "homeassistant.components.tankerkoenig.async_setup_entry" + "homeassistant.components.tankerkoenig.async_setup_entry", return_value=True ) as mock_setup_entry, patch( "homeassistant.components.tankerkoenig.config_flow.getNearbyStations", return_value=MOCK_NEARVY_STATIONS_OK, @@ -138,6 +147,7 @@ async def test_user_already_configured(hass: HomeAssistant): ) assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" async def test_exception_security(hass: HomeAssistant): @@ -184,7 +194,7 @@ async def test_user_no_stations(hass: HomeAssistant): async def test_import(hass: HomeAssistant): """Test starting a flow by import.""" with patch( - "homeassistant.components.tankerkoenig.async_setup_entry" + "homeassistant.components.tankerkoenig.async_setup_entry", return_value=True ) as mock_setup_entry, patch( "homeassistant.components.tankerkoenig.config_flow.getNearbyStations", return_value=MOCK_NEARVY_STATIONS_OK, @@ -217,25 +227,31 @@ async def test_options_flow(hass: HomeAssistant): mock_config = MockConfigEntry( domain=DOMAIN, - data=MOCK_USER_DATA, + data=MOCK_OPTIONS_DATA, options={CONF_SHOW_ON_MAP: True}, unique_id=f"{DOMAIN}_{MOCK_USER_DATA[CONF_LOCATION][CONF_LATITUDE]}_{MOCK_USER_DATA[CONF_LOCATION][CONF_LONGITUDE]}", ) mock_config.add_to_hass(hass) with patch( - "homeassistant.components.tankerkoenig.async_setup_entry" - ) as mock_setup_entry: - await mock_config.async_setup(hass) + "homeassistant.components.tankerkoenig.async_setup_entry", return_value=True + ) as mock_setup_entry, patch( + "homeassistant.components.tankerkoenig.config_flow.getNearbyStations", + return_value=MOCK_NEARVY_STATIONS_OK, + ): + await hass.config_entries.async_setup(mock_config.entry_id) await hass.async_block_till_done() assert mock_setup_entry.called - result = await hass.config_entries.options.async_init(mock_config.entry_id) + result = await hass.config_entries.options.async_init(mock_config.entry_id) assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "init" result = await hass.config_entries.options.async_configure( result["flow_id"], - user_input={CONF_SHOW_ON_MAP: False}, + user_input={ + CONF_SHOW_ON_MAP: False, + CONF_STATIONS: MOCK_OPTIONS_DATA[CONF_STATIONS], + }, ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY assert not mock_config.options[CONF_SHOW_ON_MAP] diff --git a/tests/components/tasmota/test_cover.py b/tests/components/tasmota/test_cover.py index e88df08a80c..06471e11757 100644 --- a/tests/components/tasmota/test_cover.py +++ b/tests/components/tasmota/test_cover.py @@ -30,6 +30,19 @@ from .test_common import ( from tests.common import async_fire_mqtt_message +COVER_SUPPORT = ( + cover.SUPPORT_OPEN + | cover.SUPPORT_CLOSE + | cover.SUPPORT_STOP + | cover.SUPPORT_SET_POSITION +) +TILT_SUPPORT = ( + cover.SUPPORT_OPEN_TILT + | cover.SUPPORT_CLOSE_TILT + | cover.SUPPORT_STOP_TILT + | cover.SUPPORT_SET_TILT_POSITION +) + async def test_missing_relay(hass, mqtt_mock, setup_tasmota): """Test no cover is discovered if relays are missing.""" @@ -64,11 +77,46 @@ async def test_multiple_covers( assert len(hass.states.async_all("cover")) == num_covers -async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): +async def test_tilt_support(hass, mqtt_mock, setup_tasmota): + """Test tilt support detection.""" + config = copy.deepcopy(DEFAULT_CONFIG) + config["rl"] = [3, 3, 3, 3, 3, 3, 3, 3] + config["sht"] = [ + [0, 0, 0], # Default settings, no tilt + [-90, 90, 24], # Tilt configured + [-90, 90, 0], # Duration 0, no tilt + [-90, -90, 24], # min+max same, no tilt + ] + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all("cover")) == 4 + + state = hass.states.get("cover.tasmota_cover_1") + assert state.attributes["supported_features"] == COVER_SUPPORT + + state = hass.states.get("cover.tasmota_cover_2") + assert state.attributes["supported_features"] == COVER_SUPPORT | TILT_SUPPORT + + state = hass.states.get("cover.tasmota_cover_3") + assert state.attributes["supported_features"] == COVER_SUPPORT + + state = hass.states.get("cover.tasmota_cover_4") + assert state.attributes["supported_features"] == COVER_SUPPORT + + +async def test_controlling_state_via_mqtt_tilt(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT.""" config = copy.deepcopy(DEFAULT_CONFIG) config["rl"][0] = 3 config["rl"][1] = 3 + config["sht"] = [[-90, 90, 24]] mac = config["mac"] async_fire_mqtt_message( @@ -86,40 +134,39 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): await hass.async_block_till_done() state = hass.states.get("cover.tasmota_cover_1") assert state.state == STATE_UNKNOWN - assert ( - state.attributes["supported_features"] - == cover.SUPPORT_OPEN - | cover.SUPPORT_CLOSE - | cover.SUPPORT_STOP - | cover.SUPPORT_SET_POSITION - ) + assert state.attributes["supported_features"] == COVER_SUPPORT | TILT_SUPPORT assert not state.attributes.get(ATTR_ASSUMED_STATE) # Periodic updates async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/SENSOR", - '{"Shutter1":{"Position":54,"Direction":-1}}', + '{"Shutter1":{"Position":54,"Direction":-1,"Tilt":-90}}', ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closing" assert state.attributes["current_position"] == 54 + assert state.attributes["current_tilt_position"] == 0 async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/SENSOR", - '{"Shutter1":{"Position":100,"Direction":1}}', + '{"Shutter1":{"Position":100,"Direction":1,"Tilt":90}}', ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "opening" assert state.attributes["current_position"] == 100 + assert state.attributes["current_tilt_position"] == 100 async_fire_mqtt_message( - hass, "tasmota_49A3BC/tele/SENSOR", '{"Shutter1":{"Position":0,"Direction":0}}' + hass, + "tasmota_49A3BC/tele/SENSOR", + '{"Shutter1":{"Position":0,"Direction":0,"Tilt":0}}', ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closed" assert state.attributes["current_position"] == 0 + assert state.attributes["current_tilt_position"] == 50 async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/SENSOR", '{"Shutter1":{"Position":1,"Direction":0}}' @@ -141,29 +188,32 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"Shutter1":{"Position":54,"Direction":-1}}}', + '{"StatusSNS":{"Shutter1":{"Position":54,"Direction":-1,"Tilt":-90}}}', ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closing" assert state.attributes["current_position"] == 54 + assert state.attributes["current_tilt_position"] == 0 async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"Shutter1":{"Position":100,"Direction":1}}}', + '{"StatusSNS":{"Shutter1":{"Position":100,"Direction":1,"Tilt":90}}}', ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "opening" assert state.attributes["current_position"] == 100 + assert state.attributes["current_tilt_position"] == 100 async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"Shutter1":{"Position":0,"Direction":0}}}', + '{"StatusSNS":{"Shutter1":{"Position":0,"Direction":0,"Tilt":0}}}', ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closed" assert state.attributes["current_position"] == 0 + assert state.attributes["current_tilt_position"] == 50 async_fire_mqtt_message( hass, @@ -187,27 +237,32 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/RESULT", - '{"Shutter1":{"Position":54,"Direction":-1}}', + '{"Shutter1":{"Position":54,"Direction":-1,"Tilt":-90}}', ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closing" assert state.attributes["current_position"] == 54 + assert state.attributes["current_tilt_position"] == 0 async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/RESULT", - '{"Shutter1":{"Position":100,"Direction":1}}', + '{"Shutter1":{"Position":100,"Direction":1,"Tilt":90}}', ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "opening" assert state.attributes["current_position"] == 100 + assert state.attributes["current_tilt_position"] == 100 async_fire_mqtt_message( - hass, "tasmota_49A3BC/stat/RESULT", '{"Shutter1":{"Position":0,"Direction":0}}' + hass, + "tasmota_49A3BC/stat/RESULT", + '{"Shutter1":{"Position":0,"Direction":0,"Tilt":0}}', ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closed" assert state.attributes["current_position"] == 0 + assert state.attributes["current_tilt_position"] == 50 async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/RESULT", '{"Shutter1":{"Position":1,"Direction":0}}' @@ -226,7 +281,10 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): assert state.attributes["current_position"] == 100 -async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmota): +@pytest.mark.parametrize("tilt", ("", ',"Tilt":0')) +async def test_controlling_state_via_mqtt_inverted( + hass, mqtt_mock, setup_tasmota, tilt +): """Test state update via MQTT.""" config = copy.deepcopy(DEFAULT_CONFIG) config["rl"][0] = 3 @@ -249,20 +307,13 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot await hass.async_block_till_done() state = hass.states.get("cover.tasmota_cover_1") assert state.state == STATE_UNKNOWN - assert ( - state.attributes["supported_features"] - == cover.SUPPORT_OPEN - | cover.SUPPORT_CLOSE - | cover.SUPPORT_STOP - | cover.SUPPORT_SET_POSITION - ) - assert not state.attributes.get(ATTR_ASSUMED_STATE) + assert state.attributes["supported_features"] == COVER_SUPPORT # Periodic updates async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/SENSOR", - '{"Shutter1":{"Position":54,"Direction":-1}}', + '{"Shutter1":{"Position":54,"Direction":-1' + tilt + "}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "opening" @@ -271,21 +322,25 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/SENSOR", - '{"Shutter1":{"Position":100,"Direction":1}}', + '{"Shutter1":{"Position":100,"Direction":1' + tilt + "}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closing" assert state.attributes["current_position"] == 0 async_fire_mqtt_message( - hass, "tasmota_49A3BC/tele/SENSOR", '{"Shutter1":{"Position":0,"Direction":0}}' + hass, + "tasmota_49A3BC/tele/SENSOR", + '{"Shutter1":{"Position":0,"Direction":0' + tilt + "}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "open" assert state.attributes["current_position"] == 100 async_fire_mqtt_message( - hass, "tasmota_49A3BC/tele/SENSOR", '{"Shutter1":{"Position":99,"Direction":0}}' + hass, + "tasmota_49A3BC/tele/SENSOR", + '{"Shutter1":{"Position":99,"Direction":0' + tilt + "}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "open" @@ -294,7 +349,7 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/SENSOR", - '{"Shutter1":{"Position":100,"Direction":0}}', + '{"Shutter1":{"Position":100,"Direction":0' + tilt + "}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closed" @@ -304,7 +359,7 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"Shutter1":{"Position":54,"Direction":-1}}}', + '{"StatusSNS":{"Shutter1":{"Position":54,"Direction":-1' + tilt + "}}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "opening" @@ -313,7 +368,7 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"Shutter1":{"Position":100,"Direction":1}}}', + '{"StatusSNS":{"Shutter1":{"Position":100,"Direction":1' + tilt + "}}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closing" @@ -322,7 +377,7 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"Shutter1":{"Position":0,"Direction":0}}}', + '{"StatusSNS":{"Shutter1":{"Position":0,"Direction":0' + tilt + "}}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "open" @@ -331,7 +386,7 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"Shutter1":{"Position":99,"Direction":0}}}', + '{"StatusSNS":{"Shutter1":{"Position":99,"Direction":0' + tilt + "}}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "open" @@ -340,7 +395,7 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"Shutter1":{"Position":100,"Direction":0}}}', + '{"StatusSNS":{"Shutter1":{"Position":100,"Direction":0' + tilt + "}}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closed" @@ -350,7 +405,7 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/RESULT", - '{"Shutter1":{"Position":54,"Direction":-1}}', + '{"Shutter1":{"Position":54,"Direction":-1' + tilt + "}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "opening" @@ -359,21 +414,25 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/RESULT", - '{"Shutter1":{"Position":100,"Direction":1}}', + '{"Shutter1":{"Position":100,"Direction":1' + tilt + "}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closing" assert state.attributes["current_position"] == 0 async_fire_mqtt_message( - hass, "tasmota_49A3BC/stat/RESULT", '{"Shutter1":{"Position":0,"Direction":0}}' + hass, + "tasmota_49A3BC/stat/RESULT", + '{"Shutter1":{"Position":0,"Direction":0' + tilt + "}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "open" assert state.attributes["current_position"] == 100 async_fire_mqtt_message( - hass, "tasmota_49A3BC/stat/RESULT", '{"Shutter1":{"Position":1,"Direction":0}}' + hass, + "tasmota_49A3BC/stat/RESULT", + '{"Shutter1":{"Position":1,"Direction":0' + tilt + "}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "open" @@ -382,7 +441,7 @@ async def test_controlling_state_via_mqtt_inverted(hass, mqtt_mock, setup_tasmot async_fire_mqtt_message( hass, "tasmota_49A3BC/stat/RESULT", - '{"Shutter1":{"Position":100,"Direction":0}}', + '{"Shutter1":{"Position":100,"Direction":0' + tilt + "}}", ) state = hass.states.get("cover.tasmota_cover_1") assert state.state == "closed" @@ -405,6 +464,7 @@ async def test_sending_mqtt_commands(hass, mqtt_mock, setup_tasmota): config["dn"] = "Test" config["rl"][0] = 3 config["rl"][1] = 3 + config["sht"] = [[-90, 90, 24]] mac = config["mac"] async_fire_mqtt_message( @@ -461,6 +521,45 @@ async def test_sending_mqtt_commands(hass, mqtt_mock, setup_tasmota): ) mqtt_mock.async_publish.reset_mock() + # Close the cover tilt and verify MQTT message is sent + await call_service(hass, "cover.test_cover_1", "close_cover_tilt") + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/ShutterTilt1", "CLOSE", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Open the cover tilt and verify MQTT message is sent + await call_service(hass, "cover.test_cover_1", "open_cover_tilt") + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/ShutterTilt1", "OPEN", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Stop the cover tilt and verify MQTT message is sent + await call_service(hass, "cover.test_cover_1", "stop_cover_tilt") + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/ShutterStop1", "", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Set tilt position and verify MQTT message is sent + await call_service( + hass, "cover.test_cover_1", "set_cover_tilt_position", tilt_position=0 + ) + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/ShutterTilt1", "-90", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Set tilt position and verify MQTT message is sent + await call_service( + hass, "cover.test_cover_1", "set_cover_tilt_position", tilt_position=100 + ) + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/ShutterTilt1", "90", 0, False + ) + mqtt_mock.async_publish.reset_mock() + async def test_sending_mqtt_commands_inverted(hass, mqtt_mock, setup_tasmota): """Test the sending MQTT commands.""" diff --git a/tests/components/template/conftest.py b/tests/components/template/conftest.py index 410ce21e4c6..5ccc9e6479a 100644 --- a/tests/components/template/conftest.py +++ b/tests/components/template/conftest.py @@ -1,6 +1,4 @@ """template conftest.""" -import json - import pytest from homeassistant.setup import async_setup_component @@ -15,18 +13,8 @@ def calls(hass): @pytest.fixture -def config_addon(): - """Add entra configuration items.""" - return None - - -@pytest.fixture -async def start_ha(hass, count, domain, config_addon, config, caplog): +async def start_ha(hass, count, domain, config, caplog): """Do setup of integration.""" - if config_addon: - for key, value in config_addon.items(): - config = config.replace(key, value) - config = json.loads(config) with assert_setup_component(count, domain): assert await async_setup_component( hass, diff --git a/tests/components/template/fixtures/broken_configuration.yaml b/tests/components/template/fixtures/broken_configuration.yaml index de0c8aebd9d..00f773e2fd3 100644 --- a/tests/components/template/fixtures/broken_configuration.yaml +++ b/tests/components/template/fixtures/broken_configuration.yaml @@ -7,8 +7,8 @@ sensor: friendly_name: Combined Sense Energy Usage unit_of_measurement: kW value_template: - "{{ ((states('sensor.energy_usage') | float) + (states('sensor.energy_usage_2') - | float)) / 1000 }}" + "{{ ((states('sensor.energy_usage') | float(default=0)) + (states('sensor.energy_usage_2') + | float(default=0))) / 1000 }}" watching_tv_in_master_bedroom: friendly_name: Watching TV in Master Bedroom value_template: diff --git a/tests/components/template/fixtures/sensor_configuration.yaml b/tests/components/template/fixtures/sensor_configuration.yaml index 02c8cc373f4..f1afef26b87 100644 --- a/tests/components/template/fixtures/sensor_configuration.yaml +++ b/tests/components/template/fixtures/sensor_configuration.yaml @@ -14,8 +14,8 @@ sensor: friendly_name: Combined Sense Energy Usage unit_of_measurement: kW value_template: - "{{ ((states('sensor.energy_usage') | float) + (states('sensor.energy_usage_2') - | float)) / 1000 }}" + "{{ ((states('sensor.energy_usage') | float(default=0)) + (states('sensor.energy_usage_2') + | float(default=0))) / 1000 }}" watching_tv_in_master_bedroom: friendly_name: Watching TV in Master Bedroom value_template: diff --git a/tests/components/template/test_alarm_control_panel.py b/tests/components/template/test_alarm_control_panel.py index cd29794db8d..8f9ab39f7c0 100644 --- a/tests/components/template/test_alarm_control_panel.py +++ b/tests/components/template/test_alarm_control_panel.py @@ -1,7 +1,12 @@ """The tests for the Template alarm control panel platform.""" import pytest +from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN from homeassistant.const import ( + ATTR_DOMAIN, + ATTR_ENTITY_ID, + ATTR_SERVICE_DATA, + EVENT_CALL_SERVICE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, @@ -10,13 +15,61 @@ from homeassistant.const import ( STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, ) - -from tests.components.alarm_control_panel import common +from homeassistant.core import callback TEMPLATE_NAME = "alarm_control_panel.test_template_panel" PANEL_NAME = "alarm_control_panel.test" +@pytest.fixture +def service_calls(hass): + """Track service call events for alarm_control_panel.test.""" + events = [] + entity_id = "alarm_control_panel.test" + + @callback + def capture_events(event): + if event.data[ATTR_DOMAIN] != ALARM_DOMAIN: + return + if event.data[ATTR_SERVICE_DATA][ATTR_ENTITY_ID] != [entity_id]: + return + events.append(event) + + hass.bus.async_listen(EVENT_CALL_SERVICE, capture_events) + + return events + + +OPTIMISTIC_TEMPLATE_ALARM_CONFIG = { + "arm_away": { + "service": "alarm_control_panel.alarm_arm_away", + "entity_id": "alarm_control_panel.test", + "data": {"code": "{{ this.entity_id }}"}, + }, + "arm_home": { + "service": "alarm_control_panel.alarm_arm_home", + "entity_id": "alarm_control_panel.test", + "data": {"code": "{{ this.entity_id }}"}, + }, + "arm_night": { + "service": "alarm_control_panel.alarm_arm_night", + "entity_id": "alarm_control_panel.test", + "data": {"code": "{{ this.entity_id }}"}, + }, + "disarm": { + "service": "alarm_control_panel.alarm_disarm", + "entity_id": "alarm_control_panel.test", + "data": {"code": "{{ this.entity_id }}"}, + }, +} + + +TEMPLATE_ALARM_CONFIG = { + "value_template": "{{ states('alarm_control_panel.test') }}", + **OPTIMISTIC_TEMPLATE_ALARM_CONFIG, +} + + @pytest.mark.parametrize("count,domain", [(1, "alarm_control_panel")]) @pytest.mark.parametrize( "config", @@ -24,31 +77,7 @@ PANEL_NAME = "alarm_control_panel.test" { "alarm_control_panel": { "platform": "template", - "panels": { - "test_template_panel": { - "value_template": "{{ states('alarm_control_panel.test') }}", - "arm_away": { - "service": "alarm_control_panel.alarm_arm_away", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_home": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_night": { - "service": "alarm_control_panel.alarm_arm_night", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "disarm": { - "service": "alarm_control_panel.alarm_disarm", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - } - }, + "panels": {"test_template_panel": TEMPLATE_ALARM_CONFIG}, } }, ], @@ -83,30 +112,7 @@ async def test_template_state_text(hass, start_ha): { "alarm_control_panel": { "platform": "template", - "panels": { - "test_template_panel": { - "arm_away": { - "service": "alarm_control_panel.alarm_arm_away", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_home": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_night": { - "service": "alarm_control_panel.alarm_arm_night", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "disarm": { - "service": "alarm_control_panel.alarm_disarm", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - } - }, + "panels": {"test_template_panel": OPTIMISTIC_TEMPLATE_ALARM_CONFIG}, } }, ], @@ -118,13 +124,15 @@ async def test_optimistic_states(hass, start_ha): await hass.async_block_till_done() assert state.state == "unknown" - for func, set_state in [ - (common.async_alarm_arm_away, STATE_ALARM_ARMED_AWAY), - (common.async_alarm_arm_home, STATE_ALARM_ARMED_HOME), - (common.async_alarm_arm_night, STATE_ALARM_ARMED_NIGHT), - (common.async_alarm_disarm, STATE_ALARM_DISARMED), + for service, set_state in [ + ("alarm_arm_away", STATE_ALARM_ARMED_AWAY), + ("alarm_arm_home", STATE_ALARM_ARMED_HOME), + ("alarm_arm_night", STATE_ALARM_ARMED_NIGHT), + ("alarm_disarm", STATE_ALARM_DISARMED), ]: - await func(hass, entity_id=TEMPLATE_NAME) + await hass.services.async_call( + ALARM_DOMAIN, service, {"entity_id": TEMPLATE_NAME}, blocking=True + ) await hass.async_block_till_done() assert hass.states.get(TEMPLATE_NAME).state == set_state @@ -140,26 +148,7 @@ async def test_optimistic_states(hass, start_ha): "panels": { "test_template_panel": { "value_template": "{% if blah %}", - "arm_away": { - "service": "alarm_control_panel.alarm_arm_away", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_home": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_night": { - "service": "alarm_control_panel.alarm_arm_night", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "disarm": { - "service": "alarm_control_panel.alarm_disarm", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, + **OPTIMISTIC_TEMPLATE_ALARM_CONFIG, } }, } @@ -173,26 +162,7 @@ async def test_optimistic_states(hass, start_ha): "panels": { "bad name here": { "value_template": "disarmed", - "arm_away": { - "service": "alarm_control_panel.alarm_arm_away", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_home": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_night": { - "service": "alarm_control_panel.alarm_arm_night", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "disarm": { - "service": "alarm_control_panel.alarm_disarm", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, + **OPTIMISTIC_TEMPLATE_ALARM_CONFIG, } }, } @@ -221,26 +191,7 @@ async def test_optimistic_states(hass, start_ha): "panels": { "test_template_panel": { "value_template": "disarmed", - "arm_away": { - "service": "alarm_control_panel.alarm_arm_away", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_home": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_night": { - "service": "alarm_control_panel.alarm_arm_night", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "disarm": { - "service": "alarm_control_panel.alarm_disarm", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, + **OPTIMISTIC_TEMPLATE_ALARM_CONFIG, "code_format": "bad_format", } }, @@ -267,26 +218,7 @@ async def test_template_syntax_error(hass, msg, start_ha, caplog_setup_text): "test_template_panel": { "name": "Template Alarm Panel", "value_template": "disarmed", - "arm_away": { - "service": "alarm_control_panel.alarm_arm_away", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_home": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_night": { - "service": "alarm_control_panel.alarm_arm_night", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "disarm": { - "service": "alarm_control_panel.alarm_disarm", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, + **OPTIMISTIC_TEMPLATE_ALARM_CONFIG, } }, } @@ -302,131 +234,34 @@ async def test_name(hass, start_ha): @pytest.mark.parametrize("count,domain", [(1, "alarm_control_panel")]) @pytest.mark.parametrize( - "config,func", + "config", [ - ( - { - "alarm_control_panel": { - "platform": "template", - "panels": { - "test_template_panel": { - "value_template": "{{ states('alarm_control_panel.test') }}", - "arm_away": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_home": {"service": "test.automation"}, - "arm_night": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "disarm": { - "service": "alarm_control_panel.alarm_disarm", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - } - }, - } - }, - common.async_alarm_arm_home, - ), - ( - { - "alarm_control_panel": { - "platform": "template", - "panels": { - "test_template_panel": { - "value_template": "{{ states('alarm_control_panel.test') }}", - "arm_home": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_away": {"service": "test.automation"}, - "arm_night": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "disarm": { - "service": "alarm_control_panel.alarm_disarm", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - } - }, - }, - }, - common.async_alarm_arm_away, - ), - ( - { - "alarm_control_panel": { - "platform": "template", - "panels": { - "test_template_panel": { - "value_template": "{{ states('alarm_control_panel.test') }}", - "arm_home": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_night": {"service": "test.automation"}, - "arm_away": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "disarm": { - "service": "alarm_control_panel.alarm_disarm", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - } - }, - } - }, - common.async_alarm_arm_night, - ), - ( - { - "alarm_control_panel": { - "platform": "template", - "panels": { - "test_template_panel": { - "value_template": "{{ states('alarm_control_panel.test') }}", - "arm_home": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "disarm": {"service": "test.automation"}, - "arm_away": { - "service": "alarm_control_panel.alarm_arm_home", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - "arm_night": { - "service": "alarm_control_panel.alarm_disarm", - "entity_id": "alarm_control_panel.test", - "data": {"code": "1234"}, - }, - } - }, - } - }, - common.async_alarm_disarm, - ), + { + "alarm_control_panel": { + "platform": "template", + "panels": {"test_template_panel": TEMPLATE_ALARM_CONFIG}, + } + }, ], ) -async def test_arm_home_action(hass, func, start_ha, calls): - """Test arm home action.""" - await func(hass, entity_id=TEMPLATE_NAME) +@pytest.mark.parametrize( + "service", + [ + "alarm_arm_home", + "alarm_arm_away", + "alarm_arm_night", + "alarm_disarm", + ], +) +async def test_actions(hass, service, start_ha, service_calls): + """Test alarm actions.""" + await hass.services.async_call( + ALARM_DOMAIN, service, {"entity_id": TEMPLATE_NAME}, blocking=True + ) await hass.async_block_till_done() - assert len(calls) == 1 + assert len(service_calls) == 1 + assert service_calls[0].data["service"] == service + assert service_calls[0].data["service_data"]["code"] == TEMPLATE_NAME @pytest.mark.parametrize("count,domain", [(1, "alarm_control_panel")]) diff --git a/tests/components/template/test_button.py b/tests/components/template/test_button.py index aa671bebd89..48bedd8e928 100644 --- a/tests/components/template/test_button.py +++ b/tests/components/template/test_button.py @@ -2,8 +2,6 @@ import datetime as dt from unittest.mock import patch -import pytest - from homeassistant import setup from homeassistant.components.button.const import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS from homeassistant.components.template.button import DEFAULT_NAME @@ -16,19 +14,13 @@ from homeassistant.const import ( ) from homeassistant.helpers.entity_registry import async_get -from tests.common import assert_setup_component, async_mock_service +from tests.common import assert_setup_component _TEST_BUTTON = "button.template_button" _TEST_OPTIONS_BUTTON = "button.test" -@pytest.fixture -def calls(hass): - """Track calls to a mock service.""" - return async_mock_service(hass, "test", "automation") - - -async def test_missing_optional_config(hass, calls): +async def test_missing_optional_config(hass): """Test: missing optional template is ok.""" with assert_setup_component(1, "template"): assert await setup.async_setup_component( @@ -50,7 +42,7 @@ async def test_missing_optional_config(hass, calls): _verify(hass, STATE_UNKNOWN) -async def test_missing_required_keys(hass, calls): +async def test_missing_required_keys(hass): """Test: missing required fields will fail.""" with assert_setup_component(0, "template"): assert await setup.async_setup_component( @@ -76,7 +68,10 @@ async def test_all_optional_config(hass, calls): "template": { "unique_id": "test", "button": { - "press": {"service": "test.automation"}, + "press": { + "service": "test.automation", + "data_template": {"caller": "{{ this.entity_id }}"}, + }, "device_class": "restart", "unique_id": "test", "name": "test", @@ -112,6 +107,7 @@ async def test_all_optional_config(hass, calls): ) assert len(calls) == 1 + assert calls[0].data["caller"] == _TEST_OPTIONS_BUTTON _verify( hass, @@ -128,7 +124,7 @@ async def test_all_optional_config(hass, calls): assert er.async_get_entity_id("button", "template", "test-test") -async def test_name_template(hass, calls): +async def test_name_template(hass): """Test: name template.""" with assert_setup_component(1, "template"): assert await setup.async_setup_component( @@ -158,7 +154,7 @@ async def test_name_template(hass, calls): ) -async def test_unique_id(hass, calls): +async def test_unique_id(hass): """Test: unique id is ok.""" with assert_setup_component(1, "template"): assert await setup.async_setup_component( diff --git a/tests/components/template/test_cover.py b/tests/components/template/test_cover.py index b4bc00ee6a2..d08e8587703 100644 --- a/tests/components/template/test_cover.py +++ b/tests/components/template/test_cover.py @@ -28,6 +28,24 @@ from tests.common import assert_setup_component ENTITY_COVER = "cover.test_template_cover" +OPEN_CLOSE_COVER_CONFIG = { + "open_cover": { + "service": "test.automation", + "data_template": { + "action": "open_cover", + "caller": "{{ this.entity_id }}", + }, + }, + "close_cover": { + "service": "test.automation", + "data_template": { + "action": "close_cover", + "caller": "{{ this.entity_id }}", + }, + }, +} + + @pytest.mark.parametrize("count,domain", [(1, DOMAIN)]) @pytest.mark.parametrize( "config, states", @@ -38,15 +56,8 @@ ENTITY_COVER = "cover.test_template_cover" "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "value_template": "{{ states.cover.test_state.state }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, } }, } @@ -90,16 +101,9 @@ ENTITY_COVER = "cover.test_template_cover" "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "position_template": "{{ states.cover.test.attributes.position }}", "value_template": "{{ states.cover.test_state.state }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, } }, } @@ -148,15 +152,8 @@ async def test_template_state_text(hass, states, start_ha, caplog): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "value_template": "{{ 1 == 1 }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, } }, } @@ -178,15 +175,8 @@ async def test_template_state_boolean(hass, start_ha): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "position_template": "{{ states.cover.test.attributes.position }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test", - }, } }, } @@ -202,11 +192,8 @@ async def test_template_position(hass, start_ha): (STATE_CLOSED, 42, STATE_OPEN), (STATE_OPEN, 0.0, STATE_CLOSED), ]: - state = hass.states.async_set("cover.test", set_state) - await hass.async_block_till_done() - entity = hass.states.get("cover.test") attrs["position"] = pos - hass.states.async_set(entity.entity_id, entity.state, attributes=attrs) + hass.states.async_set("cover.test", set_state, attributes=attrs) await hass.async_block_till_done() state = hass.states.get("cover.test_template_cover") assert state.attributes.get("current_position") == pos @@ -222,16 +209,9 @@ async def test_template_position(hass, start_ha): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "value_template": "{{ 1 == 1 }}", "tilt_template": "{{ 42 }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, } }, } @@ -253,16 +233,9 @@ async def test_template_tilt(hass, start_ha): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "position_template": "{{ -1 }}", "tilt_template": "{{ 110 }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, } }, } @@ -272,20 +245,13 @@ async def test_template_tilt(hass, start_ha): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "position_template": "{{ on }}", "tilt_template": "{% if states.cover.test_state.state %}" "on" "{% else %}" "off" "{% endif %}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, }, }, } @@ -316,8 +282,11 @@ async def test_template_out_of_bounds(hass, start_ha): "test_template_cover": { "value_template": "{{ 1 == 1 }}", "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", + "service": "test.automation", + "data_template": { + "action": "open_cover", + "caller": "{{ this.entity_id }}", + }, }, } }, @@ -340,12 +309,8 @@ async def test_template_open_or_position(hass, start_ha, caplog_setup_text): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "position_template": "{{ 0 }}", - "open_cover": {"service": "test.automation"}, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, } }, } @@ -363,6 +328,8 @@ async def test_open_action(hass, start_ha, calls): await hass.async_block_till_done() assert len(calls) == 1 + assert calls[0].data["action"] == "open_cover" + assert calls[0].data["caller"] == "cover.test_template_cover" @pytest.mark.parametrize("count,domain", [(1, DOMAIN)]) @@ -374,13 +341,15 @@ async def test_open_action(hass, start_ha, calls): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "position_template": "{{ 100 }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", + "stop_cover": { + "service": "test.automation", + "data_template": { + "action": "stop_cover", + "caller": "{{ this.entity_id }}", + }, }, - "close_cover": {"service": "test.automation"}, - "stop_cover": {"service": "test.automation"}, } }, } @@ -403,6 +372,10 @@ async def test_close_stop_action(hass, start_ha, calls): await hass.async_block_till_done() assert len(calls) == 2 + assert calls[0].data["action"] == "close_cover" + assert calls[0].data["caller"] == "cover.test_template_cover" + assert calls[1].data["action"] == "stop_cover" + assert calls[1].data["caller"] == "cover.test_template_cover" @pytest.mark.parametrize("count,domain", [(1, "input_number")]) @@ -423,11 +396,13 @@ async def test_set_position(hass, start_ha, calls): "platform": "template", "covers": { "test_template_cover": { - "position_template": "{{ states.input_number.test.state | int }}", "set_cover_position": { - "service": "input_number.set_value", - "entity_id": "input_number.test", - "data_template": {"value": "{{ position }}"}, + "service": "test.automation", + "data_template": { + "action": "set_cover_position", + "caller": "{{ this.entity_id }}", + "position": "{{ position }}", + }, }, } }, @@ -450,6 +425,10 @@ async def test_set_position(hass, start_ha, calls): await hass.async_block_till_done() state = hass.states.get("cover.test_template_cover") assert state.attributes.get("current_position") == 100.0 + assert len(calls) == 1 + assert calls[-1].data["action"] == "set_cover_position" + assert calls[-1].data["caller"] == "cover.test_template_cover" + assert calls[-1].data["position"] == 100 await hass.services.async_call( DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True @@ -457,6 +436,10 @@ async def test_set_position(hass, start_ha, calls): await hass.async_block_till_done() state = hass.states.get("cover.test_template_cover") assert state.attributes.get("current_position") == 0.0 + assert len(calls) == 2 + assert calls[-1].data["action"] == "set_cover_position" + assert calls[-1].data["caller"] == "cover.test_template_cover" + assert calls[-1].data["position"] == 0 await hass.services.async_call( DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True @@ -464,6 +447,10 @@ async def test_set_position(hass, start_ha, calls): await hass.async_block_till_done() state = hass.states.get("cover.test_template_cover") assert state.attributes.get("current_position") == 100.0 + assert len(calls) == 3 + assert calls[-1].data["action"] == "set_cover_position" + assert calls[-1].data["caller"] == "cover.test_template_cover" + assert calls[-1].data["position"] == 100 await hass.services.async_call( DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True @@ -471,6 +458,10 @@ async def test_set_position(hass, start_ha, calls): await hass.async_block_till_done() state = hass.states.get("cover.test_template_cover") assert state.attributes.get("current_position") == 0.0 + assert len(calls) == 4 + assert calls[-1].data["action"] == "set_cover_position" + assert calls[-1].data["caller"] == "cover.test_template_cover" + assert calls[-1].data["position"] == 0 await hass.services.async_call( DOMAIN, @@ -481,6 +472,10 @@ async def test_set_position(hass, start_ha, calls): await hass.async_block_till_done() state = hass.states.get("cover.test_template_cover") assert state.attributes.get("current_position") == 25.0 + assert len(calls) == 5 + assert calls[-1].data["action"] == "set_cover_position" + assert calls[-1].data["caller"] == "cover.test_template_cover" + assert calls[-1].data["position"] == 25 @pytest.mark.parametrize("count,domain", [(1, DOMAIN)]) @@ -492,16 +487,15 @@ async def test_set_position(hass, start_ha, calls): "platform": "template", "covers": { "test_template_cover": { - "position_template": "{{ 100 }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", + **OPEN_CLOSE_COVER_CONFIG, + "set_cover_tilt_position": { + "service": "test.automation", + "data_template": { + "action": "set_cover_tilt_position", + "caller": "{{ this.entity_id }}", + "tilt_position": "{{ tilt }}", + }, }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, - "set_cover_tilt_position": {"service": "test.automation"}, } }, } @@ -509,17 +503,18 @@ async def test_set_position(hass, start_ha, calls): ], ) @pytest.mark.parametrize( - "service,attr", + "service,attr,tilt_position", [ ( SERVICE_SET_COVER_TILT_POSITION, {ATTR_ENTITY_ID: ENTITY_COVER, ATTR_TILT_POSITION: 42}, + 42, ), - (SERVICE_OPEN_COVER_TILT, {ATTR_ENTITY_ID: ENTITY_COVER}), - (SERVICE_CLOSE_COVER_TILT, {ATTR_ENTITY_ID: ENTITY_COVER}), + (SERVICE_OPEN_COVER_TILT, {ATTR_ENTITY_ID: ENTITY_COVER}, 100), + (SERVICE_CLOSE_COVER_TILT, {ATTR_ENTITY_ID: ENTITY_COVER}, 0), ], ) -async def test_set_tilt_position(hass, service, attr, start_ha, calls): +async def test_set_tilt_position(hass, service, attr, start_ha, calls, tilt_position): """Test the set_tilt_position command.""" await hass.services.async_call( DOMAIN, @@ -530,6 +525,9 @@ async def test_set_tilt_position(hass, service, attr, start_ha, calls): await hass.async_block_till_done() assert len(calls) == 1 + assert calls[-1].data["action"] == "set_cover_tilt_position" + assert calls[-1].data["caller"] == "cover.test_template_cover" + assert calls[-1].data["tilt_position"] == tilt_position @pytest.mark.parametrize("count,domain", [(1, DOMAIN)]) @@ -633,15 +631,8 @@ async def test_set_tilt_position_optimistic(hass, start_ha, calls): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "value_template": "{{ states.cover.test_state.state }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, "icon_template": "{% if states.cover.test_state.state %}" "mdi:check" "{% endif %}", @@ -673,15 +664,8 @@ async def test_icon_template(hass, start_ha): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "value_template": "{{ states.cover.test_state.state }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, "entity_picture_template": "{% if states.cover.test_state.state %}" "/local/cover.png" "{% endif %}", @@ -713,15 +697,8 @@ async def test_entity_picture_template(hass, start_ha): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "value_template": "open", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, "availability_template": "{{ is_state('availability_state.state','on') }}", } }, @@ -751,15 +728,8 @@ async def test_availability_template(hass, start_ha): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "value_template": "open", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, } }, } @@ -781,16 +751,9 @@ async def test_availability_without_availability_template(hass, start_ha): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "availability_template": "{{ x - 12 }}", "value_template": "open", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, } }, } @@ -814,16 +777,9 @@ async def test_invalid_availability_template_keeps_component_available( "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "value_template": "{{ states.cover.test_state.state }}", "device_class": "door", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, } }, } @@ -845,16 +801,9 @@ async def test_device_class(hass, start_ha): "platform": "template", "covers": { "test_template_cover": { + **OPEN_CLOSE_COVER_CONFIG, "value_template": "{{ states.cover.test_state.state }}", "device_class": "barnacle_bill", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, } }, } @@ -876,28 +825,14 @@ async def test_invalid_device_class(hass, start_ha): "platform": "template", "covers": { "test_template_cover_01": { + **OPEN_CLOSE_COVER_CONFIG, "unique_id": "not-so-unique-anymore", "value_template": "{{ true }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, }, "test_template_cover_02": { + **OPEN_CLOSE_COVER_CONFIG, "unique_id": "not-so-unique-anymore", "value_template": "{{ false }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, }, }, } @@ -918,16 +853,9 @@ async def test_unique_id(hass, start_ha): "platform": "template", "covers": { "garage_door": { + **OPEN_CLOSE_COVER_CONFIG, "friendly_name": "Garage Door", "value_template": "{{ is_state('binary_sensor.garage_door_sensor', 'off') }}", - "open_cover": { - "service": "cover.open_cover", - "entity_id": "cover.test_state", - }, - "close_cover": { - "service": "cover.close_cover", - "entity_id": "cover.test_state", - }, }, }, } diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index ccd273571b5..f7805ae41d6 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -383,17 +383,22 @@ async def test_invalid_availability_template_keeps_component_available( assert "x" in caplog_setup_text -async def test_on_off(hass): +async def test_on_off(hass, calls): """Test turn on and turn off.""" await _register_components(hass) + expected_calls = 0 - for func, state in [ - (common.async_turn_on, STATE_ON), - (common.async_turn_off, STATE_OFF), + for func, state, action in [ + (common.async_turn_on, STATE_ON, "turn_on"), + (common.async_turn_off, STATE_OFF, "turn_off"), ]: await func(hass, _TEST_FAN) assert hass.states.get(_STATE_INPUT_BOOLEAN).state == state _verify(hass, state, 0, None, None, None) + expected_calls += 1 + assert len(calls) == expected_calls + assert calls[-1].data["action"] == action + assert calls[-1].data["caller"] == _TEST_FAN async def test_set_invalid_direction_from_initial_stage(hass, calls): @@ -407,29 +412,43 @@ async def test_set_invalid_direction_from_initial_stage(hass, calls): _verify(hass, STATE_ON, 0, None, None, None) -async def test_set_osc(hass): +async def test_set_osc(hass, calls): """Test set oscillating.""" await _register_components(hass) + expected_calls = 0 await common.async_turn_on(hass, _TEST_FAN) + expected_calls += 1 for state in [True, False]: await common.async_oscillate(hass, _TEST_FAN, state) assert hass.states.get(_OSC_INPUT).state == str(state) _verify(hass, STATE_ON, 0, state, None, None) + expected_calls += 1 + assert len(calls) == expected_calls + assert calls[-1].data["action"] == "set_oscillating" + assert calls[-1].data["caller"] == _TEST_FAN + assert calls[-1].data["option"] == state -async def test_set_direction(hass): +async def test_set_direction(hass, calls): """Test set valid direction.""" await _register_components(hass) + expected_calls = 0 await common.async_turn_on(hass, _TEST_FAN) + expected_calls += 1 for cmd in [DIRECTION_FORWARD, DIRECTION_REVERSE]: await common.async_set_direction(hass, _TEST_FAN, cmd) assert hass.states.get(_DIRECTION_INPUT_SELECT).state == cmd _verify(hass, STATE_ON, 0, None, cmd, None) + expected_calls += 1 + assert len(calls) == expected_calls + assert calls[-1].data["action"] == "set_direction" + assert calls[-1].data["caller"] == _TEST_FAN + assert calls[-1].data["option"] == cmd -async def test_set_invalid_direction(hass): +async def test_set_invalid_direction(hass, calls): """Test set invalid direction when fan has valid direction.""" await _register_components(hass) @@ -440,30 +459,36 @@ async def test_set_invalid_direction(hass): _verify(hass, STATE_ON, 0, None, DIRECTION_FORWARD, None) -async def test_preset_modes(hass): +async def test_preset_modes(hass, calls): """Test preset_modes.""" await _register_components( hass, ["off", "low", "medium", "high", "auto", "smart"], ["auto", "smart"] ) await common.async_turn_on(hass, _TEST_FAN) - for extra, state in [ - ("auto", "auto"), - ("smart", "smart"), - ("invalid", "smart"), + for extra, state, expected_calls in [ + ("auto", "auto", 2), + ("smart", "smart", 3), + ("invalid", "smart", 3), ]: await common.async_set_preset_mode(hass, _TEST_FAN, extra) assert hass.states.get(_PRESET_MODE_INPUT_SELECT).state == state + assert len(calls) == expected_calls + assert calls[-1].data["action"] == "set_preset_mode" + assert calls[-1].data["caller"] == _TEST_FAN + assert calls[-1].data["option"] == state await common.async_turn_on(hass, _TEST_FAN, preset_mode="auto") assert hass.states.get(_PRESET_MODE_INPUT_SELECT).state == "auto" -async def test_set_percentage(hass): +async def test_set_percentage(hass, calls): """Test set valid speed percentage.""" await _register_components(hass) + expected_calls = 0 await common.async_turn_on(hass, _TEST_FAN) + expected_calls += 1 for state, value in [ (STATE_ON, 100), (STATE_ON, 66), @@ -472,13 +497,18 @@ async def test_set_percentage(hass): await common.async_set_percentage(hass, _TEST_FAN, value) assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == value _verify(hass, state, value, None, None, None) + expected_calls += 1 + assert len(calls) == expected_calls + assert calls[-1].data["action"] == "set_value" + assert calls[-1].data["caller"] == _TEST_FAN + assert calls[-1].data["value"] == value await common.async_turn_on(hass, _TEST_FAN, percentage=50) assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 50 _verify(hass, STATE_ON, 50, None, None, None) -async def test_increase_decrease_speed(hass): +async def test_increase_decrease_speed(hass, calls): """Test set valid increase and decrease speed.""" await _register_components(hass, speed_count=3) @@ -495,7 +525,7 @@ async def test_increase_decrease_speed(hass): _verify(hass, state, value, None, None, None) -async def test_increase_decrease_speed_default_speed_count(hass): +async def test_increase_decrease_speed_default_speed_count(hass, calls): """Test set valid increase and decrease speed.""" await _register_components(hass) @@ -512,7 +542,7 @@ async def test_increase_decrease_speed_default_speed_count(hass): _verify(hass, state, value, None, None, None) -async def test_set_invalid_osc_from_initial_state(hass): +async def test_set_invalid_osc_from_initial_state(hass, calls): """Test set invalid oscillating when fan is in initial state.""" await _register_components(hass) @@ -523,7 +553,7 @@ async def test_set_invalid_osc_from_initial_state(hass): _verify(hass, STATE_ON, 0, None, None, None) -async def test_set_invalid_osc(hass): +async def test_set_invalid_osc(hass, calls): """Test set invalid oscillating when fan has valid osc.""" await _register_components(hass) @@ -616,10 +646,19 @@ async def _register_components( "percentage_template": "{{ states('input_number.percentage') }}", "oscillating_template": "{{ states('input_select.osc') }}", "direction_template": "{{ states('input_select.direction') }}", - "turn_on": { - "service": "input_boolean.turn_on", - "entity_id": _STATE_INPUT_BOOLEAN, - }, + "turn_on": [ + { + "service": "input_boolean.turn_on", + "entity_id": _STATE_INPUT_BOOLEAN, + }, + { + "service": "test.automation", + "data_template": { + "action": "turn_on", + "caller": "{{ this.entity_id }}", + }, + }, + ], "turn_off": [ { "service": "input_boolean.turn_off", @@ -632,35 +671,82 @@ async def _register_components( "value": 0, }, }, + { + "service": "test.automation", + "data_template": { + "action": "turn_off", + "caller": "{{ this.entity_id }}", + }, + }, ], - "set_preset_mode": { - "service": "input_select.select_option", - "data_template": { - "entity_id": _PRESET_MODE_INPUT_SELECT, - "option": "{{ preset_mode }}", + "set_preset_mode": [ + { + "service": "input_select.select_option", + "data_template": { + "entity_id": _PRESET_MODE_INPUT_SELECT, + "option": "{{ preset_mode }}", + }, }, - }, - "set_percentage": { - "service": "input_number.set_value", - "data_template": { - "entity_id": _PERCENTAGE_INPUT_NUMBER, - "value": "{{ percentage }}", + { + "service": "test.automation", + "data_template": { + "action": "set_preset_mode", + "caller": "{{ this.entity_id }}", + "option": "{{ preset_mode }}", + }, }, - }, - "set_oscillating": { - "service": "input_select.select_option", - "data_template": { - "entity_id": _OSC_INPUT, - "option": "{{ oscillating }}", + ], + "set_percentage": [ + { + "service": "input_number.set_value", + "data_template": { + "entity_id": _PERCENTAGE_INPUT_NUMBER, + "value": "{{ percentage }}", + }, }, - }, - "set_direction": { - "service": "input_select.select_option", - "data_template": { - "entity_id": _DIRECTION_INPUT_SELECT, - "option": "{{ direction }}", + { + "service": "test.automation", + "data_template": { + "action": "set_value", + "caller": "{{ this.entity_id }}", + "value": "{{ percentage }}", + }, }, - }, + ], + "set_oscillating": [ + { + "service": "input_select.select_option", + "data_template": { + "entity_id": _OSC_INPUT, + "option": "{{ oscillating }}", + }, + }, + { + "service": "test.automation", + "data_template": { + "action": "set_oscillating", + "caller": "{{ this.entity_id }}", + "option": "{{ oscillating }}", + }, + }, + ], + "set_direction": [ + { + "service": "input_select.select_option", + "data_template": { + "entity_id": _DIRECTION_INPUT_SELECT, + "option": "{{ direction }}", + }, + }, + { + "service": "test.automation", + "data_template": { + "action": "set_direction", + "caller": "{{ this.entity_id }}", + "option": "{{ direction }}", + }, + }, + ], } if preset_modes: diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index 642fa5601cf..ca03c6f5d82 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -1,5 +1,4 @@ """The tests for the Template light platform.""" -import logging import pytest @@ -11,6 +10,12 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + SUPPORT_TRANSITION, + SUPPORT_WHITE_VALUE, + ColorMode, LightEntityFeature, ) from homeassistant.const import ( @@ -21,224 +26,266 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) +from homeassistant.setup import async_setup_component -_LOGGER = logging.getLogger(__name__) +from tests.common import assert_setup_component # Represent for light's availability _STATE_AVAILABILITY_BOOLEAN = "availability_boolean.state" -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +OPTIMISTIC_ON_OFF_LIGHT_CONFIG = { + "turn_on": { + "service": "test.automation", + "data_template": { + "action": "turn_on", + "caller": "{{ this.entity_id }}", + }, + }, + "turn_off": { + "service": "test.automation", + "data_template": { + "action": "turn_off", + "caller": "{{ this.entity_id }}", + }, + }, +} + + +OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG = { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "set_level": { + "service": "test.automation", + "data_template": { + "action": "set_level", + "brightness": "{{brightness}}", + "caller": "{{ this.entity_id }}", + }, + }, +} + + +OPTIMISTIC_COLOR_TEMP_LIGHT_CONFIG = { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "set_temperature": { + "service": "test.automation", + "data_template": { + "action": "set_temperature", + "caller": "{{ this.entity_id }}", + "color_temp": "{{color_temp}}", + }, + }, +} + + +OPTIMISTIC_HS_COLOR_LIGHT_CONFIG = { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "set_color": { + "service": "test.automation", + "data_template": { + "action": "set_color", + "caller": "{{ this.entity_id }}", + "s": "{{s}}", + "h": "{{h}}", + }, + }, +} + + +OPTIMISTIC_WHITE_VALUE_LIGHT_CONFIG = { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "set_white_value": { + "service": "test.automation", + "data_template": { + "action": "set_white_value", + "caller": "{{ this.entity_id }}", + "white_value": "{{white_value}}", + }, + }, +} + + +async def async_setup_light(hass, count, light_config): + """Do setup of light integration.""" + config = {"light": {"platform": "template", "lights": light_config}} + + with assert_setup_component(count, light.DOMAIN): + assert await async_setup_component( + hass, + light.DOMAIN, + config, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + +@pytest.fixture +async def setup_light(hass, count, light_config): + """Do setup of light integration.""" + await async_setup_light(hass, count, light_config) + + +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes", + [(SUPPORT_BRIGHTNESS, [ColorMode.BRIGHTNESS])], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{states.test['big.fat...']}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": "{{states.test['big.fat...']}}", } }, ], ) -async def test_template_state_invalid(hass, start_ha): +async def test_template_state_invalid( + hass, supported_features, supported_color_modes, setup_light +): """Test template state with render error.""" - assert hass.states.get("light.test_template_light").state == STATE_OFF + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + assert "color_mode" not in state.attributes + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes", + [(SUPPORT_BRIGHTNESS, [ColorMode.BRIGHTNESS])], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ states.light.test_state.state }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": "{{ states.light.test_state.state }}", } }, ], ) -async def test_template_state_text(hass, start_ha): +async def test_template_state_text( + hass, supported_features, supported_color_modes, setup_light +): """Test the state text of a template.""" - for set_state in [STATE_ON, STATE_OFF]: - hass.states.async_set("light.test_state", set_state) - await hass.async_block_till_done() - assert hass.states.get("light.test_template_light").state == set_state + set_state = STATE_ON + hass.states.async_set("light.test_state", set_state) + await hass.async_block_till_done() + state = hass.states.get("light.test_template_light") + assert state.state == set_state + assert state.attributes["color_mode"] == ColorMode.UNKNOWN # Brightness is None + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features + + set_state = STATE_OFF + hass.states.async_set("light.test_state", set_state) + await hass.async_block_till_done() + state = hass.states.get("light.test_template_light") + assert state.state == set_state + assert "color_mode" not in state.attributes + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config_addon,expected_state", - [ - ({"replace1": '"{{ 1 == 1 }}"'}, STATE_ON), - ({"replace1": '"{{ 1 == 2 }}"'}, STATE_OFF), - ], + "supported_features,supported_color_modes", + [(SUPPORT_BRIGHTNESS, [ColorMode.BRIGHTNESS])], ) @pytest.mark.parametrize( - "config", + "value_template,expected_state,expected_color_mode", [ - """{ - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": replace1, - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state" - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state" - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}" - } - } - } - } - } - }""", + ( + "{{ 1 == 1 }}", + STATE_ON, + ColorMode.UNKNOWN, + ), + ( + "{{ 1 == 2 }}", + STATE_OFF, + None, + ), ], ) -async def test_templatex_state_boolean(hass, expected_state, start_ha): +async def test_templatex_state_boolean( + hass, + expected_color_mode, + expected_state, + supported_features, + supported_color_modes, + count, + value_template, +): """Test the setting of the state with boolean on.""" - assert hass.states.get("light.test_template_light").state == expected_state + light_config = { + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": value_template, + } + } + await async_setup_light(hass, count, light_config) + state = hass.states.get("light.test_template_light") + assert state.state == expected_state + assert state.attributes.get("color_mode") == expected_color_mode + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(0, light.DOMAIN)]) +@pytest.mark.parametrize("count", [0]) @pytest.mark.parametrize( - "config", + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{%- if false -%}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": "{%- if false -%}", } }, { - "light": { - "platform": "template", - "lights": { - "bad name here": { - "value_template": "{{ 1== 1}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - { - "light": { - "platform": "template", - "switches": {"test_template_light": "Invalid"}, + "bad name here": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": "{{ 1== 1}}", } }, + {"test_template_light": "Invalid"}, ], ) -async def test_template_syntax_error(hass, start_ha): +async def test_template_syntax_error(hass, setup_light): """Test templating syntax error.""" assert hass.states.async_all("light") == [] -SET_VAL1 = '"value_template": "{{ 1== 1}}",' -SET_VAL2 = '"turn_on": {"service": "light.turn_on","entity_id": "light.test_state"},' -SET_VAL3 = '"turn_off": {"service": "light.turn_off","entity_id": "light.test_state"},' - - -@pytest.mark.parametrize("domain", [light.DOMAIN]) @pytest.mark.parametrize( - "config_addon, count", + "light_config, count", [ - ({"replace2": f"{SET_VAL2}{SET_VAL3}"}, 1), - ({"replace2": f"{SET_VAL1}{SET_VAL2}"}, 0), - ({"replace2": f"{SET_VAL2}{SET_VAL3}"}, 1), + ( + { + "light_one": { + "value_template": "{{ 1== 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, + }, + } + }, + 0, + ), ], ) -@pytest.mark.parametrize( - "config", - [ - """{"light": {"platform": "template", "lights": { - "light_one": { - replace2 - "set_level": {"service": "light.turn_on", - "data_template": {"entity_id": "light.test_state","brightness": "{{brightness}}" - }}}}}}""" - ], -) -async def test_missing_key(hass, count, start_ha): +async def test_missing_key(hass, count, setup_light): """Test missing template.""" if count: assert hass.states.async_all("light") != [] @@ -246,41 +293,34 @@ async def test_missing_key(hass, count, start_ha): assert hass.states.async_all("light") == [] -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes", + [(SUPPORT_BRIGHTNESS, [ColorMode.BRIGHTNESS])], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{states.light.test_state.state}}", - "turn_on": {"service": "test.automation"}, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": "{{states.light.test_state.state}}", } }, ], ) -async def test_on_action(hass, start_ha, calls): +async def test_on_action( + hass, setup_light, calls, supported_features, supported_color_modes +): """Test on action.""" hass.states.async_set("light.test_state", STATE_OFF) await hass.async_block_till_done() state = hass.states.get("light.test_template_light") assert state.state == STATE_OFF + assert "color_mode" not in state.attributes + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features await hass.services.async_call( light.DOMAIN, @@ -290,50 +330,61 @@ async def test_on_action(hass, start_ha, calls): ) assert len(calls) == 1 + assert calls[-1].data["action"] == "turn_on" + assert calls[-1].data["caller"] == "light.test_template_light" + + assert state.state == STATE_OFF + assert "color_mode" not in state.attributes + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes", + [(SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION, [ColorMode.BRIGHTNESS])], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{states.light.test_state.state}}", - "turn_on": { - "service": "test.automation", - "data_template": { - "transition": "{{transition}}", - }, - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "supports_transition_template": "{{true}}", - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - "transition": "{{transition}}", - }, - }, - } + "test_template_light": { + "value_template": "{{states.light.test_state.state}}", + "turn_on": { + "service": "test.automation", + "data_template": { + "transition": "{{transition}}", + }, + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "supports_transition_template": "{{true}}", + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + "transition": "{{transition}}", + }, }, } }, ], ) -async def test_on_action_with_transition(hass, start_ha, calls): +async def test_on_action_with_transition( + hass, setup_light, calls, supported_features, supported_color_modes +): """Test on action with transition.""" hass.states.async_set("light.test_state", STATE_OFF) await hass.async_block_till_done() state = hass.states.get("light.test_template_light") assert state.state == STATE_OFF + assert "color_mode" not in state.attributes + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features await hass.services.async_call( light.DOMAIN, @@ -345,41 +396,44 @@ async def test_on_action_with_transition(hass, start_ha, calls): assert len(calls) == 1 assert calls[0].data["transition"] == 5 + assert state.state == STATE_OFF + assert "color_mode" not in state.attributes + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) + +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes,expected_color_mode", + [(SUPPORT_BRIGHTNESS, [ColorMode.BRIGHTNESS], ColorMode.BRIGHTNESS)], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "turn_on": {"service": "test.automation"}, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, } }, ], ) -async def test_on_action_optimistic(hass, start_ha, calls): +async def test_on_action_optimistic( + hass, + setup_light, + calls, + supported_features, + supported_color_modes, + expected_color_mode, +): """Test on action with optimistic state.""" hass.states.async_set("light.test_state", STATE_OFF) await hass.async_block_till_done() state = hass.states.get("light.test_template_light") assert state.state == STATE_OFF + assert "color_mode" not in state.attributes + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features await hass.services.async_call( light.DOMAIN, @@ -390,46 +444,59 @@ async def test_on_action_optimistic(hass, start_ha, calls): state = hass.states.get("light.test_template_light") assert len(calls) == 1 + assert calls[-1].data["action"] == "turn_on" + assert calls[-1].data["caller"] == "light.test_template_light" assert state.state == STATE_ON + assert state.attributes["color_mode"] == ColorMode.UNKNOWN # Brightness is None + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features + + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_BRIGHTNESS: 100}, + blocking=True, + ) + + state = hass.states.get("light.test_template_light") + assert len(calls) == 2 + assert calls[-1].data["action"] == "set_level" + assert calls[-1].data["brightness"] == 100 + assert calls[-1].data["caller"] == "light.test_template_light" + assert state.state == STATE_ON + assert state.attributes["color_mode"] == expected_color_mode + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes", + [(SUPPORT_BRIGHTNESS, [ColorMode.BRIGHTNESS])], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{states.light.test_state.state}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "test.automation", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": "{{states.light.test_state.state}}", } }, ], ) -async def test_off_action(hass, start_ha, calls): +async def test_off_action( + hass, setup_light, calls, supported_features, supported_color_modes +): """Test off action.""" hass.states.async_set("light.test_state", STATE_ON) await hass.async_block_till_done() state = hass.states.get("light.test_template_light") assert state.state == STATE_ON + assert state.attributes["color_mode"] == ColorMode.UNKNOWN # Brightness is None + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features await hass.services.async_call( light.DOMAIN, @@ -439,50 +506,60 @@ async def test_off_action(hass, start_ha, calls): ) assert len(calls) == 1 + assert calls[-1].data["action"] == "turn_off" + assert calls[-1].data["caller"] == "light.test_template_light" + assert state.state == STATE_ON + assert state.attributes["color_mode"] == ColorMode.UNKNOWN # Brightness is None + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [(1)]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes", + [(SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION, [ColorMode.BRIGHTNESS])], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{states.light.test_state.state}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "test.automation", - "data_template": { - "transition": "{{transition}}", - }, - }, - "supports_transition_template": "{{true}}", - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - "transition": "{{transition}}", - }, - }, - } + "test_template_light": { + "value_template": "{{states.light.test_state.state}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "test.automation", + "data_template": { + "transition": "{{transition}}", + }, + }, + "supports_transition_template": "{{true}}", + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + "transition": "{{transition}}", + }, }, } }, ], ) -async def test_off_action_with_transition(hass, start_ha, calls): +async def test_off_action_with_transition( + hass, setup_light, calls, supported_features, supported_color_modes +): """Test off action with transition.""" hass.states.async_set("light.test_state", STATE_ON) await hass.async_block_till_done() state = hass.states.get("light.test_template_light") assert state.state == STATE_ON + assert state.attributes["color_mode"] == ColorMode.UNKNOWN # Brightness is None + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features await hass.services.async_call( light.DOMAIN, @@ -493,39 +570,36 @@ async def test_off_action_with_transition(hass, start_ha, calls): assert len(calls) == 1 assert calls[0].data["transition"] == 2 + assert state.state == STATE_ON + assert state.attributes["color_mode"] == ColorMode.UNKNOWN # Brightness is None + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes", + [(SUPPORT_BRIGHTNESS, [ColorMode.BRIGHTNESS])], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": {"service": "test.automation"}, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, } }, ], ) -async def test_off_action_optimistic(hass, start_ha, calls): +async def test_off_action_optimistic( + hass, setup_light, calls, supported_features, supported_color_modes +): """Test off action with optimistic state.""" state = hass.states.get("light.test_template_light") assert state.state == STATE_OFF + assert "color_mode" not in state.attributes + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features await hass.services.async_call( light.DOMAIN, @@ -537,40 +611,35 @@ async def test_off_action_optimistic(hass, start_ha, calls): assert len(calls) == 1 state = hass.states.get("light.test_template_light") assert state.state == STATE_OFF + assert "color_mode" not in state.attributes + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes,expected_color_mode", + [(SUPPORT_WHITE_VALUE, [ColorMode.RGBW], ColorMode.UNKNOWN)], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{1 == 1}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_white_value": { - "service": "test.automation", - "data_template": { - "entity_id": "test.test_state", - "white_value": "{{white_value}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_WHITE_VALUE_LIGHT_CONFIG, + "value_template": "{{1 == 1}}", } }, ], ) -async def test_white_value_action_no_template(hass, start_ha, calls): +async def test_white_value_action_no_template( + hass, + setup_light, + calls, + supported_color_modes, + supported_features, + expected_color_mode, +): """Test setting white value with optimistic template.""" state = hass.states.get("light.test_template_light") assert state.attributes.get("white_value") is None @@ -583,79 +652,85 @@ async def test_white_value_action_no_template(hass, start_ha, calls): ) assert len(calls) == 1 - assert calls[0].data["white_value"] == 124 + assert calls[-1].data["action"] == "set_white_value" + assert calls[-1].data["caller"] == "light.test_template_light" + assert calls[-1].data["white_value"] == 124 state = hass.states.get("light.test_template_light") - assert state is not None assert state.attributes.get("white_value") == 124 + assert state.state == STATE_ON + assert state.attributes["color_mode"] == expected_color_mode # hs_color is None + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "expected_white_value,config_addon", + "supported_features,supported_color_modes,expected_color_mode", + [(SUPPORT_WHITE_VALUE, [ColorMode.RGBW], ColorMode.UNKNOWN)], +) +@pytest.mark.parametrize( + "expected_white_value,white_value_template", [ - (255, {"replace3": "{{255}}"}), - (None, {"replace3": "{{256}}"}), - (None, {"replace3": "{{x - 12}}"}), - (None, {"replace3": "{{ none }}"}), - (None, {"replace3": ""}), + (255, "{{255}}"), + (None, "{{256}}"), + (None, "{{x-12}}"), + (None, "{{ none }}"), + (None, ""), ], ) -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) -@pytest.mark.parametrize( - "config", - [ - """{ - "light": {"platform": "template","lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": {"service": "light.turn_on","entity_id": "light.test_state"}, - "turn_off": {"service": "light.turn_off","entity_id": "light.test_state"}, - "set_white_value": {"service": "light.turn_on", - "data_template": {"entity_id": "light.test_state", - "white_value": "{{white_value}}"}}, - "white_value_template": "replace3" - }}}}""", - ], -) -async def test_white_value_template(hass, expected_white_value, start_ha): +async def test_white_value_template( + hass, + expected_white_value, + supported_features, + supported_color_modes, + expected_color_mode, + count, + white_value_template, +): """Test the template for the white value.""" + light_config = { + "test_template_light": { + **OPTIMISTIC_WHITE_VALUE_LIGHT_CONFIG, + "value_template": "{{ 1 == 1 }}", + "white_value_template": white_value_template, + } + } + await async_setup_light(hass, count, light_config) + state = hass.states.get("light.test_template_light") assert state is not None assert state.attributes.get("white_value") == expected_white_value + assert state.state == STATE_ON + assert state.attributes["color_mode"] == expected_color_mode # hs_color is None + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes,expected_color_mode", + [(SUPPORT_BRIGHTNESS, [ColorMode.BRIGHTNESS], ColorMode.BRIGHTNESS)], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{1 == 1}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "test.automation", - "data_template": { - "entity_id": "test.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": "{{1 == 1}}", } }, ], ) -async def test_level_action_no_template(hass, start_ha, calls): +async def test_level_action_no_template( + hass, + setup_light, + calls, + supported_features, + supported_color_modes, + expected_color_mode, +): """Test setting brightness with optimistic template.""" state = hass.states.get("light.test_template_light") assert state.attributes.get("brightness") is None @@ -668,129 +743,130 @@ async def test_level_action_no_template(hass, start_ha, calls): ) assert len(calls) == 1 - assert calls[0].data["brightness"] == 124 + assert calls[-1].data["action"] == "set_level" + assert calls[-1].data["brightness"] == 124 + assert calls[-1].data["caller"] == "light.test_template_light" state = hass.states.get("light.test_template_light") - _LOGGER.info(str(state.attributes)) - assert state is not None - assert state.attributes.get("brightness") == 124 + assert state.state == STATE_ON + assert state.attributes["brightness"] == 124 + assert state.attributes["color_mode"] == expected_color_mode + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "expected_level,config_addon", + "expected_level,level_template,expected_color_mode", [ - (255, {"replace4": '"{{255}}"'}), - (None, {"replace4": '"{{256}}"'}), - (None, {"replace4": '"{{x - 12}}"'}), - (None, {"replace4": '"{{ none }}"'}), - (None, {"replace4": '""'}), - (None, {"replace4": "\"{{ state_attr('light.nolight', 'brightness') }}\""}), + (255, "{{255}}", ColorMode.BRIGHTNESS), + (None, "{{256}}", ColorMode.UNKNOWN), + (None, "{{x - 12}}", ColorMode.UNKNOWN), + (None, "{{ none }}", ColorMode.UNKNOWN), + (None, "", ColorMode.UNKNOWN), + ( + None, + "{{ state_attr('light.nolight', 'brightness') }}", + ColorMode.UNKNOWN, + ), ], ) @pytest.mark.parametrize( - "config", - [ - """{"light": {"platform": "template", "lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": {"service": "light.turn_on","entity_id": "light.test_state"}, - "turn_off": {"service": "light.turn_off","entity_id": "light.test_state"}, - "set_level": {"service": "light.turn_on","data_template": { - "entity_id": "light.test_state","brightness": "{{brightness}}"}}, - "level_template": replace4 - }}}}""", - ], + "supported_features,supported_color_modes", + [(SUPPORT_BRIGHTNESS, [ColorMode.BRIGHTNESS])], ) -async def test_level_template(hass, expected_level, start_ha): +async def test_level_template( + hass, + expected_level, + supported_features, + supported_color_modes, + expected_color_mode, + count, + level_template, +): """Test the template for the level.""" + light_config = { + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": "{{ 1 == 1 }}", + "level_template": level_template, + } + } + await async_setup_light(hass, count, light_config) state = hass.states.get("light.test_template_light") - assert state is not None assert state.attributes.get("brightness") == expected_level + assert state.state == STATE_ON + assert state.attributes["color_mode"] == expected_color_mode + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "expected_temp,config_addon", + "expected_temp,temperature_template,expected_color_mode", [ - (500, {"replace5": '"{{500}}"'}), - (None, {"replace5": '"{{501}}"'}), - (None, {"replace5": '"{{x - 12}}"'}), - (None, {"replace5": '"None"'}), - (None, {"replace5": '"{{ none }}"'}), - (None, {"replace5": '""'}), + (500, "{{500}}", ColorMode.COLOR_TEMP), + (None, "{{501}}", ColorMode.UNKNOWN), + (None, "{{x - 12}}", ColorMode.UNKNOWN), + (None, "None", ColorMode.UNKNOWN), + (None, "{{ none }}", ColorMode.UNKNOWN), + (None, "", ColorMode.UNKNOWN), ], ) @pytest.mark.parametrize( - "config", - [ - """{ - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state" - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state" - }, - "set_temperature": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "color_temp": "{{color_temp}}" - } - }, - "temperature_template": replace5 - } - } - } - }""" - ], + "supported_features,supported_color_modes", + [(SUPPORT_COLOR_TEMP, [ColorMode.COLOR_TEMP])], ) -async def test_temperature_template(hass, expected_temp, start_ha): +async def test_temperature_template( + hass, + expected_temp, + supported_features, + supported_color_modes, + expected_color_mode, + count, + temperature_template, +): """Test the template for the temperature.""" + light_config = { + "test_template_light": { + **OPTIMISTIC_COLOR_TEMP_LIGHT_CONFIG, + "value_template": "{{ 1 == 1 }}", + "temperature_template": temperature_template, + } + } + await async_setup_light(hass, count, light_config) state = hass.states.get("light.test_template_light") - assert state is not None assert state.attributes.get("color_temp") == expected_temp + assert state.state == STATE_ON + assert state.attributes["color_mode"] == expected_color_mode + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes,expected_color_mode", + [(SUPPORT_COLOR_TEMP, [ColorMode.COLOR_TEMP], ColorMode.COLOR_TEMP)], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{1 == 1}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_temperature": { - "service": "test.automation", - "data_template": { - "entity_id": "test.test_state", - "color_temp": "{{color_temp}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_COLOR_TEMP_LIGHT_CONFIG, + "value_template": "{{1 == 1}}", } }, ], ) -async def test_temperature_action_no_template(hass, start_ha, calls): +async def test_temperature_action_no_template( + hass, + setup_light, + calls, + supported_features, + supported_color_modes, + expected_color_mode, +): """Test setting temperature with optimistic template.""" state = hass.states.get("light.test_template_light") assert state.attributes.get("color_template") is None @@ -803,47 +879,33 @@ async def test_temperature_action_no_template(hass, start_ha, calls): ) assert len(calls) == 1 - assert calls[0].data["color_temp"] == 345 + assert calls[-1].data["action"] == "set_temperature" + assert calls[-1].data["caller"] == "light.test_template_light" + assert calls[-1].data["color_temp"] == 345 state = hass.states.get("light.test_template_light") - _LOGGER.info(str(state.attributes)) assert state is not None assert state.attributes.get("color_temp") == 345 + assert state.state == STATE_ON + assert state.attributes["color_mode"] == expected_color_mode + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "friendly_name": "Template light", - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "friendly_name": "Template light", + "value_template": "{{ 1 == 1 }}", } }, ], ) -async def test_friendly_name(hass, start_ha): +async def test_friendly_name(hass, setup_light): """Test the accessibility of the friendly_name attribute.""" state = hass.states.get("light.test_template_light") @@ -852,42 +914,23 @@ async def test_friendly_name(hass, start_ha): assert state.attributes.get("friendly_name") == "Template light" -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "friendly_name": "Template light", - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - "icon_template": "{% if states.light.test_state.state %}" - "mdi:check" - "{% endif %}", - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "friendly_name": "Template light", + "value_template": "{{ 1 == 1 }}", + "icon_template": "{% if states.light.test_state.state %}" + "mdi:check" + "{% endif %}", } }, ], ) -async def test_icon_template(hass, start_ha): +async def test_icon_template(hass, setup_light): """Test icon template.""" state = hass.states.get("light.test_template_light") assert state.attributes.get("icon") == "" @@ -900,42 +943,23 @@ async def test_icon_template(hass, start_ha): assert state.attributes["icon"] == "mdi:check" -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "friendly_name": "Template light", - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - "entity_picture_template": "{% if states.light.test_state.state %}" - "/local/light.png" - "{% endif %}", - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "friendly_name": "Template light", + "value_template": "{{ 1 == 1 }}", + "entity_picture_template": "{% if states.light.test_state.state %}" + "/local/light.png" + "{% endif %}", } }, ], ) -async def test_entity_picture_template(hass, start_ha): +async def test_entity_picture_template(hass, setup_light): """Test entity_picture template.""" state = hass.states.get("light.test_template_light") assert state.attributes.get("entity_picture") == "" @@ -948,49 +972,30 @@ async def test_entity_picture_template(hass, start_ha): assert state.attributes["entity_picture"] == "/local/light.png" -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes, expected_color_mode", + [(SUPPORT_COLOR, [ColorMode.HS], ColorMode.HS)], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{1 == 1}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_color": [ - { - "service": "test.automation", - "data_template": { - "entity_id": "test.test_state", - "h": "{{h}}", - "s": "{{s}}", - }, - }, - { - "service": "test.automation", - "data_template": { - "entity_id": "test.test_state", - "s": "{{s}}", - "h": "{{h}}", - }, - }, - ], - } - }, + "test_template_light": { + **OPTIMISTIC_HS_COLOR_LIGHT_CONFIG, + "value_template": "{{1 == 1}}", } }, ], ) -async def test_color_action_no_template(hass, start_ha, calls): +async def test_color_action_no_template( + hass, + setup_light, + calls, + supported_features, + supported_color_modes, + expected_color_mode, +): """Test setting color with optimistic template.""" state = hass.states.get("light.test_template_light") assert state.attributes.get("hs_color") is None @@ -1002,94 +1007,203 @@ async def test_color_action_no_template(hass, start_ha, calls): blocking=True, ) - assert len(calls) == 2 - assert calls[0].data["h"] == 40 - assert calls[0].data["s"] == 50 - assert calls[1].data["h"] == 40 - assert calls[1].data["s"] == 50 + assert len(calls) == 1 + assert calls[-1].data["action"] == "set_color" + assert calls[-1].data["caller"] == "light.test_template_light" + assert calls[-1].data["h"] == 40 + assert calls[-1].data["s"] == 50 state = hass.states.get("light.test_template_light") - _LOGGER.info(str(state.attributes)) - assert state is not None - assert calls[0].data["h"] == 40 - assert calls[0].data["s"] == 50 - assert calls[1].data["h"] == 40 - assert calls[1].data["s"] == 50 + assert state.state == STATE_ON + assert state.attributes["color_mode"] == expected_color_mode + assert state.attributes.get("hs_color") == (40, 50) + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "expected_hs,config_addon", + "expected_hs,color_template,expected_color_mode", [ - ((360, 100), {"replace6": '"{{(360, 100)}}"'}), - ((359.9, 99.9), {"replace6": '"{{(359.9, 99.9)}}"'}), - (None, {"replace6": '"{{(361, 100)}}"'}), - (None, {"replace6": '"{{(360, 101)}}"'}), - (None, {"replace6": '"[{{(360)}},{{null}}]"'}), - (None, {"replace6": '"{{x - 12}}"'}), - (None, {"replace6": '""'}), - (None, {"replace6": '"{{ none }}"'}), + ((360, 100), "{{(360, 100)}}", ColorMode.HS), + ((359.9, 99.9), "{{(359.9, 99.9)}}", ColorMode.HS), + (None, "{{(361, 100)}}", ColorMode.UNKNOWN), + (None, "{{(360, 101)}}", ColorMode.UNKNOWN), + (None, "[{{(360)}},{{null}}]", ColorMode.UNKNOWN), + (None, "{{x - 12}}", ColorMode.UNKNOWN), + (None, "", ColorMode.UNKNOWN), + (None, "{{ none }}", ColorMode.UNKNOWN), ], ) @pytest.mark.parametrize( - "config", - [ - """{"light": {"platform": "template","lights": {"test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": {"service": "light.turn_on","entity_id": "light.test_state"}, - "turn_off": {"service": "light.turn_off","entity_id": "light.test_state"}, - "set_color": [{"service": "input_number.set_value", - "data_template": {"entity_id": "input_number.h","color_temp": "{{h}}" - }}], - "color_template": replace6 - }}}}""" - ], + "supported_features,supported_color_modes", + [(SUPPORT_COLOR, [ColorMode.HS])], ) -async def test_color_template(hass, expected_hs, start_ha): +async def test_color_template( + hass, + expected_hs, + supported_features, + supported_color_modes, + expected_color_mode, + count, + color_template, +): """Test the template for the color.""" + light_config = { + "test_template_light": { + **OPTIMISTIC_HS_COLOR_LIGHT_CONFIG, + "value_template": "{{ 1 == 1 }}", + "color_template": color_template, + } + } + await async_setup_light(hass, count, light_config) state = hass.states.get("light.test_template_light") - assert state is not None assert state.attributes.get("hs_color") == expected_hs + assert state.state == STATE_ON + assert state.attributes["color_mode"] == expected_color_mode + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "supported_features,supported_color_modes", + [(SUPPORT_COLOR | SUPPORT_COLOR_TEMP, [ColorMode.COLOR_TEMP, ColorMode.HS])], +) +@pytest.mark.parametrize( + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{true}}", - "turn_on": {"service": "test.automation"}, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", + "test_template_light": { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "value_template": "{{1 == 1}}", + "set_color": [ + { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "h": "{{h}}", + "s": "{{s}}", }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - "set_effect": { - "service": "test.automation", - "data_template": { - "entity_id": "test.test_state", - "effect": "{{effect}}", - }, - }, - "effect_list_template": "{{ ['Disco', 'Police'] }}", - "effect_template": "{{ 'Disco' }}", - } + }, + ], + "set_temperature": { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "color_temp": "{{color_temp}}", + }, }, } }, ], ) -async def test_effect_action_valid_effect(hass, start_ha, calls): +async def test_color_and_temperature_actions_no_template( + hass, setup_light, calls, supported_features, supported_color_modes +): + """Test setting color and color temperature with optimistic template.""" + state = hass.states.get("light.test_template_light") + assert state.attributes.get("hs_color") is None + + # Optimistically set color, light should be in hs_color mode + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_HS_COLOR: (40, 50)}, + blocking=True, + ) + + assert len(calls) == 1 + assert calls[-1].data["h"] == 40 + assert calls[-1].data["s"] == 50 + + state = hass.states.get("light.test_template_light") + assert state.attributes["color_mode"] == ColorMode.HS + assert "color_temp" not in state.attributes + assert state.attributes["hs_color"] == (40, 50) + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features + + # Optimistically set color temp, light should be in color temp mode + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_COLOR_TEMP: 123}, + blocking=True, + ) + + assert len(calls) == 2 + assert calls[-1].data["color_temp"] == 123 + + state = hass.states.get("light.test_template_light") + assert state.attributes["color_mode"] == ColorMode.COLOR_TEMP + assert state.attributes["color_temp"] == 123 + assert "hs_color" in state.attributes # Color temp represented as hs_color + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features + + # Optimistically set color, light should again be in hs_color mode + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_HS_COLOR: (10, 20)}, + blocking=True, + ) + + assert len(calls) == 3 + assert calls[-1].data["h"] == 10 + assert calls[-1].data["s"] == 20 + + state = hass.states.get("light.test_template_light") + assert state.attributes["color_mode"] == ColorMode.HS + assert "color_temp" not in state.attributes + assert state.attributes["hs_color"] == (10, 20) + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features + + # Optimistically set color temp, light should again be in color temp mode + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_COLOR_TEMP: 234}, + blocking=True, + ) + + assert len(calls) == 4 + assert calls[-1].data["color_temp"] == 234 + + state = hass.states.get("light.test_template_light") + assert state.attributes["color_mode"] == ColorMode.COLOR_TEMP + assert state.attributes["color_temp"] == 234 + assert "hs_color" in state.attributes # Color temp represented as hs_color + assert state.attributes["supported_color_modes"] == supported_color_modes + assert state.attributes["supported_features"] == supported_features + + +@pytest.mark.parametrize("count", [1]) +@pytest.mark.parametrize( + "light_config", + [ + { + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": "{{true}}", + "set_effect": { + "service": "test.automation", + "data_template": { + "action": "set_effect", + "caller": "{{ this.entity_id }}", + "entity_id": "test.test_state", + "effect": "{{effect}}", + }, + }, + "effect_list_template": "{{ ['Disco', 'Police'] }}", + "effect_template": "{{ 'Disco' }}", + } + }, + ], +) +async def test_effect_action_valid_effect(hass, setup_light, calls): """Test setting valid effect with template.""" state = hass.states.get("light.test_template_light") assert state is not None @@ -1102,51 +1216,37 @@ async def test_effect_action_valid_effect(hass, start_ha, calls): ) assert len(calls) == 1 - assert calls[0].data["effect"] == "Disco" + assert calls[-1].data["action"] == "set_effect" + assert calls[-1].data["caller"] == "light.test_template_light" + assert calls[-1].data["effect"] == "Disco" state = hass.states.get("light.test_template_light") assert state is not None assert state.attributes.get("effect") == "Disco" -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{true}}", - "turn_on": {"service": "test.automation"}, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - "set_effect": { - "service": "test.automation", - "data_template": { - "entity_id": "test.test_state", - "effect": "{{effect}}", - }, - }, - "effect_list_template": "{{ ['Disco', 'Police'] }}", - "effect_template": "{{ None }}", - } + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "value_template": "{{true}}", + "set_effect": { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "effect": "{{effect}}", + }, }, + "effect_list_template": "{{ ['Disco', 'Police'] }}", + "effect_template": "{{ None }}", } }, ], ) -async def test_effect_action_invalid_effect(hass, start_ha, calls): +async def test_effect_action_invalid_effect(hass, setup_light, calls): """Test setting invalid effect with template.""" state = hass.states.get("light.test_template_light") assert state is not None @@ -1166,176 +1266,191 @@ async def test_effect_action_invalid_effect(hass, start_ha, calls): assert state.attributes.get("effect") is None -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "expected_effect_list,config_addon", + "expected_effect_list,effect_list_template", [ ( ["Strobe color", "Police", "Christmas", "RGB", "Random Loop"], - { - "replace7": "\"{{ ['Strobe color', 'Police', 'Christmas', 'RGB', 'Random Loop'] }}\"" - }, + "{{ ['Strobe color', 'Police', 'Christmas', 'RGB', 'Random Loop'] }}", ), ( ["Police", "RGB", "Random Loop"], - {"replace7": "\"{{ ['Police', 'RGB', 'Random Loop'] }}\""}, + "{{ ['Police', 'RGB', 'Random Loop'] }}", ), - (None, {"replace7": '"{{ [] }}"'}), - (None, {"replace7": "\"{{ '[]' }}\""}), - (None, {"replace7": '"{{ 124 }}"'}), - (None, {"replace7": "\"{{ '124' }}\""}), - (None, {"replace7": '"{{ none }}"'}), - (None, {"replace7": '""'}), + (None, "{{ [] }}"), + (None, "{{ '[]' }}"), + (None, "{{ 124 }}"), + (None, "{{ '124' }}"), + (None, "{{ none }}"), + (None, ""), ], ) -@pytest.mark.parametrize( - "config", - [ - """{"light": {"platform": "template","lights": {"test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": {"service": "light.turn_on","entity_id": "light.test_state"}, - "turn_off": {"service": "light.turn_off","entity_id": "light.test_state"}, - "set_effect": {"service": "test.automation", - "data_template": {"entity_id": "test.test_state","effect": "{{effect}}"}}, - "effect_template": "{{ None }}", - "effect_list_template": replace7 - }}}}""", - ], -) -async def test_effect_list_template(hass, expected_effect_list, start_ha): +async def test_effect_list_template( + hass, expected_effect_list, count, effect_list_template +): """Test the template for the effect list.""" + light_config = { + "test_template_light": { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "value_template": "{{ 1 == 1 }}", + "set_effect": { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "effect": "{{effect}}", + }, + }, + "effect_template": "{{ None }}", + "effect_list_template": effect_list_template, + } + } + await async_setup_light(hass, count, light_config) state = hass.states.get("light.test_template_light") assert state is not None assert state.attributes.get("effect_list") == expected_effect_list -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "expected_effect,config_addon", + "expected_effect,effect_template", [ - (None, {"replace8": '"Disco"'}), - (None, {"replace8": '"None"'}), - (None, {"replace8": '"{{ None }}"'}), - ("Police", {"replace8": '"Police"'}), - ("Strobe color", {"replace8": "\"{{ 'Strobe color' }}\""}), + (None, "Disco"), + (None, "None"), + (None, "{{ None }}"), + ("Police", "Police"), + ("Strobe color", "{{ 'Strobe color' }}"), ], ) -@pytest.mark.parametrize( - "config", - [ - """{"light": {"platform": "template","lights": {"test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": {"service": "light.turn_on","entity_id": "light.test_state"}, - "turn_off": {"service": "light.turn_off","entity_id": "light.test_state"}, - "set_effect": {"service": "test.automation","data_template": { - "entity_id": "test.test_state","effect": "{{effect}}"}}, - "effect_list_template": "{{ ['Strobe color', 'Police', 'Christmas', 'RGB', 'Random Loop'] }}", - "effect_template": replace8 - }}}}""", - ], -) -async def test_effect_template(hass, expected_effect, start_ha): +async def test_effect_template(hass, expected_effect, count, effect_template): """Test the template for the effect.""" + light_config = { + "test_template_light": { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "value_template": "{{ 1 == 1 }}", + "set_effect": { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "effect": "{{effect}}", + }, + }, + "effect_list_template": "{{ ['Strobe color', 'Police', 'Christmas', 'RGB', 'Random Loop'] }}", + "effect_template": effect_template, + } + } + await async_setup_light(hass, count, light_config) state = hass.states.get("light.test_template_light") assert state is not None assert state.attributes.get("effect") == expected_effect -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "expected_min_mireds,config_addon", + "expected_min_mireds,min_mireds_template", [ - (118, {"replace9": '"{{118}}"'}), - (153, {"replace9": '"{{x - 12}}"'}), - (153, {"replace9": '"None"'}), - (153, {"replace9": '"{{ none }}"'}), - (153, {"replace9": '""'}), - (153, {"replace9": "\"{{ 'a' }}\""}), + (118, "{{118}}"), + (153, "{{x - 12}}"), + (153, "None"), + (153, "{{ none }}"), + (153, ""), + (153, "{{ 'a' }}"), ], ) -@pytest.mark.parametrize( - "config", - [ - """{"light": {"platform": "template","lights": {"test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": {"service": "light.turn_on","entity_id": "light.test_state"}, - "turn_off": {"service": "light.turn_off","entity_id": "light.test_state"}, - "set_temperature": {"service": "light.turn_on","data_template": { - "entity_id": "light.test_state","color_temp": "{{color_temp}}"}}, - "temperature_template": "{{200}}", - "min_mireds_template": replace9 - }}}}""", - ], -) -async def test_min_mireds_template(hass, expected_min_mireds, start_ha): +async def test_min_mireds_template( + hass, expected_min_mireds, count, min_mireds_template +): """Test the template for the min mireds.""" + light_config = { + "test_template_light": { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "value_template": "{{ 1 == 1 }}", + "set_temperature": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "color_temp": "{{color_temp}}", + }, + }, + "temperature_template": "{{200}}", + "min_mireds_template": min_mireds_template, + } + } + await async_setup_light(hass, count, light_config) state = hass.states.get("light.test_template_light") assert state is not None assert state.attributes.get("min_mireds") == expected_min_mireds -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "expected_max_mireds,config_addon", + "expected_max_mireds,max_mireds_template", [ - (488, {"template1": '"{{488}}"'}), - (500, {"template1": '"{{x - 12}}"'}), - (500, {"template1": '"None"'}), - (500, {"template1": '"{{ none }}"'}), - (500, {"template1": '""'}), - (500, {"template1": "\"{{ 'a' }}\""}), + (488, "{{488}}"), + (500, "{{x - 12}}"), + (500, "None"), + (500, "{{ none }}"), + (500, ""), + (500, "{{ 'a' }}"), ], ) -@pytest.mark.parametrize( - "config", - [ - """{"light": {"platform": "template","lights": {"test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": {"service": "light.turn_on","entity_id": "light.test_state"}, - "turn_off": {"service": "light.turn_off","entity_id": "light.test_state"}, - "set_temperature": {"service": "light.turn_on","data_template": { - "entity_id": "light.test_state","color_temp": "{{color_temp}}"}}, - "temperature_template": "{{200}}", - "max_mireds_template": template1 - }}}}""", - ], -) -async def test_max_mireds_template(hass, expected_max_mireds, start_ha): +async def test_max_mireds_template( + hass, expected_max_mireds, count, max_mireds_template +): """Test the template for the max mireds.""" + light_config = { + "test_template_light": { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "value_template": "{{ 1 == 1 }}", + "set_temperature": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "color_temp": "{{color_temp}}", + }, + }, + "temperature_template": "{{200}}", + "max_mireds_template": max_mireds_template, + } + } + await async_setup_light(hass, count, light_config) state = hass.states.get("light.test_template_light") assert state is not None assert state.attributes.get("max_mireds") == expected_max_mireds -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "expected_supports_transition,config_addon", + "expected_supports_transition,supports_transition_template", [ - (True, {"template2": '"{{true}}"'}), - (True, {"template2": '"{{1 == 1}}"'}), - (False, {"template2": '"{{false}}"'}), - (False, {"template2": '"{{ none }}"'}), - (False, {"template2": '""'}), - (False, {"template2": '"None"'}), - ], -) -@pytest.mark.parametrize( - "config", - [ - """{"light": {"platform": "template","lights": {"test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": {"service": "light.turn_on","entity_id": "light.test_state"}, - "turn_off": {"service": "light.turn_off","entity_id": "light.test_state"}, - "set_temperature": {"service": "light.turn_on","data_template": { - "entity_id": "light.test_state","color_temp": "{{color_temp}}"}}, - "supports_transition_template": template2 - }}}}""", + (True, "{{true}}"), + (True, "{{1 == 1}}"), + (False, "{{false}}"), + (False, "{{ none }}"), + (False, ""), + (False, "None"), ], ) async def test_supports_transition_template( - hass, expected_supports_transition, start_ha + hass, expected_supports_transition, count, supports_transition_template ): """Test the template for the supports transition.""" + light_config = { + "test_template_light": { + "value_template": "{{ 1 == 1 }}", + "turn_on": {"service": "light.turn_on", "entity_id": "light.test_state"}, + "turn_off": {"service": "light.turn_off", "entity_id": "light.test_state"}, + "set_temperature": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "color_temp": "{{color_temp}}", + }, + }, + "supports_transition_template": supports_transition_template, + } + } + await async_setup_light(hass, count, light_config) state = hass.states.get("light.test_template_light") expected_value = 1 @@ -1349,38 +1464,19 @@ async def test_supports_transition_template( ) != expected_value -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "availability_template": "{{ is_state('availability_boolean.state', 'on') }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "availability_template": "{{ is_state('availability_boolean.state', 'on') }}", } }, ], ) -async def test_available_template_with_entities(hass, start_ha): +async def test_available_template_with_entities(hass, setup_light): """Test availability templates with values from other entities.""" # When template returns true.. hass.states.async_set(_STATE_AVAILABILITY_BOOLEAN, STATE_ON) @@ -1397,80 +1493,42 @@ async def test_available_template_with_entities(hass, start_ha): assert hass.states.get("light.test_template_light").state == STATE_UNAVAILABLE -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "availability_template": "{{ x - 12 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, + "test_template_light": { + **OPTIMISTIC_BRIGHTNESS_LIGHT_CONFIG, + "availability_template": "{{ x - 12 }}", } }, ], ) async def test_invalid_availability_template_keeps_component_available( - hass, start_ha, caplog_setup_text + hass, setup_light, caplog_setup_text ): """Test that an invalid availability keeps the device available.""" assert hass.states.get("light.test_template_light").state != STATE_UNAVAILABLE assert ("UndefinedError: 'x' is undefined") in caplog_setup_text -@pytest.mark.parametrize("count,domain", [(1, light.DOMAIN)]) +@pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( - "config", + "light_config", [ { - "light": { - "platform": "template", - "lights": { - "test_template_light_01": { - "unique_id": "not-so-unique-anymore", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - }, - "test_template_light_02": { - "unique_id": "not-so-unique-anymore", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - }, - }, - } + "test_template_light_01": { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "unique_id": "not-so-unique-anymore", + }, + "test_template_light_02": { + **OPTIMISTIC_ON_OFF_LIGHT_CONFIG, + "unique_id": "not-so-unique-anymore", + }, }, ], ) -async def test_unique_id(hass, start_ha): +async def test_unique_id(hass, setup_light): """Test unique_id option only creates one light per id.""" assert len(hass.states.async_all("light")) == 1 diff --git a/tests/components/template/test_lock.py b/tests/components/template/test_lock.py index 80c83e0885a..1ed5296f681 100644 --- a/tests/components/template/test_lock.py +++ b/tests/components/template/test_lock.py @@ -5,6 +5,24 @@ from homeassistant import setup from homeassistant.components import lock from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE +OPTIMISTIC_LOCK_CONFIG = { + "platform": "template", + "lock": { + "service": "test.automation", + "data_template": { + "action": "lock", + "caller": "{{ this.entity_id }}", + }, + }, + "unlock": { + "service": "test.automation", + "data_template": { + "action": "unlock", + "caller": "{{ this.entity_id }}", + }, + }, +} + @pytest.mark.parametrize("count,domain", [(1, lock.DOMAIN)]) @pytest.mark.parametrize( @@ -12,17 +30,9 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVA [ { lock.DOMAIN: { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "name": "Test template lock", "value_template": "{{ states.switch.test_state.state }}", - "lock": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "unlock": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, ], @@ -48,16 +58,8 @@ async def test_template_state(hass, start_ha): [ { lock.DOMAIN: { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "value_template": "{{ 1 == 1 }}", - "lock": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "unlock": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, ], @@ -74,16 +76,8 @@ async def test_template_state_boolean_on(hass, start_ha): [ { lock.DOMAIN: { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "value_template": "{{ 1 == 2 }}", - "lock": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "unlock": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, ], @@ -155,16 +149,8 @@ async def test_template_syntax_error(hass, start_ha): [ { lock.DOMAIN: { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "value_template": "{{ 1 + 1 }}", - "lock": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "unlock": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, ], @@ -186,19 +172,15 @@ async def test_template_static(hass, start_ha): [ { lock.DOMAIN: { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "value_template": "{{ states.switch.test_state.state }}", - "lock": {"service": "test.automation"}, - "unlock": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, ], ) async def test_lock_action(hass, start_ha, calls): """Test lock action.""" + await setup.async_setup_component(hass, "switch", {}) hass.states.async_set("switch.test_state", STATE_OFF) await hass.async_block_till_done() @@ -211,6 +193,8 @@ async def test_lock_action(hass, start_ha, calls): await hass.async_block_till_done() assert len(calls) == 1 + assert calls[0].data["action"] == "lock" + assert calls[0].data["caller"] == "lock.template_lock" @pytest.mark.parametrize("count,domain", [(1, lock.DOMAIN)]) @@ -219,19 +203,15 @@ async def test_lock_action(hass, start_ha, calls): [ { lock.DOMAIN: { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "value_template": "{{ states.switch.test_state.state }}", - "lock": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "unlock": {"service": "test.automation"}, } }, ], ) async def test_unlock_action(hass, start_ha, calls): """Test unlock action.""" + await setup.async_setup_component(hass, "switch", {}) hass.states.async_set("switch.test_state", STATE_ON) await hass.async_block_till_done() @@ -244,6 +224,8 @@ async def test_unlock_action(hass, start_ha, calls): await hass.async_block_till_done() assert len(calls) == 1 + assert calls[0].data["action"] == "unlock" + assert calls[0].data["caller"] == "lock.template_lock" @pytest.mark.parametrize("count,domain", [(1, lock.DOMAIN)]) @@ -252,10 +234,8 @@ async def test_unlock_action(hass, start_ha, calls): [ { lock.DOMAIN: { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "value_template": "{{ states.input_select.test_state.state }}", - "lock": {"service": "test.automation"}, - "unlock": {"service": "test.automation"}, } }, ], @@ -264,7 +244,7 @@ async def test_unlock_action(hass, start_ha, calls): "test_state", [lock.STATE_UNLOCKING, lock.STATE_LOCKING, lock.STATE_JAMMED] ) async def test_lock_state(hass, test_state, start_ha): - """Test unlocking.""" + """Test value template.""" hass.states.async_set("input_select.test_state", test_state) await hass.async_block_till_done() @@ -278,13 +258,8 @@ async def test_lock_state(hass, test_state, start_ha): [ { lock.DOMAIN: { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "value_template": "{{ states('switch.test_state') }}", - "lock": {"service": "switch.turn_on", "entity_id": "switch.test_state"}, - "unlock": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, "availability_template": "{{ is_state('availability_state.state', 'on') }}", } }, @@ -313,14 +288,9 @@ async def test_available_template_with_entities(hass, start_ha): [ { lock.DOMAIN: { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "value_template": "{{ 1 + 1 }}", "availability_template": "{{ x - 12 }}", - "lock": {"service": "switch.turn_on", "entity_id": "switch.test_state"}, - "unlock": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, ], @@ -339,15 +309,10 @@ async def test_invalid_availability_template_keeps_component_available( [ { lock.DOMAIN: { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "name": "test_template_lock_01", "unique_id": "not-so-unique-anymore", "value_template": "{{ true }}", - "lock": {"service": "switch.turn_on", "entity_id": "switch.test_state"}, - "unlock": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, ], @@ -359,15 +324,10 @@ async def test_unique_id(hass, start_ha): lock.DOMAIN, { "lock": { - "platform": "template", + **OPTIMISTIC_LOCK_CONFIG, "name": "test_template_lock_02", "unique_id": "not-so-unique-anymore", "value_template": "{{ false }}", - "lock": {"service": "switch.turn_on", "entity_id": "switch.test_state"}, - "unlock": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, }, }, ) diff --git a/tests/components/template/test_number.py b/tests/components/template/test_number.py index 98460335047..ea29bb303df 100644 --- a/tests/components/template/test_number.py +++ b/tests/components/template/test_number.py @@ -1,5 +1,4 @@ """The tests for the Template number platform.""" -import pytest from homeassistant import setup from homeassistant.components.input_number import ( @@ -19,11 +18,7 @@ from homeassistant.const import ATTR_ICON, CONF_ENTITY_ID, STATE_UNKNOWN from homeassistant.core import Context from homeassistant.helpers.entity_registry import async_get -from tests.common import ( - assert_setup_component, - async_capture_events, - async_mock_service, -) +from tests.common import assert_setup_component, async_capture_events _TEST_NUMBER = "number.template_number" # Represent for number's value @@ -47,13 +42,7 @@ _VALUE_INPUT_NUMBER_CONFIG = { } -@pytest.fixture -def calls(hass): - """Track calls to a mock service.""" - return async_mock_service(hass, "test", "automation") - - -async def test_missing_optional_config(hass, calls): +async def test_missing_optional_config(hass): """Test: missing optional template is ok.""" with assert_setup_component(1, "template"): assert await setup.async_setup_component( @@ -77,7 +66,7 @@ async def test_missing_optional_config(hass, calls): _verify(hass, 4, 1, 0.0, 100.0) -async def test_missing_required_keys(hass, calls): +async def test_missing_required_keys(hass): """Test: missing required fields will fail.""" with assert_setup_component(0, "template"): assert await setup.async_setup_component( @@ -112,7 +101,7 @@ async def test_missing_required_keys(hass, calls): assert hass.states.async_all("number") == [] -async def test_all_optional_config(hass, calls): +async def test_all_optional_config(hass): """Test: including all optional templates is ok.""" with assert_setup_component(1, "template"): assert await setup.async_setup_component( @@ -184,13 +173,23 @@ async def test_templates_with_entities(hass, calls): "step": f"{{{{ states('{_STEP_INPUT_NUMBER}') }}}}", "min": f"{{{{ states('{_MINIMUM_INPUT_NUMBER}') }}}}", "max": f"{{{{ states('{_MAXIMUM_INPUT_NUMBER}') }}}}", - "set_value": { - "service": "input_number.set_value", - "data_template": { - "entity_id": _VALUE_INPUT_NUMBER, - "value": "{{ value }}", + "set_value": [ + { + "service": "input_number.set_value", + "data_template": { + "entity_id": _VALUE_INPUT_NUMBER, + "value": "{{ value }}", + }, }, - }, + { + "service": "test.automation", + "data_template": { + "action": "set_value", + "caller": "{{ this.entity_id }}", + "value": "{{ value }}", + }, + }, + ], "optimistic": True, "unique_id": "a", }, @@ -258,6 +257,12 @@ async def test_templates_with_entities(hass, calls): ) _verify(hass, 2, 2, 2, 6) + # Check this variable can be used in set_value script + assert len(calls) == 1 + assert calls[-1].data["action"] == "set_value" + assert calls[-1].data["caller"] == _TEST_NUMBER + assert calls[-1].data["value"] == 2 + async def test_trigger_number(hass): """Test trigger based template number.""" diff --git a/tests/components/template/test_select.py b/tests/components/template/test_select.py index 66f67d93754..a58de63f186 100644 --- a/tests/components/template/test_select.py +++ b/tests/components/template/test_select.py @@ -1,6 +1,4 @@ """The tests for the Template select platform.""" -import pytest - from homeassistant import setup from homeassistant.components.input_select import ( ATTR_OPTION as INPUT_SELECT_ATTR_OPTION, @@ -19,24 +17,14 @@ from homeassistant.const import ATTR_ICON, CONF_ENTITY_ID, STATE_UNKNOWN from homeassistant.core import Context from homeassistant.helpers.entity_registry import async_get -from tests.common import ( - assert_setup_component, - async_capture_events, - async_mock_service, -) +from tests.common import assert_setup_component, async_capture_events _TEST_SELECT = "select.template_select" # Represent for select's current_option _OPTION_INPUT_SELECT = "input_select.option" -@pytest.fixture -def calls(hass): - """Track calls to a mock service.""" - return async_mock_service(hass, "test", "automation") - - -async def test_missing_optional_config(hass, calls): +async def test_missing_optional_config(hass): """Test: missing optional template is ok.""" with assert_setup_component(1, "template"): assert await setup.async_setup_component( @@ -60,7 +48,7 @@ async def test_missing_optional_config(hass, calls): _verify(hass, "a", ["a", "b"]) -async def test_multiple_configs(hass, calls): +async def test_multiple_configs(hass): """Test: multiple select entities get created.""" with assert_setup_component(1, "template"): assert await setup.async_setup_component( @@ -92,7 +80,7 @@ async def test_multiple_configs(hass, calls): _verify(hass, "a", ["a", "b"], f"{_TEST_SELECT}_2") -async def test_missing_required_keys(hass, calls): +async def test_missing_required_keys(hass): """Test: missing required fields will fail.""" with assert_setup_component(0, "template"): assert await setup.async_setup_component( @@ -170,13 +158,23 @@ async def test_templates_with_entities(hass, calls): "select": { "state": f"{{{{ states('{_OPTION_INPUT_SELECT}') }}}}", "options": f"{{{{ state_attr('{_OPTION_INPUT_SELECT}', '{INPUT_SELECT_ATTR_OPTIONS}') }}}}", - "select_option": { - "service": "input_select.select_option", - "data_template": { - "entity_id": _OPTION_INPUT_SELECT, - "option": "{{ option }}", + "select_option": [ + { + "service": "input_select.select_option", + "data_template": { + "entity_id": _OPTION_INPUT_SELECT, + "option": "{{ option }}", + }, }, - }, + { + "service": "test.automation", + "data_template": { + "action": "select_option", + "caller": "{{ this.entity_id }}", + "option": "{{ option }}", + }, + }, + ], "optimistic": True, "unique_id": "a", }, @@ -224,6 +222,12 @@ async def test_templates_with_entities(hass, calls): ) _verify(hass, "c", ["a", "b", "c"]) + # Check this variable can be used in set_value script + assert len(calls) == 1 + assert calls[-1].data["action"] == "select_option" + assert calls[-1].data["caller"] == _TEST_SELECT + assert calls[-1].data["option"] == "c" + async def test_trigger_select(hass): """Test trigger based template select.""" @@ -290,7 +294,7 @@ def _verify(hass, expected_current_option, expected_options, entity_name=_TEST_S assert attributes.get(SELECT_ATTR_OPTIONS) == expected_options -async def test_template_icon_with_entities(hass, calls): +async def test_template_icon_with_entities(hass): """Test templates with values from other entities.""" with assert_setup_component(1, "input_select"): assert await setup.async_setup_component( diff --git a/tests/components/template/test_switch.py b/tests/components/template/test_switch.py index 2628b0afa49..3c9c22154c9 100644 --- a/tests/components/template/test_switch.py +++ b/tests/components/template/test_switch.py @@ -1,6 +1,5 @@ """The tests for the Template switch platform.""" -import pytest from homeassistant import setup from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN @@ -15,18 +14,24 @@ from homeassistant.const import ( from homeassistant.core import CoreState, State from homeassistant.setup import async_setup_component -from tests.common import ( - assert_setup_component, - async_mock_service, - mock_component, - mock_restore_cache, -) +from tests.common import assert_setup_component, mock_component, mock_restore_cache - -@pytest.fixture -def calls(hass): - """Track calls to a mock service.""" - return async_mock_service(hass, "test", "automation") +OPTIMISTIC_SWITCH_CONFIG = { + "turn_on": { + "service": "test.automation", + "data_template": { + "action": "turn_on", + "caller": "{{ this.entity_id }}", + }, + }, + "turn_off": { + "service": "test.automation", + "data_template": { + "action": "turn_off", + "caller": "{{ this.entity_id }}", + }, + }, +} async def test_template_state_text(hass): @@ -40,15 +45,8 @@ async def test_template_state_text(hass): "platform": "template", "switches": { "test_template_switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{{ states.switch.test_state.state }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, } @@ -83,15 +81,8 @@ async def test_template_state_boolean_on(hass): "platform": "template", "switches": { "test_template_switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, } @@ -117,15 +108,8 @@ async def test_template_state_boolean_off(hass): "platform": "template", "switches": { "test_template_switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{{ 1 == 2 }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, } @@ -151,15 +135,8 @@ async def test_icon_template(hass): "platform": "template", "switches": { "test_template_switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{{ states.switch.test_state.state }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, "icon_template": "{% if states.switch.test_state.state %}" "mdi:check" "{% endif %}", @@ -194,15 +171,8 @@ async def test_entity_picture_template(hass): "platform": "template", "switches": { "test_template_switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{{ states.switch.test_state.state }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, "entity_picture_template": "{% if states.switch.test_state.state %}" "/local/switch.png" "{% endif %}", @@ -237,15 +207,8 @@ async def test_template_syntax_error(hass): "platform": "template", "switches": { "test_template_switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{% if rubbish %}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, } @@ -270,15 +233,8 @@ async def test_invalid_name_does_not_create(hass): "platform": "template", "switches": { "test INVALID switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{{ rubbish }", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, } @@ -403,12 +359,8 @@ async def test_on_action(hass, calls): "platform": "template", "switches": { "test_template_switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{{ states.switch.test_state.state }}", - "turn_on": {"service": "test.automation"}, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, } }, } @@ -433,6 +385,8 @@ async def test_on_action(hass, calls): ) assert len(calls) == 1 + assert calls[-1].data["action"] == "turn_on" + assert calls[-1].data["caller"] == "switch.test_template_switch" async def test_on_action_optimistic(hass, calls): @@ -445,11 +399,7 @@ async def test_on_action_optimistic(hass, calls): "platform": "template", "switches": { "test_template_switch": { - "turn_on": {"service": "test.automation"}, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, + **OPTIMISTIC_SWITCH_CONFIG, } }, } @@ -473,9 +423,12 @@ async def test_on_action_optimistic(hass, calls): ) state = hass.states.get("switch.test_template_switch") - assert len(calls) == 1 assert state.state == STATE_ON + assert len(calls) == 1 + assert calls[-1].data["action"] == "turn_on" + assert calls[-1].data["caller"] == "switch.test_template_switch" + async def test_off_action(hass, calls): """Test off action.""" @@ -487,12 +440,8 @@ async def test_off_action(hass, calls): "platform": "template", "switches": { "test_template_switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{{ states.switch.test_state.state }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": {"service": "test.automation"}, } }, } @@ -517,6 +466,8 @@ async def test_off_action(hass, calls): ) assert len(calls) == 1 + assert calls[-1].data["action"] == "turn_off" + assert calls[-1].data["caller"] == "switch.test_template_switch" async def test_off_action_optimistic(hass, calls): @@ -529,11 +480,7 @@ async def test_off_action_optimistic(hass, calls): "platform": "template", "switches": { "test_template_switch": { - "turn_off": {"service": "test.automation"}, - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, + **OPTIMISTIC_SWITCH_CONFIG, } }, } @@ -557,9 +504,12 @@ async def test_off_action_optimistic(hass, calls): ) state = hass.states.get("switch.test_template_switch") - assert len(calls) == 1 assert state.state == STATE_OFF + assert len(calls) == 1 + assert calls[-1].data["action"] == "turn_off" + assert calls[-1].data["caller"] == "switch.test_template_switch" + async def test_restore_state(hass): """Test state restoration.""" @@ -582,12 +532,10 @@ async def test_restore_state(hass): "platform": "template", "switches": { "s1": { - "turn_on": {"service": "test.automation"}, - "turn_off": {"service": "test.automation"}, + **OPTIMISTIC_SWITCH_CONFIG, }, "s2": { - "turn_on": {"service": "test.automation"}, - "turn_off": {"service": "test.automation"}, + **OPTIMISTIC_SWITCH_CONFIG, }, }, } @@ -614,15 +562,8 @@ async def test_available_template_with_entities(hass): "platform": "template", "switches": { "test_template_switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, "availability_template": "{{ is_state('availability_state.state', 'on') }}", } }, @@ -655,15 +596,8 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap "platform": "template", "switches": { "test_template_switch": { + **OPTIMISTIC_SWITCH_CONFIG, "value_template": "{{ true }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, "availability_template": "{{ x - 12 }}", } }, @@ -689,28 +623,14 @@ async def test_unique_id(hass): "platform": "template", "switches": { "test_template_switch_01": { + **OPTIMISTIC_SWITCH_CONFIG, "unique_id": "not-so-unique-anymore", "value_template": "{{ true }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, }, "test_template_switch_02": { + **OPTIMISTIC_SWITCH_CONFIG, "unique_id": "not-so-unique-anymore", "value_template": "{{ false }}", - "turn_on": { - "service": "switch.turn_on", - "entity_id": "switch.test_state", - }, - "turn_off": { - "service": "switch.turn_off", - "entity_id": "switch.test_state", - }, }, }, } diff --git a/tests/components/template/test_vacuum.py b/tests/components/template/test_vacuum.py index 1fd875f2df8..e454696d12a 100644 --- a/tests/components/template/test_vacuum.py +++ b/tests/components/template/test_vacuum.py @@ -342,7 +342,7 @@ async def test_unused_services(hass): _verify(hass, STATE_UNKNOWN, None) -async def test_state_services(hass): +async def test_state_services(hass, calls): """Test state services.""" await _register_components(hass) @@ -353,6 +353,9 @@ async def test_state_services(hass): # verify assert hass.states.get(_STATE_INPUT_SELECT).state == STATE_CLEANING _verify(hass, STATE_CLEANING, None) + assert len(calls) == 1 + assert calls[-1].data["action"] == "start" + assert calls[-1].data["caller"] == _TEST_VACUUM # Pause vacuum await common.async_pause(hass, _TEST_VACUUM) @@ -361,6 +364,9 @@ async def test_state_services(hass): # verify assert hass.states.get(_STATE_INPUT_SELECT).state == STATE_PAUSED _verify(hass, STATE_PAUSED, None) + assert len(calls) == 2 + assert calls[-1].data["action"] == "pause" + assert calls[-1].data["caller"] == _TEST_VACUUM # Stop vacuum await common.async_stop(hass, _TEST_VACUUM) @@ -369,6 +375,9 @@ async def test_state_services(hass): # verify assert hass.states.get(_STATE_INPUT_SELECT).state == STATE_IDLE _verify(hass, STATE_IDLE, None) + assert len(calls) == 3 + assert calls[-1].data["action"] == "stop" + assert calls[-1].data["caller"] == _TEST_VACUUM # Return vacuum to base await common.async_return_to_base(hass, _TEST_VACUUM) @@ -377,9 +386,12 @@ async def test_state_services(hass): # verify assert hass.states.get(_STATE_INPUT_SELECT).state == STATE_RETURNING _verify(hass, STATE_RETURNING, None) + assert len(calls) == 4 + assert calls[-1].data["action"] == "return_to_base" + assert calls[-1].data["caller"] == _TEST_VACUUM -async def test_clean_spot_service(hass): +async def test_clean_spot_service(hass, calls): """Test clean spot service.""" await _register_components(hass) @@ -389,9 +401,12 @@ async def test_clean_spot_service(hass): # verify assert hass.states.get(_SPOT_CLEANING_INPUT_BOOLEAN).state == STATE_ON + assert len(calls) == 1 + assert calls[-1].data["action"] == "clean_spot" + assert calls[-1].data["caller"] == _TEST_VACUUM -async def test_locate_service(hass): +async def test_locate_service(hass, calls): """Test locate service.""" await _register_components(hass) @@ -401,9 +416,12 @@ async def test_locate_service(hass): # verify assert hass.states.get(_LOCATING_INPUT_BOOLEAN).state == STATE_ON + assert len(calls) == 1 + assert calls[-1].data["action"] == "locate" + assert calls[-1].data["caller"] == _TEST_VACUUM -async def test_set_fan_speed(hass): +async def test_set_fan_speed(hass, calls): """Test set valid fan speed.""" await _register_components(hass) @@ -413,6 +431,10 @@ async def test_set_fan_speed(hass): # verify assert hass.states.get(_FAN_SPEED_INPUT_SELECT).state == "high" + assert len(calls) == 1 + assert calls[-1].data["action"] == "set_fan_speed" + assert calls[-1].data["caller"] == _TEST_VACUUM + assert calls[-1].data["option"] == "high" # Set fan's speed to medium await common.async_set_fan_speed(hass, "medium", _TEST_VACUUM) @@ -420,9 +442,13 @@ async def test_set_fan_speed(hass): # verify assert hass.states.get(_FAN_SPEED_INPUT_SELECT).state == "medium" + assert len(calls) == 2 + assert calls[-1].data["action"] == "set_fan_speed" + assert calls[-1].data["caller"] == _TEST_VACUUM + assert calls[-1].data["option"] == "medium" -async def test_set_invalid_fan_speed(hass): +async def test_set_invalid_fan_speed(hass, calls): """Test set invalid fan speed when fan has valid speed.""" await _register_components(hass) @@ -522,37 +548,107 @@ async def _register_components(hass): test_vacuum_config = { "value_template": "{{ states('input_select.state') }}", "fan_speed_template": "{{ states('input_select.fan_speed') }}", - "start": { - "service": "input_select.select_option", - "data": {"entity_id": _STATE_INPUT_SELECT, "option": STATE_CLEANING}, - }, - "pause": { - "service": "input_select.select_option", - "data": {"entity_id": _STATE_INPUT_SELECT, "option": STATE_PAUSED}, - }, - "stop": { - "service": "input_select.select_option", - "data": {"entity_id": _STATE_INPUT_SELECT, "option": STATE_IDLE}, - }, - "return_to_base": { - "service": "input_select.select_option", - "data": {"entity_id": _STATE_INPUT_SELECT, "option": STATE_RETURNING}, - }, - "clean_spot": { - "service": "input_boolean.turn_on", - "entity_id": _SPOT_CLEANING_INPUT_BOOLEAN, - }, - "locate": { - "service": "input_boolean.turn_on", - "entity_id": _LOCATING_INPUT_BOOLEAN, - }, - "set_fan_speed": { - "service": "input_select.select_option", - "data_template": { - "entity_id": _FAN_SPEED_INPUT_SELECT, - "option": "{{ fan_speed }}", + "start": [ + { + "service": "input_select.select_option", + "data": { + "entity_id": _STATE_INPUT_SELECT, + "option": STATE_CLEANING, + }, }, - }, + { + "service": "test.automation", + "data_template": { + "action": "start", + "caller": "{{ this.entity_id }}", + }, + }, + ], + "pause": [ + { + "service": "input_select.select_option", + "data": {"entity_id": _STATE_INPUT_SELECT, "option": STATE_PAUSED}, + }, + { + "service": "test.automation", + "data_template": { + "action": "pause", + "caller": "{{ this.entity_id }}", + }, + }, + ], + "stop": [ + { + "service": "input_select.select_option", + "data": {"entity_id": _STATE_INPUT_SELECT, "option": STATE_IDLE}, + }, + { + "service": "test.automation", + "data_template": { + "action": "stop", + "caller": "{{ this.entity_id }}", + }, + }, + ], + "return_to_base": [ + { + "service": "input_select.select_option", + "data": { + "entity_id": _STATE_INPUT_SELECT, + "option": STATE_RETURNING, + }, + }, + { + "service": "test.automation", + "data_template": { + "action": "return_to_base", + "caller": "{{ this.entity_id }}", + }, + }, + ], + "clean_spot": [ + { + "service": "input_boolean.turn_on", + "entity_id": _SPOT_CLEANING_INPUT_BOOLEAN, + }, + { + "service": "test.automation", + "data_template": { + "action": "clean_spot", + "caller": "{{ this.entity_id }}", + }, + }, + ], + "locate": [ + { + "service": "input_boolean.turn_on", + "entity_id": _LOCATING_INPUT_BOOLEAN, + }, + { + "service": "test.automation", + "data_template": { + "action": "locate", + "caller": "{{ this.entity_id }}", + }, + }, + ], + "set_fan_speed": [ + { + "service": "input_select.select_option", + "data_template": { + "entity_id": _FAN_SPEED_INPUT_SELECT, + "option": "{{ fan_speed }}", + }, + }, + { + "service": "test.automation", + "data_template": { + "action": "set_fan_speed", + "caller": "{{ this.entity_id }}", + "option": "{{ fan_speed }}", + }, + }, + ], "fan_speeds": ["low", "medium", "high"], "attribute_templates": { "test_attribute": "It {{ states.sensor.test_state.state }}." diff --git a/tests/components/totalconnect/common.py b/tests/components/totalconnect/common.py index ec0c182895f..65b10718fd5 100644 --- a/tests/components/totalconnect/common.py +++ b/tests/components/totalconnect/common.py @@ -345,3 +345,28 @@ async def setup_platform(hass, platform): await hass.async_block_till_done() return mock_entry + + +async def init_integration(hass): + """Set up the TotalConnect integration.""" + # first set up a config entry and add it to hass + mock_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_DATA) + mock_entry.add_to_hass(hass) + + responses = [ + RESPONSE_AUTHENTICATE, + RESPONSE_PARTITION_DETAILS, + RESPONSE_GET_ZONE_DETAILS_SUCCESS, + RESPONSE_DISARMED, + RESPONSE_DISARMED, + ] + + with patch( + TOTALCONNECT_REQUEST, + side_effect=responses, + ) as mock_request: + await hass.config_entries.async_setup(mock_entry.entry_id) + assert mock_request.call_count == 5 + await hass.async_block_till_done() + + return mock_entry diff --git a/tests/components/totalconnect/test_alarm_control_panel.py b/tests/components/totalconnect/test_alarm_control_panel.py index 3066dfff172..07cb5f3d40a 100644 --- a/tests/components/totalconnect/test_alarm_control_panel.py +++ b/tests/components/totalconnect/test_alarm_control_panel.py @@ -30,6 +30,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_registry as er from homeassistant.util import dt from .common import ( @@ -76,7 +77,7 @@ async def test_attributes(hass: HomeAssistant) -> None: mock_request.assert_called_once() assert state.attributes.get(ATTR_FRIENDLY_NAME) == "test" - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(ENTITY_ID) # TotalConnect partition #1 alarm device unique_id is the location_id assert entry.unique_id == LOCATION_ID diff --git a/tests/components/totalconnect/test_config_flow.py b/tests/components/totalconnect/test_config_flow.py index 631553a4af4..78b121dda77 100644 --- a/tests/components/totalconnect/test_config_flow.py +++ b/tests/components/totalconnect/test_config_flow.py @@ -4,9 +4,14 @@ from unittest.mock import patch from total_connect_client.exceptions import AuthenticationError from homeassistant import data_entry_flow -from homeassistant.components.totalconnect.const import CONF_USERCODES, DOMAIN +from homeassistant.components.totalconnect.const import ( + AUTO_BYPASS, + CONF_USERCODES, + DOMAIN, +) from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER from homeassistant.const import CONF_PASSWORD +from homeassistant.core import HomeAssistant from .common import ( CONFIG_DATA, @@ -190,3 +195,42 @@ async def test_no_locations(hass): await hass.async_block_till_done() assert mock_request.call_count == 1 + + +async def test_options_flow(hass: HomeAssistant): + """Test config flow options.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=CONFIG_DATA, + unique_id=USERNAME, + ) + config_entry.add_to_hass(hass) + + responses = [ + RESPONSE_AUTHENTICATE, + RESPONSE_PARTITION_DETAILS, + RESPONSE_GET_ZONE_DETAILS_SUCCESS, + RESPONSE_DISARMED, + RESPONSE_DISARMED, + RESPONSE_DISARMED, + ] + + with patch(TOTALCONNECT_REQUEST, side_effect=responses): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={AUTO_BYPASS: True} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options == {AUTO_BYPASS: True} + await hass.async_block_till_done() + + assert await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/totalconnect/test_diagnostics.py b/tests/components/totalconnect/test_diagnostics.py new file mode 100644 index 00000000000..9c6b1975097 --- /dev/null +++ b/tests/components/totalconnect/test_diagnostics.py @@ -0,0 +1,32 @@ +"""Test TotalConnect diagnostics.""" + +from homeassistant.components.diagnostics import REDACTED + +from .common import LOCATION_ID, init_integration + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_entry_diagnostics(hass, hass_client): + """Test config entry diagnostics.""" + entry = await init_integration(hass) + + result = await get_diagnostics_for_config_entry(hass, hass_client, entry) + + client = result["client"] + assert client["invalid_credentials"] is False + + user = result["user"] + assert user["master"] is False + + location = result["locations"][0] + assert location["location_id"] == LOCATION_ID + + device = location["devices"][0] + assert device["serial_number"] == REDACTED + + partition = location["partitions"][0] + assert partition["name"] == "Test1" + + zone = location["zones"][0] + assert zone["zone_id"] == "1" diff --git a/tests/components/trafikverket_train/test_config_flow.py b/tests/components/trafikverket_train/test_config_flow.py index b8e3548af2e..09539584cbc 100644 --- a/tests/components/trafikverket_train/test_config_flow.py +++ b/tests/components/trafikverket_train/test_config_flow.py @@ -66,81 +66,6 @@ async def test_form(hass: HomeAssistant) -> None: ) -async def test_import_flow_success(hass: HomeAssistant) -> None: - """Test a successful import of yaml.""" - - with patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_API_KEY: "1234567890", - CONF_FROM: "Stockholm C", - CONF_TO: "Uppsala C", - CONF_TIME: "10:00", - CONF_WEEKDAY: ["mon", "fri"], - }, - ) - await hass.async_block_till_done() - - assert result2["type"] == RESULT_TYPE_CREATE_ENTRY - assert result2["title"] == "Stockholm C to Uppsala C at 10:00" - assert result2["data"] == { - "api_key": "1234567890", - "name": "Stockholm C to Uppsala C at 10:00", - "from": "Stockholm C", - "to": "Uppsala C", - "time": "10:00", - "weekday": ["mon", "fri"], - } - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_import_flow_already_exist(hass: HomeAssistant) -> None: - """Test import of yaml already exist.""" - - MockConfigEntry( - domain=DOMAIN, - data={ - CONF_API_KEY: "1234567890", - CONF_NAME: "Stockholm C to Uppsala C", - CONF_FROM: "Stockholm C", - CONF_TO: "Uppsala C", - CONF_TIME: "10:00", - CONF_WEEKDAY: WEEKDAYS, - }, - unique_id=f"stockholmc-uppsalac-10:00-{WEEKDAYS}", - ).add_to_hass(hass) - - with patch( - "homeassistant.components.trafikverket_train.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - ): - result3 = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_NAME: "Stockholm C to Uppsala C", - CONF_API_KEY: "1234567890", - CONF_FROM: "Stockholm C", - CONF_TO: "Uppsala C", - CONF_TIME: "10:00", - CONF_WEEKDAY: WEEKDAYS, - }, - ) - await hass.async_block_till_done() - - assert result3["type"] == RESULT_TYPE_ABORT - assert result3["reason"] == "already_configured" - - @pytest.mark.parametrize( "error_message,base_error", [ diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 7fd8cc0facb..7b348489059 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -8,6 +8,7 @@ import voluptuous as vol from homeassistant.components import media_source, tts from homeassistant.components.demo.tts import DemoProvider from homeassistant.components.media_player.const import ( + ATTR_MEDIA_ANNOUNCE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, DOMAIN as DOMAIN_MP, @@ -29,7 +30,7 @@ async def get_media_source_url(hass, media_content_id): if media_source.DOMAIN not in hass.config.components: assert await async_setup_component(hass, media_source.DOMAIN, {}) - resolved = await media_source.async_resolve_media(hass, media_content_id) + resolved = await media_source.async_resolve_media(hass, media_content_id, None) return resolved.url @@ -91,6 +92,7 @@ async def test_setup_component_and_test_service(hass, empty_cache_dir): ) assert len(calls) == 1 + assert calls[0].data[ATTR_MEDIA_ANNOUNCE] is True assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC assert ( await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID]) diff --git a/tests/components/tts/test_media_source.py b/tests/components/tts/test_media_source.py index 22edfef5358..8af1ad9d3bb 100644 --- a/tests/components/tts/test_media_source.py +++ b/tests/components/tts/test_media_source.py @@ -68,7 +68,7 @@ async def test_browsing(hass): async def test_resolving(hass, mock_get_tts_audio): """Test resolving.""" media = await media_source.async_resolve_media( - hass, "media-source://tts/demo?message=Hello%20World" + hass, "media-source://tts/demo?message=Hello%20World", None ) assert media.url.startswith("/api/tts_proxy/") assert media.mime_type == "audio/mpeg" @@ -82,7 +82,9 @@ async def test_resolving(hass, mock_get_tts_audio): # Pass language and options mock_get_tts_audio.reset_mock() media = await media_source.async_resolve_media( - hass, "media-source://tts/demo?message=Bye%20World&language=de&voice=Paulus" + hass, + "media-source://tts/demo?message=Bye%20World&language=de&voice=Paulus", + None, ) assert media.url.startswith("/api/tts_proxy/") assert media.mime_type == "audio/mpeg" @@ -98,16 +100,18 @@ async def test_resolving_errors(hass): """Test resolving.""" # No message added with pytest.raises(media_source.Unresolvable): - await media_source.async_resolve_media(hass, "media-source://tts/demo") + await media_source.async_resolve_media(hass, "media-source://tts/demo", None) # Non-existing provider with pytest.raises(media_source.Unresolvable): await media_source.async_resolve_media( - hass, "media-source://tts/non-existing?message=bla" + hass, "media-source://tts/non-existing?message=bla", None ) # Non-existing option with pytest.raises(media_source.Unresolvable): await media_source.async_resolve_media( - hass, "media-source://tts/non-existing?message=bla&non_existing_option=bla" + hass, + "media-source://tts/non-existing?message=bla&non_existing_option=bla", + None, ) diff --git a/tests/components/twentemilieu/test_calendar.py b/tests/components/twentemilieu/test_calendar.py index 0a0f32be212..b9f1dd9247d 100644 --- a/tests/components/twentemilieu/test_calendar.py +++ b/tests/components/twentemilieu/test_calendar.py @@ -79,4 +79,6 @@ async def test_api_events( "start": {"date": "2022-01-06"}, "end": {"date": "2022-01-06"}, "summary": "Christmas Tree Pickup", + "description": None, + "location": None, } diff --git a/tests/components/unifi/conftest.py b/tests/components/unifi/conftest.py index e21c458386f..e2b77ac1ed1 100644 --- a/tests/components/unifi/conftest.py +++ b/tests/components/unifi/conftest.py @@ -34,7 +34,7 @@ def mock_unifi_websocket(): def mock_discovery(): """No real network traffic allowed.""" with patch( - "homeassistant.components.unifi.config_flow.async_discover_unifi", + "homeassistant.components.unifi.config_flow._async_discover_unifi", return_value=None, ) as mock: yield mock diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 321cbdfd9e8..e774c5a551d 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -7,7 +7,7 @@ import aiounifi from homeassistant import config_entries, data_entry_flow from homeassistant.components import ssdp -from homeassistant.components.unifi.config_flow import async_discover_unifi +from homeassistant.components.unifi.config_flow import _async_discover_unifi from homeassistant.components.unifi.const import ( CONF_ALLOW_BANDWIDTH_SENSORS, CONF_ALLOW_UPTIME_SENSORS, @@ -686,10 +686,10 @@ async def test_form_ssdp_gets_form_with_ignored_entry(hass): async def test_discover_unifi_positive(hass): """Verify positive run of UniFi discovery.""" with patch("socket.gethostbyname", return_value=True): - assert await async_discover_unifi(hass) + assert await _async_discover_unifi(hass) async def test_discover_unifi_negative(hass): """Verify negative run of UniFi discovery.""" with patch("socket.gethostbyname", side_effect=socket.gaierror): - assert await async_discover_unifi(hass) is None + assert await _async_discover_unifi(hass) is None diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 532f19c35ae..e5d53a6d882 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -3,7 +3,12 @@ from datetime import timedelta from unittest.mock import patch -from aiounifi.controller import MESSAGE_CLIENT, MESSAGE_CLIENT_REMOVED, MESSAGE_DEVICE +from aiounifi.controller import ( + MESSAGE_CLIENT, + MESSAGE_CLIENT_REMOVED, + MESSAGE_DEVICE, + MESSAGE_EVENT, +) from aiounifi.websocket import STATE_DISCONNECTED, STATE_RUNNING from homeassistant import config_entries @@ -169,6 +174,140 @@ async def test_tracked_clients( assert hass.states.get("device_tracker.client_1").state == STATE_HOME +async def test_tracked_wireless_clients_event_source( + hass, aioclient_mock, mock_unifi_websocket, mock_device_registry +): + """Verify tracking of wireless clients based on event source.""" + client = { + "ap_mac": "00:00:00:00:02:01", + "essid": "ssid", + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + config_entry = await setup_unifi_integration( + hass, aioclient_mock, clients_response=[client] + ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + + # State change signalling works with events + + # Connected event + + event = { + "user": client["mac"], + "ssid": client["essid"], + "ap": client["ap_mac"], + "radio": "na", + "channel": "44", + "hostname": client["hostname"], + "key": "EVT_WU_Connected", + "subsystem": "wlan", + "site_id": "name", + "time": 1587753456179, + "datetime": "2020-04-24T18:37:36Z", + "msg": f'User{[client["mac"]]} has connected to AP[{client["ap_mac"]}] with SSID "{client["essid"]}" on "channel 44(na)"', + "_id": "5ea331fa30c49e00f90ddc1a", + } + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [event], + } + ) + await hass.async_block_till_done() + + assert hass.states.get("device_tracker.client").state == STATE_HOME + + # Disconnected event + + event = { + "user": client["mac"], + "ssid": client["essid"], + "hostname": client["hostname"], + "ap": client["ap_mac"], + "duration": 467, + "bytes": 459039, + "key": "EVT_WU_Disconnected", + "subsystem": "wlan", + "site_id": "name", + "time": 1587752927000, + "datetime": "2020-04-24T18:28:47Z", + "msg": f'User{[client["mac"]]} disconnected from "{client["essid"]}" (7m 47s connected, 448.28K bytes, last AP[{client["ap_mac"]}])', + "_id": "5ea32ff730c49e00f90dca1a", + } + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [event], + } + ) + await hass.async_block_till_done() + assert hass.states.get("device_tracker.client").state == STATE_HOME + + # Change time to mark client as away + new_time = dt_util.utcnow() + controller.option_detection_time + with patch("homeassistant.util.dt.utcnow", return_value=new_time): + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + + # To limit false positives in client tracker + # data sources are prioritized when available + # once real data is received events will be ignored. + + # New data + + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client], + } + ) + await hass.async_block_till_done() + + assert hass.states.get("device_tracker.client").state == STATE_HOME + + # Disconnection event will be ignored + + event = { + "user": client["mac"], + "ssid": client["essid"], + "hostname": client["hostname"], + "ap": client["ap_mac"], + "duration": 467, + "bytes": 459039, + "key": "EVT_WU_Disconnected", + "subsystem": "wlan", + "site_id": "name", + "time": 1587752927000, + "datetime": "2020-04-24T18:28:47Z", + "msg": f'User{[client["mac"]]} disconnected from "{client["essid"]}" (7m 47s connected, 448.28K bytes, last AP[{client["ap_mac"]}])', + "_id": "5ea32ff730c49e00f90dca1a", + } + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [event], + } + ) + await hass.async_block_till_done() + assert hass.states.get("device_tracker.client").state == STATE_HOME + + # Change time to mark client as away + new_time = dt_util.utcnow() + controller.option_detection_time + with patch("homeassistant.util.dt.utcnow", return_value=new_time): + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + + assert hass.states.get("device_tracker.client").state == STATE_HOME + + async def test_tracked_devices( hass, aioclient_mock, mock_unifi_websocket, mock_device_registry ): @@ -499,6 +638,7 @@ async def test_option_track_devices(hass, aioclient_mock, mock_device_registry): "board_rev": 3, "device_id": "mock-id", "last_seen": 1562600145, + "ip": "10.0.1.1", "mac": "00:00:00:00:01:01", "model": "US16P150", "name": "Device", diff --git a/tests/components/unifi/test_diagnostics.py b/tests/components/unifi/test_diagnostics.py index ccec7fcb48a..6584b947293 100644 --- a/tests/components/unifi/test_diagnostics.py +++ b/tests/components/unifi/test_diagnostics.py @@ -14,6 +14,7 @@ from homeassistant.components.unifi.switch import ( OUTLET_SWITCH, POE_SWITCH, ) +from homeassistant.components.unifi.update import DEVICE_UPDATE from homeassistant.const import Platform from .test_controller import setup_unifi_integration @@ -161,6 +162,9 @@ async def test_entry_diagnostics(hass, hass_client, aioclient_mock): POE_SWITCH: ["00:00:00:00:00:00"], OUTLET_SWITCH: [], }, + str(Platform.UPDATE): { + DEVICE_UPDATE: ["00:00:00:00:00:01"], + }, }, "clients": { "00:00:00:00:00:00": { diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index b2d37ad7ee3..f183e1c22ff 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, patch from homeassistant.components import unifi from homeassistant.components.unifi import async_flatten_entry_data from homeassistant.components.unifi.const import CONF_CONTROLLER, DOMAIN as UNIFI_DOMAIN -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from .test_controller import ( @@ -53,9 +53,10 @@ async def test_controller_mac(hass): assert len(mock_controller.mock_calls) == 2 - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( - config_entry_id=entry.entry_id, connections={(CONNECTION_NETWORK_MAC, "mac1")} + config_entry_id=entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "mac1")}, ) assert device.configuration_url == "https://123:443" assert device.manufacturer == "Ubiquiti Networks" diff --git a/tests/components/unifi/test_services.py b/tests/components/unifi/test_services.py index 27e4ddea930..0c6f20869c8 100644 --- a/tests/components/unifi/test_services.py +++ b/tests/components/unifi/test_services.py @@ -9,7 +9,7 @@ from homeassistant.components.unifi.services import ( SUPPORTED_SERVICES, ) from homeassistant.const import ATTR_DEVICE_ID -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers import device_registry as dr from .test_controller import setup_unifi_integration @@ -62,10 +62,10 @@ async def test_reconnect_client(hass, aioclient_mock): f"https://{controller.host}:1234/api/s/{controller.site}/cmd/stamgr", ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(CONNECTION_NETWORK_MAC, clients[0]["mac"])}, + connections={(dr.CONNECTION_NETWORK_MAC, clients[0]["mac"])}, ) await hass.services.async_call( @@ -98,7 +98,7 @@ async def test_reconnect_device_without_mac(hass, aioclient_mock): aioclient_mock.clear_requests() - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, connections={("other connection", "not mac")}, @@ -132,10 +132,10 @@ async def test_reconnect_client_controller_unavailable(hass, aioclient_mock): f"https://{controller.host}:1234/api/s/{controller.site}/cmd/stamgr", ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(CONNECTION_NETWORK_MAC, clients[0]["mac"])}, + connections={(dr.CONNECTION_NETWORK_MAC, clients[0]["mac"])}, ) await hass.services.async_call( @@ -153,10 +153,10 @@ async def test_reconnect_client_unknown_mac(hass, aioclient_mock): aioclient_mock.clear_requests() - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(CONNECTION_NETWORK_MAC, "mac unknown to controller")}, + connections={(dr.CONNECTION_NETWORK_MAC, "mac unknown to controller")}, ) await hass.services.async_call( @@ -182,10 +182,10 @@ async def test_reconnect_wired_client(hass, aioclient_mock): aioclient_mock.clear_requests() - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(CONNECTION_NETWORK_MAC, clients[0]["mac"])}, + connections={(dr.CONNECTION_NETWORK_MAC, clients[0]["mac"])}, ) await hass.services.async_call( diff --git a/tests/components/unifi/test_update.py b/tests/components/unifi/test_update.py new file mode 100644 index 00000000000..b5eb9c1d02e --- /dev/null +++ b/tests/components/unifi/test_update.py @@ -0,0 +1,167 @@ +"""The tests for the UniFi Network update platform.""" + +from aiounifi.controller import MESSAGE_DEVICE +from aiounifi.websocket import STATE_DISCONNECTED, STATE_RUNNING + +from homeassistant.components.update import ( + ATTR_IN_PROGRESS, + ATTR_INSTALLED_VERSION, + ATTR_LATEST_VERSION, + DOMAIN as UPDATE_DOMAIN, + UpdateDeviceClass, +) +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) + +from .test_controller import setup_unifi_integration + + +async def test_no_entities(hass, aioclient_mock): + """Test the update_clients function when no clients are found.""" + await setup_unifi_integration(hass, aioclient_mock) + + assert len(hass.states.async_entity_ids(UPDATE_DOMAIN)) == 0 + + +async def test_device_updates( + hass, aioclient_mock, mock_unifi_websocket, mock_device_registry +): + """Test the update_items function with some devices.""" + device_1 = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device 1", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + "upgrade_to_firmware": "4.3.17.11279", + } + device_2 = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "ip": "10.0.1.2", + "mac": "00:00:00:00:01:02", + "model": "US16P150", + "name": "Device 2", + "next_interval": 20, + "state": 0, + "type": "usw", + "version": "4.0.42.10433", + } + await setup_unifi_integration( + hass, + aioclient_mock, + devices_response=[device_1, device_2], + ) + + assert len(hass.states.async_entity_ids(UPDATE_DOMAIN)) == 2 + + device_1_state = hass.states.get("update.device_1") + assert device_1_state.state == STATE_ON + assert device_1_state.attributes[ATTR_INSTALLED_VERSION] == "4.0.42.10433" + assert device_1_state.attributes[ATTR_LATEST_VERSION] == "4.3.17.11279" + assert device_1_state.attributes[ATTR_IN_PROGRESS] is False + assert device_1_state.attributes[ATTR_DEVICE_CLASS] == UpdateDeviceClass.FIRMWARE + + device_2_state = hass.states.get("update.device_2") + assert device_2_state.state == STATE_OFF + assert device_2_state.attributes[ATTR_INSTALLED_VERSION] == "4.0.42.10433" + assert device_2_state.attributes[ATTR_LATEST_VERSION] == "4.0.42.10433" + assert device_2_state.attributes[ATTR_IN_PROGRESS] is False + assert device_2_state.attributes[ATTR_DEVICE_CLASS] == UpdateDeviceClass.FIRMWARE + + # Simulate start of update + + device_1["state"] = 4 + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_DEVICE}, + "data": [device_1], + } + ) + await hass.async_block_till_done() + + device_1_state = hass.states.get("update.device_1") + assert device_1_state.state == STATE_ON + assert device_1_state.attributes[ATTR_INSTALLED_VERSION] == "4.0.42.10433" + assert device_1_state.attributes[ATTR_LATEST_VERSION] == "4.3.17.11279" + assert device_1_state.attributes[ATTR_IN_PROGRESS] is True + + # Simulate update finished + + device_1["state"] = "0" + device_1["version"] = "4.3.17.11279" + device_1["upgradable"] = False + del device_1["upgrade_to_firmware"] + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_DEVICE}, + "data": [device_1], + } + ) + await hass.async_block_till_done() + + device_1_state = hass.states.get("update.device_1") + assert device_1_state.state == STATE_OFF + assert device_1_state.attributes[ATTR_INSTALLED_VERSION] == "4.3.17.11279" + assert device_1_state.attributes[ATTR_LATEST_VERSION] == "4.3.17.11279" + assert device_1_state.attributes[ATTR_IN_PROGRESS] is False + + +async def test_controller_state_change( + hass, aioclient_mock, mock_unifi_websocket, mock_device_registry +): + """Verify entities state reflect on controller becoming unavailable.""" + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + "upgrade_to_firmware": "4.3.17.11279", + } + + await setup_unifi_integration( + hass, + aioclient_mock, + devices_response=[device], + ) + + assert len(hass.states.async_entity_ids(UPDATE_DOMAIN)) == 1 + assert hass.states.get("update.device").state == STATE_ON + + # Controller unavailable + mock_unifi_websocket(state=STATE_DISCONNECTED) + await hass.async_block_till_done() + + assert hass.states.get("update.device").state == STATE_UNAVAILABLE + + # Controller available + mock_unifi_websocket(state=STATE_RUNNING) + await hass.async_block_till_done() + + assert hass.states.get("update.device").state == STATE_ON diff --git a/tests/components/unifiprotect/conftest.py b/tests/components/unifiprotect/conftest.py index f495b2bc8f7..3986e4cd5a3 100644 --- a/tests/components/unifiprotect/conftest.py +++ b/tests/components/unifiprotect/conftest.py @@ -14,14 +14,15 @@ import pytest from pyunifiprotect.data import ( NVR, Camera, + Chime, Doorlock, Light, Liveview, + ProtectAdoptableDeviceModel, Sensor, Viewer, WSSubscriptionMessage, ) -from pyunifiprotect.data.base import ProtectAdoptableDeviceModel from homeassistant.components.unifiprotect.const import DOMAIN from homeassistant.const import Platform @@ -49,6 +50,7 @@ class MockBootstrap: liveviews: dict[str, Any] events: dict[str, Any] doorlocks: dict[str, Any] + chimes: dict[str, Any] def reset_objects(self) -> None: """Reset all devices on bootstrap for tests.""" @@ -59,11 +61,25 @@ class MockBootstrap: self.liveviews = {} self.events = {} self.doorlocks = {} + self.chimes = {} def process_ws_packet(self, msg: WSSubscriptionMessage) -> None: """Fake process method for tests.""" pass + def unifi_dict(self) -> dict[str, Any]: + """Return UniFi formatted dict representation of the NVR.""" + return { + "nvr": self.nvr.unifi_dict(), + "cameras": [c.unifi_dict() for c in self.cameras.values()], + "lights": [c.unifi_dict() for c in self.lights.values()], + "sensors": [c.unifi_dict() for c in self.sensors.values()], + "viewers": [c.unifi_dict() for c in self.viewers.values()], + "liveviews": [c.unifi_dict() for c in self.liveviews.values()], + "doorlocks": [c.unifi_dict() for c in self.doorlocks.values()], + "chimes": [c.unifi_dict() for c in self.chimes.values()], + } + @dataclass class MockEntityFixture: @@ -127,6 +143,7 @@ def mock_bootstrap_fixture(mock_nvr: NVR): liveviews={}, events={}, doorlocks={}, + chimes={}, ) @@ -220,6 +237,14 @@ def mock_doorlock(): return Doorlock.from_unifi_dict(**data) +@pytest.fixture +def mock_chime(): + """Mock UniFi Protect Chime device.""" + + data = json.loads(load_fixture("sample_chime.json", integration=DOMAIN)) + return Chime.from_unifi_dict(**data) + + @pytest.fixture def now(): """Return datetime object that will be consistent throughout test.""" diff --git a/tests/components/unifiprotect/fixtures/sample_chime.json b/tests/components/unifiprotect/fixtures/sample_chime.json new file mode 100644 index 00000000000..975cfcebaea --- /dev/null +++ b/tests/components/unifiprotect/fixtures/sample_chime.json @@ -0,0 +1,48 @@ +{ + "mac": "BEEEE2FBE413", + "host": "192.168.144.146", + "connectionHost": "192.168.234.27", + "type": "UP Chime", + "name": "Xaorvu Tvsv", + "upSince": 1651882870009, + "uptime": 567870, + "lastSeen": 1652450740009, + "connectedSince": 1652448904587, + "state": "CONNECTED", + "hardwareRevision": null, + "firmwareVersion": "1.3.4", + "latestFirmwareVersion": "1.3.4", + "firmwareBuild": "58bd350.220401.1859", + "isUpdating": false, + "isAdopting": false, + "isAdopted": true, + "isAdoptedByOther": false, + "isProvisioned": false, + "isRebooting": false, + "isSshEnabled": true, + "canAdopt": false, + "isAttemptingToConnect": false, + "volume": 100, + "isProbingForWifi": false, + "apMac": null, + "apRssi": null, + "elementInfo": null, + "lastRing": 1652116059940, + "isWirelessUplinkEnabled": true, + "wiredConnectionState": { + "phyRate": null + }, + "wifiConnectionState": { + "channel": null, + "frequency": null, + "phyRate": null, + "signalQuality": 100, + "signalStrength": -44, + "ssid": null + }, + "cameraIds": [], + "id": "cf1a330397c08f919d02bd7c", + "isConnected": true, + "marketName": "UP Chime", + "modelKey": "chime" +} diff --git a/tests/components/unifiprotect/test_button.py b/tests/components/unifiprotect/test_button.py index 0064781c6ce..f3b76cb7abb 100644 --- a/tests/components/unifiprotect/test_button.py +++ b/tests/components/unifiprotect/test_button.py @@ -5,7 +5,7 @@ from __future__ import annotations from unittest.mock import AsyncMock import pytest -from pyunifiprotect.data import Camera +from pyunifiprotect.data.devices import Chime from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, Platform @@ -15,42 +15,39 @@ from homeassistant.helpers import entity_registry as er from .conftest import MockEntityFixture, assert_entity_counts, enable_entity -@pytest.fixture(name="camera") -async def camera_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera +@pytest.fixture(name="chime") +async def chime_fixture( + hass: HomeAssistant, mock_entry: MockEntityFixture, mock_chime: Chime ): """Fixture for a single camera for testing the button platform.""" - camera_obj = mock_camera.copy(deep=True) - camera_obj._api = mock_entry.api - camera_obj.channels[0]._api = mock_entry.api - camera_obj.channels[1]._api = mock_entry.api - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" + chime_obj = mock_chime.copy(deep=True) + chime_obj._api = mock_entry.api + chime_obj.name = "Test Chime" - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, + mock_entry.api.bootstrap.chimes = { + chime_obj.id: chime_obj, } await hass.config_entries.async_setup(mock_entry.entry.entry_id) await hass.async_block_till_done() - assert_entity_counts(hass, Platform.BUTTON, 1, 0) + assert_entity_counts(hass, Platform.BUTTON, 3, 2) - return (camera_obj, "button.test_camera_reboot_device") + return chime_obj -async def test_button( +async def test_reboot_button( hass: HomeAssistant, mock_entry: MockEntityFixture, - camera: tuple[Camera, str], + chime: Chime, ): """Test button entity.""" mock_entry.api.reboot_device = AsyncMock() - unique_id = f"{camera[0].id}" - entity_id = camera[1] + unique_id = f"{chime.id}_reboot" + entity_id = "button.test_chime_reboot_device" entity_registry = er.async_get(hass) entity = entity_registry.async_get(entity_id) @@ -67,3 +64,31 @@ async def test_button( "button", "press", {ATTR_ENTITY_ID: entity_id}, blocking=True ) mock_entry.api.reboot_device.assert_called_once() + + +async def test_chime_button( + hass: HomeAssistant, + mock_entry: MockEntityFixture, + chime: Chime, +): + """Test button entity.""" + + mock_entry.api.play_speaker = AsyncMock() + + unique_id = f"{chime.id}_play" + entity_id = "button.test_chime_play_chime" + + entity_registry = er.async_get(hass) + entity = entity_registry.async_get(entity_id) + assert entity + assert not entity.disabled + assert entity.unique_id == unique_id + + state = hass.states.get(entity_id) + assert state + assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION + + await hass.services.async_call( + "button", "press", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + mock_entry.api.play_speaker.assert_called_once() diff --git a/tests/components/unifiprotect/test_camera.py b/tests/components/unifiprotect/test_camera.py index 362481bfb03..d7fc2a62325 100644 --- a/tests/components/unifiprotect/test_camera.py +++ b/tests/components/unifiprotect/test_camera.py @@ -6,9 +6,7 @@ from copy import copy from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import Camera as ProtectCamera -from pyunifiprotect.data.devices import CameraChannel -from pyunifiprotect.data.types import StateType +from pyunifiprotect.data import Camera as ProtectCamera, CameraChannel, StateType from pyunifiprotect.exceptions import NvrError from homeassistant.components.camera import ( diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index 59304ce915f..80e845591b1 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -7,7 +7,7 @@ from unittest.mock import patch import pytest from pyunifiprotect import NotAuthorized, NvrError -from pyunifiprotect.data.nvr import NVR +from pyunifiprotect.data import NVR from homeassistant import config_entries from homeassistant.components import dhcp, ssdp diff --git a/tests/components/unifiprotect/test_diagnostics.py b/tests/components/unifiprotect/test_diagnostics.py new file mode 100644 index 00000000000..b58e164e913 --- /dev/null +++ b/tests/components/unifiprotect/test_diagnostics.py @@ -0,0 +1,56 @@ +"""Test UniFi Protect diagnostics.""" + +from pyunifiprotect.data import NVR, Light + +from homeassistant.core import HomeAssistant + +from .conftest import MockEntityFixture + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics( + hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light, hass_client +): + """Test generating diagnostics for a config entry.""" + + light1 = mock_light.copy() + light1._api = mock_entry.api + light1.name = "Test Light 1" + light1.id = "lightid1" + + mock_entry.api.bootstrap.lights = { + light1.id: light1, + } + await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.async_block_till_done() + + diag = await get_diagnostics_for_config_entry(hass, hass_client, mock_entry.entry) + + nvr_obj: NVR = mock_entry.api.bootstrap.nvr + # validate some of the data + assert "nvr" in diag and isinstance(diag["nvr"], dict) + nvr = diag["nvr"] + # should have been anonymized + assert nvr["id"] != nvr_obj.id + assert nvr["mac"] != nvr_obj.mac + assert nvr["host"] != str(nvr_obj.host) + # should have been kept + assert nvr["firmwareVersion"] == nvr_obj.firmware_version + assert nvr["version"] == str(nvr_obj.version) + assert nvr["type"] == nvr_obj.type + + assert ( + "lights" in diag + and isinstance(diag["lights"], list) + and len(diag["lights"]) == 1 + ) + light = diag["lights"][0] + # should have been anonymized + assert light["id"] != light1.id + assert light["name"] != light1.mac + assert light["mac"] != light1.mac + assert light["host"] != str(light1.host) + # should have been kept + assert light["firmwareVersion"] == light1.firmware_version + assert light["type"] == light1.type diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index 77bf900d87e..95c2ee0b511 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -1,14 +1,17 @@ """Test the UniFi Protect setup flow.""" +# pylint: disable=protected-access from __future__ import annotations from unittest.mock import AsyncMock, patch from pyunifiprotect import NotAuthorized, NvrError -from pyunifiprotect.data import NVR +from pyunifiprotect.data import NVR, Light from homeassistant.components.unifiprotect.const import CONF_DISABLE_RTSP, DOMAIN from homeassistant.config_entries import ConfigEntry, ConfigEntryState +from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from . import _patch_discovery from .conftest import MockBootstrap, MockEntityFixture @@ -175,3 +178,146 @@ async def test_setup_starts_discovery( assert mock_entry.entry.state == ConfigEntryState.LOADED await hass.async_block_till_done() assert len(hass.config_entries.flow.async_progress_by_handler(DOMAIN)) == 1 + + +async def test_migrate_reboot_button( + hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light +): + """Test migrating unique ID of reboot button.""" + + light1 = mock_light.copy() + light1._api = mock_entry.api + light1.name = "Test Light 1" + light1.id = "lightid1" + + light2 = mock_light.copy() + light2._api = mock_entry.api + light2.name = "Test Light 2" + light2.id = "lightid2" + mock_entry.api.bootstrap.lights = { + light1.id: light1, + light2.id: light2, + } + mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap) + + registry = er.async_get(hass) + registry.async_get_or_create( + Platform.BUTTON, DOMAIN, light1.id, config_entry=mock_entry.entry + ) + registry.async_get_or_create( + Platform.BUTTON, + DOMAIN, + f"{light2.id}_reboot", + config_entry=mock_entry.entry, + ) + + await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.async_block_till_done() + + assert mock_entry.entry.state == ConfigEntryState.LOADED + assert mock_entry.api.update.called + assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac + + buttons = [] + for entity in er.async_entries_for_config_entry( + registry, mock_entry.entry.entry_id + ): + if entity.domain == Platform.BUTTON.value: + buttons.append(entity) + print(entity.entity_id) + assert len(buttons) == 2 + + assert registry.async_get(f"{Platform.BUTTON}.test_light_1_reboot_device") is None + assert registry.async_get(f"{Platform.BUTTON}.test_light_1_reboot_device_2") is None + light = registry.async_get(f"{Platform.BUTTON}.unifiprotect_lightid1") + assert light is not None + assert light.unique_id == f"{light1.id}_reboot" + + assert registry.async_get(f"{Platform.BUTTON}.test_light_2_reboot_device") is None + assert registry.async_get(f"{Platform.BUTTON}.test_light_2_reboot_device_2") is None + light = registry.async_get(f"{Platform.BUTTON}.unifiprotect_lightid2_reboot") + assert light is not None + assert light.unique_id == f"{light2.id}_reboot" + + +async def test_migrate_reboot_button_no_device( + hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light +): + """Test migrating unique ID of reboot button if UniFi Protect device ID changed.""" + + light1 = mock_light.copy() + light1._api = mock_entry.api + light1.name = "Test Light 1" + light1.id = "lightid1" + + mock_entry.api.bootstrap.lights = { + light1.id: light1, + } + mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap) + + registry = er.async_get(hass) + registry.async_get_or_create( + Platform.BUTTON, DOMAIN, "lightid2", config_entry=mock_entry.entry + ) + + await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.async_block_till_done() + + assert mock_entry.entry.state == ConfigEntryState.LOADED + assert mock_entry.api.update.called + assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac + + buttons = [] + for entity in er.async_entries_for_config_entry( + registry, mock_entry.entry.entry_id + ): + if entity.domain == Platform.BUTTON.value: + buttons.append(entity) + assert len(buttons) == 2 + + light = registry.async_get(f"{Platform.BUTTON}.unifiprotect_lightid2") + assert light is not None + assert light.unique_id == "lightid2" + + +async def test_migrate_reboot_button_fail( + hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light +): + """Test migrating unique ID of reboot button.""" + + light1 = mock_light.copy() + light1._api = mock_entry.api + light1.name = "Test Light 1" + light1.id = "lightid1" + + mock_entry.api.bootstrap.lights = { + light1.id: light1, + } + mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap) + + registry = er.async_get(hass) + registry.async_get_or_create( + Platform.BUTTON, + DOMAIN, + light1.id, + config_entry=mock_entry.entry, + suggested_object_id=light1.name, + ) + registry.async_get_or_create( + Platform.BUTTON, + DOMAIN, + f"{light1.id}_reboot", + config_entry=mock_entry.entry, + suggested_object_id=light1.name, + ) + + await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.async_block_till_done() + + assert mock_entry.entry.state == ConfigEntryState.LOADED + assert mock_entry.api.update.called + assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac + + light = registry.async_get(f"{Platform.BUTTON}.test_light_1") + assert light is not None + assert light.unique_id == f"{light1.id}" diff --git a/tests/components/unifiprotect/test_lock.py b/tests/components/unifiprotect/test_lock.py index 740cb61b3c4..0a02fcb22a4 100644 --- a/tests/components/unifiprotect/test_lock.py +++ b/tests/components/unifiprotect/test_lock.py @@ -6,8 +6,7 @@ from copy import copy from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import Doorlock -from pyunifiprotect.data.types import LockStatusType +from pyunifiprotect.data import Doorlock, LockStatusType from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.const import ( diff --git a/tests/components/unifiprotect/test_select.py b/tests/components/unifiprotect/test_select.py index d09fa421ec3..fc0abbe29ca 100644 --- a/tests/components/unifiprotect/test_select.py +++ b/tests/components/unifiprotect/test_select.py @@ -7,16 +7,19 @@ from datetime import timedelta from unittest.mock import AsyncMock, Mock, patch import pytest -from pyunifiprotect.data import Camera, Light -from pyunifiprotect.data.devices import LCDMessage, Viewer -from pyunifiprotect.data.nvr import DoorbellMessage, Liveview -from pyunifiprotect.data.types import ( +from pyunifiprotect.data import ( + Camera, DoorbellMessageType, IRLEDMode, + LCDMessage, + Light, LightModeEnableType, LightModeType, + Liveview, RecordingMode, + Viewer, ) +from pyunifiprotect.data.nvr import DoorbellMessage from homeassistant.components.select.const import ATTR_OPTIONS from homeassistant.components.unifiprotect.const import ( diff --git a/tests/components/unifiprotect/test_sensor.py b/tests/components/unifiprotect/test_sensor.py index 971ee47405b..dff746c167f 100644 --- a/tests/components/unifiprotect/test_sensor.py +++ b/tests/components/unifiprotect/test_sensor.py @@ -7,10 +7,16 @@ from datetime import datetime, timedelta from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import NVR, Camera, Event, Sensor +from pyunifiprotect.data import ( + NVR, + Camera, + Event, + EventType, + Sensor, + SmartDetectObjectType, +) from pyunifiprotect.data.base import WifiConnectionState, WiredConnectionState from pyunifiprotect.data.nvr import EventMetadata -from pyunifiprotect.data.types import EventType, SmartDetectObjectType from homeassistant.components.unifiprotect.const import ( ATTR_EVENT_SCORE, diff --git a/tests/components/unifiprotect/test_services.py b/tests/components/unifiprotect/test_services.py index 82e4434ad08..22f7fdd1a6c 100644 --- a/tests/components/unifiprotect/test_services.py +++ b/tests/components/unifiprotect/test_services.py @@ -5,19 +5,21 @@ from __future__ import annotations from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import Light +from pyunifiprotect.data import Camera, Light, ModelType +from pyunifiprotect.data.devices import Chime from pyunifiprotect.exceptions import BadRequest from homeassistant.components.unifiprotect.const import ATTR_MESSAGE, DOMAIN from homeassistant.components.unifiprotect.services import ( SERVICE_ADD_DOORBELL_TEXT, SERVICE_REMOVE_DOORBELL_TEXT, + SERVICE_SET_CHIME_PAIRED, SERVICE_SET_DEFAULT_DOORBELL_TEXT, ) -from homeassistant.const import ATTR_DEVICE_ID +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er from .conftest import MockEntityFixture @@ -29,7 +31,7 @@ async def device_fixture(hass: HomeAssistant, mock_entry: MockEntityFixture): await hass.config_entries.async_setup(mock_entry.entry.entry_id) await hass.async_block_till_done() - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) return list(device_registry.devices.values())[0] @@ -48,7 +50,7 @@ async def subdevice_fixture( await hass.config_entries.async_setup(mock_entry.entry.entry_id) await hass.async_block_till_done() - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) return [d for d in device_registry.devices.values() if d.name != "UnifiProtect"][0] @@ -143,3 +145,70 @@ async def test_set_default_doorbell_text( blocking=True, ) nvr.set_default_doorbell_message.assert_called_once_with("Test Message") + + +async def test_set_chime_paired_doorbells( + hass: HomeAssistant, + mock_entry: MockEntityFixture, + mock_chime: Chime, + mock_camera: Camera, +): + """Test set_chime_paired_doorbells.""" + + mock_entry.api.update_device = AsyncMock() + + mock_chime._api = mock_entry.api + mock_chime.name = "Test Chime" + mock_chime._initial_data = mock_chime.dict() + mock_entry.api.bootstrap.chimes = { + mock_chime.id: mock_chime, + } + + camera1 = mock_camera.copy() + camera1.id = "cameraid1" + camera1.name = "Test Camera 1" + camera1._api = mock_entry.api + camera1.channels[0]._api = mock_entry.api + camera1.channels[1]._api = mock_entry.api + camera1.channels[2]._api = mock_entry.api + camera1.feature_flags.has_chime = True + + camera2 = mock_camera.copy() + camera2.id = "cameraid2" + camera2.name = "Test Camera 2" + camera2._api = mock_entry.api + camera2.channels[0]._api = mock_entry.api + camera2.channels[1]._api = mock_entry.api + camera2.channels[2]._api = mock_entry.api + camera2.feature_flags.has_chime = True + + mock_entry.api.bootstrap.cameras = { + camera1.id: camera1, + camera2.id: camera2, + } + + await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.async_block_till_done() + + registry = er.async_get(hass) + chime_entry = registry.async_get("button.test_chime_play_chime") + camera_entry = registry.async_get("binary_sensor.test_camera_2_doorbell") + assert chime_entry is not None + assert camera_entry is not None + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CHIME_PAIRED, + { + ATTR_DEVICE_ID: chime_entry.device_id, + "doorbells": { + ATTR_ENTITY_ID: ["binary_sensor.test_camera_1_doorbell"], + ATTR_DEVICE_ID: [camera_entry.device_id], + }, + }, + blocking=True, + ) + + mock_entry.api.update_device.assert_called_once_with( + ModelType.CHIME, mock_chime.id, {"cameraIds": [camera1.id, camera2.id]} + ) diff --git a/tests/components/unifiprotect/test_switch.py b/tests/components/unifiprotect/test_switch.py index c54d04a8cb7..7918ea0b6cf 100644 --- a/tests/components/unifiprotect/test_switch.py +++ b/tests/components/unifiprotect/test_switch.py @@ -5,12 +5,16 @@ from __future__ import annotations from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import Camera, Light -from pyunifiprotect.data.types import RecordingMode, SmartDetectObjectType, VideoMode +from pyunifiprotect.data import ( + Camera, + Light, + RecordingMode, + SmartDetectObjectType, + VideoMode, +) from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.components.unifiprotect.switch import ( - ALL_DEVICES_SWITCHES, CAMERA_SWITCHES, LIGHT_SWITCHES, ProtectSwitchEntityDescription, @@ -29,7 +33,9 @@ from .conftest import ( CAMERA_SWITCHES_BASIC = [ d for d in CAMERA_SWITCHES - if d.name != "Detections: Face" and d.name != "Detections: Package" + if d.name != "Detections: Face" + and d.name != "Detections: Package" + and d.name != "SSH Enabled" ] CAMERA_SWITCHES_NO_EXTRA = [ d for d in CAMERA_SWITCHES_BASIC if d.name not in ("High FPS", "Privacy Mode") @@ -215,7 +221,7 @@ async def test_switch_setup_light( entity_registry = er.async_get(hass) - description = LIGHT_SWITCHES[0] + description = LIGHT_SWITCHES[1] unique_id, entity_id = ids_from_device_description( Platform.SWITCH, light, description @@ -230,7 +236,7 @@ async def test_switch_setup_light( assert state.state == STATE_OFF assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - description = ALL_DEVICES_SWITCHES[0] + description = LIGHT_SWITCHES[0] unique_id = f"{light.id}_{description.key}" entity_id = f"switch.test_light_{description.name.lower().replace(' ', '_')}" @@ -271,7 +277,7 @@ async def test_switch_setup_camera_all( assert state.state == STATE_OFF assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - description = ALL_DEVICES_SWITCHES[0] + description = CAMERA_SWITCHES[0] description_entity_name = ( description.name.lower().replace(":", "").replace(" ", "_") @@ -301,7 +307,7 @@ async def test_switch_setup_camera_none( entity_registry = er.async_get(hass) - for description in CAMERA_SWITCHES: + for description in CAMERA_SWITCHES_BASIC: if description.ufp_required_field is not None: continue @@ -318,7 +324,7 @@ async def test_switch_setup_camera_none( assert state.state == STATE_OFF assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - description = ALL_DEVICES_SWITCHES[0] + description = CAMERA_SWITCHES[0] description_entity_name = ( description.name.lower().replace(":", "").replace(" ", "_") @@ -342,7 +348,7 @@ async def test_switch_setup_camera_none( async def test_switch_light_status(hass: HomeAssistant, light: Light): """Tests status light switch for lights.""" - description = LIGHT_SWITCHES[0] + description = LIGHT_SWITCHES[1] light.__fields__["set_status_light"] = Mock() light.set_status_light = AsyncMock() @@ -367,7 +373,7 @@ async def test_switch_camera_ssh( ): """Tests SSH switch for cameras.""" - description = ALL_DEVICES_SWITCHES[0] + description = CAMERA_SWITCHES[0] camera.__fields__["set_ssh"] = Mock() camera.set_ssh = AsyncMock() @@ -418,7 +424,7 @@ async def test_switch_camera_simple( async def test_switch_camera_highfps(hass: HomeAssistant, camera: Camera): """Tests High FPS switch for cameras.""" - description = CAMERA_SWITCHES[2] + description = CAMERA_SWITCHES[3] camera.__fields__["set_video_mode"] = Mock() camera.set_video_mode = AsyncMock() @@ -441,7 +447,7 @@ async def test_switch_camera_highfps(hass: HomeAssistant, camera: Camera): async def test_switch_camera_privacy(hass: HomeAssistant, camera: Camera): """Tests Privacy Mode switch for cameras.""" - description = CAMERA_SWITCHES[3] + description = CAMERA_SWITCHES[4] camera.__fields__["set_privacy"] = Mock() camera.set_privacy = AsyncMock() @@ -468,7 +474,7 @@ async def test_switch_camera_privacy_already_on( ): """Tests Privacy Mode switch for cameras with privacy mode defaulted on.""" - description = CAMERA_SWITCHES[3] + description = CAMERA_SWITCHES[4] camera_privacy.__fields__["set_privacy"] = Mock() camera_privacy.set_privacy = AsyncMock() diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 4a1838aba12..d4fe2ce64ae 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -21,6 +21,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import Context, callback +from homeassistant.helpers.event import async_track_state_change_event from homeassistant.setup import async_setup_component from tests.common import async_mock_service, get_fixture_path @@ -1136,8 +1137,8 @@ async def test_master_state_with_template(hass): events = [] - hass.helpers.event.async_track_state_change_event( - "media_player.tv", callback(lambda event: events.append(event)) + async_track_state_change_event( + hass, "media_player.tv", callback(lambda event: events.append(event)) ) context = Context() diff --git a/tests/components/upnp/conftest.py b/tests/components/upnp/conftest.py index dd22db878cf..687518bb46d 100644 --- a/tests/components/upnp/conftest.py +++ b/tests/components/upnp/conftest.py @@ -1,33 +1,23 @@ """Configuration for SSDP tests.""" from __future__ import annotations -from collections.abc import Sequence -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import AsyncMock, MagicMock, create_autospec, patch from urllib.parse import urlparse from async_upnp_client.client import UpnpDevice -from async_upnp_client.event_handler import UpnpEventHandler -from async_upnp_client.profiles.igd import StatusInfo +from async_upnp_client.profiles.igd import IgdDevice, StatusInfo import pytest from homeassistant.components import ssdp from homeassistant.components.upnp.const import ( - BYTES_RECEIVED, - BYTES_SENT, CONFIG_ENTRY_LOCATION, CONFIG_ENTRY_MAC_ADDRESS, CONFIG_ENTRY_ORIGINAL_UDN, CONFIG_ENTRY_ST, CONFIG_ENTRY_UDN, DOMAIN, - PACKETS_RECEIVED, - PACKETS_SENT, - ROUTER_IP, - ROUTER_UPTIME, - WAN_STATUS, ) from homeassistant.core import HomeAssistant -from homeassistant.util import dt from tests.common import MockConfigEntry @@ -59,160 +49,37 @@ TEST_DISCOVERY = ssdp.SsdpServiceInfo( ) -class MockUpnpDevice: - """Mock async_upnp_client UpnpDevice.""" - - def __init__(self, location: str) -> None: - """Initialize.""" - self.device_url = location - - @property - def manufacturer(self) -> str: - """Get manufacturer.""" - return TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_MANUFACTURER] - - @property - def name(self) -> str: - """Get name.""" - return TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME] - - @property - def model_name(self) -> str: - """Get the model name.""" - return TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_MODEL_NAME] - - @property - def device_type(self) -> str: - """Get the device type.""" - return TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_DEVICE_TYPE] - - @property - def udn(self) -> str: - """Get the UDN.""" - return TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_UDN] - - @property - def usn(self) -> str: - """Get the USN.""" - return f"{self.udn}::{self.device_type}" - - @property - def unique_id(self) -> str: - """Get the unique id.""" - return self.usn - - def reinit(self, new_upnp_device: UpnpDevice) -> None: - """Reinitialize.""" - self.device_url = new_upnp_device.device_url - - -class MockIgdDevice: - """Mock async_upnp_client IgdDevice.""" - - def __init__(self, device: MockUpnpDevice, event_handler: UpnpEventHandler) -> None: - """Initialize mock device.""" - self.device = device - self.profile_device = device - - self._timestamp = dt.utcnow() - self.traffic_times_polled = 0 - self.status_times_polled = 0 - - self.traffic_data = { - BYTES_RECEIVED: 0, - BYTES_SENT: 0, - PACKETS_RECEIVED: 0, - PACKETS_SENT: 0, - } - self.status_data = { - WAN_STATUS: "Connected", - ROUTER_UPTIME: 10, - ROUTER_IP: "8.9.10.11", - } - - @property - def name(self) -> str: - """Get the name of the device.""" - return self.profile_device.name - - @property - def manufacturer(self) -> str: - """Get the manufacturer of this device.""" - return self.profile_device.manufacturer - - @property - def model_name(self) -> str: - """Get the model name of this device.""" - return self.profile_device.model_name - - @property - def udn(self) -> str: - """Get the UDN of the device.""" - return self.profile_device.udn - - @property - def device_type(self) -> str: - """Get the device type of this device.""" - return self.profile_device.device_type - - async def async_get_total_bytes_received(self) -> int | None: - """Get total bytes received.""" - self.traffic_times_polled += 1 - return self.traffic_data[BYTES_RECEIVED] - - async def async_get_total_bytes_sent(self) -> int | None: - """Get total bytes sent.""" - return self.traffic_data[BYTES_SENT] - - async def async_get_total_packets_received(self) -> int | None: - """Get total packets received.""" - return self.traffic_data[PACKETS_RECEIVED] - - async def async_get_total_packets_sent(self) -> int | None: - """Get total packets sent.""" - return self.traffic_data[PACKETS_SENT] - - async def async_get_external_ip_address( - self, services: Sequence[str] | None = None - ) -> str | None: - """ - Get the external IP address. - - :param services List of service names to try to get action from, defaults to [WANIPC,WANPPP] - """ - return self.status_data[ROUTER_IP] - - async def async_get_status_info( - self, services: Sequence[str] | None = None - ) -> StatusInfo | None: - """ - Get status info. - - :param services List of service names to try to get action from, defaults to [WANIPC,WANPPP] - """ - self.status_times_polled += 1 - return StatusInfo( - self.status_data[WAN_STATUS], "", self.status_data[ROUTER_UPTIME] - ) - - @pytest.fixture(autouse=True) -def mock_upnp_device(): - """Mock homeassistant.components.upnp.Device.""" +def mock_igd_device() -> IgdDevice: + """Mock async_upnp_client device.""" + mock_upnp_device = create_autospec(UpnpDevice, instance=True) + mock_upnp_device.device_url = TEST_DISCOVERY.ssdp_location - async def mock_async_create_upnp_device( - hass: HomeAssistant, location: str - ) -> UpnpDevice: - """Create UPnP device.""" - return MockUpnpDevice(location) + mock_igd_device = create_autospec(IgdDevice) + mock_igd_device.name = TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME] + mock_igd_device.manufacturer = TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_MANUFACTURER] + mock_igd_device.model_name = TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_MODEL_NAME] + mock_igd_device.udn = TEST_DISCOVERY.ssdp_udn + mock_igd_device.device = mock_upnp_device + + mock_igd_device.async_get_total_bytes_received.return_value = 0 + mock_igd_device.async_get_total_bytes_sent.return_value = 0 + mock_igd_device.async_get_total_packets_received.return_value = 0 + mock_igd_device.async_get_total_packets_sent.return_value = 0 + mock_igd_device.async_get_status_info.return_value = StatusInfo( + "Connected", + "", + 10, + ) + mock_igd_device.async_get_external_ip_address.return_value = "8.9.10.11" with patch( - "homeassistant.components.upnp.device.async_create_upnp_device", - side_effect=mock_async_create_upnp_device, - ) as mock_async_create_upnp_device, patch( - "homeassistant.components.upnp.device.IgdDevice", new=MockIgdDevice - ) as mock_igd_device: - yield mock_async_create_upnp_device, mock_igd_device + "homeassistant.components.upnp.device.UpnpFactory.async_create_device" + ), patch( + "homeassistant.components.upnp.device.IgdDevice.__new__", + return_value=mock_igd_device, + ): + yield mock_igd_device @pytest.fixture @@ -297,11 +164,11 @@ async def ssdp_no_discovery(): @pytest.fixture -async def config_entry( +async def mock_config_entry( hass: HomeAssistant, mock_get_source_ip, ssdp_instant_discovery, - mock_upnp_device, + mock_igd_device: IgdDevice, mock_mac_address_from_host, ): """Create an initialized integration.""" @@ -316,6 +183,7 @@ async def config_entry( CONFIG_ENTRY_MAC_ADDRESS: TEST_MAC_ADDRESS, }, ) + entry.igd_device = mock_igd_device # Load config_entry. entry.add_to_hass(hass) diff --git a/tests/components/upnp/test_binary_sensor.py b/tests/components/upnp/test_binary_sensor.py index 22264792420..24e5cdce47c 100644 --- a/tests/components/upnp/test_binary_sensor.py +++ b/tests/components/upnp/test_binary_sensor.py @@ -2,36 +2,34 @@ from datetime import timedelta -from homeassistant.components.upnp.const import ( - DOMAIN, - ROUTER_IP, - ROUTER_UPTIME, - WAN_STATUS, -) +from async_upnp_client.profiles.igd import StatusInfo + +from homeassistant.components.upnp.const import DEFAULT_SCAN_INTERVAL from homeassistant.core import HomeAssistant import homeassistant.util.dt as dt_util -from .conftest import MockIgdDevice - from tests.common import MockConfigEntry, async_fire_time_changed -async def test_upnp_binary_sensors(hass: HomeAssistant, config_entry: MockConfigEntry): +async def test_upnp_binary_sensors( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +): """Test normal sensors.""" # First poll. wan_status_state = hass.states.get("binary_sensor.mock_name_wan_status") assert wan_status_state.state == "on" # Second poll. - mock_device: MockIgdDevice = hass.data[DOMAIN][ - config_entry.entry_id - ].device._igd_device - mock_device.status_data = { - WAN_STATUS: "Disconnected", - ROUTER_UPTIME: 100, - ROUTER_IP: "", - } - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31)) + mock_igd_device = mock_config_entry.igd_device + mock_igd_device.async_get_status_info.return_value = StatusInfo( + "Disconnected", + "", + 40, + ) + + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(seconds=DEFAULT_SCAN_INTERVAL) + ) await hass.async_block_till_done() wan_status_state = hass.states.get("binary_sensor.mock_name_wan_status") diff --git a/tests/components/upnp/test_sensor.py b/tests/components/upnp/test_sensor.py index 6c00f63a479..2abd357ac31 100644 --- a/tests/components/upnp/test_sensor.py +++ b/tests/components/upnp/test_sensor.py @@ -3,114 +3,93 @@ from datetime import timedelta from unittest.mock import patch +from async_upnp_client.profiles.igd import StatusInfo import pytest -from homeassistant.components.upnp import UpnpDataUpdateCoordinator -from homeassistant.components.upnp.const import ( - BYTES_RECEIVED, - BYTES_SENT, - DEFAULT_SCAN_INTERVAL, - DOMAIN, - PACKETS_RECEIVED, - PACKETS_SENT, - ROUTER_IP, - ROUTER_UPTIME, - WAN_STATUS, -) +from homeassistant.components.upnp.const import DEFAULT_SCAN_INTERVAL from homeassistant.core import HomeAssistant import homeassistant.util.dt as dt_util -from .conftest import MockIgdDevice - from tests.common import MockConfigEntry, async_fire_time_changed -async def test_upnp_sensors(hass: HomeAssistant, config_entry: MockConfigEntry): +async def test_upnp_sensors(hass: HomeAssistant, mock_config_entry: MockConfigEntry): """Test normal sensors.""" # First poll. - b_received_state = hass.states.get("sensor.mock_name_b_received") - b_sent_state = hass.states.get("sensor.mock_name_b_sent") - packets_received_state = hass.states.get("sensor.mock_name_packets_received") - packets_sent_state = hass.states.get("sensor.mock_name_packets_sent") - external_ip_state = hass.states.get("sensor.mock_name_external_ip") - wan_status_state = hass.states.get("sensor.mock_name_wan_status") - assert b_received_state.state == "0" - assert b_sent_state.state == "0" - assert packets_received_state.state == "0" - assert packets_sent_state.state == "0" - assert external_ip_state.state == "8.9.10.11" - assert wan_status_state.state == "Connected" + assert hass.states.get("sensor.mock_name_b_received").state == "0" + assert hass.states.get("sensor.mock_name_b_sent").state == "0" + assert hass.states.get("sensor.mock_name_packets_received").state == "0" + assert hass.states.get("sensor.mock_name_packets_sent").state == "0" + assert hass.states.get("sensor.mock_name_external_ip").state == "8.9.10.11" + assert hass.states.get("sensor.mock_name_wan_status").state == "Connected" # Second poll. - mock_device: MockIgdDevice = hass.data[DOMAIN][ - config_entry.entry_id - ].device._igd_device - mock_device.traffic_data = { - BYTES_RECEIVED: 10240, - BYTES_SENT: 20480, - PACKETS_RECEIVED: 30, - PACKETS_SENT: 40, - } - mock_device.status_data = { - WAN_STATUS: "Disconnected", - ROUTER_UPTIME: 100, - ROUTER_IP: "", - } + mock_igd_device = mock_config_entry.igd_device + mock_igd_device.async_get_total_bytes_received.return_value = 10240 + mock_igd_device.async_get_total_bytes_sent.return_value = 20480 + mock_igd_device.async_get_total_packets_received.return_value = 30 + mock_igd_device.async_get_total_packets_sent.return_value = 40 + mock_igd_device.async_get_status_info.return_value = StatusInfo( + "Disconnected", + "", + 40, + ) + mock_igd_device.async_get_external_ip_address.return_value = "" + now = dt_util.utcnow() async_fire_time_changed(hass, now + timedelta(seconds=DEFAULT_SCAN_INTERVAL)) await hass.async_block_till_done() - b_received_state = hass.states.get("sensor.mock_name_b_received") - b_sent_state = hass.states.get("sensor.mock_name_b_sent") - packets_received_state = hass.states.get("sensor.mock_name_packets_received") - packets_sent_state = hass.states.get("sensor.mock_name_packets_sent") - external_ip_state = hass.states.get("sensor.mock_name_external_ip") - wan_status_state = hass.states.get("sensor.mock_name_wan_status") - assert b_received_state.state == "10240" - assert b_sent_state.state == "20480" - assert packets_received_state.state == "30" - assert packets_sent_state.state == "40" - assert external_ip_state.state == "" - assert wan_status_state.state == "Disconnected" + assert hass.states.get("sensor.mock_name_b_received").state == "10240" + assert hass.states.get("sensor.mock_name_b_sent").state == "20480" + assert hass.states.get("sensor.mock_name_packets_received").state == "30" + assert hass.states.get("sensor.mock_name_packets_sent").state == "40" + assert hass.states.get("sensor.mock_name_external_ip").state == "" + assert hass.states.get("sensor.mock_name_wan_status").state == "Disconnected" -async def test_derived_upnp_sensors(hass: HomeAssistant, config_entry: MockConfigEntry): +async def test_derived_upnp_sensors( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +): """Test derived sensors.""" - coordinator: UpnpDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - # First poll. - kib_s_received_state = hass.states.get("sensor.mock_name_kib_s_received") - kib_s_sent_state = hass.states.get("sensor.mock_name_kib_s_sent") - packets_s_received_state = hass.states.get("sensor.mock_name_packets_s_received") - packets_s_sent_state = hass.states.get("sensor.mock_name_packets_s_sent") - assert kib_s_received_state.state == "unknown" - assert kib_s_sent_state.state == "unknown" - assert packets_s_received_state.state == "unknown" - assert packets_s_sent_state.state == "unknown" + assert hass.states.get("sensor.mock_name_kib_s_received").state == "unknown" + assert hass.states.get("sensor.mock_name_kib_s_sent").state == "unknown" + assert hass.states.get("sensor.mock_name_packets_s_received").state == "unknown" + assert hass.states.get("sensor.mock_name_packets_s_sent").state == "unknown" # Second poll. + mock_igd_device = mock_config_entry.igd_device + mock_igd_device.async_get_total_bytes_received.return_value = int( + 10240 * DEFAULT_SCAN_INTERVAL + ) + mock_igd_device.async_get_total_bytes_sent.return_value = int( + 20480 * DEFAULT_SCAN_INTERVAL + ) + mock_igd_device.async_get_total_packets_received.return_value = int( + 30 * DEFAULT_SCAN_INTERVAL + ) + mock_igd_device.async_get_total_packets_sent.return_value = int( + 40 * DEFAULT_SCAN_INTERVAL + ) + now = dt_util.utcnow() with patch( "homeassistant.components.upnp.device.utcnow", return_value=now + timedelta(seconds=DEFAULT_SCAN_INTERVAL), ): - mock_device: MockIgdDevice = coordinator.device._igd_device - mock_device.traffic_data = { - BYTES_RECEIVED: int(10240 * DEFAULT_SCAN_INTERVAL), - BYTES_SENT: int(20480 * DEFAULT_SCAN_INTERVAL), - PACKETS_RECEIVED: int(30 * DEFAULT_SCAN_INTERVAL), - PACKETS_SENT: int(40 * DEFAULT_SCAN_INTERVAL), - } async_fire_time_changed(hass, now + timedelta(seconds=DEFAULT_SCAN_INTERVAL)) await hass.async_block_till_done() - kib_s_received_state = hass.states.get("sensor.mock_name_kib_s_received") - kib_s_sent_state = hass.states.get("sensor.mock_name_kib_s_sent") - packets_s_received_state = hass.states.get( - "sensor.mock_name_packets_s_received" - ) - packets_s_sent_state = hass.states.get("sensor.mock_name_packets_s_sent") - assert float(kib_s_received_state.state) == pytest.approx(10.0, rel=0.1) - assert float(kib_s_sent_state.state) == pytest.approx(20.0, rel=0.1) - assert float(packets_s_received_state.state) == pytest.approx(30.0, rel=0.1) - assert float(packets_s_sent_state.state) == pytest.approx(40.0, rel=0.1) + assert float( + hass.states.get("sensor.mock_name_kib_s_received").state + ) == pytest.approx(10.0, rel=0.1) + assert float( + hass.states.get("sensor.mock_name_kib_s_sent").state + ) == pytest.approx(20.0, rel=0.1) + assert float( + hass.states.get("sensor.mock_name_packets_s_received").state + ) == pytest.approx(30.0, rel=0.1) + assert float( + hass.states.get("sensor.mock_name_packets_s_sent").state + ) == pytest.approx(40.0, rel=0.1) diff --git a/tests/components/uptimerobot/test_init.py b/tests/components/uptimerobot/test_init.py index 8efa51e05a6..00e7f5c27e0 100644 --- a/tests/components/uptimerobot/test_init.py +++ b/tests/components/uptimerobot/test_init.py @@ -11,10 +11,7 @@ from homeassistant.components.uptimerobot.const import ( ) from homeassistant.const import STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import ( - async_entries_for_config_entry, - async_get_registry, -) +from homeassistant.helpers import device_registry as dr from homeassistant.util import dt from .common import ( @@ -187,9 +184,9 @@ async def test_update_errors(hass: HomeAssistant, caplog: LogCaptureFixture): async def test_device_management(hass: HomeAssistant): """Test that we are adding and removing devices for monitors returned from the API.""" mock_entry = await setup_uptimerobot_integration(hass) - dev_reg = await async_get_registry(hass) + dev_reg = dr.async_get(hass) - devices = async_entries_for_config_entry(dev_reg, mock_entry.entry_id) + devices = dr.async_entries_for_config_entry(dev_reg, mock_entry.entry_id) assert len(devices) == 1 assert devices[0].identifiers == {(DOMAIN, "1234")} @@ -207,7 +204,7 @@ async def test_device_management(hass: HomeAssistant): async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL) await hass.async_block_till_done() - devices = async_entries_for_config_entry(dev_reg, mock_entry.entry_id) + devices = dr.async_entries_for_config_entry(dev_reg, mock_entry.entry_id) assert len(devices) == 2 assert devices[0].identifiers == {(DOMAIN, "1234")} assert devices[1].identifiers == {(DOMAIN, "12345")} @@ -224,7 +221,7 @@ async def test_device_management(hass: HomeAssistant): async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL) await hass.async_block_till_done() - devices = async_entries_for_config_entry(dev_reg, mock_entry.entry_id) + devices = dr.async_entries_for_config_entry(dev_reg, mock_entry.entry_id) assert len(devices) == 1 assert devices[0].identifiers == {(DOMAIN, "1234")} diff --git a/tests/components/usb/test_init.py b/tests/components/usb/test_init.py index f1cf67bfb78..f4245a0e0d6 100644 --- a/tests/components/usb/test_init.py +++ b/tests/components/usb/test_init.py @@ -833,24 +833,3 @@ def test_human_readable_device_name(): assert "Silicon Labs" in name assert "10C4" in name assert "8A2A" in name - - -@pytest.mark.usefixtures("mock_integration_frame") -async def test_service_info_compatibility(hass, caplog): - """Test compatibility with old-style dict. - - To be removed in 2022.6 - """ - discovery_info = usb.UsbServiceInfo( - device=slae_sh_device.device, - vid=12345, - pid=12345, - serial_number=slae_sh_device.serial_number, - manufacturer=slae_sh_device.manufacturer, - description=slae_sh_device.description, - ) - - # Ensure first call get logged - with patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()): - assert discovery_info["vid"] == 12345 - assert "Detected integration that accessed discovery_info['vid']" in caplog.text diff --git a/tests/components/vallox/conftest.py b/tests/components/vallox/conftest.py index e7ea6ee6d6e..ef9fd2a0e4b 100644 --- a/tests/components/vallox/conftest.py +++ b/tests/components/vallox/conftest.py @@ -50,10 +50,30 @@ def patch_profile_home(): @pytest.fixture(autouse=True) -def patch_uuid(): - """Patch the Vallox entity UUID.""" +def patch_model(): + """Patch the Vallox model response.""" with patch( - "homeassistant.components.vallox.calculate_uuid", + "homeassistant.components.vallox._api_get_model", + return_value="Vallox Testmodel", + ): + yield + + +@pytest.fixture(autouse=True) +def patch_sw_version(): + """Patch the Vallox SW version response.""" + with patch( + "homeassistant.components.vallox._api_get_sw_version", + return_value="0.1.2", + ): + yield + + +@pytest.fixture(autouse=True) +def patch_uuid(): + """Patch the Vallox UUID response.""" + with patch( + "homeassistant.components.vallox._api_get_uuid", return_value=_random_uuid(), ): yield diff --git a/tests/components/vallox/test_sensor.py b/tests/components/vallox/test_sensor.py index dea3f79fbd8..9c96f6f0a68 100644 --- a/tests/components/vallox/test_sensor.py +++ b/tests/components/vallox/test_sensor.py @@ -51,7 +51,7 @@ async def test_remaining_filter_returns_timestamp( """Test that the remaining time for filter sensor returns a timestamp.""" # Act with patch( - "homeassistant.components.vallox.calculate_next_filter_change_date", + "homeassistant.components.vallox._api_get_next_filter_change_date", return_value=dt.now().date(), ), patch_metrics(metrics={}): await hass.config_entries.async_setup(mock_entry.entry_id) @@ -68,7 +68,7 @@ async def test_remaining_time_for_filter_none_returned_from_vallox( """Test that the remaining time for filter sensor returns 'unknown' when Vallox returns None.""" # Act with patch( - "homeassistant.components.vallox.calculate_next_filter_change_date", + "homeassistant.components.vallox._api_get_next_filter_change_date", return_value=None, ), patch_metrics(metrics={}): await hass.config_entries.async_setup(mock_entry.entry_id) @@ -98,7 +98,7 @@ async def test_remaining_time_for_filter_in_the_future( # Act with patch( - "homeassistant.components.vallox.calculate_next_filter_change_date", + "homeassistant.components.vallox._api_get_next_filter_change_date", return_value=mocked_filter_end_date, ), patch_metrics(metrics={}): await hass.config_entries.async_setup(mock_entry.entry_id) @@ -122,7 +122,7 @@ async def test_remaining_time_for_filter_today( # Act with patch( - "homeassistant.components.vallox.calculate_next_filter_change_date", + "homeassistant.components.vallox._api_get_next_filter_change_date", return_value=mocked_filter_end_date, ), patch_metrics(metrics={}): await hass.config_entries.async_setup(mock_entry.entry_id) @@ -146,7 +146,7 @@ async def test_remaining_time_for_filter_in_the_past( # Act with patch( - "homeassistant.components.vallox.calculate_next_filter_change_date", + "homeassistant.components.vallox._api_get_next_filter_change_date", return_value=mocked_filter_end_date, ), patch_metrics(metrics={}): await hass.config_entries.async_setup(mock_entry.entry_id) diff --git a/tests/components/vicare/__init__.py b/tests/components/vicare/__init__.py index ae9df782886..66cbfdc1d26 100644 --- a/tests/components/vicare/__init__.py +++ b/tests/components/vicare/__init__.py @@ -13,10 +13,4 @@ ENTRY_CONFIG: Final[dict[str, str]] = { CONF_HEATING_TYPE: "auto", } -ENTRY_CONFIG_NO_HEATING_TYPE: Final[dict[str, str]] = { - CONF_USERNAME: "foo@bar.com", - CONF_PASSWORD: "1234", - CONF_CLIENT_ID: "5678", -} - MOCK_MAC = "B874241B7B9" diff --git a/tests/components/vicare/test_config_flow.py b/tests/components/vicare/test_config_flow.py index 0bedb0d73b8..3096f8a492c 100644 --- a/tests/components/vicare/test_config_flow.py +++ b/tests/components/vicare/test_config_flow.py @@ -5,10 +5,10 @@ from PyViCare.PyViCareUtils import PyViCareInvalidCredentialsError from homeassistant import config_entries, data_entry_flow from homeassistant.components import dhcp -from homeassistant.components.vicare.const import CONF_CIRCUIT, DOMAIN, VICARE_NAME +from homeassistant.components.vicare.const import DOMAIN from homeassistant.const import CONF_CLIENT_ID, CONF_PASSWORD, CONF_USERNAME -from . import ENTRY_CONFIG, ENTRY_CONFIG_NO_HEATING_TYPE, MOCK_MAC +from . import ENTRY_CONFIG, MOCK_MAC from tests.common import MockConfigEntry @@ -25,8 +25,6 @@ async def test_form(hass): "homeassistant.components.vicare.config_flow.vicare_login", return_value=None, ), patch( - "homeassistant.components.vicare.async_setup", return_value=True - ) as mock_setup, patch( "homeassistant.components.vicare.async_setup_entry", return_value=True, ) as mock_setup_entry: @@ -43,89 +41,9 @@ async def test_form(hass): assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result2["title"] == "ViCare" assert result2["data"] == ENTRY_CONFIG - assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 -async def test_import(hass): - """Test that the import works.""" - - with patch( - "homeassistant.components.vicare.config_flow.vicare_login", - return_value=True, - ), patch( - "homeassistant.components.vicare.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.vicare.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=ENTRY_CONFIG, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == VICARE_NAME - assert result["data"] == ENTRY_CONFIG - - await hass.async_block_till_done() - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_import_removes_circuit(hass): - """Test that the import works.""" - - with patch( - "homeassistant.components.vicare.config_flow.vicare_login", - return_value=True, - ), patch( - "homeassistant.components.vicare.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.vicare.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - ENTRY_CONFIG[CONF_CIRCUIT] = 1 - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=ENTRY_CONFIG, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == VICARE_NAME - assert result["data"] == ENTRY_CONFIG - - await hass.async_block_till_done() - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_import_adds_heating_type(hass): - """Test that the import works.""" - - with patch( - "homeassistant.components.vicare.config_flow.vicare_login", - return_value=True, - ), patch( - "homeassistant.components.vicare.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.vicare.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=ENTRY_CONFIG_NO_HEATING_TYPE, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == VICARE_NAME - assert result["data"] == ENTRY_CONFIG - - await hass.async_block_till_done() - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 - - async def test_invalid_login(hass) -> None: """Test a flow with an invalid Vicare login.""" result = await hass.config_entries.flow.async_init( @@ -171,8 +89,6 @@ async def test_form_dhcp(hass): "homeassistant.components.vicare.config_flow.vicare_login", return_value=None, ), patch( - "homeassistant.components.vicare.async_setup", return_value=True - ) as mock_setup, patch( "homeassistant.components.vicare.async_setup_entry", return_value=True, ) as mock_setup_entry: @@ -189,27 +105,9 @@ async def test_form_dhcp(hass): assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result2["title"] == "ViCare" assert result2["data"] == ENTRY_CONFIG - assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 -async def test_import_single_instance_allowed(hass): - """Test that configuring more than one instance is rejected.""" - mock_entry = MockConfigEntry( - domain=DOMAIN, - data=ENTRY_CONFIG, - ) - mock_entry.add_to_hass(hass) - - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=ENTRY_CONFIG, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "single_instance_allowed" - - async def test_dhcp_single_instance_allowed(hass): """Test that configuring more than one instance is rejected.""" mock_entry = MockConfigEntry( diff --git a/tests/components/voicerss/test_tts.py b/tests/components/voicerss/test_tts.py index 3e74d9dc815..099b280625f 100644 --- a/tests/components/voicerss/test_tts.py +++ b/tests/components/voicerss/test_tts.py @@ -33,7 +33,7 @@ async def get_media_source_url(hass, media_content_id): if media_source.DOMAIN not in hass.config.components: assert await async_setup_component(hass, media_source.DOMAIN, {}) - resolved = await media_source.async_resolve_media(hass, media_content_id) + resolved = await media_source.async_resolve_media(hass, media_content_id, None) return resolved.url diff --git a/tests/components/vulcan/test_config_flow.py b/tests/components/vulcan/test_config_flow.py index 91f0dd769c8..20f030bb99f 100644 --- a/tests/components/vulcan/test_config_flow.py +++ b/tests/components/vulcan/test_config_flow.py @@ -3,17 +3,20 @@ import json from unittest import mock from unittest.mock import patch -from vulcan import Account +from vulcan import ( + Account, + ExpiredTokenException, + InvalidPINException, + InvalidSymbolException, + InvalidTokenException, + UnauthorizedCertificateException, +) from vulcan.model import Student from homeassistant import config_entries, data_entry_flow from homeassistant.components.vulcan import config_flow, const, register -from homeassistant.components.vulcan.config_flow import ( - ClientConnectionError, - Keystore, - VulcanAPIException, -) -from homeassistant.const import CONF_PIN, CONF_REGION, CONF_SCAN_INTERVAL, CONF_TOKEN +from homeassistant.components.vulcan.config_flow import ClientConnectionError, Keystore +from homeassistant.const import CONF_PIN, CONF_REGION, CONF_TOKEN from tests.common import MockConfigEntry, load_fixture @@ -56,13 +59,20 @@ async def test_config_flow_auth_success( assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "auth" assert result["errors"] is None - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_TOKEN: "token", CONF_REGION: "region", CONF_PIN: "000000"}, - ) + + with patch( + "homeassistant.components.vulcan.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_TOKEN: "token", CONF_REGION: "region", CONF_PIN: "000000"}, + ) + await hass.async_block_till_done() assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Jan Kowalski" + assert len(mock_setup_entry.mock_calls) == 1 @mock.patch("homeassistant.components.vulcan.config_flow.Vulcan.get_students") @@ -96,13 +106,18 @@ async def test_config_flow_auth_success_with_multiple_students( assert result["step_id"] == "select_student" assert result["errors"] == {} - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"student": "0"}, - ) + with patch( + "homeassistant.components.vulcan.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"student": "0"}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Jan Kowalski" + assert len(mock_setup_entry.mock_calls) == 1 @mock.patch("homeassistant.components.vulcan.config_flow.Vulcan.get_students") @@ -120,14 +135,53 @@ async def test_config_flow_reauth_success( MockConfigEntry( domain=const.DOMAIN, unique_id="0", - data={"student_id": "0", "login": "example@example.com"}, + data={"student_id": "0"}, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( const.DOMAIN, context={"source": config_entries.SOURCE_REAUTH} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "reauth" + assert result["step_id"] == "reauth_confirm" + assert result["errors"] == {} + + with patch( + "homeassistant.components.vulcan.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_TOKEN: "token", CONF_REGION: "region", CONF_PIN: "000000"}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "reauth_successful" + assert len(mock_setup_entry.mock_calls) == 1 + + +@mock.patch("homeassistant.components.vulcan.config_flow.Vulcan.get_students") +@mock.patch("homeassistant.components.vulcan.config_flow.Keystore.create") +@mock.patch("homeassistant.components.vulcan.config_flow.Account.register") +async def test_config_flow_reauth_without_matching_entries( + mock_account, mock_keystore, mock_student, hass +): + """Test a aborted config flow reauth caused by leak of matching entries.""" + mock_keystore.return_value = fake_keystore + mock_account.return_value = fake_account + mock_student.return_value = [ + Student.load(load_fixture("fake_student_1.json", "vulcan")) + ] + MockConfigEntry( + domain=const.DOMAIN, + unique_id="0", + data={"student_id": "1"}, + ).add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_REAUTH} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "reauth_confirm" assert result["errors"] == {} result = await hass.config_entries.flow.async_configure( @@ -136,7 +190,7 @@ async def test_config_flow_reauth_success( ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "reauth_successful" + assert result["reason"] == "no_matching_entries" @mock.patch("homeassistant.components.vulcan.config_flow.Keystore.create") @@ -149,11 +203,11 @@ async def test_config_flow_reauth_with_errors(mock_account, mock_keystore, hass) const.DOMAIN, context={"source": config_entries.SOURCE_REAUTH} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "reauth" + assert result["step_id"] == "reauth_confirm" assert result["errors"] == {} with patch( "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=VulcanAPIException("Invalid token."), + side_effect=InvalidTokenException, ): result = await hass.config_entries.flow.async_configure( @@ -162,12 +216,12 @@ async def test_config_flow_reauth_with_errors(mock_account, mock_keystore, hass) ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "reauth" + assert result["step_id"] == "reauth_confirm" assert result["errors"] == {"base": "invalid_token"} with patch( "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=VulcanAPIException("Expired token."), + side_effect=ExpiredTokenException, ): result = await hass.config_entries.flow.async_configure( @@ -176,12 +230,12 @@ async def test_config_flow_reauth_with_errors(mock_account, mock_keystore, hass) ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "reauth" + assert result["step_id"] == "reauth_confirm" assert result["errors"] == {"base": "expired_token"} with patch( "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=VulcanAPIException("Invalid PIN."), + side_effect=InvalidPINException, ): result = await hass.config_entries.flow.async_configure( @@ -190,12 +244,12 @@ async def test_config_flow_reauth_with_errors(mock_account, mock_keystore, hass) ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "reauth" + assert result["step_id"] == "reauth_confirm" assert result["errors"] == {"base": "invalid_pin"} with patch( "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=VulcanAPIException("Unknown error"), + side_effect=InvalidSymbolException, ): result = await hass.config_entries.flow.async_configure( @@ -204,37 +258,9 @@ async def test_config_flow_reauth_with_errors(mock_account, mock_keystore, hass) ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "reauth" - assert result["errors"] == {"base": "unknown"} - - with patch( - "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=RuntimeError("Internal Server Error (ArgumentException)"), - ): - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_TOKEN: "token", CONF_REGION: "region", CONF_PIN: "000000"}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "reauth" + assert result["step_id"] == "reauth_confirm" assert result["errors"] == {"base": "invalid_symbol"} - with patch( - "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=RuntimeError("Unknown error"), - ): - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_TOKEN: "token", CONF_REGION: "region", CONF_PIN: "000000"}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "reauth" - assert result["errors"] == {"base": "unknown"} - with patch( "homeassistant.components.vulcan.config_flow.Account.register", side_effect=ClientConnectionError, @@ -246,7 +272,7 @@ async def test_config_flow_reauth_with_errors(mock_account, mock_keystore, hass) ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "reauth" + assert result["step_id"] == "reauth_confirm" assert result["errors"] == {"base": "cannot_connect"} with patch( @@ -260,7 +286,7 @@ async def test_config_flow_reauth_with_errors(mock_account, mock_keystore, hass) ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "reauth" + assert result["step_id"] == "reauth_confirm" assert result["errors"] == {"base": "unknown"} @@ -297,13 +323,18 @@ async def test_multiple_config_entries(mock_account, mock_keystore, mock_student assert result["step_id"] == "auth" assert result["errors"] is None - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_TOKEN: "token", CONF_REGION: "region", CONF_PIN: "000000"}, - ) + with patch( + "homeassistant.components.vulcan.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_TOKEN: "token", CONF_REGION: "region", CONF_PIN: "000000"}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Jan Kowalski" + assert len(mock_setup_entry.mock_calls) == 2 @mock.patch("homeassistant.components.vulcan.config_flow.Vulcan.get_students") @@ -326,13 +357,18 @@ async def test_multiple_config_entries_using_saved_credentials(mock_student, has assert result["step_id"] == "add_next_config_entry" assert result["errors"] == {} - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"use_saved_credentials": True}, - ) + with patch( + "homeassistant.components.vulcan.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"use_saved_credentials": True}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Jan Kowalski" + assert len(mock_setup_entry.mock_calls) == 2 @mock.patch("homeassistant.components.vulcan.config_flow.Vulcan.get_students") @@ -364,13 +400,18 @@ async def test_multiple_config_entries_using_saved_credentials_2(mock_student, h assert result["step_id"] == "select_student" assert result["errors"] == {} - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"student": "0"}, - ) + with patch( + "homeassistant.components.vulcan.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"student": "0"}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Jan Kowalski" + assert len(mock_setup_entry.mock_calls) == 2 @mock.patch("homeassistant.components.vulcan.config_flow.Vulcan.get_students") @@ -410,13 +451,18 @@ async def test_multiple_config_entries_using_saved_credentials_3(mock_student, h assert result["step_id"] == "select_saved_credentials" assert result["errors"] is None - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"credentials": "123"}, - ) + with patch( + "homeassistant.components.vulcan.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"credentials": "123"}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Jan Kowalski" + assert len(mock_setup_entry.mock_calls) == 3 @mock.patch("homeassistant.components.vulcan.config_flow.Vulcan.get_students") @@ -465,13 +511,18 @@ async def test_multiple_config_entries_using_saved_credentials_4(mock_student, h assert result["step_id"] == "select_student" assert result["errors"] == {} - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"student": "0"}, - ) + with patch( + "homeassistant.components.vulcan.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"student": "0"}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Jan Kowalski" + assert len(mock_setup_entry.mock_calls) == 3 async def test_multiple_config_entries_without_valid_saved_credentials(hass): @@ -504,7 +555,7 @@ async def test_multiple_config_entries_without_valid_saved_credentials(hass): ) with patch( "homeassistant.components.vulcan.config_flow.Vulcan.get_students", - side_effect=VulcanAPIException("The certificate is not authorized."), + side_effect=UnauthorizedCertificateException, ): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "select_saved_credentials" @@ -614,54 +665,6 @@ async def test_multiple_config_entries_using_saved_credentials_with_unknown_erro assert result["errors"] == {"base": "unknown"} -async def test_multiple_config_entries_using_saved_credentials_with_unknown_api_error( - hass, -): - """Test a unsuccessful config flow for multiple config entries without valid saved credentials.""" - MockConfigEntry( - entry_id="456", - domain=const.DOMAIN, - unique_id="234567", - data=json.loads(load_fixture("fake_config_entry_data.json", "vulcan")) - | {"student_id": "456"}, - ).add_to_hass(hass) - MockConfigEntry( - entry_id="123", - domain=const.DOMAIN, - unique_id="123456", - data=json.loads(load_fixture("fake_config_entry_data.json", "vulcan")), - ).add_to_hass(hass) - - result = await hass.config_entries.flow.async_init( - const.DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "add_next_config_entry" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"use_saved_credentials": True}, - ) - with patch( - "homeassistant.components.vulcan.config_flow.Vulcan.get_students", - side_effect=VulcanAPIException("Unknown error"), - ): - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "select_saved_credentials" - assert result["errors"] is None - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"credentials": "123"}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "auth" - assert result["errors"] == {"base": "unknown"} - - @mock.patch("homeassistant.components.vulcan.config_flow.Vulcan.get_students") @mock.patch("homeassistant.components.vulcan.config_flow.Keystore.create") @mock.patch("homeassistant.components.vulcan.config_flow.Account.register") @@ -704,7 +707,7 @@ async def test_config_flow_auth_invalid_token(mock_keystore, hass): mock_keystore.return_value = fake_keystore with patch( "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=VulcanAPIException("Invalid token."), + side_effect=InvalidTokenException, ): result = await hass.config_entries.flow.async_init( const.DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -730,7 +733,7 @@ async def test_config_flow_auth_invalid_region(mock_keystore, hass): mock_keystore.return_value = fake_keystore with patch( "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=RuntimeError("Internal Server Error (ArgumentException)"), + side_effect=InvalidSymbolException, ): result = await hass.config_entries.flow.async_init( const.DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -756,7 +759,7 @@ async def test_config_flow_auth_invalid_pin(mock_keystore, hass): mock_keystore.return_value = fake_keystore with patch( "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=VulcanAPIException("Invalid PIN."), + side_effect=InvalidPINException, ): result = await hass.config_entries.flow.async_init( const.DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -782,7 +785,7 @@ async def test_config_flow_auth_expired_token(mock_keystore, hass): mock_keystore.return_value = fake_keystore with patch( "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=VulcanAPIException("Expired token."), + side_effect=ExpiredTokenException, ): result = await hass.config_entries.flow.async_init( const.DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -802,58 +805,6 @@ async def test_config_flow_auth_expired_token(mock_keystore, hass): assert result["errors"] == {"base": "expired_token"} -@mock.patch("homeassistant.components.vulcan.config_flow.Keystore.create") -async def test_config_flow_auth_api_unknown_error(mock_keystore, hass): - """Test a config flow with unknown API error.""" - mock_keystore.return_value = fake_keystore - with patch( - "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=VulcanAPIException("Unknown error"), - ): - result = await hass.config_entries.flow.async_init( - const.DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "auth" - assert result["errors"] is None - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_TOKEN: "3S10000", CONF_REGION: "region", CONF_PIN: "000000"}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "auth" - assert result["errors"] == {"base": "unknown"} - - -@mock.patch("homeassistant.components.vulcan.config_flow.Keystore.create") -async def test_config_flow_auth_api_unknown_runtime_error(mock_keystore, hass): - """Test a config flow with runtime error.""" - mock_keystore.return_value = fake_keystore - with patch( - "homeassistant.components.vulcan.config_flow.Account.register", - side_effect=RuntimeError("Unknown error"), - ): - result = await hass.config_entries.flow.async_init( - const.DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "auth" - assert result["errors"] is None - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_TOKEN: "3S10000", CONF_REGION: "region", CONF_PIN: "000000"}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "auth" - assert result["errors"] == {"base": "unknown"} - - @mock.patch("homeassistant.components.vulcan.config_flow.Keystore.create") async def test_config_flow_auth_connection_error(mock_keystore, hass): """Test a config flow with connection error.""" @@ -904,32 +855,3 @@ async def test_config_flow_auth_unknown_error(mock_keystore, hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "auth" assert result["errors"] == {"base": "unknown"} - - -@mock.patch("homeassistant.components.vulcan.Vulcan.get_students") -async def test_options_flow(mock_student, hass): - """Test config flow options.""" - mock_student.return_value = [ - Student.load(load_fixture("fake_student_1.json", "vulcan")) - ] - config_entry = MockConfigEntry( - domain=const.DOMAIN, - unique_id="0", - data=json.loads(load_fixture("fake_config_entry_data.json", "vulcan")), - ) - config_entry.add_to_hass(hass) - - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - result = await hass.config_entries.options.async_init(config_entry.entry_id) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "init" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={CONF_SCAN_INTERVAL: 2137} - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert config_entry.options == {CONF_SCAN_INTERVAL: 2137} diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 007e130ff6f..4d3302f7c13 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -1351,6 +1351,25 @@ async def test_manifest_list(hass, websocket_client): ] +async def test_manifest_list_specific_integrations(hass, websocket_client): + """Test loading manifests for specific integrations.""" + websocket_api = await async_get_integration(hass, "websocket_api") + + await websocket_client.send_json( + {"id": 5, "type": "manifest/list", "integrations": ["hue", "websocket_api"]} + ) + hue = await async_get_integration(hass, "hue") + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + assert sorted(msg["result"], key=lambda manifest: manifest["domain"]) == [ + hue.manifest, + websocket_api.manifest, + ] + + async def test_manifest_get(hass, websocket_client): """Test getting a manifest.""" hue = await async_get_integration(hass, "hue") diff --git a/tests/components/wemo/conftest.py b/tests/components/wemo/conftest.py index af7002a2500..1a5998c1f94 100644 --- a/tests/components/wemo/conftest.py +++ b/tests/components/wemo/conftest.py @@ -1,5 +1,6 @@ """Fixtures for pywemo.""" import asyncio +import contextlib from unittest.mock import create_autospec, patch import pytest @@ -52,9 +53,9 @@ def pywemo_discovery_responder_fixture(): yield -@pytest.fixture(name="pywemo_device") -def pywemo_device_fixture(pywemo_registry, pywemo_model): - """Fixture for WeMoDevice instances.""" +@contextlib.contextmanager +def create_pywemo_device(pywemo_registry, pywemo_model): + """Create a WeMoDevice instance.""" cls = getattr(pywemo, pywemo_model) device = create_autospec(cls, instance=True) device.host = MOCK_HOST @@ -68,7 +69,7 @@ def pywemo_device_fixture(pywemo_registry, pywemo_model): device.supports_long_press.return_value = cls.supports_long_press() if issubclass(cls, pywemo.Insight): - device.get_standby_state = pywemo.StandbyState.OFF + device.standby_state = pywemo.StandbyState.OFF device.current_power_watts = MOCK_INSIGHT_CURRENT_WATTS device.today_kwh = MOCK_INSIGHT_TODAY_KWH device.threshold_power_watts = MOCK_INSIGHT_STATE_THRESHOLD_POWER @@ -76,6 +77,12 @@ def pywemo_device_fixture(pywemo_registry, pywemo_model): device.today_on_time = 5678 device.total_on_time = 9012 + if issubclass(cls, pywemo.Maker): + device.has_sensor = 1 + device.sensor_state = 1 + device.switch_mode = 1 + device.switch_state = 0 + url = f"http://{MOCK_HOST}:{MOCK_PORT}/setup.xml" with patch("pywemo.setup_url_for_address", return_value=url), patch( "pywemo.discovery.device_from_description", return_value=device @@ -83,15 +90,21 @@ def pywemo_device_fixture(pywemo_registry, pywemo_model): yield device +@pytest.fixture(name="pywemo_device") +def pywemo_device_fixture(pywemo_registry, pywemo_model): + """Fixture for WeMoDevice instances.""" + with create_pywemo_device(pywemo_registry, pywemo_model) as pywemo_device: + yield pywemo_device + + @pytest.fixture(name="wemo_entity_suffix") def wemo_entity_suffix_fixture(): """Fixture to select a specific entity for wemo_entity.""" return "" -@pytest.fixture(name="wemo_entity") -async def async_wemo_entity_fixture(hass, pywemo_device, wemo_entity_suffix): - """Fixture for a Wemo entity in hass.""" +async def async_create_wemo_entity(hass, pywemo_device, wemo_entity_suffix): + """Create a hass entity for a wemo device.""" assert await async_setup_component( hass, DOMAIN, @@ -106,7 +119,13 @@ async def async_wemo_entity_fixture(hass, pywemo_device, wemo_entity_suffix): entity_registry = er.async_get(hass) for entry in entity_registry.entities.values(): - if entry.entity_id.endswith(wemo_entity_suffix): + if entry.entity_id.endswith(wemo_entity_suffix or pywemo_device.name.lower()): return entry return None + + +@pytest.fixture(name="wemo_entity") +async def async_wemo_entity_fixture(hass, pywemo_device, wemo_entity_suffix): + """Fixture for a Wemo entity in hass.""" + return await async_create_wemo_entity(hass, pywemo_device, wemo_entity_suffix) diff --git a/tests/components/wemo/test_binary_sensor.py b/tests/components/wemo/test_binary_sensor.py index 481a6348688..99a5df47e25 100644 --- a/tests/components/wemo/test_binary_sensor.py +++ b/tests/components/wemo/test_binary_sensor.py @@ -1,6 +1,7 @@ """Tests for the Wemo binary_sensor entity.""" import pytest +from pywemo import StandbyState from homeassistant.components.homeassistant import ( DOMAIN as HA_DOMAIN, @@ -80,19 +81,6 @@ class TestMaker(EntityTestHelpers): """Select the MakerBinarySensor entity.""" return MakerBinarySensor._name_suffix.lower() - @pytest.fixture(name="pywemo_device") - def pywemo_device_fixture(self, pywemo_device): - """Fixture for WeMoDevice instances.""" - pywemo_device.maker_params = { - "hassensor": 1, - "sensorstate": 1, - "switchmode": 1, - "switchstate": 0, - } - pywemo_device.has_sensor = pywemo_device.maker_params["hassensor"] - pywemo_device.sensor_state = pywemo_device.maker_params["sensorstate"] - yield pywemo_device - async def test_registry_state_callback( self, hass, pywemo_registry, pywemo_device, wemo_entity ): @@ -123,41 +111,27 @@ class TestInsight(EntityTestHelpers): """Select the InsightBinarySensor entity.""" return InsightBinarySensor._name_suffix.lower() - @pytest.fixture(name="pywemo_device") - def pywemo_device_fixture(self, pywemo_device): - """Fixture for WeMoDevice instances.""" - pywemo_device.insight_params = { - "currentpower": 1.0, - "todaymw": 200000000.0, - "state": "0", - "onfor": 0, - "ontoday": 0, - "ontotal": 0, - "powerthreshold": 0, - } - yield pywemo_device - async def test_registry_state_callback( self, hass, pywemo_registry, pywemo_device, wemo_entity ): """Verify that the binary_sensor receives state updates from the registry.""" # On state. pywemo_device.get_state.return_value = 1 - pywemo_device.insight_params["state"] = "1" + pywemo_device.standby_state = StandbyState.ON pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") await hass.async_block_till_done() assert hass.states.get(wemo_entity.entity_id).state == STATE_ON # Standby (Off) state. pywemo_device.get_state.return_value = 1 - pywemo_device.insight_params["state"] = "8" + pywemo_device.standby_state = StandbyState.STANDBY pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") await hass.async_block_till_done() assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF # Off state. pywemo_device.get_state.return_value = 0 - pywemo_device.insight_params["state"] = "1" + pywemo_device.standby_state = StandbyState.OFF pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") await hass.async_block_till_done() assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF diff --git a/tests/components/wemo/test_fan.py b/tests/components/wemo/test_fan.py index 56bf8939181..d58f8edbc3d 100644 --- a/tests/components/wemo/test_fan.py +++ b/tests/components/wemo/test_fan.py @@ -19,6 +19,7 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, STATE_OFF, STAT from homeassistant.setup import async_setup_component from . import entity_test_helpers +from .conftest import async_create_wemo_entity @pytest.fixture @@ -161,21 +162,14 @@ async def test_fan_set_percentage( pywemo_device.set_state.assert_called_with(expected_fan_mode) -class TestInitialFanMode: - """Test that the FanMode is set to High when turned on the first time.""" - - @pytest.fixture - def pywemo_device(self, pywemo_device): - """Set the FanMode to off initially.""" - pywemo_device.fan_mode = FanMode.Off - yield pywemo_device - - async def test_fan_mode_high_initially(self, hass, pywemo_device, wemo_entity): - """Verify the FanMode is set to High when turned on.""" - assert await hass.services.async_call( - FAN_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, - blocking=True, - ) - pywemo_device.set_state.assert_called_with(FanMode.High) +async def test_fan_mode_high_initially(hass, pywemo_device): + """Verify the FanMode is set to High when turned on.""" + pywemo_device.fan_mode = FanMode.Off + wemo_entity = await async_create_wemo_entity(hass, pywemo_device, "") + assert await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + pywemo_device.set_state.assert_called_with(FanMode.High) diff --git a/tests/components/wemo/test_light_bridge.py b/tests/components/wemo/test_light_bridge.py index 3184335b173..afcdb37cba9 100644 --- a/tests/components/wemo/test_light_bridge.py +++ b/tests/components/wemo/test_light_bridge.py @@ -8,7 +8,13 @@ from homeassistant.components.homeassistant import ( DOMAIN as HA_DOMAIN, SERVICE_UPDATE_ENTITY, ) -from homeassistant.components.light import ATTR_COLOR_TEMP, DOMAIN as LIGHT_DOMAIN +from homeassistant.components.light import ( + ATTR_COLOR_MODE, + ATTR_COLOR_TEMP, + ATTR_SUPPORTED_COLOR_MODES, + DOMAIN as LIGHT_DOMAIN, + ColorMode, +) from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON from homeassistant.setup import async_setup_component @@ -32,6 +38,7 @@ def pywemo_bridge_light_fixture(pywemo_device): light.name = pywemo_device.name light.bridge = pywemo_device light.state = {"onoff": 0, "available": True} + light.capabilities = ["onoff", "levelcontrol", "colortemperature"] pywemo_device.Lights = {pywemo_device.serialnumber: light} return light @@ -102,6 +109,8 @@ async def test_light_update_entity( ) state = hass.states.get(wemo_entity.entity_id) assert state.attributes.get(ATTR_COLOR_TEMP) == 432 + assert state.attributes.get(ATTR_SUPPORTED_COLOR_MODES) == [ColorMode.COLOR_TEMP] + assert state.attributes.get(ATTR_COLOR_MODE) == ColorMode.COLOR_TEMP assert state.state == STATE_ON # Off state. diff --git a/tests/components/wemo/test_sensor.py b/tests/components/wemo/test_sensor.py index ab6753975f1..7e0c8fa72f0 100644 --- a/tests/components/wemo/test_sensor.py +++ b/tests/components/wemo/test_sensor.py @@ -12,21 +12,6 @@ def pywemo_model(): return "Insight" -@pytest.fixture(name="pywemo_device") -def pywemo_device_fixture(pywemo_device): - """Fixture for WeMoDevice instances.""" - pywemo_device.insight_params = { - "currentpower": 1.0, - "todaymw": 200000000.0, - "state": 0, - "onfor": 0, - "ontoday": 0, - "ontotal": 0, - "powerthreshold": 0, - } - yield pywemo_device - - class InsightTestTemplate(EntityTestHelpers): """Base class for testing WeMo Insight Sensors.""" diff --git a/tests/components/wemo/test_switch.py b/tests/components/wemo/test_switch.py index 9c1dc804645..0f23aa8b8b6 100644 --- a/tests/components/wemo/test_switch.py +++ b/tests/components/wemo/test_switch.py @@ -1,17 +1,38 @@ """Tests for the Wemo switch entity.""" import pytest -from pywemo.exceptions import ActionException +import pywemo from homeassistant.components.homeassistant import ( DOMAIN as HA_DOMAIN, SERVICE_UPDATE_ENTITY, ) from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.components.wemo.switch import ( + ATTR_CURRENT_STATE_DETAIL, + ATTR_ON_LATEST_TIME, + ATTR_ON_TODAY_TIME, + ATTR_ON_TOTAL_TIME, + ATTR_POWER_THRESHOLD, + ATTR_SENSOR_STATE, + ATTR_SWITCH_MODE, + MAKER_SWITCH_MOMENTARY, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + STATE_OFF, + STATE_ON, + STATE_STANDBY, + STATE_UNKNOWN, +) from homeassistant.setup import async_setup_component from . import entity_test_helpers +from .conftest import ( + MOCK_INSIGHT_STATE_THRESHOLD_POWER, + async_create_wemo_entity, + create_pywemo_device, +) @pytest.fixture @@ -80,7 +101,7 @@ async def test_available_after_update( hass, pywemo_registry, pywemo_device, wemo_entity ): """Test the avaliability when an On call fails and after an update.""" - pywemo_device.on.side_effect = ActionException + pywemo_device.on.side_effect = pywemo.exceptions.ActionException pywemo_device.get_state.return_value = 1 await entity_test_helpers.test_avaliable_after_update( hass, pywemo_registry, pywemo_device, wemo_entity, SWITCH_DOMAIN @@ -90,3 +111,64 @@ async def test_available_after_update( async def test_turn_off_state(hass, wemo_entity): """Test that the device state is updated after turning off.""" await entity_test_helpers.test_turn_off_state(hass, wemo_entity, SWITCH_DOMAIN) + + +async def test_insight_state_attributes(hass, pywemo_registry): + """Verify the switch attributes are set for the Insight device.""" + await async_setup_component(hass, HA_DOMAIN, {}) + with create_pywemo_device(pywemo_registry, "Insight") as insight: + wemo_entity = await async_create_wemo_entity(hass, insight, "") + attributes = hass.states.get(wemo_entity.entity_id).attributes + assert attributes[ATTR_ON_LATEST_TIME] == "00d 00h 20m 34s" + assert attributes[ATTR_ON_TODAY_TIME] == "00d 01h 34m 38s" + assert attributes[ATTR_ON_TOTAL_TIME] == "00d 02h 30m 12s" + assert attributes[ATTR_POWER_THRESHOLD] == MOCK_INSIGHT_STATE_THRESHOLD_POWER + assert attributes[ATTR_CURRENT_STATE_DETAIL] == STATE_OFF + + async def async_update(): + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + + # Test 'ON' state detail value. + insight.standby_state = pywemo.StandbyState.ON + await async_update() + attributes = hass.states.get(wemo_entity.entity_id).attributes + assert attributes[ATTR_CURRENT_STATE_DETAIL] == STATE_ON + + # Test 'STANDBY' state detail value. + insight.standby_state = pywemo.StandbyState.STANDBY + await async_update() + attributes = hass.states.get(wemo_entity.entity_id).attributes + assert attributes[ATTR_CURRENT_STATE_DETAIL] == STATE_STANDBY + + # Test 'UNKNOWN' state detail value. + insight.standby_state = None + await async_update() + attributes = hass.states.get(wemo_entity.entity_id).attributes + assert attributes[ATTR_CURRENT_STATE_DETAIL] == STATE_UNKNOWN + + +async def test_maker_state_attributes(hass, pywemo_registry): + """Verify the switch attributes are set for the Insight device.""" + await async_setup_component(hass, HA_DOMAIN, {}) + with create_pywemo_device(pywemo_registry, "Maker") as maker: + wemo_entity = await async_create_wemo_entity(hass, maker, "") + attributes = hass.states.get(wemo_entity.entity_id).attributes + assert attributes[ATTR_SENSOR_STATE] == STATE_OFF + assert attributes[ATTR_SWITCH_MODE] == MAKER_SWITCH_MOMENTARY + + # Test 'ON' sensor state and 'TOGGLE' switch mode values. + maker.sensor_state = 0 + maker.switch_mode = 0 + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + attributes = hass.states.get(wemo_entity.entity_id).attributes + assert attributes[ATTR_SENSOR_STATE] == STATE_ON diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index 465c26deb32..db26f7328ce 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -59,7 +59,6 @@ def test_config_schema_basic_config() -> None: def test_config_schema_client_id() -> None: """Test schema.""" - config_schema_assert_fail({CONF_CLIENT_SECRET: "my_client_secret"}) config_schema_assert_fail( {CONF_CLIENT_SECRET: "my_client_secret", CONF_CLIENT_ID: ""} ) @@ -70,7 +69,6 @@ def test_config_schema_client_id() -> None: def test_config_schema_client_secret() -> None: """Test schema.""" - config_schema_assert_fail({CONF_CLIENT_ID: "my_client_id"}) config_schema_assert_fail({CONF_CLIENT_ID: "my_client_id", CONF_CLIENT_SECRET: ""}) config_schema_validate( {CONF_CLIENT_ID: "my_client_id", CONF_CLIENT_SECRET: "my_client_secret"} diff --git a/tests/components/wled/test_button.py b/tests/components/wled/test_button.py index a09c7e2aaa3..210d67a24a0 100644 --- a/tests/components/wled/test_button.py +++ b/tests/components/wled/test_button.py @@ -1,5 +1,5 @@ """Tests for the WLED button platform.""" -from unittest.mock import AsyncMock, MagicMock +from unittest.mock import MagicMock from freezegun import freeze_time import pytest @@ -17,6 +17,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import EntityCategory @@ -55,168 +56,41 @@ async def test_button_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED buttons.""" mock_wled.reset.side_effect = WLEDError - await hass.services.async_call( - BUTTON_DOMAIN, - SERVICE_PRESS, - {ATTR_ENTITY_ID: "button.wled_rgb_light_restart"}, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: "button.wled_rgb_light_restart"}, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("button.wled_rgb_light_restart") assert state assert state.state == "2021-11-04T16:37:00+00:00" - assert "Invalid response from API" in caplog.text async def test_button_connection_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED buttons.""" mock_wled.reset.side_effect = WLEDConnectionError - await hass.services.async_call( - BUTTON_DOMAIN, - SERVICE_PRESS, - {ATTR_ENTITY_ID: "button.wled_rgb_light_restart"}, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: "button.wled_rgb_light_restart"}, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("button.wled_rgb_light_restart") assert state assert state.state == STATE_UNAVAILABLE - assert "Error communicating with API" in caplog.text - - -async def test_button_update_stay_stable( - hass: HomeAssistant, - entity_registry_enabled_by_default: AsyncMock, - init_integration: MockConfigEntry, - mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test the update button. - - There is both an update for beta and stable available, however, the device - is currently running a stable version. Therefore, the update button should - update the the next stable (even though beta is newer). - """ - entity_registry = er.async_get(hass) - - entry = entity_registry.async_get("button.wled_rgb_light_update") - assert entry - assert entry.unique_id == "aabbccddeeff_update" - assert entry.entity_category is EntityCategory.CONFIG - - state = hass.states.get("button.wled_rgb_light_update") - assert state - assert state.state == STATE_UNKNOWN - assert state.attributes[ATTR_DEVICE_CLASS] == ButtonDeviceClass.UPDATE - - await hass.services.async_call( - BUTTON_DOMAIN, - SERVICE_PRESS, - {ATTR_ENTITY_ID: "button.wled_rgb_light_update"}, - blocking=True, - ) - await hass.async_block_till_done() - assert mock_wled.upgrade.call_count == 1 - mock_wled.upgrade.assert_called_with(version="0.12.0") - assert ( - "The WLED update button 'button.wled_rgb_light_update' is deprecated" - in caplog.text - ) - - -@pytest.mark.parametrize("mock_wled", ["wled/rgbw.json"], indirect=True) -async def test_button_update_beta_to_stable( - hass: HomeAssistant, - entity_registry_enabled_by_default: AsyncMock, - init_integration: MockConfigEntry, - mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test the update button. - - There is both an update for beta and stable available the device - is currently a beta, however, a newer stable is available. Therefore, the - update button should update to the next stable. - """ - await hass.services.async_call( - BUTTON_DOMAIN, - SERVICE_PRESS, - {ATTR_ENTITY_ID: "button.wled_rgbw_light_update"}, - blocking=True, - ) - await hass.async_block_till_done() - assert mock_wled.upgrade.call_count == 1 - mock_wled.upgrade.assert_called_with(version="0.8.6") - assert ( - "The WLED update button 'button.wled_rgbw_light_update' is deprecated" - in caplog.text - ) - - -@pytest.mark.parametrize("mock_wled", ["wled/rgb_single_segment.json"], indirect=True) -async def test_button_update_stay_beta( - hass: HomeAssistant, - entity_registry_enabled_by_default: AsyncMock, - init_integration: MockConfigEntry, - mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test the update button. - - There is an update for beta and the device is currently a beta. Therefore, - the update button should update to the next beta. - """ - await hass.services.async_call( - BUTTON_DOMAIN, - SERVICE_PRESS, - {ATTR_ENTITY_ID: "button.wled_rgb_light_update"}, - blocking=True, - ) - await hass.async_block_till_done() - assert mock_wled.upgrade.call_count == 1 - mock_wled.upgrade.assert_called_with(version="0.8.6b2") - assert ( - "The WLED update button 'button.wled_rgb_light_update' is deprecated" - in caplog.text - ) - - -@pytest.mark.parametrize("mock_wled", ["wled/rgb_websocket.json"], indirect=True) -async def test_button_no_update_available( - hass: HomeAssistant, - entity_registry_enabled_by_default: AsyncMock, - init_integration: MockConfigEntry, - mock_wled: MagicMock, -) -> None: - """Test the update button. There is no update available.""" - state = hass.states.get("button.wled_websocket_update") - assert state - assert state.state == STATE_UNAVAILABLE - - -async def test_disabled_by_default( - hass: HomeAssistant, init_integration: MockConfigEntry -) -> None: - """Test that the update button is disabled by default.""" - registry = er.async_get(hass) - - state = hass.states.get("button.wled_rgb_light_update") - assert state is None - - entry = registry.async_get("button.wled_rgb_light_update") - assert entry - assert entry.disabled - assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index fb2efce404a..b52cf91a11e 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -25,6 +25,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util @@ -305,23 +306,22 @@ async def test_light_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED lights.""" mock_wled.segment.side_effect = WLEDError - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.wled_rgb_light"}, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.wled_rgb_light"}, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("light.wled_rgb_light") assert state assert state.state == STATE_ON - assert "Invalid response from API" in caplog.text assert mock_wled.segment.call_count == 1 mock_wled.segment.assert_called_with(on=False, segment_id=0, transition=None) @@ -330,23 +330,22 @@ async def test_light_connection_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED switches.""" mock_wled.segment.side_effect = WLEDConnectionError - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.wled_rgb_light"}, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.wled_rgb_light"}, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("light.wled_rgb_light") assert state assert state.state == STATE_UNAVAILABLE - assert "Error communicating with API" in caplog.text assert mock_wled.segment.call_count == 1 mock_wled.segment.assert_called_with(on=False, segment_id=0, transition=None) diff --git a/tests/components/wled/test_number.py b/tests/components/wled/test_number.py index e4b8958b077..5c9d562e0c0 100644 --- a/tests/components/wled/test_number.py +++ b/tests/components/wled/test_number.py @@ -14,6 +14,7 @@ from homeassistant.components.number.const import ( from homeassistant.components.wled.const import SCAN_INTERVAL from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util @@ -109,26 +110,25 @@ async def test_speed_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED numbers.""" mock_wled.segment.side_effect = WLEDError - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_speed", - ATTR_VALUE: 42, - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_speed", + ATTR_VALUE: 42, + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("number.wled_rgb_light_segment_1_speed") assert state assert state.state == "16" - assert "Invalid response from API" in caplog.text assert mock_wled.segment.call_count == 1 mock_wled.segment.assert_called_with(segment_id=1, speed=42) @@ -137,26 +137,25 @@ async def test_speed_connection_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED numbers.""" mock_wled.segment.side_effect = WLEDConnectionError - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_speed", - ATTR_VALUE: 42, - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_speed", + ATTR_VALUE: 42, + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("number.wled_rgb_light_segment_1_speed") assert state assert state.state == STATE_UNAVAILABLE - assert "Error communicating with API" in caplog.text assert mock_wled.segment.call_count == 1 mock_wled.segment.assert_called_with(segment_id=1, speed=42) @@ -250,26 +249,25 @@ async def test_intensity_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED numbers.""" mock_wled.segment.side_effect = WLEDError - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_intensity", - ATTR_VALUE: 21, - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_intensity", + ATTR_VALUE: 21, + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("number.wled_rgb_light_segment_1_intensity") assert state assert state.state == "64" - assert "Invalid response from API" in caplog.text assert mock_wled.segment.call_count == 1 mock_wled.segment.assert_called_with(segment_id=1, intensity=21) @@ -278,25 +276,24 @@ async def test_intensity_connection_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED numbers.""" mock_wled.segment.side_effect = WLEDConnectionError - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_intensity", - ATTR_VALUE: 128, - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.wled_rgb_light_segment_1_intensity", + ATTR_VALUE: 128, + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("number.wled_rgb_light_segment_1_intensity") assert state assert state.state == STATE_UNAVAILABLE - assert "Error communicating with API" in caplog.text assert mock_wled.segment.call_count == 1 mock_wled.segment.assert_called_with(segment_id=1, intensity=128) diff --git a/tests/components/wled/test_select.py b/tests/components/wled/test_select.py index d798a723246..ebe3c7ca018 100644 --- a/tests/components/wled/test_select.py +++ b/tests/components/wled/test_select.py @@ -16,6 +16,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import EntityCategory import homeassistant.util.dt as dt_util @@ -161,26 +162,25 @@ async def test_color_palette_select_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED selects.""" mock_wled.segment.side_effect = WLEDError - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - { - ATTR_ENTITY_ID: "select.wled_rgb_light_segment_1_color_palette", - ATTR_OPTION: "Icefire", - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgb_light_segment_1_color_palette", + ATTR_OPTION: "Icefire", + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("select.wled_rgb_light_segment_1_color_palette") assert state assert state.state == "Random Cycle" - assert "Invalid response from API" in caplog.text assert mock_wled.segment.call_count == 1 mock_wled.segment.assert_called_with(segment_id=1, palette="Icefire") @@ -189,26 +189,25 @@ async def test_color_palette_select_connection_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED selects.""" mock_wled.segment.side_effect = WLEDConnectionError - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - { - ATTR_ENTITY_ID: "select.wled_rgb_light_segment_1_color_palette", - ATTR_OPTION: "Icefire", - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgb_light_segment_1_color_palette", + ATTR_OPTION: "Icefire", + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("select.wled_rgb_light_segment_1_color_palette") assert state assert state.state == STATE_UNAVAILABLE - assert "Error communicating with API" in caplog.text assert mock_wled.segment.call_count == 1 mock_wled.segment.assert_called_with(segment_id=1, palette="Icefire") @@ -280,26 +279,25 @@ async def test_preset_select_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED selects.""" mock_wled.preset.side_effect = WLEDError - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - { - ATTR_ENTITY_ID: "select.wled_rgbw_light_preset", - ATTR_OPTION: "Preset 2", - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgbw_light_preset", + ATTR_OPTION: "Preset 2", + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("select.wled_rgbw_light_preset") assert state assert state.state == "Preset 1" - assert "Invalid response from API" in caplog.text assert mock_wled.preset.call_count == 1 mock_wled.preset.assert_called_with(preset="Preset 2") @@ -309,26 +307,25 @@ async def test_preset_select_connection_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED selects.""" mock_wled.preset.side_effect = WLEDConnectionError - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - { - ATTR_ENTITY_ID: "select.wled_rgbw_light_preset", - ATTR_OPTION: "Preset 2", - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgbw_light_preset", + ATTR_OPTION: "Preset 2", + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("select.wled_rgbw_light_preset") assert state assert state.state == STATE_UNAVAILABLE - assert "Error communicating with API" in caplog.text assert mock_wled.preset.call_count == 1 mock_wled.preset.assert_called_with(preset="Preset 2") @@ -400,26 +397,25 @@ async def test_playlist_select_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED selects.""" mock_wled.playlist.side_effect = WLEDError - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - { - ATTR_ENTITY_ID: "select.wled_rgbw_light_playlist", - ATTR_OPTION: "Playlist 2", - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgbw_light_playlist", + ATTR_OPTION: "Playlist 2", + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("select.wled_rgbw_light_playlist") assert state assert state.state == "Playlist 1" - assert "Invalid response from API" in caplog.text assert mock_wled.playlist.call_count == 1 mock_wled.playlist.assert_called_with(playlist="Playlist 2") @@ -429,26 +425,25 @@ async def test_playlist_select_connection_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED selects.""" mock_wled.playlist.side_effect = WLEDConnectionError - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - { - ATTR_ENTITY_ID: "select.wled_rgbw_light_playlist", - ATTR_OPTION: "Playlist 2", - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgbw_light_playlist", + ATTR_OPTION: "Playlist 2", + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("select.wled_rgbw_light_playlist") assert state assert state.state == STATE_UNAVAILABLE - assert "Error communicating with API" in caplog.text assert mock_wled.playlist.call_count == 1 mock_wled.playlist.assert_called_with(playlist="Playlist 2") @@ -489,26 +484,25 @@ async def test_live_select_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED selects.""" mock_wled.live.side_effect = WLEDError - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - { - ATTR_ENTITY_ID: "select.wled_rgb_light_live_override", - ATTR_OPTION: "1", - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgb_light_live_override", + ATTR_OPTION: "1", + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("select.wled_rgb_light_live_override") assert state assert state.state == "0" - assert "Invalid response from API" in caplog.text assert mock_wled.live.call_count == 1 mock_wled.live.assert_called_with(live=1) @@ -517,25 +511,24 @@ async def test_live_select_connection_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED selects.""" mock_wled.live.side_effect = WLEDConnectionError - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - { - ATTR_ENTITY_ID: "select.wled_rgb_light_live_override", - ATTR_OPTION: "2", - }, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgb_light_live_override", + ATTR_OPTION: "2", + }, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("select.wled_rgb_light_live_override") assert state assert state.state == STATE_UNAVAILABLE - assert "Error communicating with API" in caplog.text assert mock_wled.live.call_count == 1 mock_wled.live.assert_called_with(live=2) diff --git a/tests/components/wled/test_switch.py b/tests/components/wled/test_switch.py index 2bf494284f5..1902a324764 100644 --- a/tests/components/wled/test_switch.py +++ b/tests/components/wled/test_switch.py @@ -23,6 +23,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import EntityCategory import homeassistant.util.dt as dt_util @@ -175,46 +176,44 @@ async def test_switch_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED switches.""" mock_wled.nightlight.side_effect = WLEDError - await hass.services.async_call( - SWITCH_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "switch.wled_rgb_light_nightlight"}, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Invalid response from WLED API"): + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.wled_rgb_light_nightlight"}, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("switch.wled_rgb_light_nightlight") assert state assert state.state == STATE_OFF - assert "Invalid response from API" in caplog.text async def test_switch_connection_error( hass: HomeAssistant, init_integration: MockConfigEntry, mock_wled: MagicMock, - caplog: pytest.LogCaptureFixture, ) -> None: """Test error handling of the WLED switches.""" mock_wled.nightlight.side_effect = WLEDConnectionError - await hass.services.async_call( - SWITCH_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "switch.wled_rgb_light_nightlight"}, - blocking=True, - ) - await hass.async_block_till_done() + with pytest.raises(HomeAssistantError, match="Error communicating with WLED API"): + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.wled_rgb_light_nightlight"}, + blocking=True, + ) + await hass.async_block_till_done() state = hass.states.get("switch.wled_rgb_light_nightlight") assert state assert state.state == STATE_UNAVAILABLE - assert "Error communicating with API" in caplog.text @pytest.mark.parametrize("mock_wled", ["wled/rgb_single_segment.json"], indirect=True) diff --git a/tests/components/ws66i/__init__.py b/tests/components/ws66i/__init__.py new file mode 100644 index 00000000000..3106b858d0c --- /dev/null +++ b/tests/components/ws66i/__init__.py @@ -0,0 +1 @@ +"""Tests for the ws66i component.""" diff --git a/tests/components/ws66i/test_config_flow.py b/tests/components/ws66i/test_config_flow.py new file mode 100644 index 00000000000..4fe3554941d --- /dev/null +++ b/tests/components/ws66i/test_config_flow.py @@ -0,0 +1,152 @@ +"""Test the WS66i 6-Zone Amplifier config flow.""" +from unittest.mock import patch + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components.ws66i.const import ( + CONF_SOURCE_1, + CONF_SOURCE_2, + CONF_SOURCE_3, + CONF_SOURCE_4, + CONF_SOURCE_5, + CONF_SOURCE_6, + CONF_SOURCES, + DOMAIN, + INIT_OPTIONS_DEFAULT, +) +from homeassistant.const import CONF_IP_ADDRESS + +from .test_media_player import AttrDict + +from tests.common import MockConfigEntry + +CONFIG = {CONF_IP_ADDRESS: "1.1.1.1"} + + +async def test_form(hass): + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.ws66i.config_flow.get_ws66i", + ) as mock_ws66i, patch( + "homeassistant.components.ws66i.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + + ws66i_instance = mock_ws66i.return_value + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG + ) + await hass.async_block_till_done() + + ws66i_instance.open.assert_called_once() + ws66i_instance.close.assert_called_once() + + assert result2["type"] == "create_entry" + assert result2["title"] == "WS66i Amp" + assert result2["data"] == {CONF_IP_ADDRESS: CONFIG[CONF_IP_ADDRESS]} + + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_cannot_connect(hass): + """Test cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch("homeassistant.components.ws66i.config_flow.get_ws66i") as mock_ws66i: + ws66i_instance = mock_ws66i.return_value + ws66i_instance.open.side_effect = ConnectionError + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_wrong_ip(hass): + """Test cannot connect error with bad IP.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch("homeassistant.components.ws66i.config_flow.get_ws66i") as mock_ws66i: + ws66i_instance = mock_ws66i.return_value + ws66i_instance.zone_status.return_value = None + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_generic_exception(hass): + """Test generic exception.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch("homeassistant.components.ws66i.config_flow.get_ws66i") as mock_ws66i: + ws66i_instance = mock_ws66i.return_value + ws66i_instance.open.side_effect = Exception + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} + + +async def test_options_flow(hass): + """Test config flow options.""" + conf = {CONF_IP_ADDRESS: "1.1.1.1", CONF_SOURCES: INIT_OPTIONS_DEFAULT} + + config_entry = MockConfigEntry( + domain=DOMAIN, + data=conf, + options={CONF_SOURCES: INIT_OPTIONS_DEFAULT}, + ) + config_entry.add_to_hass(hass) + + with patch("homeassistant.components.ws66i.get_ws66i") as mock_ws66i: + ws66i_instance = mock_ws66i.return_value + ws66i_instance.zone_status.return_value = AttrDict( + power=True, volume=0, mute=True, source=1, treble=0, bass=0, balance=10 + ) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_SOURCE_1: "one", + CONF_SOURCE_2: "too", + CONF_SOURCE_3: "tree", + CONF_SOURCE_4: "for", + CONF_SOURCE_5: "feeve", + CONF_SOURCE_6: "roku", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options[CONF_SOURCES] == { + "1": "one", + "2": "too", + "3": "tree", + "4": "for", + "5": "feeve", + "6": "roku", + } diff --git a/tests/components/ws66i/test_init.py b/tests/components/ws66i/test_init.py new file mode 100644 index 00000000000..557c53e97aa --- /dev/null +++ b/tests/components/ws66i/test_init.py @@ -0,0 +1,80 @@ +"""Test the WS66i 6-Zone Amplifier init file.""" +from unittest.mock import patch + +from homeassistant.components.ws66i.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState + +from .test_media_player import ( + MOCK_CONFIG, + MOCK_DEFAULT_OPTIONS, + MOCK_OPTIONS, + MockWs66i, +) + +from tests.common import MockConfigEntry + +ZONE_1_ID = "media_player.zone_11" + + +async def test_cannot_connect(hass): + """Test connection error.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data=MOCK_CONFIG, options=MOCK_OPTIONS + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.ws66i.get_ws66i", + new=lambda *a: MockWs66i(fail_open=True), + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.SETUP_RETRY + assert hass.states.get(ZONE_1_ID) is None + + +async def test_cannot_connect_2(hass): + """Test connection error pt 2.""" + # Another way to test same case as test_cannot_connect + ws66i = MockWs66i() + config_entry = MockConfigEntry( + domain=DOMAIN, data=MOCK_CONFIG, options=MOCK_DEFAULT_OPTIONS + ) + config_entry.add_to_hass(hass) + + with patch.object(MockWs66i, "open", side_effect=ConnectionError): + with patch( + "homeassistant.components.ws66i.get_ws66i", + new=lambda *a: ws66i, + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.SETUP_RETRY + assert hass.states.get(ZONE_1_ID) is None + + +async def test_unload_config_entry(hass): + """Test unloading config entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data=MOCK_CONFIG, options=MOCK_OPTIONS + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.ws66i.get_ws66i", + new=lambda *a: MockWs66i(), + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert hass.data[DOMAIN][config_entry.entry_id] + + with patch.object(MockWs66i, "close") as method_call: + await config_entry.async_unload(hass) + await hass.async_block_till_done() + + assert method_call.called + + assert not hass.data[DOMAIN] diff --git a/tests/components/ws66i/test_media_player.py b/tests/components/ws66i/test_media_player.py new file mode 100644 index 00000000000..fbe6a7b2782 --- /dev/null +++ b/tests/components/ws66i/test_media_player.py @@ -0,0 +1,496 @@ +"""The tests for WS66i Media player platform.""" +from collections import defaultdict +from unittest.mock import patch + +from homeassistant.components.media_player import MediaPlayerEntityFeature +from homeassistant.components.media_player.const import ( + ATTR_INPUT_SOURCE, + ATTR_INPUT_SOURCE_LIST, + ATTR_MEDIA_VOLUME_LEVEL, + DOMAIN as MEDIA_PLAYER_DOMAIN, + SERVICE_SELECT_SOURCE, +) +from homeassistant.components.ws66i.const import ( + CONF_SOURCES, + DOMAIN, + INIT_OPTIONS_DEFAULT, + MAX_VOL, + POLL_INTERVAL, +) +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ( + CONF_IP_ADDRESS, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + SERVICE_VOLUME_DOWN, + SERVICE_VOLUME_MUTE, + SERVICE_VOLUME_SET, + SERVICE_VOLUME_UP, + STATE_ON, + STATE_UNAVAILABLE, +) +from homeassistant.helpers import entity_registry as er +from homeassistant.util.dt import utcnow + +from tests.common import MockConfigEntry, async_fire_time_changed + +MOCK_SOURCE_DIC = { + "1": "one", + "2": "two", + "3": "three", + "4": "four", + "5": "five", + "6": "six", +} +MOCK_CONFIG = {CONF_IP_ADDRESS: "fake ip"} +MOCK_OPTIONS = {CONF_SOURCES: MOCK_SOURCE_DIC} +MOCK_DEFAULT_OPTIONS = {CONF_SOURCES: INIT_OPTIONS_DEFAULT} + +ZONE_1_ID = "media_player.zone_11" +ZONE_2_ID = "media_player.zone_12" +ZONE_7_ID = "media_player.zone_21" + + +class AttrDict(dict): + """Helper class for mocking attributes.""" + + def __setattr__(self, name, value): + """Set attribute.""" + self[name] = value + + def __getattr__(self, item): + """Get attribute.""" + try: + return self[item] + except KeyError as err: + # The reason for doing this is because of the deepcopy in my code + raise AttributeError(item) from err + + +class MockWs66i: + """Mock for pyws66i object.""" + + def __init__(self, fail_open=False, fail_zone_check=None): + """Init mock object.""" + self.zones = defaultdict( + lambda: AttrDict( + power=True, volume=0, mute=True, source=1, treble=0, bass=0, balance=10 + ) + ) + self.fail_open = fail_open + self.fail_zone_check = fail_zone_check + + def open(self): + """Open socket. Do nothing.""" + if self.fail_open is True: + raise ConnectionError() + + def close(self): + """Close socket. Do nothing.""" + + def zone_status(self, zone_id): + """Get zone status.""" + if self.fail_zone_check is not None and zone_id in self.fail_zone_check: + return None + status = self.zones[zone_id] + status.zone = zone_id + return AttrDict(status) + + def set_source(self, zone_id, source_idx): + """Set source for zone.""" + self.zones[zone_id].source = source_idx + + def set_power(self, zone_id, power): + """Turn zone on/off.""" + self.zones[zone_id].power = power + + def set_mute(self, zone_id, mute): + """Mute/unmute zone.""" + self.zones[zone_id].mute = mute + + def set_volume(self, zone_id, volume): + """Set volume for zone.""" + self.zones[zone_id].volume = volume + + def restore_zone(self, zone): + """Restore zone status.""" + self.zones[zone.zone] = AttrDict(zone) + + +async def test_setup_success(hass): + """Test connection success.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data=MOCK_CONFIG, options=MOCK_OPTIONS + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.ws66i.get_ws66i", + new=lambda *a: MockWs66i(), + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.LOADED + assert hass.states.get(ZONE_1_ID) is not None + + +async def _setup_ws66i(hass, ws66i) -> MockConfigEntry: + config_entry = MockConfigEntry( + domain=DOMAIN, data=MOCK_CONFIG, options=MOCK_DEFAULT_OPTIONS + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.ws66i.get_ws66i", + new=lambda *a: ws66i, + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + return config_entry + + +async def _setup_ws66i_with_options(hass, ws66i) -> MockConfigEntry: + config_entry = MockConfigEntry( + domain=DOMAIN, data=MOCK_CONFIG, options=MOCK_OPTIONS + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.ws66i.get_ws66i", + new=lambda *a: ws66i, + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + return config_entry + + +async def _call_media_player_service(hass, name, data): + await hass.services.async_call( + MEDIA_PLAYER_DOMAIN, name, service_data=data, blocking=True + ) + + +async def test_update(hass): + """Test updating values from ws66i.""" + ws66i = MockWs66i() + _ = await _setup_ws66i_with_options(hass, ws66i) + + # Changing media player to new state + await _call_media_player_service( + hass, SERVICE_VOLUME_SET, {"entity_id": ZONE_1_ID, "volume_level": 0.0} + ) + await _call_media_player_service( + hass, SERVICE_SELECT_SOURCE, {"entity_id": ZONE_1_ID, "source": "one"} + ) + + ws66i.set_source(11, 3) + ws66i.set_volume(11, MAX_VOL) + + with patch.object(MockWs66i, "open") as method_call: + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + + assert not method_call.called + + state = hass.states.get(ZONE_1_ID) + + assert hass.states.is_state(ZONE_1_ID, STATE_ON) + assert state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == 1.0 + assert state.attributes[ATTR_INPUT_SOURCE] == "three" + + +async def test_failed_update(hass): + """Test updating failure from ws66i.""" + ws66i = MockWs66i() + _ = await _setup_ws66i_with_options(hass, ws66i) + + # Changing media player to new state + await _call_media_player_service( + hass, SERVICE_VOLUME_SET, {"entity_id": ZONE_1_ID, "volume_level": 0.0} + ) + await _call_media_player_service( + hass, SERVICE_SELECT_SOURCE, {"entity_id": ZONE_1_ID, "source": "one"} + ) + + ws66i.set_source(11, 3) + ws66i.set_volume(11, MAX_VOL) + + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + + # Failed update, close called + with patch.object(MockWs66i, "zone_status", return_value=None): + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + + assert hass.states.is_state(ZONE_1_ID, STATE_UNAVAILABLE) + + # A connection re-attempt fails + with patch.object(MockWs66i, "zone_status", return_value=None): + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + + # A connection re-attempt succeeds + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + + # confirm entity is back on + state = hass.states.get(ZONE_1_ID) + + assert hass.states.is_state(ZONE_1_ID, STATE_ON) + assert state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == 1.0 + assert state.attributes[ATTR_INPUT_SOURCE] == "three" + + +async def test_supported_features(hass): + """Test supported features property.""" + await _setup_ws66i(hass, MockWs66i()) + + state = hass.states.get(ZONE_1_ID) + assert ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + == state.attributes["supported_features"] + ) + + +async def test_source_list(hass): + """Test source list property.""" + await _setup_ws66i(hass, MockWs66i()) + + state = hass.states.get(ZONE_1_ID) + # Note, the list is sorted! + assert state.attributes[ATTR_INPUT_SOURCE_LIST] == list( + INIT_OPTIONS_DEFAULT.values() + ) + + +async def test_source_list_with_options(hass): + """Test source list property.""" + await _setup_ws66i_with_options(hass, MockWs66i()) + + state = hass.states.get(ZONE_1_ID) + # Note, the list is sorted! + assert state.attributes[ATTR_INPUT_SOURCE_LIST] == list(MOCK_SOURCE_DIC.values()) + + +async def test_select_source(hass): + """Test source selection methods.""" + ws66i = MockWs66i() + await _setup_ws66i_with_options(hass, ws66i) + + await _call_media_player_service( + hass, + SERVICE_SELECT_SOURCE, + {"entity_id": ZONE_1_ID, ATTR_INPUT_SOURCE: "three"}, + ) + assert ws66i.zones[11].source == 3 + + +async def test_source_select(hass): + """Test source selection simulated from keypad.""" + ws66i = MockWs66i() + _ = await _setup_ws66i_with_options(hass, ws66i) + + ws66i.set_source(11, 5) + + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get(ZONE_1_ID) + + assert state.attributes.get(ATTR_INPUT_SOURCE) == "five" + + +async def test_turn_on_off(hass): + """Test turning on the zone.""" + ws66i = MockWs66i() + await _setup_ws66i(hass, ws66i) + + await _call_media_player_service(hass, SERVICE_TURN_OFF, {"entity_id": ZONE_1_ID}) + assert not ws66i.zones[11].power + + await _call_media_player_service(hass, SERVICE_TURN_ON, {"entity_id": ZONE_1_ID}) + assert ws66i.zones[11].power + + +async def test_mute_volume(hass): + """Test mute functionality.""" + ws66i = MockWs66i() + await _setup_ws66i(hass, ws66i) + + await _call_media_player_service( + hass, SERVICE_VOLUME_SET, {"entity_id": ZONE_1_ID, "volume_level": 0.5} + ) + await _call_media_player_service( + hass, SERVICE_VOLUME_MUTE, {"entity_id": ZONE_1_ID, "is_volume_muted": False} + ) + assert not ws66i.zones[11].mute + + await _call_media_player_service( + hass, SERVICE_VOLUME_MUTE, {"entity_id": ZONE_1_ID, "is_volume_muted": True} + ) + assert ws66i.zones[11].mute + + +async def test_volume_up_down(hass): + """Test increasing volume by one.""" + ws66i = MockWs66i() + _ = await _setup_ws66i(hass, ws66i) + + await _call_media_player_service( + hass, SERVICE_VOLUME_SET, {"entity_id": ZONE_1_ID, "volume_level": 0.0} + ) + assert ws66i.zones[11].volume == 0 + + await _call_media_player_service( + hass, SERVICE_VOLUME_DOWN, {"entity_id": ZONE_1_ID} + ) + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + # should not go below zero + assert ws66i.zones[11].volume == 0 + + await _call_media_player_service(hass, SERVICE_VOLUME_UP, {"entity_id": ZONE_1_ID}) + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + assert ws66i.zones[11].volume == 1 + + await _call_media_player_service( + hass, SERVICE_VOLUME_SET, {"entity_id": ZONE_1_ID, "volume_level": 1.0} + ) + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + assert ws66i.zones[11].volume == MAX_VOL + + await _call_media_player_service(hass, SERVICE_VOLUME_UP, {"entity_id": ZONE_1_ID}) + + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + # should not go above 38 (MAX_VOL) + assert ws66i.zones[11].volume == MAX_VOL + + await _call_media_player_service( + hass, SERVICE_VOLUME_DOWN, {"entity_id": ZONE_1_ID} + ) + assert ws66i.zones[11].volume == MAX_VOL - 1 + + +async def test_volume_while_mute(hass): + """Test increasing volume by one.""" + ws66i = MockWs66i() + _ = await _setup_ws66i(hass, ws66i) + + # Set vol to a known value + await _call_media_player_service( + hass, SERVICE_VOLUME_SET, {"entity_id": ZONE_1_ID, "volume_level": 0.0} + ) + assert ws66i.zones[11].volume == 0 + + # Set mute to a known value, False + await _call_media_player_service( + hass, SERVICE_VOLUME_MUTE, {"entity_id": ZONE_1_ID, "is_volume_muted": False} + ) + assert not ws66i.zones[11].mute + + # Mute the zone + await _call_media_player_service( + hass, SERVICE_VOLUME_MUTE, {"entity_id": ZONE_1_ID, "is_volume_muted": True} + ) + assert ws66i.zones[11].mute + + # Increase volume. Mute state should go back to unmutted + await _call_media_player_service(hass, SERVICE_VOLUME_UP, {"entity_id": ZONE_1_ID}) + assert ws66i.zones[11].volume == 1 + assert not ws66i.zones[11].mute + + # Mute the zone again + await _call_media_player_service( + hass, SERVICE_VOLUME_MUTE, {"entity_id": ZONE_1_ID, "is_volume_muted": True} + ) + assert ws66i.zones[11].mute + + # Decrease volume. Mute state should go back to unmutted + await _call_media_player_service( + hass, SERVICE_VOLUME_DOWN, {"entity_id": ZONE_1_ID} + ) + assert ws66i.zones[11].volume == 0 + assert not ws66i.zones[11].mute + + # Mute the zone again + await _call_media_player_service( + hass, SERVICE_VOLUME_MUTE, {"entity_id": ZONE_1_ID, "is_volume_muted": True} + ) + assert ws66i.zones[11].mute + + # Set to max volume. Mute state should go back to unmutted + await _call_media_player_service( + hass, SERVICE_VOLUME_SET, {"entity_id": ZONE_1_ID, "volume_level": 1.0} + ) + assert ws66i.zones[11].volume == MAX_VOL + assert not ws66i.zones[11].mute + + +async def test_first_run_with_available_zones(hass): + """Test first run with all zones available.""" + ws66i = MockWs66i() + await _setup_ws66i(hass, ws66i) + + registry = er.async_get(hass) + + entry = registry.async_get(ZONE_7_ID) + assert not entry.disabled + + +async def test_first_run_with_failing_zones(hass): + """Test first run with failed zones.""" + ws66i = MockWs66i() + + with patch.object(MockWs66i, "zone_status", return_value=None): + await _setup_ws66i(hass, ws66i) + + registry = er.async_get(hass) + + entry = registry.async_get(ZONE_1_ID) + assert entry is None + + entry = registry.async_get(ZONE_7_ID) + assert entry is None + + +async def test_register_all_entities(hass): + """Test run with all entities registered.""" + ws66i = MockWs66i() + await _setup_ws66i(hass, ws66i) + + registry = er.async_get(hass) + + entry = registry.async_get(ZONE_1_ID) + assert not entry.disabled + + entry = registry.async_get(ZONE_7_ID) + assert not entry.disabled + + +async def test_register_entities_in_1_amp_only(hass): + """Test run with only zones 11-16 registered.""" + ws66i = MockWs66i(fail_zone_check=[21]) + await _setup_ws66i(hass, ws66i) + + registry = er.async_get(hass) + + entry = registry.async_get(ZONE_1_ID) + assert not entry.disabled + + entry = registry.async_get(ZONE_2_ID) + assert not entry.disabled + + entry = registry.async_get(ZONE_7_ID) + assert entry is None diff --git a/tests/components/yandextts/test_tts.py b/tests/components/yandextts/test_tts.py index fdc204384a5..8549b51c341 100644 --- a/tests/components/yandextts/test_tts.py +++ b/tests/components/yandextts/test_tts.py @@ -27,7 +27,7 @@ async def get_media_source_url(hass, media_content_id): if media_source.DOMAIN not in hass.config.components: assert await async_setup_component(hass, media_source.DOMAIN, {}) - resolved = await media_source.async_resolve_media(hass, media_content_id) + resolved = await media_source.async_resolve_media(hass, media_content_id, None) return resolved.url diff --git a/tests/components/yolink/__init__.py b/tests/components/yolink/__init__.py new file mode 100644 index 00000000000..a72667661b9 --- /dev/null +++ b/tests/components/yolink/__init__.py @@ -0,0 +1 @@ +"""Tests for the yolink integration.""" diff --git a/tests/components/yolink/test_config_flow.py b/tests/components/yolink/test_config_flow.py new file mode 100644 index 00000000000..5d6bb8fd727 --- /dev/null +++ b/tests/components/yolink/test_config_flow.py @@ -0,0 +1,210 @@ +"""Test yolink config flow.""" +import asyncio +from http import HTTPStatus +from unittest.mock import patch + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components import application_credentials +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow + +from tests.common import MockConfigEntry + +CLIENT_ID = "12345" +CLIENT_SECRET = "6789" +YOLINK_HOST = "api.yosmart.com" +YOLINK_HTTP_HOST = f"http://{YOLINK_HOST}" +DOMAIN = "yolink" +OAUTH2_AUTHORIZE = f"{YOLINK_HTTP_HOST}/oauth/v2/authorization.htm" +OAUTH2_TOKEN = f"{YOLINK_HTTP_HOST}/open/yolink/token" + + +async def test_abort_if_no_configuration(hass): + """Check flow abort when no configuration.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "missing_credentials" + + +async def test_abort_if_existing_entry(hass: HomeAssistant): + """Check flow abort when an entry already exist.""" + MockConfigEntry(domain=DOMAIN, unique_id=DOMAIN).add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_full_flow( + hass, hass_client_no_auth, aioclient_mock, current_request_with_host +): + """Check full flow.""" + assert await setup.async_setup_component( + hass, + DOMAIN, + {}, + ) + await application_credentials.async_import_client_credential( + hass, + DOMAIN, + application_credentials.ClientCredential(CLIENT_ID, CLIENT_SECRET), + ) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}&scope=create" + ) + + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == HTTPStatus.OK + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + }, + ) + + with patch("homeassistant.components.yolink.api.ConfigEntryAuth"), patch( + "homeassistant.components.yolink.async_setup_entry", return_value=True + ) as mock_setup: + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert result["data"]["auth_implementation"] == DOMAIN + + result["data"]["token"].pop("expires_at") + assert result["data"]["token"] == { + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + } + + assert DOMAIN in hass.config.components + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.state is config_entries.ConfigEntryState.LOADED + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert len(mock_setup.mock_calls) == 1 + + +async def test_abort_if_authorization_timeout(hass, current_request_with_host): + """Check yolink authorization timeout.""" + assert await setup.async_setup_component( + hass, + DOMAIN, + {}, + ) + await application_credentials.async_import_client_credential( + hass, + DOMAIN, + application_credentials.ClientCredential(CLIENT_ID, CLIENT_SECRET), + ) + with patch( + "homeassistant.components.yolink.config_entry_oauth2_flow." + "LocalOAuth2Implementation.async_generate_authorize_url", + side_effect=asyncio.TimeoutError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "authorize_url_timeout" + + +async def test_reauthentication( + hass, hass_client_no_auth, aioclient_mock, current_request_with_host +): + """Test yolink reauthentication.""" + await setup.async_setup_component( + hass, + DOMAIN, + {}, + ) + + await application_credentials.async_import_client_credential( + hass, + DOMAIN, + application_credentials.ClientCredential(CLIENT_ID, CLIENT_SECRET), + ) + + old_entry = MockConfigEntry( + domain=DOMAIN, + unique_id=DOMAIN, + version=1, + data={ + "refresh_token": "outdated_fresh_token", + "access_token": "outdated_access_token", + }, + ) + old_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "unique_id": old_entry.unique_id, + "entry_id": old_entry.entry_id, + }, + data=old_entry.data, + ) + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + + result = await hass.config_entries.flow.async_configure(flows[0]["flow_id"], {}) + + # pylint: disable=protected-access + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + client = await hass_client_no_auth() + await client.get(f"/auth/external/callback?code=abcd&state={state}") + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + }, + ) + + with patch("homeassistant.components.yolink.api.ConfigEntryAuth"): + with patch( + "homeassistant.components.yolink.async_setup_entry", return_value=True + ) as mock_setup: + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + token_data = old_entry.data["token"] + assert token_data["access_token"] == "mock-access-token" + assert token_data["refresh_token"] == "mock-refresh-token" + assert token_data["type"] == "Bearer" + assert token_data["expires_in"] == 60 + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "reauth_successful" + assert len(mock_setup.mock_calls) == 1 diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 3487b57e482..88dceb9d464 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -3,7 +3,6 @@ from ipaddress import ip_address from typing import Any from unittest.mock import call, patch -import pytest from zeroconf import InterfaceChoice, IPVersion, ServiceStateChange from zeroconf.asyncio import AsyncServiceInfo @@ -147,7 +146,17 @@ def get_zeroconf_info_mock_model(model): async def test_setup(hass, mock_async_zeroconf): """Test configured options for a device are loaded via config entry.""" - with patch.object( + mock_zc = { + "_http._tcp.local.": [ + { + "domain": "shelly", + "name": "shelly*", + "properties": {"macaddress": "ffaadd*"}, + } + ], + "_Volumio._tcp.local.": [{"domain": "volumio"}], + } + with patch.dict(zc_gen.ZEROCONF, mock_zc, clear=True,), patch.object( hass.config_entries.flow, "async_init" ) as mock_config_flow, patch.object( zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock @@ -161,7 +170,7 @@ async def test_setup(hass, mock_async_zeroconf): assert len(mock_service_browser.mock_calls) == 1 expected_flow_calls = 0 - for matching_components in zc_gen.ZEROCONF.values(): + for matching_components in mock_zc.values(): domains = set() for component in matching_components: if len(component) == 1: @@ -1106,33 +1115,3 @@ async def test_no_name(hass, mock_async_zeroconf): register_call = mock_async_zeroconf.async_register_service.mock_calls[-1] info = register_call.args[0] assert info.name == "Home._home-assistant._tcp.local." - - -@pytest.mark.usefixtures("mock_integration_frame") -async def test_service_info_compatibility(hass, caplog): - """Test compatibility with old-style dict. - - To be removed in 2022.6 - """ - discovery_info = zeroconf.ZeroconfServiceInfo( - host="mock_host", - addresses=["mock_host"], - port=None, - hostname="mock_hostname", - type="mock_type", - name="mock_name", - properties={}, - ) - - with patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()): - assert discovery_info["host"] == "mock_host" - assert "Detected integration that accessed discovery_info['host']" in caplog.text - - with patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()): - assert discovery_info.get("host") == "mock_host" - assert ( - "Detected integration that accessed discovery_info.get('host')" in caplog.text - ) - - assert discovery_info.get("host", "fallback_host") == "mock_host" - assert discovery_info.get("invalid_key", "fallback_host") == "fallback_host" diff --git a/tests/components/zha/test_button.py b/tests/components/zha/test_button.py index 762d2d46e54..f692528203f 100644 --- a/tests/components/zha/test_button.py +++ b/tests/components/zha/test_button.py @@ -1,11 +1,22 @@ """Test ZHA button.""" -from unittest.mock import patch +from unittest.mock import call, patch from freezegun import freeze_time import pytest +from zhaquirks.const import ( + DEVICE_TYPE, + ENDPOINTS, + INPUT_CLUSTERS, + OUTPUT_CLUSTERS, + PROFILE_ID, +) from zigpy.const import SIG_EP_PROFILE +from zigpy.exceptions import ZigbeeException import zigpy.profiles.zha as zha +from zigpy.quirks import CustomCluster, CustomDevice +import zigpy.types as t import zigpy.zcl.clusters.general as general +from zigpy.zcl.clusters.manufacturer_specific import ManufacturerSpecificCluster import zigpy.zcl.clusters.security as security import zigpy.zcl.foundation as zcl_f @@ -14,6 +25,7 @@ from homeassistant.components.button.const import SERVICE_PRESS from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, + ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_DIAGNOSTIC, STATE_UNKNOWN, ) @@ -48,6 +60,49 @@ async def contact_sensor(hass, zigpy_device_mock, zha_device_joined_restored): return zha_device, zigpy_device.endpoints[1].identify +class FrostLockQuirk(CustomDevice): + """Quirk with frost lock attribute.""" + + class TuyaManufCluster(CustomCluster, ManufacturerSpecificCluster): + """Tuya manufacturer specific cluster.""" + + cluster_id = 0xEF00 + ep_attribute = "tuya_manufacturer" + + attributes = {0xEF01: ("frost_lock_reset", t.Bool)} + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH, + INPUT_CLUSTERS: [general.Basic.cluster_id, TuyaManufCluster], + OUTPUT_CLUSTERS: [], + }, + } + } + + +@pytest.fixture +async def tuya_water_valve(hass, zigpy_device_mock, zha_device_joined_restored): + """Tuya Water Valve fixture.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [general.Basic.cluster_id], + SIG_EP_OUTPUT: [], + SIG_EP_TYPE: zha.DeviceType.ON_OFF_SWITCH, + } + }, + manufacturer="_TZE200_htnnfasr", + quirk=FrostLockQuirk, + ) + + zha_device = await zha_device_joined_restored(zigpy_device) + return zha_device, zigpy_device.endpoints[1].tuya_manufacturer + + @freeze_time("2021-11-04 17:37:00", tz_offset=-1) async def test_button(hass, contact_sensor): """Test zha button platform.""" @@ -87,3 +142,52 @@ async def test_button(hass, contact_sensor): assert state assert state.state == "2021-11-04T16:37:00+00:00" assert state.attributes[ATTR_DEVICE_CLASS] == ButtonDeviceClass.UPDATE + + +async def test_frost_unlock(hass, tuya_water_valve): + """Test custom frost unlock zha button.""" + + entity_registry = er.async_get(hass) + zha_device, cluster = tuya_water_valve + assert cluster is not None + entity_id = await find_entity_id(DOMAIN, zha_device, hass) + assert entity_id is not None + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_UNKNOWN + assert state.attributes[ATTR_DEVICE_CLASS] == ButtonDeviceClass.RESTART + + entry = entity_registry.async_get(entity_id) + assert entry + assert entry.entity_category == ENTITY_CATEGORY_CONFIG + + with patch( + "zigpy.zcl.Cluster.request", + return_value=mock_coro([0x00, zcl_f.Status.SUCCESS]), + ): + await hass.services.async_call( + DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + assert len(cluster.write_attributes.mock_calls) == 1 + assert cluster.write_attributes.call_args == call({"frost_lock_reset": 0}) + + state = hass.states.get(entity_id) + assert state + assert state.attributes[ATTR_DEVICE_CLASS] == ButtonDeviceClass.RESTART + + cluster.write_attributes.reset_mock() + cluster.write_attributes.side_effect = ZigbeeException + + await hass.services.async_call( + DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + assert len(cluster.write_attributes.mock_calls) == 1 + assert cluster.write_attributes.call_args == call({"frost_lock_reset": 0}) diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index 0c51ecffe9b..dee04165c1e 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -52,8 +52,8 @@ async def test_discovery(detect_mock, hass): service_info = zeroconf.ZeroconfServiceInfo( host="192.168.1.200", addresses=["192.168.1.200"], - hostname="_tube_zb_gw._tcp.local.", - name="mock_name", + hostname="tube._tube_zb_gw._tcp.local.", + name="tube", port=6053, properties={"name": "tube_123456"}, type="mock_type", @@ -77,6 +77,68 @@ async def test_discovery(detect_mock, hass): } +@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True)) +@patch("zigpy_zigate.zigbee.application.ControllerApplication.probe") +async def test_zigate_via_zeroconf(probe_mock, hass): + """Test zeroconf flow -- zigate radio detected.""" + service_info = zeroconf.ZeroconfServiceInfo( + host="192.168.1.200", + addresses=["192.168.1.200"], + hostname="_zigate-zigbee-gateway._tcp.local.", + name="any", + port=1234, + properties={"radio_type": "zigate"}, + type="mock_type", + ) + flow = await hass.config_entries.flow.async_init( + "zha", context={"source": SOURCE_ZEROCONF}, data=service_info + ) + result = await hass.config_entries.flow.async_configure( + flow["flow_id"], user_input={} + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "socket://192.168.1.200:1234" + assert result["data"] == { + CONF_DEVICE: { + CONF_DEVICE_PATH: "socket://192.168.1.200:1234", + }, + CONF_RADIO_TYPE: "zigate", + } + + +@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True)) +@patch("bellows.zigbee.application.ControllerApplication.probe", return_value=True) +async def test_efr32_via_zeroconf(probe_mock, hass): + """Test zeroconf flow -- efr32 radio detected.""" + service_info = zeroconf.ZeroconfServiceInfo( + host="192.168.1.200", + addresses=["192.168.1.200"], + hostname="efr32._esphomelib._tcp.local.", + name="efr32", + port=1234, + properties={}, + type="mock_type", + ) + flow = await hass.config_entries.flow.async_init( + "zha", context={"source": SOURCE_ZEROCONF}, data=service_info + ) + result = await hass.config_entries.flow.async_configure( + flow["flow_id"], user_input={"baudrate": 115200} + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "socket://192.168.1.200:6638" + assert result["data"] == { + CONF_DEVICE: { + CONF_DEVICE_PATH: "socket://192.168.1.200:6638", + CONF_BAUDRATE: 115200, + CONF_FLOWCONTROL: "software", + }, + CONF_RADIO_TYPE: "ezsp", + } + + @patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True)) @patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True) async def test_discovery_via_zeroconf_ip_change(detect_mock, hass): @@ -183,6 +245,43 @@ async def test_discovery_via_usb(detect_mock, hass): } +@patch("zigpy_zigate.zigbee.application.ControllerApplication.probe") +async def test_zigate_discovery_via_usb(detect_mock, hass): + """Test zigate usb flow -- radio detected.""" + discovery_info = usb.UsbServiceInfo( + device="/dev/ttyZIGBEE", + pid="0403", + vid="6015", + serial_number="1234", + description="zigate radio", + manufacturer="test", + ) + result = await hass.config_entries.flow.async_init( + "zha", context={"source": SOURCE_USB}, data=discovery_info + ) + await hass.async_block_till_done() + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "confirm" + + with patch("homeassistant.components.zha.async_setup_entry"): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert ( + "zigate radio - /dev/ttyZIGBEE, s/n: 1234 - test - 6015:0403" + in result2["title"] + ) + assert result2["data"] == { + "device": { + "path": "/dev/ttyZIGBEE", + }, + CONF_RADIO_TYPE: "zigate", + } + + @patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=False) async def test_discovery_via_usb_no_radio(detect_mock, hass): """Test usb flow -- no radio detected.""" diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index 149c77314a1..4de251fda8b 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -44,7 +44,15 @@ from .zha_devices_list import ( NO_TAIL_ID = re.compile("_\\d$") UNIQUE_ID_HD = re.compile(r"^(([\da-fA-F]{2}:){7}[\da-fA-F]{2}-\d{1,3})", re.X) -IGNORE_SUFFIXES = [zigpy.zcl.clusters.general.OnOff.StartUpOnOff.__name__] +IGNORE_SUFFIXES = [ + zigpy.zcl.clusters.general.OnOff.StartUpOnOff.__name__, + "on_off_transition_time", + "on_level", + "on_transition_time", + "off_transition_time", + "default_move_rate", + "start_up_current_level", +] def contains_ignored_suffix(unique_id: str) -> bool: diff --git a/tests/components/zha/test_number.py b/tests/components/zha/test_number.py index 336800f9ccb..01946c05f1a 100644 --- a/tests/components/zha/test_number.py +++ b/tests/components/zha/test_number.py @@ -2,13 +2,14 @@ from unittest.mock import call, patch import pytest -import zigpy.profiles.zha -import zigpy.types +from zigpy.exceptions import ZigbeeException +from zigpy.profiles import zha import zigpy.zcl.clusters.general as general import zigpy.zcl.foundation as zcl_f from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN -from homeassistant.const import STATE_UNAVAILABLE, Platform +from homeassistant.const import ENTITY_CATEGORY_CONFIG, STATE_UNAVAILABLE, Platform +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from .common import ( @@ -18,7 +19,7 @@ from .common import ( send_attributes_report, update_attribute_cache, ) -from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_TYPE +from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE from tests.common import mock_coro @@ -29,7 +30,7 @@ def zigpy_analog_output_device(zigpy_device_mock): endpoints = { 1: { - SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.LEVEL_CONTROL_SWITCH, + SIG_EP_TYPE: zha.DeviceType.LEVEL_CONTROL_SWITCH, SIG_EP_INPUT: [general.AnalogOutput.cluster_id, general.Basic.cluster_id], SIG_EP_OUTPUT: [], } @@ -37,6 +38,30 @@ def zigpy_analog_output_device(zigpy_device_mock): return zigpy_device_mock(endpoints) +@pytest.fixture +async def light(zigpy_device_mock): + """Siren fixture.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_PROFILE: zha.PROFILE_ID, + SIG_EP_TYPE: zha.DeviceType.ON_OFF_LIGHT, + SIG_EP_INPUT: [ + general.Basic.cluster_id, + general.Identify.cluster_id, + general.OnOff.cluster_id, + general.LevelControl.cluster_id, + ], + SIG_EP_OUTPUT: [general.Ota.cluster_id], + } + }, + node_descriptor=b"\x02@\x84_\x11\x7fd\x00\x00,d\x00\x00", + ) + + return zigpy_device + + async def test_number(hass, zha_device_joined_restored, zigpy_analog_output_device): """Test zha number platform.""" @@ -139,3 +164,143 @@ async def test_number(hass, zha_device_joined_restored, zigpy_analog_output_devi assert hass.states.get(entity_id).state == "40.0" assert cluster.read_attributes.call_count == 10 assert "present_value" in cluster.read_attributes.call_args[0][0] + + +@pytest.mark.parametrize( + "attr, initial_value, new_value", + ( + ("on_off_transition_time", 20, 5), + ("on_level", 255, 50), + ("on_transition_time", 5, 1), + ("off_transition_time", 5, 1), + ("default_move_rate", 1, 5), + ("start_up_current_level", 254, 125), + ), +) +async def test_level_control_number( + hass, light, zha_device_joined, attr, initial_value, new_value +): + """Test zha level control number entities - new join.""" + + entity_registry = er.async_get(hass) + level_control_cluster = light.endpoints[1].level + level_control_cluster.PLUGGED_ATTR_READS = { + attr: initial_value, + } + zha_device = await zha_device_joined(light) + + entity_id = await find_entity_id( + Platform.NUMBER, + zha_device, + hass, + qualifier=attr, + ) + assert entity_id is not None + + assert level_control_cluster.read_attributes.call_count == 3 + assert ( + call( + [ + "on_off_transition_time", + "on_level", + "on_transition_time", + "off_transition_time", + "default_move_rate", + ], + allow_cache=True, + only_cache=False, + manufacturer=None, + ) + in level_control_cluster.read_attributes.call_args_list + ) + + assert ( + call( + ["start_up_current_level"], + allow_cache=True, + only_cache=False, + manufacturer=None, + ) + in level_control_cluster.read_attributes.call_args_list + ) + + assert ( + call( + [ + "current_level", + ], + allow_cache=False, + only_cache=False, + manufacturer=None, + ) + in level_control_cluster.read_attributes.call_args_list + ) + + state = hass.states.get(entity_id) + assert state + assert state.state == str(initial_value) + + entity_entry = entity_registry.async_get(entity_id) + assert entity_entry + assert entity_entry.entity_category == ENTITY_CATEGORY_CONFIG + + # Test number set_value + await hass.services.async_call( + "number", + "set_value", + { + "entity_id": entity_id, + "value": new_value, + }, + blocking=True, + ) + + assert level_control_cluster.write_attributes.call_count == 1 + assert level_control_cluster.write_attributes.call_args[0][0] == { + attr: new_value, + } + + state = hass.states.get(entity_id) + assert state + assert state.state == str(new_value) + + level_control_cluster.read_attributes.reset_mock() + await async_setup_component(hass, "homeassistant", {}) + await hass.async_block_till_done() + + await hass.services.async_call( + "homeassistant", "update_entity", {"entity_id": entity_id}, blocking=True + ) + # the mocking doesn't update the attr cache so this flips back to initial value + assert hass.states.get(entity_id).state == str(initial_value) + assert level_control_cluster.read_attributes.call_count == 1 + assert ( + call( + [ + attr, + ], + allow_cache=False, + only_cache=False, + manufacturer=None, + ) + in level_control_cluster.read_attributes.call_args_list + ) + + level_control_cluster.write_attributes.reset_mock() + level_control_cluster.write_attributes.side_effect = ZigbeeException + + await hass.services.async_call( + "number", + "set_value", + { + "entity_id": entity_id, + "value": new_value, + }, + blocking=True, + ) + + assert level_control_cluster.write_attributes.call_count == 1 + assert level_control_cluster.write_attributes.call_args[0][0] == { + attr: new_value, + } + assert hass.states.get(entity_id).state == str(initial_value) diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index a624e5f2c73..99e8a681348 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -2,13 +2,25 @@ from unittest.mock import call, patch import pytest +from zhaquirks.const import ( + DEVICE_TYPE, + ENDPOINTS, + INPUT_CLUSTERS, + OUTPUT_CLUSTERS, + PROFILE_ID, +) +from zigpy.exceptions import ZigbeeException import zigpy.profiles.zha as zha +from zigpy.quirks import CustomCluster, CustomDevice +import zigpy.types as t import zigpy.zcl.clusters.general as general +from zigpy.zcl.clusters.manufacturer_specific import ManufacturerSpecificCluster import zigpy.zcl.foundation as zcl_f from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.zha.core.group import GroupMember from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform +from homeassistant.setup import async_setup_component from .common import ( async_enable_traffic, @@ -174,6 +186,61 @@ async def test_switch(hass, zha_device_joined_restored, zigpy_device): await async_test_rejoin(hass, zigpy_device, [cluster], (1,)) +class WindowDetectionFunctionQuirk(CustomDevice): + """Quirk with window detection function attribute.""" + + class TuyaManufCluster(CustomCluster, ManufacturerSpecificCluster): + """Tuya manufacturer specific cluster.""" + + cluster_id = 0xEF00 + ep_attribute = "tuya_manufacturer" + + attributes = { + 0xEF01: ("window_detection_function", t.Bool), + 0xEF02: ("window_detection_function_inverter", t.Bool), + } + + def __init__(self, *args, **kwargs): + """Initialize with task.""" + super().__init__(*args, **kwargs) + self._attr_cache.update( + {0xEF01: False} + ) # entity won't be created without this + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH, + INPUT_CLUSTERS: [general.Basic.cluster_id, TuyaManufCluster], + OUTPUT_CLUSTERS: [], + }, + } + } + + +@pytest.fixture +async def zigpy_device_tuya(hass, zigpy_device_mock, zha_device_joined): + """Device tracker zigpy tuya device.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [general.Basic.cluster_id], + SIG_EP_OUTPUT: [], + SIG_EP_TYPE: zha.DeviceType.ON_OFF_SWITCH, + } + }, + manufacturer="_TZE200_b6wax7g0", + quirk=WindowDetectionFunctionQuirk, + ) + + zha_device = await zha_device_joined(zigpy_device) + zha_device.available = True + await hass.async_block_till_done() + return zigpy_device + + @patch( "homeassistant.components.zha.entity.UPDATE_GROUP_FROM_CHILD_DELAY", new=0, @@ -292,3 +359,122 @@ async def test_zha_group_switch_entity( # test that group light is now back on assert hass.states.get(entity_id).state == STATE_ON + + +async def test_switch_configurable(hass, zha_device_joined_restored, zigpy_device_tuya): + """Test zha configurable switch platform.""" + + zha_device = await zha_device_joined_restored(zigpy_device_tuya) + cluster = zigpy_device_tuya.endpoints.get(1).tuya_manufacturer + entity_id = await find_entity_id(Platform.SWITCH, zha_device, hass) + assert entity_id is not None + + assert hass.states.get(entity_id).state == STATE_OFF + await async_enable_traffic(hass, [zha_device], enabled=False) + # test that the switch was created and that its state is unavailable + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, [zha_device]) + + # test that the state has changed from unavailable to off + assert hass.states.get(entity_id).state == STATE_OFF + + # turn on at switch + await send_attributes_report(hass, cluster, {"window_detection_function": True}) + assert hass.states.get(entity_id).state == STATE_ON + + # turn off at switch + await send_attributes_report(hass, cluster, {"window_detection_function": False}) + assert hass.states.get(entity_id).state == STATE_OFF + + # turn on from HA + with patch( + "zigpy.zcl.Cluster.write_attributes", + return_value=mock_coro([zcl_f.Status.SUCCESS, zcl_f.Status.SUCCESS]), + ): + # turn on via UI + await hass.services.async_call( + SWITCH_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True + ) + assert len(cluster.write_attributes.mock_calls) == 1 + assert cluster.write_attributes.call_args == call( + {"window_detection_function": True} + ) + + # turn off from HA + with patch( + "zigpy.zcl.Cluster.write_attributes", + return_value=mock_coro([zcl_f.Status.SUCCESS, zcl_f.Status.SUCCESS]), + ): + # turn off via UI + await hass.services.async_call( + SWITCH_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True + ) + assert len(cluster.write_attributes.mock_calls) == 2 + assert cluster.write_attributes.call_args == call( + {"window_detection_function": False} + ) + + cluster.read_attributes.reset_mock() + await async_setup_component(hass, "homeassistant", {}) + await hass.async_block_till_done() + + await hass.services.async_call( + "homeassistant", "update_entity", {"entity_id": entity_id}, blocking=True + ) + # the mocking doesn't update the attr cache so this flips back to initial value + assert cluster.read_attributes.call_count == 2 + assert [ + call( + [ + "window_detection_function", + ], + allow_cache=False, + only_cache=False, + manufacturer=None, + ), + call( + [ + "window_detection_function_inverter", + ], + allow_cache=False, + only_cache=False, + manufacturer=None, + ), + ] == cluster.read_attributes.call_args_list + + cluster.write_attributes.reset_mock() + cluster.write_attributes.side_effect = ZigbeeException + + await hass.services.async_call( + SWITCH_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True + ) + + assert len(cluster.write_attributes.mock_calls) == 1 + assert cluster.write_attributes.call_args == call( + {"window_detection_function": False} + ) + + # test inverter + cluster.write_attributes.reset_mock() + cluster._attr_cache.update({0xEF02: True}) + + await hass.services.async_call( + SWITCH_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True + ) + assert len(cluster.write_attributes.mock_calls) == 1 + assert cluster.write_attributes.call_args == call( + {"window_detection_function": True} + ) + + await hass.services.async_call( + SWITCH_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True + ) + assert len(cluster.write_attributes.mock_calls) == 2 + assert cluster.write_attributes.call_args == call( + {"window_detection_function": False} + ) + + # test joining a new switch to the network and HA + await async_test_rejoin(hass, zigpy_device_tuya, [cluster], (0,)) diff --git a/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json b/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json index cc26ce14e3e..62b6205926b 100644 --- a/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json +++ b/tests/components/zwave_js/fixtures/aeon_smart_switch_6_state.json @@ -51,7 +51,14 @@ "index": 0, "installerIcon": 1792, "userIcon": 1792, - "commandClasses": [] + "commandClasses": [ + { + "id": 50, + "name": "Meter", + "version": 3, + "isSecure": false + } + ] } ], "values": [ diff --git a/tests/components/zwave_js/fixtures/climate_danfoss_lc_13_state.json b/tests/components/zwave_js/fixtures/climate_danfoss_lc_13_state.json index a877f82b53f..cb8e78881df 100644 --- a/tests/components/zwave_js/fixtures/climate_danfoss_lc_13_state.json +++ b/tests/components/zwave_js/fixtures/climate_danfoss_lc_13_state.json @@ -126,7 +126,63 @@ "endpoints": [ { "nodeId": 5, - "index": 0 + "index": 0, + "commandClasses": [ + { + "id": 67, + "name": "Thermostat Setpoint", + "version": 2, + "isSecure": false + }, + { + "id": 70, + "name": "Climate Control Schedule", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 1, + "isSecure": false + }, + { + "id": 117, + "name": "Protection", + "version": 2, + "isSecure": false + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": false + }, + { + "id": 129, + "name": "Clock", + "version": 1, + "isSecure": false + }, + { + "id": 132, + "name": "Wake Up", + "version": 1, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 1, + "isSecure": false + }, + { + "id": 143, + "name": "Multi Command", + "version": 1, + "isSecure": false + } + ] } ], "values": [ diff --git a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_different_endpoints_state.json b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_different_endpoints_state.json index 15823a8f6ca..ca0efb56711 100644 --- a/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_different_endpoints_state.json +++ b/tests/components/zwave_js/fixtures/climate_radio_thermostat_ct100_plus_different_endpoints_state.json @@ -62,7 +62,123 @@ }, "mandatorySupportedCCs": [32, 114, 64, 67, 134], "mandatoryControlledCCs": [] - } + }, + "commandClasses": [ + { + "id": 49, + "name": "Multilevel Sensor", + "version": 5, + "isSecure": false + }, + { + "id": 64, + "name": "Thermostat Mode", + "version": 2, + "isSecure": false + }, + { + "id": 66, + "name": "Thermostat Operating State", + "version": 2, + "isSecure": false + }, + { + "id": 67, + "name": "Thermostat Setpoint", + "version": 2, + "isSecure": false + }, + { + "id": 68, + "name": "Thermostat Fan Mode", + "version": 1, + "isSecure": false + }, + { + "id": 69, + "name": "Thermostat Fan State", + "version": 1, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 1, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 96, + "name": "Multi Channel", + "version": 4, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 3, + "isSecure": false + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": false + }, + { + "id": 129, + "name": "Clock", + "version": 1, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 2, + "isSecure": false + }, + { + "id": 135, + "name": "Indicator", + "version": 1, + "isSecure": false + }, + { + "id": 142, + "name": "Multi Channel Association", + "version": 3, + "isSecure": false + } + ] }, { "nodeId": 26, @@ -82,7 +198,123 @@ }, "mandatorySupportedCCs": [32, 114, 64, 67, 134], "mandatoryControlledCCs": [] - } + }, + "commandClasses": [ + { + "id": 49, + "name": "Multilevel Sensor", + "version": 5, + "isSecure": false + }, + { + "id": 64, + "name": "Thermostat Mode", + "version": 2, + "isSecure": false + }, + { + "id": 66, + "name": "Thermostat Operating State", + "version": 2, + "isSecure": false + }, + { + "id": 67, + "name": "Thermostat Setpoint", + "version": 2, + "isSecure": false + }, + { + "id": 68, + "name": "Thermostat Fan Mode", + "version": 1, + "isSecure": false + }, + { + "id": 69, + "name": "Thermostat Fan State", + "version": 1, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 1, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 96, + "name": "Multi Channel", + "version": 4, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 3, + "isSecure": false + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": false + }, + { + "id": 129, + "name": "Clock", + "version": 1, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 2, + "isSecure": false + }, + { + "id": 135, + "name": "Indicator", + "version": 1, + "isSecure": false + }, + { + "id": 142, + "name": "Multi Channel Association", + "version": 3, + "isSecure": false + } + ] }, { "nodeId": 26, @@ -102,7 +334,123 @@ }, "mandatorySupportedCCs": [32, 49], "mandatoryControlledCCs": [] - } + }, + "commandClasses": [ + { + "id": 49, + "name": "Multilevel Sensor", + "version": 5, + "isSecure": false + }, + { + "id": 64, + "name": "Thermostat Mode", + "version": 2, + "isSecure": false + }, + { + "id": 66, + "name": "Thermostat Operating State", + "version": 2, + "isSecure": false + }, + { + "id": 67, + "name": "Thermostat Setpoint", + "version": 2, + "isSecure": false + }, + { + "id": 68, + "name": "Thermostat Fan Mode", + "version": 1, + "isSecure": false + }, + { + "id": 69, + "name": "Thermostat Fan State", + "version": 1, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 1, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 96, + "name": "Multi Channel", + "version": 4, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 3, + "isSecure": false + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": false + }, + { + "id": 129, + "name": "Clock", + "version": 1, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 2, + "isSecure": false + }, + { + "id": 135, + "name": "Indicator", + "version": 1, + "isSecure": false + }, + { + "id": 142, + "name": "Multi Channel Association", + "version": 3, + "isSecure": false + } + ] } ], "values": [ diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 7ec7d98217b..e59a923ff44 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -28,13 +28,17 @@ from zwave_js_server.model.controller import ( ) from zwave_js_server.model.node import Node -from homeassistant.components.websocket_api.const import ERR_NOT_FOUND +from homeassistant.components.websocket_api.const import ( + ERR_INVALID_FORMAT, + ERR_NOT_FOUND, +) from homeassistant.components.zwave_js.api import ( ADDITIONAL_PROPERTIES, APPLICATION_VERSION, CLIENT_SIDE_AUTH, COMMAND_CLASS_ID, CONFIG, + DEVICE_ID, DSK, ENABLED, ENTRY_ID, @@ -70,17 +74,30 @@ from homeassistant.components.zwave_js.const import ( CONF_DATA_COLLECTION_OPTED_IN, DOMAIN, ) +from homeassistant.components.zwave_js.helpers import get_device_id from homeassistant.helpers import device_registry as dr -async def test_network_status(hass, integration, hass_ws_client): +def get_device(hass, node): + """Get device ID for a node.""" + dev_reg = dr.async_get(hass) + device_id = get_device_id(node.client.driver, node) + return dev_reg.async_get_device({device_id}) + + +async def test_network_status(hass, multisensor_6, integration, hass_ws_client): """Test the network status websocket command.""" entry = integration ws_client = await hass_ws_client(hass) + # Try API call with entry ID with patch("zwave_js_server.model.controller.Controller.async_get_state"): await ws_client.send_json( - {ID: 2, TYPE: "zwave_js/network_status", ENTRY_ID: entry.entry_id} + { + ID: 1, + TYPE: "zwave_js/network_status", + ENTRY_ID: entry.entry_id, + } ) msg = await ws_client.receive_json() result = msg["result"] @@ -89,18 +106,94 @@ async def test_network_status(hass, integration, hass_ws_client): assert result["client"]["server_version"] == "1.0.0" assert result["controller"]["inclusion_state"] == InclusionState.IDLE - # Test sending command with not loaded entry fails + # Try API call with device ID + dev_reg = dr.async_get(hass) + device = dev_reg.async_get_device( + identifiers={(DOMAIN, "3245146787-52")}, + ) + assert device + with patch("zwave_js_server.model.controller.Controller.async_get_state"): + await ws_client.send_json( + { + ID: 2, + TYPE: "zwave_js/network_status", + DEVICE_ID: device.id, + } + ) + msg = await ws_client.receive_json() + result = msg["result"] + + assert result["client"]["ws_server_url"] == "ws://test:3000/zjs" + assert result["client"]["server_version"] == "1.0.0" + assert result["controller"]["inclusion_state"] == InclusionState.IDLE + + # Test sending command with invalid config entry ID fails + await ws_client.send_json( + { + ID: 3, + TYPE: "zwave_js/network_status", + ENTRY_ID: "fake_id", + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == ERR_NOT_FOUND + + # Test sending command with invalid device ID fails + await ws_client.send_json( + { + ID: 4, + TYPE: "zwave_js/network_status", + DEVICE_ID: "fake_id", + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == ERR_NOT_FOUND + + # Test sending command with not loaded entry fails with config entry ID await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() await ws_client.send_json( - {ID: 3, TYPE: "zwave_js/network_status", ENTRY_ID: entry.entry_id} + { + ID: 5, + TYPE: "zwave_js/network_status", + ENTRY_ID: entry.entry_id, + } ) msg = await ws_client.receive_json() assert not msg["success"] assert msg["error"]["code"] == ERR_NOT_LOADED + # Test sending command with not loaded entry fails with device ID + await ws_client.send_json( + { + ID: 6, + TYPE: "zwave_js/network_status", + DEVICE_ID: device.id, + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == ERR_NOT_LOADED + + # Test sending command with no device ID or entry ID fails + await ws_client.send_json( + { + ID: 7, + TYPE: "zwave_js/network_status", + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == ERR_INVALID_FORMAT + async def test_node_ready( hass, @@ -115,14 +208,19 @@ async def test_node_ready( node_data = deepcopy(multisensor_6_state) # Copy to allow modification in tests. node = Node(client, node_data) node.data["ready"] = False - client.driver.controller.nodes[node.node_id] = node + driver = client.driver + driver.controller.nodes[node.node_id] = node + + dev_reg = dr.async_get(hass) + device = dev_reg.async_get_or_create( + config_entry_id=entry.entry_id, identifiers={get_device_id(driver, node)} + ) await ws_client.send_json( { ID: 3, TYPE: "zwave_js/node_ready", - ENTRY_ID: entry.entry_id, - "node_id": node.node_id, + DEVICE_ID: device.id, } ) @@ -153,12 +251,12 @@ async def test_node_status(hass, multisensor_6, integration, hass_ws_client): ws_client = await hass_ws_client(hass) node = multisensor_6 + device = get_device(hass, node) await ws_client.send_json( { ID: 3, TYPE: "zwave_js/node_status", - ENTRY_ID: entry.entry_id, - NODE_ID: node.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -178,8 +276,7 @@ async def test_node_status(hass, multisensor_6, integration, hass_ws_client): { ID: 4, TYPE: "zwave_js/node_status", - ENTRY_ID: entry.entry_id, - NODE_ID: 99999, + DEVICE_ID: "fake_device", } ) msg = await ws_client.receive_json() @@ -194,8 +291,7 @@ async def test_node_status(hass, multisensor_6, integration, hass_ws_client): { ID: 5, TYPE: "zwave_js/node_status", - ENTRY_ID: entry.entry_id, - NODE_ID: node.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -210,12 +306,12 @@ async def test_node_metadata(hass, wallmote_central_scene, integration, hass_ws_ ws_client = await hass_ws_client(hass) node = wallmote_central_scene + device = get_device(hass, node) await ws_client.send_json( { ID: 3, TYPE: "zwave_js/node_metadata", - ENTRY_ID: entry.entry_id, - NODE_ID: node.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -249,15 +345,13 @@ async def test_node_metadata(hass, wallmote_central_scene, integration, hass_ws_ result["device_database_url"] == "https://devices.zwave-js.io/?jumpTo=0x0086:0x0002:0x0082:0.0" ) - assert result["comments"] == [{"level": "info", "text": "test"}] # Test getting non-existent node fails await ws_client.send_json( { ID: 4, TYPE: "zwave_js/node_metadata", - ENTRY_ID: entry.entry_id, - NODE_ID: 99999, + DEVICE_ID: "fake_device", } ) msg = await ws_client.receive_json() @@ -272,8 +366,7 @@ async def test_node_metadata(hass, wallmote_central_scene, integration, hass_ws_ { ID: 5, TYPE: "zwave_js/node_metadata", - ENTRY_ID: entry.entry_id, - NODE_ID: node.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -282,77 +375,24 @@ async def test_node_metadata(hass, wallmote_central_scene, integration, hass_ws_ assert msg["error"]["code"] == ERR_NOT_LOADED -async def test_ping_node( - hass, wallmote_central_scene, integration, client, hass_ws_client -): - """Test the ping_node websocket command.""" - entry = integration +async def test_node_comments(hass, wallmote_central_scene, integration, hass_ws_client): + """Test the node comments websocket command.""" ws_client = await hass_ws_client(hass) - node = wallmote_central_scene - client.async_send_command.return_value = {"responded": True} + dev_reg = dr.async_get(hass) + device = dev_reg.async_get_device({(DOMAIN, "3245146787-35")}) + assert device await ws_client.send_json( { ID: 3, - TYPE: "zwave_js/ping_node", - ENTRY_ID: entry.entry_id, - NODE_ID: node.node_id, - } - ) - - msg = await ws_client.receive_json() - assert msg["success"] - assert msg["result"] - - # Test getting non-existent node fails - await ws_client.send_json( - { - ID: 4, - TYPE: "zwave_js/ping_node", - ENTRY_ID: entry.entry_id, - NODE_ID: 99999, + TYPE: "zwave_js/node_comments", + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() - assert not msg["success"] - assert msg["error"]["code"] == ERR_NOT_FOUND - - # Test FailedZWaveCommand is caught - with patch( - "zwave_js_server.model.node.Node.async_ping", - side_effect=FailedZWaveCommand("failed_command", 1, "error message"), - ): - await ws_client.send_json( - { - ID: 5, - TYPE: "zwave_js/ping_node", - ENTRY_ID: entry.entry_id, - NODE_ID: node.node_id, - } - ) - msg = await ws_client.receive_json() - - assert not msg["success"] - assert msg["error"]["code"] == "zwave_error" - assert msg["error"]["message"] == "Z-Wave error 1: error message" - - # Test sending command with not loaded entry fails - await hass.config_entries.async_unload(entry.entry_id) - await hass.async_block_till_done() - - await ws_client.send_json( - { - ID: 6, - TYPE: "zwave_js/ping_node", - ENTRY_ID: entry.entry_id, - NODE_ID: node.node_id, - } - ) - msg = await ws_client.receive_json() - - assert not msg["success"] - assert msg["error"]["code"] == ERR_NOT_LOADED + result = msg["result"] + assert result["comments"] == [{"level": "info", "text": "test"}] async def test_add_node( @@ -1107,6 +1147,8 @@ async def test_get_provisioning_entries(hass, integration, client, hass_ws_clien { "dsk": "test", "security_classes": [SecurityClass.S2_UNAUTHENTICATED], + "requested_security_classes": None, + "status": 0, "additional_properties": {"fake": "test"}, } ] @@ -1195,6 +1237,8 @@ async def test_parse_qr_code_string(hass, integration, client, hass_ws_client): "max_inclusion_request_interval": 1, "uuid": "test", "supported_protocols": [Protocols.ZWAVE], + "status": 0, + "requested_security_classes": None, "additional_properties": {}, } @@ -1812,19 +1856,38 @@ async def test_remove_failed_node( client, hass_ws_client, nortek_thermostat_removed_event, + nortek_thermostat_added_event, ): """Test the remove_failed_node websocket command.""" entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, nortek_thermostat) client.async_send_command.return_value = {"success": True} + # Test FailedZWaveCommand is caught + with patch( + "zwave_js_server.model.controller.Controller.async_remove_failed_node", + side_effect=FailedZWaveCommand("failed_command", 1, "error message"), + ): + await ws_client.send_json( + { + ID: 1, + TYPE: "zwave_js/remove_failed_node", + DEVICE_ID: device.id, + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "zwave_error" + assert msg["error"]["message"] == "Z-Wave error 1: error message" + await ws_client.send_json( { - ID: 3, + ID: 2, TYPE: "zwave_js/remove_failed_node", - ENTRY_ID: entry.entry_id, - NODE_ID: 67, + DEVICE_ID: device.id, } ) @@ -1846,29 +1909,15 @@ async def test_remove_failed_node( assert msg["event"]["event"] == "node removed" # Verify device was removed from device registry - device = dev_reg.async_get_device( - identifiers={(DOMAIN, "3245146787-67")}, - ) - assert device is None - - # Test FailedZWaveCommand is caught - with patch( - "zwave_js_server.model.controller.Controller.async_remove_failed_node", - side_effect=FailedZWaveCommand("failed_command", 1, "error message"), - ): - await ws_client.send_json( - { - ID: 4, - TYPE: "zwave_js/remove_failed_node", - ENTRY_ID: entry.entry_id, - NODE_ID: 67, - } + assert ( + dev_reg.async_get_device( + identifiers={(DOMAIN, "3245146787-67")}, ) - msg = await ws_client.receive_json() + is None + ) - assert not msg["success"] - assert msg["error"]["code"] == "zwave_error" - assert msg["error"]["message"] == "Z-Wave error 1: error message" + # Re-add node so we can test config entry not loaded + client.driver.receive_event(nortek_thermostat_added_event) # Test sending command with not loaded entry fails await hass.config_entries.async_unload(entry.entry_id) @@ -1876,10 +1925,9 @@ async def test_remove_failed_node( await ws_client.send_json( { - ID: 5, + ID: 3, TYPE: "zwave_js/remove_failed_node", - ENTRY_ID: entry.entry_id, - NODE_ID: 67, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2091,6 +2139,7 @@ async def test_stop_healing_network( async def test_heal_node( hass, + multisensor_6, integration, client, hass_ws_client, @@ -2098,6 +2147,7 @@ async def test_heal_node( """Test the heal_node websocket command.""" entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, multisensor_6) client.async_send_command.return_value = {"success": True} @@ -2105,8 +2155,7 @@ async def test_heal_node( { ID: 3, TYPE: "zwave_js/heal_node", - ENTRY_ID: entry.entry_id, - NODE_ID: 67, + DEVICE_ID: device.id, } ) @@ -2123,8 +2172,7 @@ async def test_heal_node( { ID: 4, TYPE: "zwave_js/heal_node", - ENTRY_ID: entry.entry_id, - NODE_ID: 67, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2141,8 +2189,7 @@ async def test_heal_node( { ID: 5, TYPE: "zwave_js/heal_node", - ENTRY_ID: entry.entry_id, - NODE_ID: 67, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2158,13 +2205,14 @@ async def test_refresh_node_info( entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, multisensor_6) + client.async_send_command_no_wait.return_value = None await ws_client.send_json( { ID: 1, TYPE: "zwave_js/refresh_node_info", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2232,8 +2280,7 @@ async def test_refresh_node_info( { ID: 2, TYPE: "zwave_js/refresh_node_info", - ENTRY_ID: entry.entry_id, - NODE_ID: 9999, + DEVICE_ID: "fake_device", } ) msg = await ws_client.receive_json() @@ -2249,8 +2296,7 @@ async def test_refresh_node_info( { ID: 3, TYPE: "zwave_js/refresh_node_info", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2267,8 +2313,7 @@ async def test_refresh_node_info( { ID: 4, TYPE: "zwave_js/refresh_node_info", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2283,14 +2328,14 @@ async def test_refresh_node_values( """Test that the refresh_node_values WS API call works.""" entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, multisensor_6) client.async_send_command_no_wait.return_value = None await ws_client.send_json( { ID: 1, TYPE: "zwave_js/refresh_node_values", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2303,26 +2348,12 @@ async def test_refresh_node_values( client.async_send_command_no_wait.reset_mock() - # Test getting non-existent node fails + # Test getting non-existent device fails await ws_client.send_json( { ID: 2, TYPE: "zwave_js/refresh_node_values", - ENTRY_ID: entry.entry_id, - NODE_ID: 99999, - } - ) - msg = await ws_client.receive_json() - assert not msg["success"] - assert msg["error"]["code"] == ERR_NOT_FOUND - - # Test getting non-existent entry fails - await ws_client.send_json( - { - ID: 3, - TYPE: "zwave_js/refresh_node_values", - ENTRY_ID: "fake_entry_id", - NODE_ID: 52, + DEVICE_ID: "fake_device", } ) msg = await ws_client.receive_json() @@ -2338,8 +2369,7 @@ async def test_refresh_node_values( { ID: 4, TYPE: "zwave_js/refresh_node_values", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2356,8 +2386,7 @@ async def test_refresh_node_values( { ID: 5, TYPE: "zwave_js/refresh_node_values", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2367,19 +2396,19 @@ async def test_refresh_node_values( async def test_refresh_node_cc_values( - hass, client, multisensor_6, integration, hass_ws_client + hass, multisensor_6, client, integration, hass_ws_client ): """Test that the refresh_node_cc_values WS API call works.""" entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, multisensor_6) client.async_send_command_no_wait.return_value = None await ws_client.send_json( { ID: 1, TYPE: "zwave_js/refresh_node_cc_values", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, COMMAND_CLASS_ID: 112, } ) @@ -2399,8 +2428,7 @@ async def test_refresh_node_cc_values( { ID: 2, TYPE: "zwave_js/refresh_node_cc_values", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, COMMAND_CLASS_ID: 9999, } ) @@ -2408,13 +2436,12 @@ async def test_refresh_node_cc_values( assert not msg["success"] assert msg["error"]["code"] == ERR_NOT_FOUND - # Test getting non-existent node fails + # Test getting non-existent device fails await ws_client.send_json( { ID: 3, TYPE: "zwave_js/refresh_node_cc_values", - ENTRY_ID: entry.entry_id, - NODE_ID: 9999, + DEVICE_ID: "fake_device", COMMAND_CLASS_ID: 112, } ) @@ -2431,8 +2458,7 @@ async def test_refresh_node_cc_values( { ID: 4, TYPE: "zwave_js/refresh_node_cc_values", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, COMMAND_CLASS_ID: 112, } ) @@ -2450,8 +2476,7 @@ async def test_refresh_node_cc_values( { ID: 5, TYPE: "zwave_js/refresh_node_cc_values", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, COMMAND_CLASS_ID: 112, } ) @@ -2462,11 +2487,12 @@ async def test_refresh_node_cc_values( async def test_set_config_parameter( - hass, client, hass_ws_client, multisensor_6, integration + hass, multisensor_6, client, hass_ws_client, integration ): """Test the set_config_parameter service.""" entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, multisensor_6) client.async_send_command_no_wait.return_value = None @@ -2474,8 +2500,7 @@ async def test_set_config_parameter( { ID: 1, TYPE: "zwave_js/set_config_parameter", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, PROPERTY: 102, PROPERTY_KEY: 1, VALUE: 1, @@ -2523,8 +2548,7 @@ async def test_set_config_parameter( { ID: 2, TYPE: "zwave_js/set_config_parameter", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, PROPERTY: 102, PROPERTY_KEY: 1, VALUE: "0x1", @@ -2573,8 +2597,7 @@ async def test_set_config_parameter( { ID: 3, TYPE: "zwave_js/set_config_parameter", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, PROPERTY: 102, PROPERTY_KEY: 1, VALUE: 1, @@ -2593,8 +2616,7 @@ async def test_set_config_parameter( { ID: 4, TYPE: "zwave_js/set_config_parameter", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, PROPERTY: 102, PROPERTY_KEY: 1, VALUE: 1, @@ -2613,8 +2635,7 @@ async def test_set_config_parameter( { ID: 5, TYPE: "zwave_js/set_config_parameter", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, PROPERTY: 102, PROPERTY_KEY: 1, VALUE: 1, @@ -2633,8 +2654,7 @@ async def test_set_config_parameter( { ID: 6, TYPE: "zwave_js/set_config_parameter", - ENTRY_ID: entry.entry_id, - NODE_ID: 9999, + DEVICE_ID: "fake_device", PROPERTY: 102, PROPERTY_KEY: 1, VALUE: 1, @@ -2653,8 +2673,7 @@ async def test_set_config_parameter( { ID: 7, TYPE: "zwave_js/set_config_parameter", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, PROPERTY: 102, PROPERTY_KEY: 1, VALUE: 1, @@ -2674,8 +2693,7 @@ async def test_set_config_parameter( { ID: 8, TYPE: "zwave_js/set_config_parameter", - ENTRY_ID: entry.entry_id, - NODE_ID: 52, + DEVICE_ID: device.id, PROPERTY: 102, PROPERTY_KEY: 1, VALUE: 1, @@ -2693,14 +2711,14 @@ async def test_get_config_parameters(hass, multisensor_6, integration, hass_ws_c entry = integration ws_client = await hass_ws_client(hass) node = multisensor_6 + device = get_device(hass, node) # Test getting configuration parameter values await ws_client.send_json( { ID: 4, TYPE: "zwave_js/get_config_parameters", - ENTRY_ID: entry.entry_id, - NODE_ID: node.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2722,8 +2740,7 @@ async def test_get_config_parameters(hass, multisensor_6, integration, hass_ws_c { ID: 5, TYPE: "zwave_js/get_config_parameters", - ENTRY_ID: entry.entry_id, - NODE_ID: 99999, + DEVICE_ID: "fake_device", } ) msg = await ws_client.receive_json() @@ -2738,8 +2755,7 @@ async def test_get_config_parameters(hass, multisensor_6, integration, hass_ws_c { ID: 6, TYPE: "zwave_js/get_config_parameters", - ENTRY_ID: entry.entry_id, - NODE_ID: node.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -2753,11 +2769,12 @@ async def test_firmware_upload_view( ): """Test the HTTP firmware upload view.""" client = await hass_client() + device = get_device(hass, multisensor_6) with patch( "homeassistant.components.zwave_js.api.begin_firmware_update", ) as mock_cmd: resp = await client.post( - f"/api/zwave_js/firmware/upload/{integration.entry_id}/{multisensor_6.node_id}", + f"/api/zwave_js/firmware/upload/{device.id}", data={"file": firmware_file}, ) assert mock_cmd.call_args[0][1:4] == (multisensor_6, "file", bytes(10)) @@ -2769,12 +2786,13 @@ async def test_firmware_upload_view_failed_command( ): """Test failed command for the HTTP firmware upload view.""" client = await hass_client() + device = get_device(hass, multisensor_6) with patch( "homeassistant.components.zwave_js.api.begin_firmware_update", side_effect=FailedCommand("test", "test"), ): resp = await client.post( - f"/api/zwave_js/firmware/upload/{integration.entry_id}/{multisensor_6.node_id}", + f"/api/zwave_js/firmware/upload/{device.id}", data={"file": firmware_file}, ) assert resp.status == HTTPStatus.BAD_REQUEST @@ -2784,9 +2802,10 @@ async def test_firmware_upload_view_invalid_payload( hass, multisensor_6, integration, hass_client ): """Test an invalid payload for the HTTP firmware upload view.""" + device = get_device(hass, multisensor_6) client = await hass_client() resp = await client.post( - f"/api/zwave_js/firmware/upload/{integration.entry_id}/{multisensor_6.node_id}", + f"/api/zwave_js/firmware/upload/{device.id}", data={"wrong_key": bytes(10)}, ) assert resp.status == HTTPStatus.BAD_REQUEST @@ -2794,40 +2813,43 @@ async def test_firmware_upload_view_invalid_payload( @pytest.mark.parametrize( "method, url", - [("post", "/api/zwave_js/firmware/upload/{}/{}")], + [("post", "/api/zwave_js/firmware/upload/{}")], ) async def test_node_view_non_admin_user( - multisensor_6, integration, hass_client, hass_admin_user, method, url + hass, multisensor_6, integration, hass_client, hass_admin_user, method, url ): """Test node level views for non-admin users.""" client = await hass_client() + device = get_device(hass, multisensor_6) # Verify we require admin user hass_admin_user.groups = [] - resp = await client.request( - method, url.format(integration.entry_id, multisensor_6.node_id) - ) + resp = await client.request(method, url.format(device.id)) assert resp.status == HTTPStatus.UNAUTHORIZED @pytest.mark.parametrize( "method, url", [ - ("post", "/api/zwave_js/firmware/upload/INVALID/1"), + ("post", "/api/zwave_js/firmware/upload/{}"), ], ) -async def test_view_invalid_entry_id(integration, hass_client, method, url): - """Test an invalid config entry id parameter.""" +async def test_view_unloaded_config_entry( + hass, multisensor_6, integration, hass_client, method, url +): + """Test an unloaded config entry raises Bad Request.""" client = await hass_client() - resp = await client.request(method, url) + device = get_device(hass, multisensor_6) + await hass.config_entries.async_unload(integration.entry_id) + resp = await client.request(method, url.format(device.id)) assert resp.status == HTTPStatus.BAD_REQUEST @pytest.mark.parametrize( "method, url", - [("post", "/api/zwave_js/firmware/upload/{}/111")], + [("post", "/api/zwave_js/firmware/upload/INVALID")], ) -async def test_view_invalid_node_id(integration, hass_client, method, url): - """Test an invalid config entry id parameter.""" +async def test_view_invalid_device_id(integration, hass_client, method, url): + """Test an invalid device id parameter.""" client = await hass_client() resp = await client.request(method, url.format(integration.entry_id)) assert resp.status == HTTPStatus.NOT_FOUND @@ -3172,10 +3194,12 @@ async def test_data_collection(hass, client, integration, hass_ws_client): result = msg["result"] assert result is None - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 2 + args = client.async_send_command.call_args_list[0][0][0] assert args["command"] == "driver.enable_statistics" assert args["applicationName"] == "Home Assistant" + args = client.async_send_command.call_args_list[1][0][0] + assert args["command"] == "driver.enable_error_reporting" assert entry.data[CONF_DATA_COLLECTION_OPTED_IN] client.async_send_command.reset_mock() @@ -3274,14 +3298,14 @@ async def test_abort_firmware_update( """Test that the abort_firmware_update WS API call works.""" entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, multisensor_6) client.async_send_command_no_wait.return_value = {} await ws_client.send_json( { ID: 1, TYPE: "zwave_js/abort_firmware_update", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -3301,8 +3325,7 @@ async def test_abort_firmware_update( { ID: 2, TYPE: "zwave_js/abort_firmware_update", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -3319,8 +3342,7 @@ async def test_abort_firmware_update( { ID: 3, TYPE: "zwave_js/abort_firmware_update", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -3330,32 +3352,19 @@ async def test_abort_firmware_update( async def test_abort_firmware_update_failures( - hass, integration, multisensor_6, client, hass_ws_client + hass, multisensor_6, client, integration, hass_ws_client ): """Test failures for the abort_firmware_update websocket command.""" entry = integration ws_client = await hass_ws_client(hass) - # Test sending command with improper entry ID fails - await ws_client.send_json( - { - ID: 1, - TYPE: "zwave_js/abort_firmware_update", - ENTRY_ID: "fake_entry_id", - NODE_ID: multisensor_6.node_id, - } - ) - msg = await ws_client.receive_json() + device = get_device(hass, multisensor_6) - assert not msg["success"] - assert msg["error"]["code"] == ERR_NOT_FOUND - - # Test sending command with improper node ID fails + # Test sending command with improper device ID fails await ws_client.send_json( { ID: 2, TYPE: "zwave_js/abort_firmware_update", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id + 100, + DEVICE_ID: "fake_device", } ) msg = await ws_client.receive_json() @@ -3371,8 +3380,7 @@ async def test_abort_firmware_update_failures( { ID: 3, TYPE: "zwave_js/abort_firmware_update", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -3382,11 +3390,11 @@ async def test_abort_firmware_update_failures( async def test_subscribe_firmware_update_status( - hass, integration, multisensor_6, client, hass_ws_client + hass, multisensor_6, integration, client, hass_ws_client ): """Test the subscribe_firmware_update_status websocket command.""" - entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, multisensor_6) client.async_send_command_no_wait.return_value = {} @@ -3394,8 +3402,7 @@ async def test_subscribe_firmware_update_status( { ID: 1, TYPE: "zwave_js/subscribe_firmware_update_status", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id, + DEVICE_ID: device.id, } ) @@ -3443,11 +3450,11 @@ async def test_subscribe_firmware_update_status( async def test_subscribe_firmware_update_status_initial_value( - hass, integration, multisensor_6, client, hass_ws_client + hass, multisensor_6, client, integration, hass_ws_client ): """Test subscribe_firmware_update_status websocket command with in progress update.""" - entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, multisensor_6) assert multisensor_6.firmware_update_progress is None @@ -3470,43 +3477,35 @@ async def test_subscribe_firmware_update_status_initial_value( { ID: 1, TYPE: "zwave_js/subscribe_firmware_update_status", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() assert msg["success"] - assert msg["result"] == {"sent_fragments": 1, "total_fragments": 10} + assert msg["result"] is None + + msg = await ws_client.receive_json() + assert msg["event"] == { + "event": "firmware update progress", + "sent_fragments": 1, + "total_fragments": 10, + } async def test_subscribe_firmware_update_status_failures( - hass, integration, multisensor_6, client, hass_ws_client + hass, multisensor_6, client, integration, hass_ws_client ): """Test failures for the subscribe_firmware_update_status websocket command.""" entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, multisensor_6) # Test sending command with improper entry ID fails await ws_client.send_json( { ID: 1, TYPE: "zwave_js/subscribe_firmware_update_status", - ENTRY_ID: "fake_entry_id", - NODE_ID: multisensor_6.node_id, - } - ) - msg = await ws_client.receive_json() - - assert not msg["success"] - assert msg["error"]["code"] == ERR_NOT_FOUND - - # Test sending command with improper node ID fails - await ws_client.send_json( - { - ID: 2, - TYPE: "zwave_js/subscribe_firmware_update_status", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id + 100, + DEVICE_ID: "fake_device", } ) msg = await ws_client.receive_json() @@ -3522,8 +3521,7 @@ async def test_subscribe_firmware_update_status_failures( { ID: 3, TYPE: "zwave_js/subscribe_firmware_update_status", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() @@ -3541,6 +3539,7 @@ async def test_check_for_config_updates(hass, client, integration, hass_ws_clien client.async_send_command.return_value = { "updateAvailable": True, "newVersion": "test", + "installedVersion": "test", } await ws_client.send_json( { @@ -3686,7 +3685,12 @@ async def test_subscribe_controller_statistics( msg = await ws_client.receive_json() assert msg["success"] - assert msg["result"] == { + assert msg["result"] is None + + msg = await ws_client.receive_json() + assert msg["event"] == { + "event": "statistics updated", + "source": "controller", "messages_tx": 0, "messages_rx": 0, "messages_dropped_tx": 0, @@ -3769,19 +3773,25 @@ async def test_subscribe_node_statistics( """Test the subscribe_node_statistics command.""" entry = integration ws_client = await hass_ws_client(hass) + device = get_device(hass, multisensor_6) await ws_client.send_json( { ID: 1, TYPE: "zwave_js/subscribe_node_statistics", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() assert msg["success"] - assert msg["result"] == { + assert msg["result"] is None + + msg = await ws_client.receive_json() + assert msg["event"] == { + "source": "node", + "event": "statistics updated", + "nodeId": multisensor_6.node_id, "commands_tx": 0, "commands_rx": 0, "commands_dropped_tx": 0, @@ -3823,8 +3833,7 @@ async def test_subscribe_node_statistics( { ID: 2, TYPE: "zwave_js/subscribe_node_statistics", - ENTRY_ID: "fake_entry_id", - NODE_ID: multisensor_6.node_id, + DEVICE_ID: "fake_device", } ) msg = await ws_client.receive_json() @@ -3832,17 +3841,6 @@ async def test_subscribe_node_statistics( assert not msg["success"] assert msg["error"]["code"] == ERR_NOT_FOUND - # Test sending command with improper node ID fails - await ws_client.send_json( - { - ID: 3, - TYPE: "zwave_js/subscribe_node_statistics", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id + 100, - } - ) - msg = await ws_client.receive_json() - # Test sending command with not loaded entry fails await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() @@ -3851,8 +3849,7 @@ async def test_subscribe_node_statistics( { ID: 4, TYPE: "zwave_js/subscribe_node_statistics", - ENTRY_ID: entry.entry_id, - NODE_ID: multisensor_6.node_id, + DEVICE_ID: device.id, } ) msg = await ws_client.receive_json() diff --git a/tests/components/zwave_js/test_button.py b/tests/components/zwave_js/test_button.py index 29858e0eb97..5ae5f8e7254 100644 --- a/tests/components/zwave_js/test_button.py +++ b/tests/components/zwave_js/test_button.py @@ -49,11 +49,12 @@ async def test_ping_entity( assert "There is no value to refresh for this entity" in caplog.text # Assert a node ping button entity is not created for the controller - node = client.driver.controller.nodes[1] + driver = client.driver + node = driver.controller.nodes[1] assert node.is_controller_node assert ( async_get(hass).async_get_entity_id( - DOMAIN, "sensor", f"{get_valueless_base_unique_id(client, node)}.ping" + DOMAIN, "sensor", f"{get_valueless_base_unique_id(driver, node)}.ping" ) is None ) diff --git a/tests/components/zwave_js/test_device_action.py b/tests/components/zwave_js/test_device_action.py index b8fa43d9cec..ad9d61b3f33 100644 --- a/tests/components/zwave_js/test_device_action.py +++ b/tests/components/zwave_js/test_device_action.py @@ -30,7 +30,9 @@ async def test_get_actions( """Test we get the expected actions from a zwave_js node.""" node = lock_schlage_be469 dev_reg = device_registry.async_get(hass) - device = dev_reg.async_get_device({get_device_id(client, node)}) + driver = client.driver + assert driver + device = dev_reg.async_get_device({get_device_id(driver, node)}) assert device expected_actions = [ { @@ -99,7 +101,9 @@ async def test_get_actions_meter( """Test we get the expected meter actions from a zwave_js node.""" node = aeon_smart_switch_6 dev_reg = device_registry.async_get(hass) - device = dev_reg.async_get_device({get_device_id(client, node)}) + driver = client.driver + assert driver + device = dev_reg.async_get_device({get_device_id(driver, node)}) assert device actions = await async_get_device_automations( hass, DeviceAutomationType.ACTION, device.id @@ -116,7 +120,9 @@ async def test_actions( ) -> None: """Test actions.""" node = climate_radio_thermostat_ct100_plus - device_id = get_device_id(client, node) + driver = client.driver + assert driver + device_id = get_device_id(driver, node) dev_reg = device_registry.async_get(hass) device = dev_reg.async_get_device({device_id}) assert device @@ -236,7 +242,9 @@ async def test_actions_multiple_calls( ) -> None: """Test actions can be called multiple times and still work.""" node = climate_radio_thermostat_ct100_plus - device_id = get_device_id(client, node) + driver = client.driver + assert driver + device_id = get_device_id(driver, node) dev_reg = device_registry.async_get(hass) device = dev_reg.async_get_device({device_id}) assert device @@ -281,7 +289,9 @@ async def test_lock_actions( ) -> None: """Test actions for locks.""" node = lock_schlage_be469 - device_id = get_device_id(client, node) + driver = client.driver + assert driver + device_id = get_device_id(driver, node) dev_reg = device_registry.async_get(hass) device = dev_reg.async_get_device({device_id}) assert device @@ -350,7 +360,9 @@ async def test_reset_meter_action( ) -> None: """Test reset_meter action.""" node = aeon_smart_switch_6 - device_id = get_device_id(client, node) + driver = client.driver + assert driver + device_id = get_device_id(driver, node) dev_reg = device_registry.async_get(hass) device = dev_reg.async_get_device({device_id}) assert device @@ -613,7 +625,9 @@ async def test_get_action_capabilities_meter_triggers( """Test we get the expected action capabilities for meter triggers.""" node = aeon_smart_switch_6 dev_reg = device_registry.async_get(hass) - device = dev_reg.async_get_device({get_device_id(client, node)}) + driver = client.driver + assert driver + device = dev_reg.async_get_device({get_device_id(driver, node)}) assert device capabilities = await device_action.async_get_action_capabilities( hass, @@ -669,7 +683,9 @@ async def test_unavailable_entity_actions( await hass.async_block_till_done() node = lock_schlage_be469 dev_reg = device_registry.async_get(hass) - device = dev_reg.async_get_device({get_device_id(client, node)}) + driver = client.driver + assert driver + device = dev_reg.async_get_device({get_device_id(driver, node)}) assert device actions = await async_get_device_automations( hass, DeviceAutomationType.ACTION, device.id diff --git a/tests/components/zwave_js/test_diagnostics.py b/tests/components/zwave_js/test_diagnostics.py index 99475cd63ec..3ac3f32b45a 100644 --- a/tests/components/zwave_js/test_diagnostics.py +++ b/tests/components/zwave_js/test_diagnostics.py @@ -5,7 +5,10 @@ import pytest from zwave_js_server.event import Event from homeassistant.components.diagnostics.const import REDACTED -from homeassistant.components.zwave_js.diagnostics import async_get_device_diagnostics +from homeassistant.components.zwave_js.diagnostics import ( + ZwaveValueMatcher, + async_get_device_diagnostics, +) from homeassistant.components.zwave_js.discovery import async_discover_node_values from homeassistant.components.zwave_js.helpers import get_device_id from homeassistant.helpers.device_registry import async_get @@ -51,7 +54,7 @@ async def test_device_diagnostics( ): """Test the device level diagnostics data dump.""" dev_reg = async_get(hass) - device = dev_reg.async_get_device({get_device_id(client, multisensor_6)}) + device = dev_reg.async_get_device({get_device_id(client.driver, multisensor_6)}) assert device # Update a value and ensure it is reflected in the node state @@ -89,7 +92,16 @@ async def test_device_diagnostics( assert len(diagnostics_data["entities"]) == len( list(async_discover_node_values(multisensor_6, device, {device.id: set()})) ) - assert diagnostics_data["state"] == multisensor_6.data + assert diagnostics_data["state"] == { + **multisensor_6.data, + "statistics": { + "commandsDroppedRX": 0, + "commandsDroppedTX": 0, + "commandsRX": 0, + "commandsTX": 0, + "timeoutResponse": 0, + }, + } async def test_device_diagnostics_error(hass, integration): @@ -100,3 +112,9 @@ async def test_device_diagnostics_error(hass, integration): ) with pytest.raises(ValueError): await async_get_device_diagnostics(hass, integration, device) + + +async def test_empty_zwave_value_matcher(): + """Test empty ZwaveValueMatcher is invalid.""" + with pytest.raises(ValueError): + ZwaveValueMatcher() diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py index 72da1fcb915..19f38d4aa57 100644 --- a/tests/components/zwave_js/test_events.py +++ b/tests/components/zwave_js/test_events.py @@ -312,7 +312,7 @@ async def test_power_level_notification(hass, hank_binary_switch, integration, c node.receive_event(event) await hass.async_block_till_done() assert len(events) == 1 - assert events[0].data["command_class_name"] == "Power Level" + assert events[0].data["command_class_name"] == "Powerlevel" assert events[0].data["command_class"] == 115 assert events[0].data["test_node_id"] == 1 assert events[0].data["status"] == 0 diff --git a/tests/components/zwave_js/test_helpers.py b/tests/components/zwave_js/test_helpers.py deleted file mode 100644 index 290c93fa084..00000000000 --- a/tests/components/zwave_js/test_helpers.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Test Z-Wave JS helpers module.""" -import pytest - -from homeassistant.components.zwave_js.helpers import ZwaveValueID - - -async def test_empty_zwave_value_id(): - """Test empty ZwaveValueID is invalid.""" - with pytest.raises(ValueError): - ZwaveValueID() diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 7b3fd773839..a2962261ac3 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -800,8 +800,10 @@ async def test_removed_device( hass, client, climate_radio_thermostat_ct100_plus, lock_schlage_be469, integration ): """Test that the device registry gets updated when a device gets removed.""" + driver = client.driver + assert driver # Verify how many nodes are available - assert len(client.driver.controller.nodes) == 2 + assert len(driver.controller.nodes) == 2 # Make sure there are the same number of devices dev_reg = dr.async_get(hass) @@ -814,7 +816,7 @@ async def test_removed_device( assert len(entity_entries) == 29 # Remove a node and reload the entry - old_node = client.driver.controller.nodes.pop(13) + old_node = driver.controller.nodes.pop(13) await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() @@ -824,7 +826,7 @@ async def test_removed_device( assert len(device_entries) == 1 entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id) assert len(entity_entries) == 17 - assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None + assert dev_reg.async_get_device({get_device_id(driver, old_node)}) is None async def test_suggested_area(hass, client, eaton_rf9640_dimmer): diff --git a/tests/components/zwave_js/test_logbook.py b/tests/components/zwave_js/test_logbook.py new file mode 100644 index 00000000000..eb02c1bbdcf --- /dev/null +++ b/tests/components/zwave_js/test_logbook.py @@ -0,0 +1,132 @@ +"""The tests for Z-Wave JS logbook.""" +from zwave_js_server.const import CommandClass + +from homeassistant.components.zwave_js.const import ( + ZWAVE_JS_NOTIFICATION_EVENT, + ZWAVE_JS_VALUE_NOTIFICATION_EVENT, +) +from homeassistant.components.zwave_js.helpers import get_device_id +from homeassistant.helpers import device_registry as dr +from homeassistant.setup import async_setup_component + +from tests.components.logbook.common import MockRow, mock_humanify + + +async def test_humanifying_zwave_js_notification_event( + hass, client, lock_schlage_be469, integration +): + """Test humanifying Z-Wave JS notification events.""" + dev_reg = dr.async_get(hass) + device = dev_reg.async_get_device( + identifiers={get_device_id(client.driver, lock_schlage_be469)} + ) + assert device + + hass.config.components.add("recorder") + assert await async_setup_component(hass, "logbook", {}) + + events = mock_humanify( + hass, + [ + MockRow( + ZWAVE_JS_NOTIFICATION_EVENT, + { + "device_id": device.id, + "command_class": CommandClass.NOTIFICATION.value, + "command_class_name": "Notification", + "label": "label", + "event_label": "event_label", + }, + ), + MockRow( + ZWAVE_JS_NOTIFICATION_EVENT, + { + "device_id": device.id, + "command_class": CommandClass.ENTRY_CONTROL.value, + "command_class_name": "Entry Control", + "event_type": 1, + "data_type": 2, + }, + ), + MockRow( + ZWAVE_JS_NOTIFICATION_EVENT, + { + "device_id": device.id, + "command_class": CommandClass.SWITCH_MULTILEVEL.value, + "command_class_name": "Multilevel Switch", + "event_type": 1, + "direction": "up", + }, + ), + MockRow( + ZWAVE_JS_NOTIFICATION_EVENT, + { + "device_id": device.id, + "command_class": CommandClass.POWERLEVEL.value, + "command_class_name": "Powerlevel", + }, + ), + ], + ) + + assert events[0]["name"] == "Touchscreen Deadbolt" + assert events[0]["domain"] == "zwave_js" + assert ( + events[0]["message"] + == "fired Notification CC 'notification' event 'label': 'event_label'" + ) + + assert events[1]["name"] == "Touchscreen Deadbolt" + assert events[1]["domain"] == "zwave_js" + assert ( + events[1]["message"] + == "fired Entry Control CC 'notification' event for event type '1' with data type '2'" + ) + + assert events[2]["name"] == "Touchscreen Deadbolt" + assert events[2]["domain"] == "zwave_js" + assert ( + events[2]["message"] + == "fired Multilevel Switch CC 'notification' event for event type '1': 'up'" + ) + + assert events[3]["name"] == "Touchscreen Deadbolt" + assert events[3]["domain"] == "zwave_js" + assert events[3]["message"] == "fired Powerlevel CC 'notification' event" + + +async def test_humanifying_zwave_js_value_notification_event( + hass, client, lock_schlage_be469, integration +): + """Test humanifying Z-Wave JS value notification events.""" + dev_reg = dr.async_get(hass) + device = dev_reg.async_get_device( + identifiers={get_device_id(client.driver, lock_schlage_be469)} + ) + assert device + + hass.config.components.add("recorder") + assert await async_setup_component(hass, "logbook", {}) + + events = mock_humanify( + hass, + [ + MockRow( + ZWAVE_JS_VALUE_NOTIFICATION_EVENT, + { + "device_id": device.id, + "command_class": CommandClass.SCENE_ACTIVATION.value, + "command_class_name": "Scene Activation", + "label": "Scene ID", + "value": "001", + }, + ), + ], + ) + + assert events[0]["name"] == "Touchscreen Deadbolt" + assert events[0]["domain"] == "zwave_js" + assert ( + events[0]["message"] + == "fired Scene Activation CC 'value notification' event for 'Scene ID': '001'" + ) diff --git a/tests/components/zwave_js/test_migrate.py b/tests/components/zwave_js/test_migrate.py index 79201ebbede..cf1e3ee7eaa 100644 --- a/tests/components/zwave_js/test_migrate.py +++ b/tests/components/zwave_js/test_migrate.py @@ -1,481 +1,15 @@ """Test the Z-Wave JS migration module.""" import copy -from unittest.mock import patch import pytest from zwave_js_server.model.node import Node -from homeassistant.components.zwave_js.api import ENTRY_ID, ID, TYPE from homeassistant.components.zwave_js.const import DOMAIN from homeassistant.components.zwave_js.helpers import get_device_id -from homeassistant.const import LIGHT_LUX from homeassistant.helpers import device_registry as dr, entity_registry as er from .common import AIR_TEMPERATURE_SENSOR, NOTIFICATION_MOTION_BINARY_SENSOR -from tests.common import MockConfigEntry, mock_device_registry, mock_registry - -# Switch device -ZWAVE_SWITCH_DEVICE_ID = "zwave_switch_device_id" -ZWAVE_SWITCH_DEVICE_NAME = "Z-Wave Switch Device" -ZWAVE_SWITCH_DEVICE_AREA = "Z-Wave Switch Area" -ZWAVE_SWITCH_ENTITY = "switch.zwave_switch_node" -ZWAVE_SWITCH_UNIQUE_ID = "102-6789" -ZWAVE_SWITCH_NAME = "Z-Wave Switch" -ZWAVE_SWITCH_ICON = "mdi:zwave-test-switch" -ZWAVE_POWER_ENTITY = "sensor.zwave_power" -ZWAVE_POWER_UNIQUE_ID = "102-5678" -ZWAVE_POWER_NAME = "Z-Wave Power" -ZWAVE_POWER_ICON = "mdi:zwave-test-power" - -# Multisensor device -ZWAVE_MULTISENSOR_DEVICE_ID = "zwave_multisensor_device_id" -ZWAVE_MULTISENSOR_DEVICE_NAME = "Z-Wave Multisensor Device" -ZWAVE_MULTISENSOR_DEVICE_AREA = "Z-Wave Multisensor Area" -ZWAVE_SOURCE_NODE_ENTITY = "sensor.zwave_source_node" -ZWAVE_SOURCE_NODE_UNIQUE_ID = "52-4321" -ZWAVE_LUMINANCE_ENTITY = "sensor.zwave_luminance" -ZWAVE_LUMINANCE_UNIQUE_ID = "52-6543" -ZWAVE_LUMINANCE_NAME = "Z-Wave Luminance" -ZWAVE_LUMINANCE_ICON = "mdi:zwave-test-luminance" -ZWAVE_BATTERY_ENTITY = "sensor.zwave_battery_level" -ZWAVE_BATTERY_UNIQUE_ID = "52-1234" -ZWAVE_BATTERY_NAME = "Z-Wave Battery Level" -ZWAVE_BATTERY_ICON = "mdi:zwave-test-battery" -ZWAVE_TAMPERING_ENTITY = "sensor.zwave_tampering" -ZWAVE_TAMPERING_UNIQUE_ID = "52-3456" -ZWAVE_TAMPERING_NAME = "Z-Wave Tampering" -ZWAVE_TAMPERING_ICON = "mdi:zwave-test-tampering" - - -@pytest.fixture(name="zwave_migration_data") -def zwave_migration_data_fixture(hass): - """Return mock zwave migration data.""" - zwave_switch_device = dr.DeviceEntry( - id=ZWAVE_SWITCH_DEVICE_ID, - name_by_user=ZWAVE_SWITCH_DEVICE_NAME, - area_id=ZWAVE_SWITCH_DEVICE_AREA, - ) - zwave_switch_entry = er.RegistryEntry( - entity_id=ZWAVE_SWITCH_ENTITY, - unique_id=ZWAVE_SWITCH_UNIQUE_ID, - platform="zwave", - name=ZWAVE_SWITCH_NAME, - icon=ZWAVE_SWITCH_ICON, - ) - zwave_multisensor_device = dr.DeviceEntry( - id=ZWAVE_MULTISENSOR_DEVICE_ID, - name_by_user=ZWAVE_MULTISENSOR_DEVICE_NAME, - area_id=ZWAVE_MULTISENSOR_DEVICE_AREA, - ) - zwave_source_node_entry = er.RegistryEntry( - entity_id=ZWAVE_SOURCE_NODE_ENTITY, - unique_id=ZWAVE_SOURCE_NODE_UNIQUE_ID, - platform="zwave", - name="Z-Wave Source Node", - ) - zwave_luminance_entry = er.RegistryEntry( - entity_id=ZWAVE_LUMINANCE_ENTITY, - unique_id=ZWAVE_LUMINANCE_UNIQUE_ID, - platform="zwave", - name=ZWAVE_LUMINANCE_NAME, - icon=ZWAVE_LUMINANCE_ICON, - unit_of_measurement="lux", - ) - zwave_battery_entry = er.RegistryEntry( - entity_id=ZWAVE_BATTERY_ENTITY, - unique_id=ZWAVE_BATTERY_UNIQUE_ID, - platform="zwave", - name=ZWAVE_BATTERY_NAME, - icon=ZWAVE_BATTERY_ICON, - unit_of_measurement="%", - ) - zwave_power_entry = er.RegistryEntry( - entity_id=ZWAVE_POWER_ENTITY, - unique_id=ZWAVE_POWER_UNIQUE_ID, - platform="zwave", - name=ZWAVE_POWER_NAME, - icon=ZWAVE_POWER_ICON, - unit_of_measurement="W", - ) - zwave_tampering_entry = er.RegistryEntry( - entity_id=ZWAVE_TAMPERING_ENTITY, - unique_id=ZWAVE_TAMPERING_UNIQUE_ID, - platform="zwave", - name=ZWAVE_TAMPERING_NAME, - icon=ZWAVE_TAMPERING_ICON, - unit_of_measurement="", # Test empty string unit normalization. - ) - - zwave_migration_data = { - ZWAVE_SWITCH_ENTITY: { - "node_id": 102, - "node_instance": 1, - "command_class": 37, - "command_class_label": "", - "value_index": 1, - "device_id": zwave_switch_device.id, - "domain": zwave_switch_entry.domain, - "entity_id": zwave_switch_entry.entity_id, - "unique_id": ZWAVE_SWITCH_UNIQUE_ID, - "unit_of_measurement": zwave_switch_entry.unit_of_measurement, - }, - ZWAVE_POWER_ENTITY: { - "node_id": 102, - "node_instance": 1, - "command_class": 50, - "command_class_label": "Power", - "value_index": 8, - "device_id": zwave_switch_device.id, - "domain": zwave_power_entry.domain, - "entity_id": zwave_power_entry.entity_id, - "unique_id": ZWAVE_POWER_UNIQUE_ID, - "unit_of_measurement": zwave_power_entry.unit_of_measurement, - }, - ZWAVE_SOURCE_NODE_ENTITY: { - "node_id": 52, - "node_instance": 1, - "command_class": 113, - "command_class_label": "SourceNodeId", - "value_index": 1, - "device_id": zwave_multisensor_device.id, - "domain": zwave_source_node_entry.domain, - "entity_id": zwave_source_node_entry.entity_id, - "unique_id": ZWAVE_SOURCE_NODE_UNIQUE_ID, - "unit_of_measurement": zwave_source_node_entry.unit_of_measurement, - }, - ZWAVE_LUMINANCE_ENTITY: { - "node_id": 52, - "node_instance": 1, - "command_class": 49, - "command_class_label": "Luminance", - "value_index": 3, - "device_id": zwave_multisensor_device.id, - "domain": zwave_luminance_entry.domain, - "entity_id": zwave_luminance_entry.entity_id, - "unique_id": ZWAVE_LUMINANCE_UNIQUE_ID, - "unit_of_measurement": zwave_luminance_entry.unit_of_measurement, - }, - ZWAVE_BATTERY_ENTITY: { - "node_id": 52, - "node_instance": 1, - "command_class": 128, - "command_class_label": "Battery Level", - "value_index": 0, - "device_id": zwave_multisensor_device.id, - "domain": zwave_battery_entry.domain, - "entity_id": zwave_battery_entry.entity_id, - "unique_id": ZWAVE_BATTERY_UNIQUE_ID, - "unit_of_measurement": zwave_battery_entry.unit_of_measurement, - }, - ZWAVE_TAMPERING_ENTITY: { - "node_id": 52, - "node_instance": 1, - "command_class": 113, - "command_class_label": "Burglar", - "value_index": 10, - "device_id": zwave_multisensor_device.id, - "domain": zwave_tampering_entry.domain, - "entity_id": zwave_tampering_entry.entity_id, - "unique_id": ZWAVE_TAMPERING_UNIQUE_ID, - "unit_of_measurement": zwave_tampering_entry.unit_of_measurement, - }, - } - - mock_device_registry( - hass, - { - zwave_switch_device.id: zwave_switch_device, - zwave_multisensor_device.id: zwave_multisensor_device, - }, - ) - mock_registry( - hass, - { - ZWAVE_SWITCH_ENTITY: zwave_switch_entry, - ZWAVE_SOURCE_NODE_ENTITY: zwave_source_node_entry, - ZWAVE_LUMINANCE_ENTITY: zwave_luminance_entry, - ZWAVE_BATTERY_ENTITY: zwave_battery_entry, - ZWAVE_POWER_ENTITY: zwave_power_entry, - ZWAVE_TAMPERING_ENTITY: zwave_tampering_entry, - }, - ) - - return zwave_migration_data - - -@pytest.fixture(name="zwave_integration") -def zwave_integration_fixture(hass, zwave_migration_data): - """Mock the zwave integration.""" - hass.config.components.add("zwave") - zwave_config_entry = MockConfigEntry(domain="zwave", data={"usb_path": "/dev/test"}) - zwave_config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.zwave.async_get_migration_data", - return_value=zwave_migration_data, - ): - yield zwave_config_entry - - -@pytest.mark.skip(reason="The old zwave integration has been removed.") -async def test_migrate_zwave( - hass, - zwave_integration, - aeon_smart_switch_6, - multisensor_6, - integration, - hass_ws_client, -): - """Test the Z-Wave to Z-Wave JS migration websocket api.""" - entry = integration - client = await hass_ws_client(hass) - - assert hass.config_entries.async_entries("zwave") - - await client.send_json( - { - ID: 5, - TYPE: "zwave_js/migrate_zwave", - ENTRY_ID: entry.entry_id, - "dry_run": False, - } - ) - msg = await client.receive_json() - result = msg["result"] - - migration_entity_map = { - ZWAVE_SWITCH_ENTITY: "switch.smart_switch_6", - ZWAVE_LUMINANCE_ENTITY: "sensor.multisensor_6_illuminance", - ZWAVE_BATTERY_ENTITY: "sensor.multisensor_6_battery_level", - } - - assert result["zwave_entity_ids"] == [ - ZWAVE_SWITCH_ENTITY, - ZWAVE_POWER_ENTITY, - ZWAVE_SOURCE_NODE_ENTITY, - ZWAVE_LUMINANCE_ENTITY, - ZWAVE_BATTERY_ENTITY, - ZWAVE_TAMPERING_ENTITY, - ] - expected_zwave_js_entities = [ - "switch.smart_switch_6", - "sensor.multisensor_6_air_temperature", - "sensor.multisensor_6_illuminance", - "sensor.multisensor_6_humidity", - "sensor.multisensor_6_ultraviolet", - "binary_sensor.multisensor_6_home_security_tampering_product_cover_removed", - "binary_sensor.multisensor_6_home_security_motion_detection", - "sensor.multisensor_6_battery_level", - "binary_sensor.multisensor_6_low_battery_level", - "light.smart_switch_6", - "sensor.smart_switch_6_electric_consumed_kwh", - "sensor.smart_switch_6_electric_consumed_w", - "sensor.smart_switch_6_electric_consumed_v", - "sensor.smart_switch_6_electric_consumed_a", - ] - # Assert that both lists have the same items without checking order - assert not set(result["zwave_js_entity_ids"]) ^ set(expected_zwave_js_entities) - assert result["migration_entity_map"] == migration_entity_map - assert result["migrated"] is True - - dev_reg = dr.async_get(hass) - ent_reg = er.async_get(hass) - - # check the device registry migration - - # check that the migrated entries have correct attributes - multisensor_device_entry = dev_reg.async_get_device( - identifiers={("zwave_js", "3245146787-52")}, connections=set() - ) - assert multisensor_device_entry - assert multisensor_device_entry.name_by_user == ZWAVE_MULTISENSOR_DEVICE_NAME - assert multisensor_device_entry.area_id == ZWAVE_MULTISENSOR_DEVICE_AREA - switch_device_entry = dev_reg.async_get_device( - identifiers={("zwave_js", "3245146787-102")}, connections=set() - ) - assert switch_device_entry - assert switch_device_entry.name_by_user == ZWAVE_SWITCH_DEVICE_NAME - assert switch_device_entry.area_id == ZWAVE_SWITCH_DEVICE_AREA - - migration_device_map = { - ZWAVE_SWITCH_DEVICE_ID: switch_device_entry.id, - ZWAVE_MULTISENSOR_DEVICE_ID: multisensor_device_entry.id, - } - - assert result["migration_device_map"] == migration_device_map - - # check the entity registry migration - - # this should have been migrated and no longer present under that id - assert not ent_reg.async_is_registered("sensor.multisensor_6_battery_level") - assert not ent_reg.async_is_registered("sensor.multisensor_6_illuminance") - - # these should not have been migrated and is still in the registry - assert ent_reg.async_is_registered(ZWAVE_SOURCE_NODE_ENTITY) - source_entry = ent_reg.async_get(ZWAVE_SOURCE_NODE_ENTITY) - assert source_entry.unique_id == ZWAVE_SOURCE_NODE_UNIQUE_ID - assert ent_reg.async_is_registered(ZWAVE_POWER_ENTITY) - source_entry = ent_reg.async_get(ZWAVE_POWER_ENTITY) - assert source_entry.unique_id == ZWAVE_POWER_UNIQUE_ID - assert ent_reg.async_is_registered(ZWAVE_TAMPERING_ENTITY) - tampering_entry = ent_reg.async_get(ZWAVE_TAMPERING_ENTITY) - assert tampering_entry.unique_id == ZWAVE_TAMPERING_UNIQUE_ID - assert ent_reg.async_is_registered("sensor.smart_switch_6_electric_consumed_w") - - # this is the new entity_ids of the zwave_js entities - assert ent_reg.async_is_registered(ZWAVE_SWITCH_ENTITY) - assert ent_reg.async_is_registered(ZWAVE_BATTERY_ENTITY) - assert ent_reg.async_is_registered(ZWAVE_LUMINANCE_ENTITY) - - # check that the migrated entries have correct attributes - switch_entry = ent_reg.async_get(ZWAVE_SWITCH_ENTITY) - assert switch_entry - assert switch_entry.unique_id == "3245146787.102-37-0-currentValue" - assert switch_entry.name == ZWAVE_SWITCH_NAME - assert switch_entry.icon == ZWAVE_SWITCH_ICON - battery_entry = ent_reg.async_get(ZWAVE_BATTERY_ENTITY) - assert battery_entry - assert battery_entry.unique_id == "3245146787.52-128-0-level" - assert battery_entry.name == ZWAVE_BATTERY_NAME - assert battery_entry.icon == ZWAVE_BATTERY_ICON - luminance_entry = ent_reg.async_get(ZWAVE_LUMINANCE_ENTITY) - assert luminance_entry - assert luminance_entry.unique_id == "3245146787.52-49-0-Illuminance" - assert luminance_entry.name == ZWAVE_LUMINANCE_NAME - assert luminance_entry.icon == ZWAVE_LUMINANCE_ICON - assert luminance_entry.unit_of_measurement == LIGHT_LUX - - # check that the zwave config entry has been removed - assert not hass.config_entries.async_entries("zwave") - - # Check that the zwave integration fails entry setup after migration - zwave_config_entry = MockConfigEntry(domain="zwave") - zwave_config_entry.add_to_hass(hass) - assert not await hass.config_entries.async_setup(zwave_config_entry.entry_id) - - -@pytest.mark.skip(reason="The old zwave integration has been removed.") -async def test_migrate_zwave_dry_run( - hass, - zwave_integration, - aeon_smart_switch_6, - multisensor_6, - integration, - hass_ws_client, -): - """Test the zwave to zwave_js migration websocket api dry run.""" - entry = integration - client = await hass_ws_client(hass) - - await client.send_json( - {ID: 5, TYPE: "zwave_js/migrate_zwave", ENTRY_ID: entry.entry_id} - ) - msg = await client.receive_json() - result = msg["result"] - - migration_entity_map = { - ZWAVE_SWITCH_ENTITY: "switch.smart_switch_6", - ZWAVE_BATTERY_ENTITY: "sensor.multisensor_6_battery_level", - } - - assert result["zwave_entity_ids"] == [ - ZWAVE_SWITCH_ENTITY, - ZWAVE_POWER_ENTITY, - ZWAVE_SOURCE_NODE_ENTITY, - ZWAVE_BATTERY_ENTITY, - ZWAVE_TAMPERING_ENTITY, - ] - expected_zwave_js_entities = [ - "switch.smart_switch_6", - "sensor.multisensor_6_air_temperature", - "sensor.multisensor_6_illuminance", - "sensor.multisensor_6_humidity", - "sensor.multisensor_6_ultraviolet", - "binary_sensor.multisensor_6_home_security_tampering_product_cover_removed", - "binary_sensor.multisensor_6_home_security_motion_detection", - "sensor.multisensor_6_battery_level", - "binary_sensor.multisensor_6_low_battery_level", - "light.smart_switch_6", - "sensor.smart_switch_6_electric_consumed_kwh", - "sensor.smart_switch_6_electric_consumed_w", - "sensor.smart_switch_6_electric_consumed_v", - "sensor.smart_switch_6_electric_consumed_a", - ] - # Assert that both lists have the same items without checking order - assert not set(result["zwave_js_entity_ids"]) ^ set(expected_zwave_js_entities) - assert result["migration_entity_map"] == migration_entity_map - - dev_reg = dr.async_get(hass) - - multisensor_device_entry = dev_reg.async_get_device( - identifiers={("zwave_js", "3245146787-52")}, connections=set() - ) - assert multisensor_device_entry - assert multisensor_device_entry.name_by_user is None - assert multisensor_device_entry.area_id is None - switch_device_entry = dev_reg.async_get_device( - identifiers={("zwave_js", "3245146787-102")}, connections=set() - ) - assert switch_device_entry - assert switch_device_entry.name_by_user is None - assert switch_device_entry.area_id is None - - migration_device_map = { - ZWAVE_SWITCH_DEVICE_ID: switch_device_entry.id, - ZWAVE_MULTISENSOR_DEVICE_ID: multisensor_device_entry.id, - } - - assert result["migration_device_map"] == migration_device_map - - assert result["migrated"] is False - - ent_reg = er.async_get(hass) - - # no real migration should have been done - assert ent_reg.async_is_registered("switch.smart_switch_6") - assert ent_reg.async_is_registered("sensor.multisensor_6_battery_level") - assert ent_reg.async_is_registered("sensor.smart_switch_6_electric_consumed_w") - - assert ent_reg.async_is_registered(ZWAVE_SOURCE_NODE_ENTITY) - source_entry = ent_reg.async_get(ZWAVE_SOURCE_NODE_ENTITY) - assert source_entry - assert source_entry.unique_id == ZWAVE_SOURCE_NODE_UNIQUE_ID - - assert ent_reg.async_is_registered(ZWAVE_BATTERY_ENTITY) - battery_entry = ent_reg.async_get(ZWAVE_BATTERY_ENTITY) - assert battery_entry - assert battery_entry.unique_id == ZWAVE_BATTERY_UNIQUE_ID - - assert ent_reg.async_is_registered(ZWAVE_POWER_ENTITY) - power_entry = ent_reg.async_get(ZWAVE_POWER_ENTITY) - assert power_entry - assert power_entry.unique_id == ZWAVE_POWER_UNIQUE_ID - - # check that the zwave config entry has not been removed - assert hass.config_entries.async_entries("zwave") - - # Check that the zwave integration can be setup after dry run - zwave_config_entry = zwave_integration - with patch("openzwave.option.ZWaveOption"), patch("openzwave.network.ZWaveNetwork"): - assert await hass.config_entries.async_setup(zwave_config_entry.entry_id) - - -async def test_migrate_zwave_not_setup( - hass, aeon_smart_switch_6, multisensor_6, integration, hass_ws_client -): - """Test the zwave to zwave_js migration websocket without zwave setup.""" - entry = integration - client = await hass_ws_client(hass) - - await client.send_json( - {ID: 5, TYPE: "zwave_js/migrate_zwave", ENTRY_ID: entry.entry_id} - ) - msg = await client.receive_json() - - assert not msg["success"] - assert msg["error"]["code"] == "zwave_not_loaded" - assert msg["error"]["message"] == "Integration zwave is not loaded" - async def test_unique_id_migration_dupes( hass, multisensor_6_state, client, integration @@ -656,12 +190,14 @@ async def test_old_entity_migration( ): """Test old entity on a different endpoint is migrated to a new one.""" node = Node(client, copy.deepcopy(hank_binary_switch_state)) + driver = client.driver + assert driver ent_reg = er.async_get(hass) dev_reg = dr.async_get(hass) device = dev_reg.async_get_or_create( config_entry_id=integration.entry_id, - identifiers={get_device_id(client, node)}, + identifiers={get_device_id(driver, node)}, manufacturer=hank_binary_switch_state["deviceConfig"]["manufacturer"], model=hank_binary_switch_state["deviceConfig"]["label"], ) @@ -670,7 +206,7 @@ async def test_old_entity_migration( entity_name = SENSOR_NAME.split(".")[1] # Create entity RegistryEntry using fake endpoint - old_unique_id = f"{client.driver.controller.home_id}.32-50-1-value-66049" + old_unique_id = f"{driver.controller.home_id}.32-50-1-value-66049" entity_entry = ent_reg.async_get_or_create( "sensor", DOMAIN, @@ -687,7 +223,7 @@ async def test_old_entity_migration( for i in range(0, 2): # Add a ready node, unique ID should be migrated event = {"node": node} - client.driver.controller.emit("node added", event) + driver.controller.emit("node added", event) await hass.async_block_till_done() # Check that new RegistryEntry is using new unique ID format @@ -702,12 +238,14 @@ async def test_different_endpoint_migration_status_sensor( ): """Test that the different endpoint migration logic skips over the status sensor.""" node = Node(client, copy.deepcopy(hank_binary_switch_state)) + driver = client.driver + assert driver ent_reg = er.async_get(hass) dev_reg = dr.async_get(hass) device = dev_reg.async_get_or_create( config_entry_id=integration.entry_id, - identifiers={get_device_id(client, node)}, + identifiers={get_device_id(driver, node)}, manufacturer=hank_binary_switch_state["deviceConfig"]["manufacturer"], model=hank_binary_switch_state["deviceConfig"]["label"], ) @@ -716,7 +254,7 @@ async def test_different_endpoint_migration_status_sensor( entity_name = SENSOR_NAME.split(".")[1] # Create entity RegistryEntry using fake endpoint - old_unique_id = f"{client.driver.controller.home_id}.32.node_status" + old_unique_id = f"{driver.controller.home_id}.32.node_status" entity_entry = ent_reg.async_get_or_create( "sensor", DOMAIN, @@ -733,7 +271,7 @@ async def test_different_endpoint_migration_status_sensor( for i in range(0, 2): # Add a ready node, unique ID should be migrated event = {"node": node} - client.driver.controller.emit("node added", event) + driver.controller.emit("node added", event) await hass.async_block_till_done() # Check that the RegistryEntry is using the same unique ID @@ -746,12 +284,14 @@ async def test_skip_old_entity_migration_for_multiple( ): """Test that multiple entities of the same value but on a different endpoint get skipped.""" node = Node(client, copy.deepcopy(hank_binary_switch_state)) + driver = client.driver + assert driver ent_reg = er.async_get(hass) dev_reg = dr.async_get(hass) device = dev_reg.async_get_or_create( config_entry_id=integration.entry_id, - identifiers={get_device_id(client, node)}, + identifiers={get_device_id(driver, node)}, manufacturer=hank_binary_switch_state["deviceConfig"]["manufacturer"], model=hank_binary_switch_state["deviceConfig"]["label"], ) @@ -760,7 +300,7 @@ async def test_skip_old_entity_migration_for_multiple( entity_name = SENSOR_NAME.split(".")[1] # Create two entity entrrys using different endpoints - old_unique_id_1 = f"{client.driver.controller.home_id}.32-50-1-value-66049" + old_unique_id_1 = f"{driver.controller.home_id}.32-50-1-value-66049" entity_entry = ent_reg.async_get_or_create( "sensor", DOMAIN, @@ -774,7 +314,7 @@ async def test_skip_old_entity_migration_for_multiple( assert entity_entry.unique_id == old_unique_id_1 # Create two entity entrrys using different endpoints - old_unique_id_2 = f"{client.driver.controller.home_id}.32-50-2-value-66049" + old_unique_id_2 = f"{driver.controller.home_id}.32-50-2-value-66049" entity_entry = ent_reg.async_get_or_create( "sensor", DOMAIN, @@ -788,12 +328,12 @@ async def test_skip_old_entity_migration_for_multiple( assert entity_entry.unique_id == old_unique_id_2 # Add a ready node, unique ID should be migrated event = {"node": node} - client.driver.controller.emit("node added", event) + driver.controller.emit("node added", event) await hass.async_block_till_done() # Check that new RegistryEntry is created using new unique ID format entity_entry = ent_reg.async_get(SENSOR_NAME) - new_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049" + new_unique_id = f"{driver.controller.home_id}.32-50-0-value-66049" assert entity_entry.unique_id == new_unique_id # Check that the old entities stuck around because we skipped the migration step @@ -806,12 +346,14 @@ async def test_old_entity_migration_notification_binary_sensor( ): """Test old entity on a different endpoint is migrated to a new one for a notification binary sensor.""" node = Node(client, copy.deepcopy(multisensor_6_state)) + driver = client.driver + assert driver ent_reg = er.async_get(hass) dev_reg = dr.async_get(hass) device = dev_reg.async_get_or_create( config_entry_id=integration.entry_id, - identifiers={get_device_id(client, node)}, + identifiers={get_device_id(driver, node)}, manufacturer=multisensor_6_state["deviceConfig"]["manufacturer"], model=multisensor_6_state["deviceConfig"]["label"], ) @@ -819,7 +361,9 @@ async def test_old_entity_migration_notification_binary_sensor( entity_name = NOTIFICATION_MOTION_BINARY_SENSOR.split(".")[1] # Create entity RegistryEntry using old unique ID format - old_unique_id = f"{client.driver.controller.home_id}.52-113-1-Home Security-Motion sensor status.8" + old_unique_id = ( + f"{driver.controller.home_id}.52-113-1-Home Security-Motion sensor status.8" + ) entity_entry = ent_reg.async_get_or_create( "binary_sensor", DOMAIN, @@ -836,12 +380,14 @@ async def test_old_entity_migration_notification_binary_sensor( for _ in range(0, 2): # Add a ready node, unique ID should be migrated event = {"node": node} - client.driver.controller.emit("node added", event) + driver.controller.emit("node added", event) await hass.async_block_till_done() # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_BINARY_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status.8" + new_unique_id = ( + f"{driver.controller.home_id}.52-113-0-Home Security-Motion sensor status.8" + ) assert entity_entry.unique_id == new_unique_id assert ( ent_reg.async_get_entity_id("binary_sensor", DOMAIN, old_unique_id) is None diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index 1d41e145a95..848c4d7b0e5 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -205,13 +205,14 @@ async def test_node_status_sensor( assert hass.states.get(NODE_STATUS_ENTITY).state != STATE_UNAVAILABLE # Assert a node status sensor entity is not created for the controller - node = client.driver.controller.nodes[1] + driver = client.driver + node = driver.controller.nodes[1] assert node.is_controller_node assert ( ent_reg.async_get_entity_id( DOMAIN, "sensor", - f"{get_valueless_base_unique_id(client, node)}.node_status", + f"{get_valueless_base_unique_id(driver, node)}.node_status", ) is None ) diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index 67f3320b5f7..b5ef083bf64 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -518,6 +518,71 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): ) +async def test_set_config_parameter_gather( + hass, + client, + multisensor_6, + climate_radio_thermostat_ct100_plus_different_endpoints, + integration, +): + """Test the set_config_parameter service gather functionality.""" + # Test setting config parameter by property and validate that the first node + # which triggers an error doesn't prevent the second one to be called. + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_ENTITY_ID: [ + AIR_TEMPERATURE_SENSOR, + CLIMATE_RADIO_THERMOSTAT_ENTITY, + ], + ATTR_CONFIG_PARAMETER: 1, + ATTR_CONFIG_VALUE: 1, + }, + blocking=True, + ) + + assert len(client.async_send_command_no_wait.call_args_list) == 0 + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 26 + assert args["valueId"] == { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "Temperature Reporting Threshold", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "description": "Reporting threshold for changes in the ambient temperature", + "label": "Temperature Reporting Threshold", + "default": 2, + "min": 0, + "max": 4, + "states": { + "0": "Disabled", + "1": "0.5\u00b0 F", + "2": "1.0\u00b0 F", + "3": "1.5\u00b0 F", + "4": "2.0\u00b0 F", + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": False, + "isFromConfig": True, + }, + "value": 1, + } + assert args["value"] == 1 + + client.async_send_command.reset_mock() + + async def test_bulk_set_config_parameters(hass, client, multisensor_6, integration): """Test the bulk_set_partial_config_parameters service.""" dev_reg = async_get_dev_reg(hass) @@ -726,6 +791,45 @@ async def test_bulk_set_config_parameters(hass, client, multisensor_6, integrati client.async_send_command.reset_mock() +async def test_bulk_set_config_parameters_gather( + hass, + client, + multisensor_6, + climate_radio_thermostat_ct100_plus_different_endpoints, + integration, +): + """Test the bulk_set_partial_config_parameters service gather functionality.""" + # Test bulk setting config parameter by property and validate that the first node + # which triggers an error doesn't prevent the second one to be called. + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS, + { + ATTR_ENTITY_ID: [ + CLIMATE_RADIO_THERMOSTAT_ENTITY, + AIR_TEMPERATURE_SENSOR, + ], + ATTR_CONFIG_PARAMETER: 102, + ATTR_CONFIG_VALUE: 241, + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 52 + assert args["valueId"] == { + "commandClass": 112, + "property": 102, + } + assert args["value"] == 241 + + client.async_send_command_no_wait.reset_mock() + + async def test_refresh_value( hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, integration ): @@ -1126,6 +1230,66 @@ async def test_set_value_options(hass, client, aeon_smart_switch_6, integration) client.async_send_command.reset_mock() +async def test_set_value_gather( + hass, + client, + multisensor_6, + climate_radio_thermostat_ct100_plus_different_endpoints, + integration, +): + """Test the set_value service gather functionality.""" + # Test setting value by property and validate that the first node + # which triggers an error doesn't prevent the second one to be called. + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: [ + CLIMATE_RADIO_THERMOSTAT_ENTITY, + AIR_TEMPERATURE_SENSOR, + ], + ATTR_COMMAND_CLASS: 112, + ATTR_PROPERTY: 102, + ATTR_PROPERTY_KEY: 1, + ATTR_VALUE: 1, + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 0 + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 52 + assert args["valueId"] == { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 102, + "propertyKey": 1, + "propertyName": "Group 2: Send battery reports", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "valueSize": 4, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": True, + "label": "Group 2: Send battery reports", + "description": "Include battery information in periodic reports to Group 2", + "isFromConfig": True, + }, + "value": 0, + } + assert args["value"] == 1 + + client.async_send_command_no_wait.reset_mock() + + async def test_multicast_set_value( hass, client, @@ -1203,11 +1367,11 @@ async def test_multicast_set_value( # Test using area ID dev_reg = async_get_dev_reg(hass) device_eurotronic = dev_reg.async_get_device( - {get_device_id(client, climate_eurotronic_spirit_z)} + {get_device_id(client.driver, climate_eurotronic_spirit_z)} ) assert device_eurotronic device_danfoss = dev_reg.async_get_device( - {get_device_id(client, climate_danfoss_lc_13)} + {get_device_id(client.driver, climate_danfoss_lc_13)} ) assert device_danfoss area_reg = async_get_area_reg(hass) @@ -1491,11 +1655,15 @@ async def test_ping( """Test ping service.""" dev_reg = async_get_dev_reg(hass) device_radio_thermostat = dev_reg.async_get_device( - {get_device_id(client, climate_radio_thermostat_ct100_plus_different_endpoints)} + { + get_device_id( + client.driver, climate_radio_thermostat_ct100_plus_different_endpoints + ) + } ) assert device_radio_thermostat device_danfoss = dev_reg.async_get_device( - {get_device_id(client, climate_danfoss_lc_13)} + {get_device_id(client.driver, climate_danfoss_lc_13)} ) assert device_danfoss @@ -1625,11 +1793,15 @@ async def test_invoke_cc_api( """Test invoke_cc_api service.""" dev_reg = async_get_dev_reg(hass) device_radio_thermostat = dev_reg.async_get_device( - {get_device_id(client, climate_radio_thermostat_ct100_plus_different_endpoints)} + { + get_device_id( + client.driver, climate_radio_thermostat_ct100_plus_different_endpoints + ) + } ) assert device_radio_thermostat device_danfoss = dev_reg.async_get_device( - {get_device_id(client, climate_danfoss_lc_13)} + {get_device_id(client.driver, climate_danfoss_lc_13)} ) assert device_danfoss @@ -1645,7 +1817,7 @@ async def test_invoke_cc_api( device_radio_thermostat.id, device_danfoss.id, ], - ATTR_COMMAND_CLASS: 132, + ATTR_COMMAND_CLASS: 67, ATTR_ENDPOINT: 0, ATTR_METHOD_NAME: "someMethod", ATTR_PARAMETERS: [1, 2], @@ -1655,7 +1827,7 @@ async def test_invoke_cc_api( assert len(client.async_send_command.call_args_list) == 1 args = client.async_send_command.call_args[0][0] assert args["command"] == "endpoint.invoke_cc_api" - assert args["commandClass"] == 132 + assert args["commandClass"] == 67 assert args["endpoint"] == 0 assert args["methodName"] == "someMethod" assert args["args"] == [1, 2] @@ -1667,7 +1839,7 @@ async def test_invoke_cc_api( assert len(client.async_send_command_no_wait.call_args_list) == 1 args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "endpoint.invoke_cc_api" - assert args["commandClass"] == 132 + assert args["commandClass"] == 67 assert args["endpoint"] == 0 assert args["methodName"] == "someMethod" assert args["args"] == [1, 2] @@ -1698,7 +1870,7 @@ async def test_invoke_cc_api( "select.living_connect_z_thermostat_local_protection_state", "sensor.living_connect_z_thermostat_node_status", ], - ATTR_COMMAND_CLASS: 132, + ATTR_COMMAND_CLASS: 67, ATTR_METHOD_NAME: "someMethod", ATTR_PARAMETERS: [1, 2], }, @@ -1707,7 +1879,7 @@ async def test_invoke_cc_api( assert len(client.async_send_command.call_args_list) == 1 args = client.async_send_command.call_args[0][0] assert args["command"] == "endpoint.invoke_cc_api" - assert args["commandClass"] == 132 + assert args["commandClass"] == 67 assert args["endpoint"] == 0 assert args["methodName"] == "someMethod" assert args["args"] == [1, 2] @@ -1719,7 +1891,7 @@ async def test_invoke_cc_api( assert len(client.async_send_command_no_wait.call_args_list) == 1 args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "endpoint.invoke_cc_api" - assert args["commandClass"] == 132 + assert args["commandClass"] == 67 assert args["endpoint"] == 0 assert args["methodName"] == "someMethod" assert args["args"] == [1, 2] @@ -1728,7 +1900,8 @@ async def test_invoke_cc_api( client.async_send_command.reset_mock() client.async_send_command_no_wait.reset_mock() - # Test failed invoke_cc_api call on one node + # Test failed invoke_cc_api call on one node. We return the error on + # the first node in the call to make sure that gather works as expected client.async_send_command.return_value = {"response": True} client.async_send_command_no_wait.side_effect = FailedZWaveCommand( "test", 12, "test" @@ -1740,10 +1913,10 @@ async def test_invoke_cc_api( SERVICE_INVOKE_CC_API, { ATTR_DEVICE_ID: [ - device_radio_thermostat.id, device_danfoss.id, + device_radio_thermostat.id, ], - ATTR_COMMAND_CLASS: 132, + ATTR_COMMAND_CLASS: 67, ATTR_ENDPOINT: 0, ATTR_METHOD_NAME: "someMethod", ATTR_PARAMETERS: [1, 2], @@ -1753,7 +1926,7 @@ async def test_invoke_cc_api( assert len(client.async_send_command.call_args_list) == 1 args = client.async_send_command.call_args[0][0] assert args["command"] == "endpoint.invoke_cc_api" - assert args["commandClass"] == 132 + assert args["commandClass"] == 67 assert args["endpoint"] == 0 assert args["methodName"] == "someMethod" assert args["args"] == [1, 2] @@ -1765,7 +1938,7 @@ async def test_invoke_cc_api( assert len(client.async_send_command_no_wait.call_args_list) == 1 args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "endpoint.invoke_cc_api" - assert args["commandClass"] == 132 + assert args["commandClass"] == 67 assert args["endpoint"] == 0 assert args["methodName"] == "someMethod" assert args["args"] == [1, 2] diff --git a/tests/components/zwave_js/test_trigger.py b/tests/components/zwave_js/test_trigger.py index 45de09e8b17..48439eede0f 100644 --- a/tests/components/zwave_js/test_trigger.py +++ b/tests/components/zwave_js/test_trigger.py @@ -10,6 +10,9 @@ from zwave_js_server.model.node import Node from homeassistant.components import automation from homeassistant.components.zwave_js import DOMAIN from homeassistant.components.zwave_js.trigger import async_validate_trigger_config +from homeassistant.components.zwave_js.triggers.helpers import ( + async_bypass_dynamic_config_validation, +) from homeassistant.const import SERVICE_RELOAD from homeassistant.helpers.device_registry import ( async_entries_for_config_entry, @@ -266,6 +269,122 @@ async def test_zwave_js_value_updated(hass, client, lock_schlage_be469, integrat await hass.services.async_call(automation.DOMAIN, SERVICE_RELOAD, blocking=True) +async def test_zwave_js_value_updated_bypass_dynamic_validation( + hass, client, lock_schlage_be469, integration +): + """Test zwave_js.value_updated trigger when bypassing dynamic validation.""" + trigger_type = f"{DOMAIN}.value_updated" + node: Node = lock_schlage_be469 + + no_value_filter = async_capture_events(hass, "no_value_filter") + + with patch( + "homeassistant.components.zwave_js.triggers.value_updated.async_bypass_dynamic_config_validation", + return_value=True, + ): + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + # no value filter + { + "trigger": { + "platform": trigger_type, + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "command_class": CommandClass.DOOR_LOCK.value, + "property": "latchStatus", + }, + "action": { + "event": "no_value_filter", + }, + }, + ] + }, + ) + + # Test that no value filter is triggered + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node.node_id, + "args": { + "commandClassName": "Door Lock", + "commandClass": 98, + "endpoint": 0, + "property": "latchStatus", + "newValue": "boo", + "prevValue": "hiss", + "propertyName": "latchStatus", + }, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + + assert len(no_value_filter) == 1 + + +async def test_zwave_js_value_updated_bypass_dynamic_validation_no_nodes( + hass, client, lock_schlage_be469, integration +): + """Test value_updated trigger when bypassing dynamic validation with no nodes.""" + trigger_type = f"{DOMAIN}.value_updated" + node: Node = lock_schlage_be469 + + no_value_filter = async_capture_events(hass, "no_value_filter") + + with patch( + "homeassistant.components.zwave_js.triggers.value_updated.async_bypass_dynamic_config_validation", + return_value=True, + ): + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + # no value filter + { + "trigger": { + "platform": trigger_type, + "entity_id": "sensor.test", + "command_class": CommandClass.DOOR_LOCK.value, + "property": "latchStatus", + }, + "action": { + "event": "no_value_filter", + }, + }, + ] + }, + ) + + # Test that no value filter is NOT triggered because automation failed setup + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node.node_id, + "args": { + "commandClassName": "Door Lock", + "commandClass": 98, + "endpoint": 0, + "property": "latchStatus", + "newValue": "boo", + "prevValue": "hiss", + "propertyName": "latchStatus", + }, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + + assert len(no_value_filter) == 0 + + async def test_zwave_js_event(hass, client, lock_schlage_be469, integration): """Test for zwave_js.event automation trigger.""" trigger_type = f"{DOMAIN}.event" @@ -641,6 +760,107 @@ async def test_zwave_js_event(hass, client, lock_schlage_be469, integration): await hass.services.async_call(automation.DOMAIN, SERVICE_RELOAD, blocking=True) +async def test_zwave_js_event_bypass_dynamic_validation( + hass, client, lock_schlage_be469, integration +): + """Test zwave_js.event trigger when bypassing dynamic config validation.""" + trigger_type = f"{DOMAIN}.event" + node: Node = lock_schlage_be469 + + node_no_event_data_filter = async_capture_events(hass, "node_no_event_data_filter") + + with patch( + "homeassistant.components.zwave_js.triggers.event.async_bypass_dynamic_config_validation", + return_value=True, + ): + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + # node filter: no event data + { + "trigger": { + "platform": trigger_type, + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "event_source": "node", + "event": "interview stage completed", + }, + "action": { + "event": "node_no_event_data_filter", + }, + }, + ] + }, + ) + + # Test that `node no event data filter` is triggered and `node event data filter` is not + event = Event( + type="interview stage completed", + data={ + "source": "node", + "event": "interview stage completed", + "stageName": "NodeInfo", + "nodeId": node.node_id, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + + assert len(node_no_event_data_filter) == 1 + + +async def test_zwave_js_event_bypass_dynamic_validation_no_nodes( + hass, client, lock_schlage_be469, integration +): + """Test event trigger when bypassing dynamic validation with no nodes.""" + trigger_type = f"{DOMAIN}.event" + node: Node = lock_schlage_be469 + + node_no_event_data_filter = async_capture_events(hass, "node_no_event_data_filter") + + with patch( + "homeassistant.components.zwave_js.triggers.event.async_bypass_dynamic_config_validation", + return_value=True, + ): + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + # node filter: no event data + { + "trigger": { + "platform": trigger_type, + "entity_id": "sensor.fake", + "event_source": "node", + "event": "interview stage completed", + }, + "action": { + "event": "node_no_event_data_filter", + }, + }, + ] + }, + ) + + # Test that `node no event data filter` is NOT triggered because automation failed + # setup + event = Event( + type="interview stage completed", + data={ + "source": "node", + "event": "interview stage completed", + "stageName": "NodeInfo", + "nodeId": node.node_id, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + + assert len(node_no_event_data_filter) == 0 + + async def test_zwave_js_event_invalid_config_entry_id( hass, client, integration, caplog ): @@ -671,35 +891,6 @@ async def test_zwave_js_event_invalid_config_entry_id( caplog.clear() -async def test_zwave_js_event_unloaded_config_entry(hass, client, integration, caplog): - """Test zwave_js.event automation trigger fails when config entry is unloaded.""" - trigger_type = f"{DOMAIN}.event" - - await hass.config_entries.async_unload(integration.entry_id) - - assert await async_setup_component( - hass, - automation.DOMAIN, - { - automation.DOMAIN: [ - { - "trigger": { - "platform": trigger_type, - "config_entry_id": integration.entry_id, - "event_source": "controller", - "event": "inclusion started", - }, - "action": { - "event": "node_no_event_data_filter", - }, - } - ] - }, - ) - - assert f"Config entry '{integration.entry_id}' not loaded" in caplog.text - - async def test_async_validate_trigger_config(hass): """Test async_validate_trigger_config.""" mock_platform = AsyncMock() @@ -735,3 +926,98 @@ async def test_invalid_trigger_configs(hass): "property": "latchStatus", }, ) + + +async def test_zwave_js_trigger_config_entry_unloaded( + hass, client, lock_schlage_be469, integration +): + """Test zwave_js triggers bypass dynamic validation when needed.""" + dev_reg = async_get_dev_reg(hass) + device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + + # Test bypass check is False + assert not async_bypass_dynamic_config_validation( + hass, + { + "platform": f"{DOMAIN}.value_updated", + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "command_class": CommandClass.DOOR_LOCK.value, + "property": "latchStatus", + }, + ) + + await hass.config_entries.async_unload(integration.entry_id) + + # Test full validation for both events + assert await async_validate_trigger_config( + hass, + { + "platform": f"{DOMAIN}.value_updated", + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "command_class": CommandClass.DOOR_LOCK.value, + "property": "latchStatus", + }, + ) + + assert await async_validate_trigger_config( + hass, + { + "platform": f"{DOMAIN}.event", + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "event_source": "node", + "event": "interview stage completed", + }, + ) + + # Test bypass check + assert async_bypass_dynamic_config_validation( + hass, + { + "platform": f"{DOMAIN}.value_updated", + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "command_class": CommandClass.DOOR_LOCK.value, + "property": "latchStatus", + }, + ) + + assert async_bypass_dynamic_config_validation( + hass, + { + "platform": f"{DOMAIN}.value_updated", + "device_id": device.id, + "command_class": CommandClass.DOOR_LOCK.value, + "property": "latchStatus", + "from": "ajar", + }, + ) + + assert async_bypass_dynamic_config_validation( + hass, + { + "platform": f"{DOMAIN}.event", + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "event_source": "node", + "event": "interview stage completed", + }, + ) + + assert async_bypass_dynamic_config_validation( + hass, + { + "platform": f"{DOMAIN}.event", + "device_id": device.id, + "event_source": "node", + "event": "interview stage completed", + "event_data": {"stageName": "ProtocolInfo"}, + }, + ) + + assert async_bypass_dynamic_config_validation( + hass, + { + "platform": f"{DOMAIN}.event", + "config_entry_id": integration.entry_id, + "event_source": "controller", + "event": "nvm convert progress", + }, + ) diff --git a/tests/conftest.py b/tests/conftest.py index 0155a965fe6..8ea6e114e9a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -561,9 +561,10 @@ async def mqtt_mock(hass, mqtt_client_mock, mqtt_config): ) entry.add_to_hass(hass) - - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() + # Do not forward the entry setup to the components here + with patch("homeassistant.components.mqtt.PLATFORMS", []): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() mqtt_component_mock = MagicMock( return_value=hass.data["mqtt"], @@ -692,10 +693,9 @@ async def _async_init_recorder_component(hass, add_config=None): if recorder.CONF_COMMIT_INTERVAL not in config: config[recorder.CONF_COMMIT_INTERVAL] = 0 - with patch( - "homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", - True, - ), patch("homeassistant.components.recorder.migration.migrate_schema"): + with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( + "homeassistant.components.recorder.migration.migrate_schema" + ): assert await async_setup_component( hass, recorder.DOMAIN, {recorder.DOMAIN: config} ) diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index 2cd3184b44b..e5d220c55df 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -114,6 +114,17 @@ async def test_abort_if_no_implementation(hass, flow_handler): assert result["reason"] == "missing_configuration" +async def test_missing_credentials_for_domain(hass, flow_handler): + """Check flow abort for integration supporting application credentials.""" + flow = flow_handler() + flow.hass = hass + + with patch("homeassistant.loader.APPLICATION_CREDENTIALS", [TEST_DOMAIN]): + result = await flow.async_step_user() + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "missing_credentials" + + async def test_abort_if_authorization_timeout( hass, flow_handler, local_impl, current_request_with_host ): @@ -212,6 +223,61 @@ async def test_abort_if_oauth_error( assert result["reason"] == "oauth_error" +async def test_abort_if_oauth_rejected( + hass, + flow_handler, + local_impl, + hass_client_no_auth, + aioclient_mock, + current_request_with_host, +): + """Check bad oauth token.""" + flow_handler.async_register_implementation(hass, local_impl) + config_entry_oauth2_flow.async_register_implementation( + hass, TEST_DOMAIN, MockOAuth2Implementation() + ) + + result = await hass.config_entries.flow.async_init( + TEST_DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pick_implementation" + + # Pick implementation + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"implementation": TEST_DOMAIN} + ) + + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["url"] == ( + f"{AUTHORIZE_URL}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}&scope=read+write" + ) + + client = await hass_client_no_auth() + resp = await client.get( + f"/auth/external/callback?error=access_denied&state={state}" + ) + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "user_rejected_authorize" + assert result["description_placeholders"] == {"error": "access_denied"} + + async def test_step_discovery(hass, flow_handler, local_impl): """Check flow triggers from discovery.""" flow_handler.async_register_implementation(hass, local_impl) @@ -537,11 +603,11 @@ async def test_implementation_provider(hass, local_impl): hass, mock_domain_with_impl ) == {TEST_DOMAIN: local_impl} - provider_source = {} + provider_source = [] async def async_provide_implementation(hass, domain): """Mock implementation provider.""" - return provider_source.get(domain) + return provider_source config_entry_oauth2_flow.async_add_implementation_provider( hass, "cloud", async_provide_implementation @@ -551,15 +617,29 @@ async def test_implementation_provider(hass, local_impl): hass, mock_domain_with_impl ) == {TEST_DOMAIN: local_impl} - provider_source[ - mock_domain_with_impl - ] = config_entry_oauth2_flow.LocalOAuth2Implementation( - hass, "cloud", CLIENT_ID, CLIENT_SECRET, AUTHORIZE_URL, TOKEN_URL + provider_source.append( + config_entry_oauth2_flow.LocalOAuth2Implementation( + hass, "cloud", CLIENT_ID, CLIENT_SECRET, AUTHORIZE_URL, TOKEN_URL + ) ) assert await config_entry_oauth2_flow.async_get_implementations( hass, mock_domain_with_impl - ) == {TEST_DOMAIN: local_impl, "cloud": provider_source[mock_domain_with_impl]} + ) == {TEST_DOMAIN: local_impl, "cloud": provider_source[0]} + + provider_source.append( + config_entry_oauth2_flow.LocalOAuth2Implementation( + hass, "other", CLIENT_ID, CLIENT_SECRET, AUTHORIZE_URL, TOKEN_URL + ) + ) + + assert await config_entry_oauth2_flow.async_get_implementations( + hass, mock_domain_with_impl + ) == { + TEST_DOMAIN: local_impl, + "cloud": provider_source[0], + "other": provider_source[1], + } async def test_oauth_session_refresh_failure( diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 3f18d565e83..933669ebc53 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -332,6 +332,25 @@ async def test_parallel_updates_sync_platform(hass): assert entity.parallel_updates._value == 1 +async def test_parallel_updates_no_update_method(hass): + """Test platform parallel_updates default set to 0.""" + platform = MockPlatform() + + mock_entity_platform(hass, "test_domain.platform", platform) + + component = EntityComponent(_LOGGER, DOMAIN, hass) + component._platforms = {} + + await component.async_setup({DOMAIN: {"platform": "platform"}}) + await hass.async_block_till_done() + + handle = list(component._platforms.values())[-1] + + entity = MockEntity() + await handle.async_add_entities([entity]) + assert entity.parallel_updates is None + + async def test_parallel_updates_sync_platform_with_constant(hass): """Test sync platform can set parallel_updates limit.""" platform = MockPlatform() diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 7644abaa558..57b89e64cce 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -21,6 +21,7 @@ from homeassistant.helpers.event import ( TrackTemplate, TrackTemplateResult, async_call_later, + async_track_entity_registry_updated_event, async_track_point_in_time, async_track_point_in_utc_time, async_track_same_state, @@ -1026,7 +1027,7 @@ async def test_track_template_result_none(hass): template_condition = Template("{{state_attr('sensor.test', 'battery')}}", hass) template_condition_var = Template( - "{{(state_attr('sensor.test', 'battery')|int) + test }}", hass + "{{(state_attr('sensor.test', 'battery')|int(default=0)) + test }}", hass ) def specific_run_callback(event, updates): @@ -1983,6 +1984,69 @@ async def test_track_template_result_and_conditional(hass): assert specific_runs[2] == "on" +async def test_track_template_result_and_conditional_upper_case(hass): + """Test tracking template with an and conditional with an upper case template.""" + specific_runs = [] + hass.states.async_set("light.a", "off") + hass.states.async_set("light.b", "off") + template_str = '{% if states.light.A.state == "on" and states.light.B.state == "on" %}on{% else %}off{% endif %}' + + template = Template(template_str, hass) + + def specific_run_callback(event, updates): + specific_runs.append(updates.pop().result) + + info = async_track_template_result( + hass, [TrackTemplate(template, None)], specific_run_callback + ) + await hass.async_block_till_done() + assert info.listeners == { + "all": False, + "domains": set(), + "entities": {"light.a"}, + "time": False, + } + + hass.states.async_set("light.b", "on") + await hass.async_block_till_done() + assert len(specific_runs) == 0 + + hass.states.async_set("light.a", "on") + await hass.async_block_till_done() + assert len(specific_runs) == 1 + assert specific_runs[0] == "on" + assert info.listeners == { + "all": False, + "domains": set(), + "entities": {"light.a", "light.b"}, + "time": False, + } + + hass.states.async_set("light.b", "off") + await hass.async_block_till_done() + assert len(specific_runs) == 2 + assert specific_runs[1] == "off" + assert info.listeners == { + "all": False, + "domains": set(), + "entities": {"light.a", "light.b"}, + "time": False, + } + + hass.states.async_set("light.a", "off") + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + hass.states.async_set("light.b", "on") + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + hass.states.async_set("light.a", "on") + await hass.async_block_till_done() + assert len(specific_runs) == 3 + assert specific_runs[2] == "on" + + async def test_track_template_result_iterator(hass): """Test tracking template.""" iterator_runs = [] @@ -2187,7 +2251,7 @@ async def test_track_template_rate_limit(hass): assert refresh_runs == [0] info.async_refresh() assert refresh_runs == [0, 1] - hass.states.async_set("sensor.two", "any") + hass.states.async_set("sensor.TWO", "any") await hass.async_block_till_done() assert refresh_runs == [0, 1] next_time = dt_util.utcnow() + timedelta(seconds=0.125) @@ -2200,7 +2264,7 @@ async def test_track_template_rate_limit(hass): hass.states.async_set("sensor.three", "any") await hass.async_block_till_done() assert refresh_runs == [0, 1, 2] - hass.states.async_set("sensor.four", "any") + hass.states.async_set("sensor.fOuR", "any") await hass.async_block_till_done() assert refresh_runs == [0, 1, 2] next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2) @@ -2385,7 +2449,7 @@ async def test_track_template_rate_limit_super_3(hass): await hass.async_block_till_done() assert refresh_runs == [] - hass.states.async_set("sensor.one", "any") + hass.states.async_set("sensor.ONE", "any") await hass.async_block_till_done() assert refresh_runs == [] info.async_refresh() @@ -2408,7 +2472,7 @@ async def test_track_template_rate_limit_super_3(hass): hass.states.async_set("sensor.four", "any") await hass.async_block_till_done() assert refresh_runs == [1, 2] - hass.states.async_set("sensor.five", "any") + hass.states.async_set("sensor.FIVE", "any") await hass.async_block_till_done() assert refresh_runs == [1, 2] next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2) @@ -2453,7 +2517,7 @@ async def test_track_template_rate_limit_suppress_listener(hass): await hass.async_block_till_done() assert refresh_runs == [0] - hass.states.async_set("sensor.one", "any") + hass.states.async_set("sensor.oNe", "any") await hass.async_block_till_done() assert refresh_runs == [0] info.async_refresh() @@ -2482,7 +2546,7 @@ async def test_track_template_rate_limit_suppress_listener(hass): "time": False, } assert refresh_runs == [0, 1, 2] - hass.states.async_set("sensor.three", "any") + hass.states.async_set("sensor.Three", "any") await hass.async_block_till_done() assert refresh_runs == [0, 1, 2] hass.states.async_set("sensor.four", "any") @@ -2509,7 +2573,7 @@ async def test_track_template_rate_limit_suppress_listener(hass): "time": False, } assert refresh_runs == [0, 1, 2, 4] - hass.states.async_set("sensor.five", "any") + hass.states.async_set("sensor.Five", "any") await hass.async_block_till_done() # Rate limit hit and the all listener is shut off assert info.listeners == { @@ -4167,12 +4231,10 @@ async def test_track_point_in_utc_time_cancel(hass): with pytest.raises(TypeError): track_point_in_utc_time("nothass", run_callback, utc_now) - unsub1 = hass.helpers.event.track_point_in_utc_time( - run_callback, utc_now + timedelta(seconds=0.1) - ) - hass.helpers.event.track_point_in_utc_time( - run_callback, utc_now + timedelta(seconds=0.1) + unsub1 = track_point_in_utc_time( + hass, run_callback, utc_now + timedelta(seconds=0.1) ) + track_point_in_utc_time(hass, run_callback, utc_now + timedelta(seconds=0.1)) unsub1() @@ -4199,12 +4261,10 @@ async def test_async_track_point_in_time_cancel(hass): utc_now = dt_util.utcnow() hst_now = utc_now.astimezone(hst_tz) - unsub1 = hass.helpers.event.async_track_point_in_time( - run_callback, hst_now + timedelta(seconds=0.1) - ) - hass.helpers.event.async_track_point_in_time( - run_callback, hst_now + timedelta(seconds=0.1) + unsub1 = async_track_point_in_time( + hass, run_callback, hst_now + timedelta(seconds=0.1) ) + async_track_point_in_time(hass, run_callback, hst_now + timedelta(seconds=0.1)) unsub1() @@ -4229,11 +4289,9 @@ async def test_async_track_entity_registry_updated_event(hass): def run_callback(event): event_data.append(event.data) - unsub1 = hass.helpers.event.async_track_entity_registry_updated_event( - entity_id, run_callback - ) - unsub2 = hass.helpers.event.async_track_entity_registry_updated_event( - new_entity_id, run_callback + unsub1 = async_track_entity_registry_updated_event(hass, entity_id, run_callback) + unsub2 = async_track_entity_registry_updated_event( + hass, new_entity_id, run_callback ) hass.bus.async_fire( EVENT_ENTITY_REGISTRY_UPDATED, {"action": "create", "entity_id": entity_id} @@ -4299,12 +4357,10 @@ async def test_async_track_entity_registry_updated_event_with_a_callback_that_th def failing_callback(event): raise ValueError - unsub1 = hass.helpers.event.async_track_entity_registry_updated_event( - entity_id, failing_callback - ) - unsub2 = hass.helpers.event.async_track_entity_registry_updated_event( - entity_id, run_callback + unsub1 = async_track_entity_registry_updated_event( + hass, entity_id, failing_callback ) + unsub2 = async_track_entity_registry_updated_event(hass, entity_id, run_callback) hass.bus.async_fire( EVENT_ENTITY_REGISTRY_UPDATED, {"action": "create", "entity_id": entity_id} ) @@ -4317,11 +4373,11 @@ async def test_async_track_entity_registry_updated_event_with_a_callback_that_th async def test_async_track_entity_registry_updated_event_with_empty_list(hass): """Test async_track_entity_registry_updated_event passing an empty list of entities.""" - unsub_single = hass.helpers.event.async_track_entity_registry_updated_event( - [], ha.callback(lambda event: None) + unsub_single = async_track_entity_registry_updated_event( + hass, [], ha.callback(lambda event: None) ) - unsub_single2 = hass.helpers.event.async_track_entity_registry_updated_event( - [], ha.callback(lambda event: None) + unsub_single2 = async_track_entity_registry_updated_event( + hass, [], ha.callback(lambda event: None) ) unsub_single2() diff --git a/tests/helpers/test_instance_id.py b/tests/helpers/test_instance_id.py index c8417c519a1..a8c040f48ba 100644 --- a/tests/helpers/test_instance_id.py +++ b/tests/helpers/test_instance_id.py @@ -1,10 +1,12 @@ """Tests for instance ID helper.""" from unittest.mock import patch +from homeassistant.helpers import instance_id + async def test_get_id_empty(hass, hass_storage): """Get unique ID.""" - uuid = await hass.helpers.instance_id.async_get() + uuid = await instance_id.async_get(hass) assert uuid is not None # Assert it's stored assert hass_storage["core.uuid"]["data"]["uuid"] == uuid @@ -15,7 +17,7 @@ async def test_get_id_migrate(hass, hass_storage): with patch( "homeassistant.util.json.load_json", return_value={"uuid": "1234"} ), patch("os.path.isfile", return_value=True), patch("os.remove") as mock_remove: - uuid = await hass.helpers.instance_id.async_get() + uuid = await instance_id.async_get(hass) assert uuid == "1234" diff --git a/tests/helpers/test_integration_platform.py b/tests/helpers/test_integration_platform.py index a9d0da0b849..584af602062 100644 --- a/tests/helpers/test_integration_platform.py +++ b/tests/helpers/test_integration_platform.py @@ -3,6 +3,7 @@ from unittest.mock import Mock from homeassistant.helpers.integration_platform import ( async_process_integration_platform_for_component, + async_process_integration_platforms, ) from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED @@ -24,8 +25,8 @@ async def test_process_integration_platforms(hass): """Process platform.""" processed.append((domain, platform)) - await hass.helpers.integration_platform.async_process_integration_platforms( - "platform_to_check", _process_platform + await async_process_integration_platforms( + hass, "platform_to_check", _process_platform ) assert len(processed) == 1 @@ -40,9 +41,7 @@ async def test_process_integration_platforms(hass): assert processed[1][1] == event_platform # Verify we only process the platform once if we call it manually - await hass.helpers.integration_platform.async_process_integration_platform_for_component( - hass, "event" - ) + await async_process_integration_platform_for_component(hass, "event") assert len(processed) == 2 diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 1b8de6ca6e2..d08477dc917 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -819,8 +819,9 @@ async def test_register_admin_service(hass, hass_read_only_user, hass_admin_user async def mock_service(call): calls.append(call) - hass.helpers.service.async_register_admin_service("test", "test", mock_service) - hass.helpers.service.async_register_admin_service( + service.async_register_admin_service(hass, "test", "test", mock_service) + service.async_register_admin_service( + hass, "test", "test2", mock_service, @@ -887,7 +888,7 @@ async def test_domain_control_not_async(hass, mock_entities): calls.append(call) with pytest.raises(exceptions.HomeAssistantError): - hass.helpers.service.verify_domain_control("test_domain")(mock_service_log) + service.verify_domain_control(hass, "test_domain")(mock_service_log) async def test_domain_control_unknown(hass, mock_entities): @@ -899,12 +900,12 @@ async def test_domain_control_unknown(hass, mock_entities): calls.append(call) with patch( - "homeassistant.helpers.entity_registry.async_get_registry", + "homeassistant.helpers.entity_registry.async_get", return_value=Mock(entities=mock_entities), ): - protected_mock_service = hass.helpers.service.verify_domain_control( - "test_domain" - )(mock_service_log) + protected_mock_service = service.verify_domain_control(hass, "test_domain")( + mock_service_log + ) hass.services.async_register( "test_domain", "test_service", protected_mock_service, schema=None @@ -940,7 +941,7 @@ async def test_domain_control_unauthorized(hass, hass_read_only_user): """Define a protected service.""" calls.append(call) - protected_mock_service = hass.helpers.service.verify_domain_control("test_domain")( + protected_mock_service = service.verify_domain_control(hass, "test_domain")( mock_service_log ) @@ -979,7 +980,7 @@ async def test_domain_control_admin(hass, hass_admin_user): """Define a protected service.""" calls.append(call) - protected_mock_service = hass.helpers.service.verify_domain_control("test_domain")( + protected_mock_service = service.verify_domain_control(hass, "test_domain")( mock_service_log ) @@ -1017,7 +1018,7 @@ async def test_domain_control_no_user(hass): """Define a protected service.""" calls.append(call) - protected_mock_service = hass.helpers.service.verify_domain_control("test_domain")( + protected_mock_service = service.verify_domain_control(hass, "test_domain")( mock_service_log ) diff --git a/tests/helpers/test_system_info.py b/tests/helpers/test_system_info.py index e4aba5fbb24..a3b6dafe600 100644 --- a/tests/helpers/test_system_info.py +++ b/tests/helpers/test_system_info.py @@ -3,11 +3,12 @@ import json from unittest.mock import patch from homeassistant.const import __version__ as current_version +from homeassistant.helpers.system_info import async_get_system_info async def test_get_system_info(hass): """Test the get system info.""" - info = await hass.helpers.system_info.async_get_system_info() + info = await async_get_system_info(hass) assert isinstance(info, dict) assert info["version"] == current_version assert info["user"] is not None @@ -19,18 +20,18 @@ async def test_container_installationtype(hass): with patch("platform.system", return_value="Linux"), patch( "os.path.isfile", return_value=True ), patch("homeassistant.helpers.system_info.getuser", return_value="root"): - info = await hass.helpers.system_info.async_get_system_info() + info = await async_get_system_info(hass) assert info["installation_type"] == "Home Assistant Container" with patch("platform.system", return_value="Linux"), patch( "os.path.isfile", side_effect=lambda file: file == "/.dockerenv" ), patch("homeassistant.helpers.system_info.getuser", return_value="user"): - info = await hass.helpers.system_info.async_get_system_info() + info = await async_get_system_info(hass) assert info["installation_type"] == "Unsupported Third Party Container" async def test_getuser_keyerror(hass): """Test getuser keyerror.""" with patch("homeassistant.helpers.system_info.getuser", side_effect=KeyError): - info = await hass.helpers.system_info.async_get_system_info() + info = await async_get_system_info(hass) assert info["user"] is None diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index d3a63460ec1..ddda17c20ac 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -233,11 +233,11 @@ def test_float_function(hass): is True ) - assert ( + # Test handling of invalid input + with pytest.raises(TemplateError): template.Template("{{ float('forgiving') }}", hass).async_render() - == "forgiving" - ) + # Test handling of default return value assert render(hass, "{{ float('bad', 1) }}") == 1 assert render(hass, "{{ float('bad', default=1) }}") == 1 @@ -248,7 +248,12 @@ def test_float_filter(hass): assert render(hass, "{{ states.sensor.temperature.state | float }}") == 12.0 assert render(hass, "{{ states.sensor.temperature.state | float > 11 }}") is True - assert render(hass, "{{ 'bad' | float }}") == 0 + + # Test handling of invalid input + with pytest.raises(TemplateError): + render(hass, "{{ 'bad' | float }}") + + # Test handling of default return value assert render(hass, "{{ 'bad' | float(1) }}") == 1 assert render(hass, "{{ 'bad' | float(default=1) }}") == 1 @@ -262,7 +267,11 @@ def test_int_filter(hass): hass.states.async_set("sensor.temperature", "0x10") assert render(hass, "{{ states.sensor.temperature.state | int(base=16) }}") == 16 - assert render(hass, "{{ 'bad' | int }}") == 0 + # Test handling of invalid input + with pytest.raises(TemplateError): + render(hass, "{{ 'bad' | int }}") + + # Test handling of default return value assert render(hass, "{{ 'bad' | int(1) }}") == 1 assert render(hass, "{{ 'bad' | int(default=1) }}") == 1 @@ -276,7 +285,11 @@ def test_int_function(hass): hass.states.async_set("sensor.temperature", "0x10") assert render(hass, "{{ int(states.sensor.temperature.state, base=16) }}") == 16 - assert render(hass, "{{ int('bad') }}") == "bad" + # Test handling of invalid input + with pytest.raises(TemplateError): + render(hass, "{{ int('bad') }}") + + # Test handling of default return value assert render(hass, "{{ int('bad', 1) }}") == 1 assert render(hass, "{{ int('bad', default=1) }}") == 1 @@ -364,12 +377,12 @@ def test_rounding_value(hass): def test_rounding_value_on_error(hass): """Test rounding value handling of error.""" - assert template.Template("{{ None | round }}", hass).async_render() is None + # Test handling of invalid input + with pytest.raises(TemplateError): + template.Template("{{ None | round }}", hass).async_render() - assert ( + with pytest.raises(TemplateError): template.Template('{{ "no_number" | round }}', hass).async_render() - == "no_number" - ) # Test handling of default return value assert render(hass, "{{ 'no_number' | round(default=1) }}") == 1 @@ -377,7 +390,7 @@ def test_rounding_value_on_error(hass): def test_multiply(hass): """Test multiply.""" - tests = {None: None, 10: 100, '"abcd"': "abcd"} + tests = {10: 100} for inp, out in tests.items(): assert ( @@ -387,6 +400,10 @@ def test_multiply(hass): == out ) + # Test handling of invalid input + with pytest.raises(TemplateError): + template.Template("{{ abcd | multiply(10) }}", hass).async_render() + # Test handling of default return value assert render(hass, "{{ 'no_number' | multiply(10, 1) }}") == 1 assert render(hass, "{{ 'no_number' | multiply(10, default=1) }}") == 1 @@ -397,9 +414,7 @@ def test_logarithm(hass): tests = [ (4, 2, 2.0), (1000, 10, 3.0), - (math.e, "", 1.0), - ('"invalid"', "_", "invalid"), - (10, '"invalid"', 10.0), + (math.e, "", 1.0), # The "" means the default base (e) will be used ] for value, base, expected in tests: @@ -417,6 +432,16 @@ def test_logarithm(hass): == expected ) + # Test handling of invalid input + with pytest.raises(TemplateError): + template.Template("{{ invalid | log(_) }}", hass).async_render() + with pytest.raises(TemplateError): + template.Template("{{ log(invalid, _) }}", hass).async_render() + with pytest.raises(TemplateError): + template.Template("{{ 10 | log(invalid) }}", hass).async_render() + with pytest.raises(TemplateError): + template.Template("{{ log(10, invalid) }}", hass).async_render() + # Test handling of default return value assert render(hass, "{{ 'no_number' | log(10, 1) }}") == 1 assert render(hass, "{{ 'no_number' | log(10, default=1) }}") == 1 @@ -432,7 +457,6 @@ def test_sine(hass): (math.pi, 0.0), (math.pi * 1.5, -1.0), (math.pi / 10, 0.309), - ('"duck"', "duck"), ] for value, expected in tests: @@ -442,6 +466,12 @@ def test_sine(hass): ) assert render(hass, f"{{{{ sin({value}) | round(3) }}}}") == expected + # Test handling of invalid input + with pytest.raises(TemplateError): + template.Template("{{ 'duck' | sin }}", hass).async_render() + with pytest.raises(TemplateError): + template.Template("{{ invalid | sin('duck') }}", hass).async_render() + # Test handling of default return value assert render(hass, "{{ 'no_number' | sin(1) }}") == 1 assert render(hass, "{{ 'no_number' | sin(default=1) }}") == 1 @@ -457,7 +487,6 @@ def test_cos(hass): (math.pi, -1.0), (math.pi * 1.5, -0.0), (math.pi / 10, 0.951), - ("'error'", "error"), ] for value, expected in tests: @@ -467,11 +496,17 @@ def test_cos(hass): ) assert render(hass, f"{{{{ cos({value}) | round(3) }}}}") == expected + # Test handling of invalid input + with pytest.raises(TemplateError): + template.Template("{{ 'error' | cos }}", hass).async_render() + with pytest.raises(TemplateError): + template.Template("{{ invalid | cos('error') }}", hass).async_render() + # Test handling of default return value - assert render(hass, "{{ 'no_number' | sin(1) }}") == 1 - assert render(hass, "{{ 'no_number' | sin(default=1) }}") == 1 - assert render(hass, "{{ sin('no_number', 1) }}") == 1 - assert render(hass, "{{ sin('no_number', default=1) }}") == 1 + assert render(hass, "{{ 'no_number' | cos(1) }}") == 1 + assert render(hass, "{{ 'no_number' | cos(default=1) }}") == 1 + assert render(hass, "{{ cos('no_number', 1) }}") == 1 + assert render(hass, "{{ cos('no_number', default=1) }}") == 1 def test_tan(hass): @@ -482,7 +517,6 @@ def test_tan(hass): (math.pi / 180 * 45, 1.0), (math.pi / 180 * 90, "1.633123935319537e+16"), (math.pi / 180 * 135, -1.0), - ("'error'", "error"), ] for value, expected in tests: @@ -492,6 +526,12 @@ def test_tan(hass): ) assert render(hass, f"{{{{ tan({value}) | round(3) }}}}") == expected + # Test handling of invalid input + with pytest.raises(TemplateError): + template.Template("{{ 'error' | tan }}", hass).async_render() + with pytest.raises(TemplateError): + template.Template("{{ invalid | tan('error') }}", hass).async_render() + # Test handling of default return value assert render(hass, "{{ 'no_number' | tan(1) }}") == 1 assert render(hass, "{{ 'no_number' | tan(default=1) }}") == 1 @@ -507,7 +547,6 @@ def test_sqrt(hass): (2, 1.414), (10, 3.162), (100, 10.0), - ("'error'", "error"), ] for value, expected in tests: @@ -517,6 +556,12 @@ def test_sqrt(hass): ) assert render(hass, f"{{{{ sqrt({value}) | round(3) }}}}") == expected + # Test handling of invalid input + with pytest.raises(TemplateError): + template.Template("{{ 'error' | sqrt }}", hass).async_render() + with pytest.raises(TemplateError): + template.Template("{{ invalid | sqrt('error') }}", hass).async_render() + # Test handling of default return value assert render(hass, "{{ 'no_number' | sqrt(1) }}") == 1 assert render(hass, "{{ 'no_number' | sqrt(default=1) }}") == 1 @@ -527,14 +572,11 @@ def test_sqrt(hass): def test_arc_sine(hass): """Test arcus sine.""" tests = [ - (-2.0, -2.0), # value error (-1.0, -1.571), (-0.5, -0.524), (0.0, 0.0), (0.5, 0.524), (1.0, 1.571), - (2.0, 2.0), # value error - ('"error"', "error"), ] for value, expected in tests: @@ -544,6 +586,19 @@ def test_arc_sine(hass): ) assert render(hass, f"{{{{ asin({value}) | round(3) }}}}") == expected + # Test handling of invalid input + invalid_tests = [ + -2.0, # value error + 2.0, # value error + '"error"', + ] + + for value in invalid_tests: + with pytest.raises(TemplateError): + template.Template("{{ %s | asin | round(3) }}" % value, hass).async_render() + with pytest.raises(TemplateError): + assert render(hass, f"{{{{ asin({value}) | round(3) }}}}") + # Test handling of default return value assert render(hass, "{{ 'no_number' | asin(1) }}") == 1 assert render(hass, "{{ 'no_number' | asin(default=1) }}") == 1 @@ -554,14 +609,11 @@ def test_arc_sine(hass): def test_arc_cos(hass): """Test arcus cosine.""" tests = [ - (-2.0, -2.0), # value error (-1.0, 3.142), (-0.5, 2.094), (0.0, 1.571), (0.5, 1.047), (1.0, 0.0), - (2.0, 2.0), # value error - ('"error"', "error"), ] for value, expected in tests: @@ -571,6 +623,19 @@ def test_arc_cos(hass): ) assert render(hass, f"{{{{ acos({value}) | round(3) }}}}") == expected + # Test handling of invalid input + invalid_tests = [ + -2.0, # value error + 2.0, # value error + '"error"', + ] + + for value in invalid_tests: + with pytest.raises(TemplateError): + template.Template("{{ %s | acos | round(3) }}" % value, hass).async_render() + with pytest.raises(TemplateError): + assert render(hass, f"{{{{ acos({value}) | round(3) }}}}") + # Test handling of default return value assert render(hass, "{{ 'no_number' | acos(1) }}") == 1 assert render(hass, "{{ 'no_number' | acos(default=1) }}") == 1 @@ -590,7 +655,6 @@ def test_arc_tan(hass): (1.0, 0.785), (2.0, 1.107), (10.0, 1.471), - ('"error"', "error"), ] for value, expected in tests: @@ -600,6 +664,12 @@ def test_arc_tan(hass): ) assert render(hass, f"{{{{ atan({value}) | round(3) }}}}") == expected + # Test handling of invalid input + with pytest.raises(TemplateError): + template.Template("{{ 'error' | atan }}", hass).async_render() + with pytest.raises(TemplateError): + template.Template("{{ invalid | atan('error') }}", hass).async_render() + # Test handling of default return value assert render(hass, "{{ 'no_number' | atan(1) }}") == 1 assert render(hass, "{{ 'no_number' | atan(default=1) }}") == 1 @@ -622,7 +692,6 @@ def test_arc_tan2(hass): (-4.0, 3.0, -0.927), (-1.0, 2.0, -0.464), (2.0, 1.0, 1.107), - ('"duck"', '"goose"', ("duck", "goose")), ] for y, x, expected in tests: @@ -639,6 +708,12 @@ def test_arc_tan2(hass): == expected ) + # Test handling of invalid input + with pytest.raises(TemplateError): + template.Template("{{ ('duck', 'goose') | atan2 }}", hass).async_render() + with pytest.raises(TemplateError): + template.Template("{{ atan2('duck', 'goose') }}", hass).async_render() + # Test handling of default return value assert render(hass, "{{ ('duck', 'goose') | atan2(1) }}") == 1 assert render(hass, "{{ ('duck', 'goose') | atan2(default=1) }}") == 1 @@ -655,8 +730,6 @@ def test_strptime(hass): ("2016-10-19", "%Y-%m-%d", None), ("2016", "%Y", None), ("15:22:05", "%H:%M:%S", None), - ("1469119144", "%Y", 1469119144), - ("invalid", "%Y", "invalid"), ] for inp, fmt, expected in tests: @@ -667,6 +740,18 @@ def test_strptime(hass): assert template.Template(temp, hass).async_render() == expected + # Test handling of invalid input + invalid_tests = [ + ("1469119144", "%Y"), + ("invalid", "%Y"), + ] + + for inp, fmt in invalid_tests: + temp = f"{{{{ strptime('{inp}', '{fmt}') }}}}" + + with pytest.raises(TemplateError): + template.Template(temp, hass).async_render() + # Test handling of default return value assert render(hass, "{{ strptime('invalid', '%Y', 1) }}") == 1 assert render(hass, "{{ strptime('invalid', '%Y', default=1) }}") == 1 @@ -677,7 +762,6 @@ def test_timestamp_custom(hass): hass.config.set_time_zone("UTC") now = dt_util.utcnow() tests = [ - (None, None, None, None), (1469119144, None, True, "2016-07-21 16:39:04"), (1469119144, "%Y", True, 2016), (1469119144, "invalid", True, "invalid"), @@ -694,6 +778,22 @@ def test_timestamp_custom(hass): assert template.Template(f"{{{{ {inp} | {fil} }}}}", hass).async_render() == out + # Test handling of invalid input + invalid_tests = [ + (None, None, None), + ] + + for inp, fmt, local in invalid_tests: + if fmt: + fil = f"timestamp_custom('{fmt}')" + elif fmt and local: + fil = f"timestamp_custom('{fmt}', {local})" + else: + fil = "timestamp_custom" + + with pytest.raises(TemplateError): + template.Template(f"{{{{ {inp} | {fil} }}}}", hass).async_render() + # Test handling of default return value assert render(hass, "{{ None | timestamp_custom('invalid', True, 1) }}") == 1 assert render(hass, "{{ None | timestamp_custom(default=1) }}") == 1 @@ -702,14 +802,25 @@ def test_timestamp_custom(hass): def test_timestamp_local(hass): """Test the timestamps to local filter.""" hass.config.set_time_zone("UTC") - tests = {None: None, 1469119144: "2016-07-21T16:39:04+00:00"} + tests = [ + (1469119144, "2016-07-21T16:39:04+00:00"), + ] - for inp, out in tests.items(): + for inp, out in tests: assert ( template.Template("{{ %s | timestamp_local }}" % inp, hass).async_render() == out ) + # Test handling of invalid input + invalid_tests = [ + None, + ] + + for inp in invalid_tests: + with pytest.raises(TemplateError): + template.Template("{{ %s | timestamp_local }}" % inp, hass).async_render() + # Test handling of default return value assert render(hass, "{{ None | timestamp_local(1) }}") == 1 assert render(hass, "{{ None | timestamp_local(default=1) }}") == 1 @@ -1002,18 +1113,26 @@ def test_ordinal(hass): def test_timestamp_utc(hass): """Test the timestamps to local filter.""" now = dt_util.utcnow() - tests = { - None: None, - 1469119144: "2016-07-21T16:39:04+00:00", - dt_util.as_timestamp(now): now.isoformat(), - } + tests = [ + (1469119144, "2016-07-21T16:39:04+00:00"), + (dt_util.as_timestamp(now), now.isoformat()), + ] - for inp, out in tests.items(): + for inp, out in tests: assert ( template.Template("{{ %s | timestamp_utc }}" % inp, hass).async_render() == out ) + # Test handling of invalid input + invalid_tests = [ + None, + ] + + for inp in invalid_tests: + with pytest.raises(TemplateError): + template.Template("{{ %s | timestamp_utc }}" % inp, hass).async_render() + # Test handling of default return value assert render(hass, "{{ None | timestamp_utc(1) }}") == 1 assert render(hass, "{{ None | timestamp_utc(default=1) }}") == 1 @@ -1021,14 +1140,12 @@ def test_timestamp_utc(hass): def test_as_timestamp(hass): """Test the as_timestamp function.""" - assert ( - template.Template('{{ as_timestamp("invalid") }}', hass).async_render() is None - ) - hass.mock = None - assert ( - template.Template("{{ as_timestamp(states.mock) }}", hass).async_render() - is None - ) + with pytest.raises(TemplateError): + template.Template('{{ as_timestamp("invalid") }}', hass).async_render() + + hass.states.async_set("test.object", None) + with pytest.raises(TemplateError): + template.Template("{{ as_timestamp(states.test.object) }}", hass).async_render() tpl = ( '{{ as_timestamp(strptime("2024-02-03T09:10:24+0000", ' @@ -1822,7 +1939,8 @@ def test_distance_function_return_none_if_invalid_state(hass): """Test distance function return None if invalid state.""" hass.states.async_set("test.object_2", "happy", {"latitude": 10}) tpl = template.Template("{{ distance(states.test.object_2) | round }}", hass) - assert tpl.async_render() is None + with pytest.raises(TemplateError): + tpl.async_render() def test_distance_function_return_none_if_invalid_coord(hass): @@ -3270,6 +3388,18 @@ def test_urlencode(hass): assert tpl.async_render() == "the%20quick%20brown%20fox%20%3D%20true" +def test_as_timedelta(hass: HomeAssistant) -> None: + """Test the as_timedelta function/filter.""" + tpl = template.Template("{{ as_timedelta('PT10M') }}", hass) + assert tpl.async_render() == "0:10:00" + + tpl = template.Template("{{ 'PT10M' | as_timedelta }}", hass) + assert tpl.async_render() == "0:10:00" + + tpl = template.Template("{{ 'T10M' | as_timedelta }}", hass) + assert tpl.async_render() is None + + def test_iif(hass: HomeAssistant) -> None: """Test the immediate if function/filter.""" tpl = template.Template("{{ (1 == 1) | iif }}", hass) @@ -3359,7 +3489,11 @@ async def test_protected_blocked(hass): async def test_demo_template(hass): """Test the demo template works as expected.""" - hass.states.async_set("sun.sun", "above", {"elevation": 50, "next_rising": "later"}) + hass.states.async_set( + "sun.sun", + "above", + {"elevation": 50, "next_rising": "2022-05-12T03:00:08.503651+00:00"}, + ) for i in range(2): hass.states.async_set(f"sensor.sensor{i}", "on") @@ -3375,7 +3509,7 @@ The temperature is {{ my_test_json.temperature }} {{ my_test_json.unit }}. {% if is_state("sun.sun", "above_horizon") -%} The sun rose {{ relative_time(states.sun.sun.last_changed) }} ago. {%- else -%} - The sun will rise at {{ as_timestamp(strptime(state_attr("sun.sun", "next_rising"), "")) | timestamp_local }}. + The sun will rise at {{ as_timestamp(state_attr("sun.sun", "next_rising")) | timestamp_local }}. {%- endif %} For loop example getting 3 entity values: diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 5520269ca9d..2e30a649a7b 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -290,12 +290,29 @@ async def test_translation_merging_loaded_apart(hass, caplog): assert "component.sensor.state.moon__phase.first_quarter" in translations translations = await translation.async_get_translations( - hass, "en", "state", integration="sensor" + hass, "en", "state", integrations={"sensor"} ) assert "component.sensor.state.moon__phase.first_quarter" in translations +async def test_translation_merging_loaded_together(hass, caplog): + """Test we merge translations of two integrations when they are loaded at the same time.""" + hass.config.components.add("hue") + hass.config.components.add("homekit") + hue_translations = await translation.async_get_translations( + hass, "en", "config", integrations={"hue"} + ) + homekit_translations = await translation.async_get_translations( + hass, "en", "config", integrations={"homekit"} + ) + + translations = await translation.async_get_translations( + hass, "en", "config", integrations={"hue", "homekit"} + ) + assert translations == hue_translations | homekit_translations + + async def test_caching(hass): """Test we cache data.""" hass.config.components.add("sensor") @@ -320,14 +337,14 @@ async def test_caching(hass): ) load_sensor_only = await translation.async_get_translations( - hass, "en", "state", integration="sensor" + hass, "en", "state", integrations={"sensor"} ) assert load_sensor_only for key in load_sensor_only: assert key.startswith("component.sensor.state.") load_light_only = await translation.async_get_translations( - hass, "en", "state", integration="light" + hass, "en", "state", integrations={"light"} ) assert load_light_only for key in load_light_only: @@ -341,7 +358,7 @@ async def test_caching(hass): side_effect=translation._build_resources, ) as mock_build: load_sensor_only = await translation.async_get_translations( - hass, "en", "title", integration="sensor" + hass, "en", "title", integrations={"sensor"} ) assert load_sensor_only for key in load_sensor_only: @@ -349,12 +366,12 @@ async def test_caching(hass): assert len(mock_build.mock_calls) == 0 assert await translation.async_get_translations( - hass, "en", "title", integration="sensor" + hass, "en", "title", integrations={"sensor"} ) assert len(mock_build.mock_calls) == 0 load_light_only = await translation.async_get_translations( - hass, "en", "title", integration="media_player" + hass, "en", "title", integrations={"media_player"} ) assert load_light_only for key in load_light_only: diff --git a/tests/pylint/test_enforce_type_hints.py b/tests/pylint/test_enforce_type_hints.py index 64be85c9a44..feb3b6b341c 100644 --- a/tests/pylint/test_enforce_type_hints.py +++ b/tests/pylint/test_enforce_type_hints.py @@ -14,6 +14,32 @@ import pytest from . import assert_adds_messages, assert_no_messages +@pytest.mark.parametrize( + ("string", "expected_x", "expected_y", "expected_z", "expected_a"), + [ + ("list[dict[str, str]]", "list", "dict", "str", "str"), + ("list[dict[str, Any]]", "list", "dict", "str", "Any"), + ], +) +def test_regex_x_of_y_of_z_comma_a( + hass_enforce_type_hints: ModuleType, + string: str, + expected_x: str, + expected_y: str, + expected_z: str, + expected_a: str, +) -> None: + """Test x_of_y_of_z_comma_a regexes.""" + matchers: dict[str, re.Pattern] = hass_enforce_type_hints._TYPE_HINT_MATCHERS + + assert (match := matchers["x_of_y_of_z_comma_a"].match(string)) + assert match.group(0) == string + assert match.group(1) == expected_x + assert match.group(2) == expected_y + assert match.group(3) == expected_z + assert match.group(4) == expected_a + + @pytest.mark.parametrize( ("string", "expected_x", "expected_y", "expected_z"), [ @@ -165,3 +191,52 @@ def test_valid_discovery_info( with assert_no_messages(linter): type_hint_checker.visit_asyncfunctiondef(func_node) + + +def test_invalid_list_dict_str_any( + linter: UnittestLinter, type_hint_checker: BaseChecker +) -> None: + """Ensure invalid hints are rejected for discovery_info.""" + type_hint_checker.module = "homeassistant.components.pylint_test.device_trigger" + func_node = astroid.extract_node( + """ + async def async_get_triggers( #@ + hass: HomeAssistant, + device_id: str + ) -> list: + pass + """ + ) + + with assert_adds_messages( + linter, + pylint.testutils.MessageTest( + msg_id="hass-return-type", + node=func_node, + args=["list[dict[str, str]]", "list[dict[str, Any]]"], + line=2, + col_offset=0, + end_line=2, + end_col_offset=28, + ), + ): + type_hint_checker.visit_asyncfunctiondef(func_node) + + +def test_valid_list_dict_str_any( + linter: UnittestLinter, type_hint_checker: BaseChecker +) -> None: + """Ensure valid hints are accepted for discovery_info.""" + type_hint_checker.module = "homeassistant.components.pylint_test.device_trigger" + func_node = astroid.extract_node( + """ + async def async_get_triggers( #@ + hass: HomeAssistant, + device_id: str + ) -> list[dict[str, Any]]: + pass + """ + ) + + with assert_no_messages(linter): + type_hint_checker.visit_asyncfunctiondef(func_node) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 3611c204ba7..2602887d1d5 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1497,7 +1497,7 @@ async def test_reload_entry_entity_registry_works(hass): ) await hass.async_block_till_done() - assert len(mock_unload_entry.mock_calls) == 1 + assert len(mock_unload_entry.mock_calls) == 2 async def test_unique_id_persisted(hass, manager): @@ -3080,3 +3080,41 @@ async def test_deprecated_disabled_by_str_set(hass, manager, caplog): ) assert entry.disabled_by is config_entries.ConfigEntryDisabler.USER assert " str for config entry disabled_by. This is deprecated " in caplog.text + + +async def test_entry_reload_concurrency(hass, manager): + """Test multiple reload calls do not cause a reload race.""" + entry = MockConfigEntry(domain="comp", state=config_entries.ConfigEntryState.LOADED) + entry.add_to_hass(hass) + + async_setup = AsyncMock(return_value=True) + loaded = 1 + + async def _async_setup_entry(*args, **kwargs): + await asyncio.sleep(0) + nonlocal loaded + loaded += 1 + return loaded == 1 + + async def _async_unload_entry(*args, **kwargs): + await asyncio.sleep(0) + nonlocal loaded + loaded -= 1 + return loaded == 0 + + mock_integration( + hass, + MockModule( + "comp", + async_setup=async_setup, + async_setup_entry=_async_setup_entry, + async_unload_entry=_async_unload_entry, + ), + ) + mock_entity_platform(hass, "config_flow.comp", None) + tasks = [] + for _ in range(15): + tasks.append(asyncio.create_task(manager.async_reload(entry.entry_id))) + await asyncio.gather(*tasks) + assert entry.state is config_entries.ConfigEntryState.LOADED + assert loaded == 1 diff --git a/tests/test_core.py b/tests/test_core.py index 6885063a79a..67513ea8b17 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,11 +1,16 @@ """Test to verify that Home Assistant core works.""" +from __future__ import annotations + # pylint: disable=protected-access +import array import asyncio from datetime import datetime, timedelta import functools +import gc import logging import os from tempfile import TemporaryDirectory +from typing import Any from unittest.mock import MagicMock, Mock, PropertyMock, patch import pytest @@ -28,6 +33,7 @@ from homeassistant.const import ( __version__, ) import homeassistant.core as ha +from homeassistant.core import State from homeassistant.exceptions import ( InvalidEntityFormatError, InvalidStateError, @@ -442,6 +448,24 @@ async def test_eventbus_filtered_listener(hass): unsub() +async def test_eventbus_run_immediately(hass): + """Test we can call events immediately.""" + calls = [] + + @ha.callback + def listener(event): + """Mock listener.""" + calls.append(event) + + unsub = hass.bus.async_listen("test", listener, run_immediately=True) + + hass.bus.async_fire("test", {"event": True}) + # No async_block_till_done here + assert len(calls) == 1 + + unsub() + + async def test_eventbus_unsubscribe_listener(hass): """Test unsubscribe listener from returned function.""" calls = [] @@ -1033,13 +1057,7 @@ def test_config_is_allowed_external_url(): async def test_event_on_update(hass): """Test that event is fired on update.""" - events = [] - - @ha.callback - def callback(event): - events.append(event) - - hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, callback) + events = async_capture_events(hass, EVENT_CORE_CONFIG_UPDATE) assert hass.config.latitude != 12 @@ -1119,13 +1137,7 @@ async def test_service_executed_with_subservices(hass): async def test_service_call_event_contains_original_data(hass): """Test that service call event contains original data.""" - events = [] - - @ha.callback - def callback(event): - events.append(event) - - hass.bus.async_listen(EVENT_CALL_SERVICE, callback) + events = async_capture_events(hass, EVENT_CALL_SERVICE) calls = async_mock_service( hass, "test", "service", vol.Schema({"number": vol.Coerce(int)}) @@ -1471,19 +1483,394 @@ async def test_reserving_states(hass): assert hass.states.async_available("light.bedroom") is True -async def test_state_change_events_match_state_time(hass): - """Test last_updated and timed_fired only call utcnow once.""" +def _ulid_timestamp(ulid: str) -> int: + encoded = ulid[:10].encode("ascii") + # This unpacks the time from the ulid + # Copied from + # https://github.com/ahawker/ulid/blob/06289583e9de4286b4d80b4ad000d137816502ca/ulid/base32.py#L296 + decoding = array.array( + "B", + ( + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + 0x0F, + 0x10, + 0x11, + 0x01, + 0x12, + 0x13, + 0x01, + 0x14, + 0x15, + 0x00, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1A, + 0xFF, + 0x1B, + 0x1C, + 0x1D, + 0x1E, + 0x1F, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + 0x0F, + 0x10, + 0x11, + 0x01, + 0x12, + 0x13, + 0x01, + 0x14, + 0x15, + 0x00, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1A, + 0xFF, + 0x1B, + 0x1C, + 0x1D, + 0x1E, + 0x1F, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + ), + ) + return int.from_bytes( + bytes( + ( + ((decoding[encoded[0]] << 5) | decoding[encoded[1]]) & 0xFF, + ((decoding[encoded[2]] << 3) | (decoding[encoded[3]] >> 2)) & 0xFF, + ( + (decoding[encoded[3]] << 6) + | (decoding[encoded[4]] << 1) + | (decoding[encoded[5]] >> 4) + ) + & 0xFF, + ((decoding[encoded[5]] << 4) | (decoding[encoded[6]] >> 1)) & 0xFF, + ( + (decoding[encoded[6]] << 7) + | (decoding[encoded[7]] << 2) + | (decoding[encoded[8]] >> 3) + ) + & 0xFF, + ((decoding[encoded[8]] << 5) | (decoding[encoded[9]])) & 0xFF, + ) + ), + byteorder="big", + ) + + +async def test_state_change_events_context_id_match_state_time(hass): + """Test last_updated, timed_fired, and the ulid all have the same time.""" + events = async_capture_events(hass, ha.EVENT_STATE_CHANGED) + hass.states.async_set("light.bedroom", "on") + await hass.async_block_till_done() + state: State = hass.states.get("light.bedroom") + assert state.last_updated == events[0].time_fired + assert len(state.context.id) == 26 + # ULIDs store time to 3 decimal places compared to python timestamps + assert _ulid_timestamp(state.context.id) == int( + state.last_updated.timestamp() * 1000 + ) + + +async def test_state_firing_event_matches_context_id_ulid_time(hass): + """Test timed_fired and the ulid have the same time.""" + events = async_capture_events(hass, EVENT_HOMEASSISTANT_STARTED) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + event = events[0] + assert len(event.context.id) == 26 + # ULIDs store time to 3 decimal places compared to python timestamps + assert _ulid_timestamp(event.context.id) == int( + events[0].time_fired.timestamp() * 1000 + ) + + +async def test_event_context(hass): + """Test we can lookup the origin of a context from an event.""" events = [] @ha.callback - def _event_listener(event): + def capture_events(event): + nonlocal events events.append(event) - hass.bus.async_listen(ha.EVENT_STATE_CHANGED, _event_listener) + cancel = hass.bus.async_listen("dummy_event", capture_events) + cancel2 = hass.bus.async_listen("dummy_event_2", capture_events) - hass.states.async_set("light.bedroom", "on") + hass.bus.async_fire("dummy_event") await hass.async_block_till_done() - state = hass.states.get("light.bedroom") - assert state.last_updated == events[0].time_fired + dummy_event: ha.Event = events[0] + + hass.bus.async_fire("dummy_event_2", context=dummy_event.context) + await hass.async_block_till_done() + context_id = dummy_event.context.id + + dummy_event2: ha.Event = events[1] + assert dummy_event2.context == dummy_event.context + assert dummy_event2.context.id == context_id + cancel() + cancel2() + + assert dummy_event2.context.origin_event == dummy_event + + +def _get_full_name(obj) -> str: + """Get the full name of an object in memory.""" + objtype = type(obj) + name = objtype.__name__ + if module := getattr(objtype, "__module__", None): + return f"{module}.{name}" + return name + + +def _get_by_type(full_name: str) -> list[Any]: + """Get all objects in memory with a specific type.""" + return [obj for obj in gc.get_objects() if _get_full_name(obj) == full_name] + + +# The logger will hold a strong reference to the event for the life of the tests +# so we must patch it out +@pytest.mark.skipif( + not os.environ.get("DEBUG_MEMORY"), + reason="Takes too long on the CI", +) +@patch.object(ha._LOGGER, "debug", lambda *args: None) +async def test_state_changed_events_to_not_leak_contexts(hass): + """Test state changed events do not leak contexts.""" + gc.collect() + # Other tests can log Contexts which keep them in memory + # so we need to look at how many exist at the start + init_count = len(_get_by_type("homeassistant.core.Context")) + + assert len(_get_by_type("homeassistant.core.Context")) == init_count + for i in range(20): + hass.states.async_set("light.switch", str(i)) + await hass.async_block_till_done() + gc.collect() + + assert len(_get_by_type("homeassistant.core.Context")) == init_count + 2 + + hass.states.async_remove("light.switch") + await hass.async_block_till_done() + gc.collect() + + assert len(_get_by_type("homeassistant.core.Context")) == init_count diff --git a/tests/test_loader.py b/tests/test_loader.py index 9f2aaff58b7..96694c43c7f 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -327,6 +327,26 @@ def _get_test_integration(hass, name, config_flow): ) +def _get_test_integration_with_application_credentials(hass, name): + """Return a generated test integration with application_credentials support.""" + return loader.Integration( + hass, + f"homeassistant.components.{name}", + None, + { + "name": name, + "domain": name, + "config_flow": True, + "dependencies": ["application_credentials"], + "requirements": [], + "zeroconf": [f"_{name}._tcp.local."], + "homekit": {"models": [name]}, + "ssdp": [{"manufacturer": name, "modelName": name}], + "mqtt": [f"{name}/discovery"], + }, + ) + + def _get_test_integration_with_zeroconf_matcher(hass, name, config_flow): """Return a generated test integration with a zeroconf matcher.""" return loader.Integration( @@ -479,6 +499,23 @@ async def test_get_zeroconf(hass): ] +async def test_get_application_credentials(hass): + """Verify that custom components with application_credentials are found.""" + test_1_integration = _get_test_integration(hass, "test_1", True) + test_2_integration = _get_test_integration_with_application_credentials( + hass, "test_2" + ) + + with patch("homeassistant.loader.async_get_custom_components") as mock_get: + mock_get.return_value = { + "test_1": test_1_integration, + "test_2": test_2_integration, + } + application_credentials = await loader.async_get_application_credentials(hass) + assert "test_2" in application_credentials + assert "test_1" not in application_credentials + + async def test_get_zeroconf_back_compat(hass): """Verify that custom components with zeroconf are found and legacy matchers are converted.""" test_1_integration = _get_test_integration(hass, "test_1", True) diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index d2c453f070d..79cd4e5e0df 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -1,4 +1,6 @@ """Test Home Assistant date util methods.""" +from __future__ import annotations + from datetime import datetime, timedelta import pytest @@ -106,6 +108,12 @@ def test_utc_from_timestamp(): ) +def test_timestamp_to_utc(): + """Test we can convert a utc datetime to a timestamp.""" + utc_now = dt_util.utcnow() + assert dt_util.utc_to_timestamp(utc_now) == utc_now.timestamp() + + def test_as_timestamp(): """Test as_timestamp method.""" ts = 1462401234 @@ -136,6 +144,32 @@ def test_parse_datetime_returns_none_for_incorrect_format(): assert dt_util.parse_datetime("not a datetime string") is None +@pytest.mark.parametrize( + "duration_string,expected_result", + [ + ("PT10M", timedelta(minutes=10)), + ("PT0S", timedelta(0)), + ("P10DT11H11M01S", timedelta(days=10, hours=11, minutes=11, seconds=1)), + ( + "4 1:20:30.111111", + timedelta(days=4, hours=1, minutes=20, seconds=30, microseconds=111111), + ), + ("4 1:2:30", timedelta(days=4, hours=1, minutes=2, seconds=30)), + ("3 days 04:05:06", timedelta(days=3, hours=4, minutes=5, seconds=6)), + ("P1YT10M", None), + ("P1MT10M", None), + ("1MT10M", None), + ("P1MT100M", None), + ("P1234", None), + ], +) +def test_parse_duration( + duration_string: str, expected_result: timedelta | None +) -> None: + """Test that parse_duration returns the expected result.""" + assert dt_util.parse_duration(duration_string) == expected_result + + def test_get_age(): """Test get_age.""" diff = dt_util.now() - timedelta(seconds=0) @@ -635,3 +669,53 @@ def test_find_next_time_expression_time_leave_dst_chicago_past_the_fold_ahead_2_ assert dt_util.as_utc(next_time) == datetime( 2021, 11, 7, 8, 20, 1, tzinfo=dt_util.UTC ) + + +def test_find_next_time_expression_microseconds(): + """Test finding next time expression with microsecond clock drift.""" + hour_minute_second = (None, "5", "10") + test_time = datetime(2022, 5, 13, 0, 5, 9, tzinfo=dt_util.UTC) + matching_hours, matching_minutes, matching_seconds = _get_matches( + *hour_minute_second + ) + next_time = dt_util.find_next_time_expression_time( + test_time, matching_seconds, matching_minutes, matching_hours + ) + assert next_time == datetime(2022, 5, 13, 0, 5, 10, tzinfo=dt_util.UTC) + next_time_last_microsecond_plus_one = next_time.replace( + microsecond=999999 + ) + timedelta(seconds=1) + time_after = dt_util.find_next_time_expression_time( + next_time_last_microsecond_plus_one, + matching_seconds, + matching_minutes, + matching_hours, + ) + assert time_after == datetime(2022, 5, 13, 1, 5, 10, tzinfo=dt_util.UTC) + + +def test_find_next_time_expression_tenth_second_pattern_does_not_drift_entering_dst(): + """Test finding next time expression tenth second pattern does not drift entering dst.""" + tz = dt_util.get_time_zone("America/Chicago") + dt_util.set_default_time_zone(tz) + tenth_second_pattern = (None, None, "10") + # Entering DST, clocks go forward + test_time = datetime(2021, 3, 15, 2, 30, 0, tzinfo=tz, fold=0) + matching_hours, matching_minutes, matching_seconds = _get_matches( + *tenth_second_pattern + ) + next_time = dt_util.find_next_time_expression_time( + test_time, matching_seconds, matching_minutes, matching_hours + ) + assert next_time == datetime(2021, 3, 15, 2, 30, 10, tzinfo=tz) + prev_target = next_time + for i in range(1000): + next_target = dt_util.find_next_time_expression_time( + prev_target.replace(microsecond=999999) + timedelta(seconds=1), + matching_seconds, + matching_minutes, + matching_hours, + ) + assert (next_target - prev_target).total_seconds() == 60 + assert next_target.second == 10 + prev_target = next_target diff --git a/tests/util/test_ulid.py b/tests/util/test_ulid.py index fc5d2af8c87..9f297bf373e 100644 --- a/tests/util/test_ulid.py +++ b/tests/util/test_ulid.py @@ -6,6 +6,11 @@ import homeassistant.util.ulid as ulid_util async def test_ulid_util_uuid_hex(): - """Verify we can generate a ulid.""" + """Verify we can generate a ulid in hex.""" assert len(ulid_util.ulid_hex()) == 32 assert uuid.UUID(ulid_util.ulid_hex()) + + +async def test_ulid_util_uuid(): + """Verify we can generate a ulid.""" + assert len(ulid_util.ulid()) == 26 diff --git a/tox.ini b/tox.ini index 441960fbbf5..b39caacf471 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ isolated_build = True [testenv] basepython = {env:PYTHON3_PATH:python3} # pip version duplicated in homeassistant/package_constraints.txt -pip_version = pip>=21.0,<22.1 +pip_version = pip>=21.0,<22.2 install_command = python -m pip install --use-deprecated legacy-resolver {opts} {packages} commands = {envpython} -X dev -m pytest --timeout=9 --durations=10 -n auto --dist=loadfile -qq -o console_output_style=count -p no:sugar {posargs}